记宋宝华老师两天培训所得

公司在11.1-11.2两天请来了宋宝华老师来为我们培训,虽然讲的都是基于C语言的有关Linux内核的知识,我听不太懂,不过一些设计相关的常识技巧还是挺适用的。用自己的语言理解记录一下。

1.函数参数类型是boolean的,改为两个函数

我经常会写出一个类似show(boolean isShow)函数,传入一个boolean类型的参数判断显示或者隐藏。

这样对于程序逻辑性来说并不好,函数名称既然叫show,那么这个函数就应该只做show的事,隐藏的事就再写一个hide()函数去完成。

如果非要传入boolean变量,可以把参数名改成showOrHide(boolean isShow)。在Android源码中也可以看到view.setEnabled(boolean enabled),我们知道传入一个值可以改变enabled属性,可是如果把函数名改成view.enabled(boolean enabled),会不会觉得很奇怪?既然函数名是enabled,为什么还可以传一个参数使得函数变成了完全相反功能的disabled。就是这个道理。

2.如果有异常参数,抛崩溃异常,而不是由函数内部处理。

我经常写函数的第一句话就是判断传入的参数是否为空,如果为空直接return;或者赋给一个缺省值。如果参数是int型的,还会判断是否小于0或者大于最大范围值,如果超过了值域范围,我还会“强行”把参数的值“拉回”正确的范围内。

这样其实是错误的处理方式!

如果出现异常数据,说明程序到这已经错了,应该尽早报告出来,哪怕是程序崩溃,所谓早死早超生。我们经常会遇到一个问题,崩溃的地方就是原因所在,也就是“所见即所得”,这样的问题一般很容易解决;我们有时候还会发现出问题的地方没有一点错误,即不是问题发生的地方,也就是“所见不是所得”。我们可以假设一下,前面运行了很多步,到了我们写的这个函数,已经出现了错误的参数,可是我们对异常的参数进行了强行转换,然后用正确范围的参数继续下面的步骤,这中间发生了很可怕的事有木有!就是不对接!

举个例子,就好像领导给了你一个文件让你给张三(通过了很多层传到你这了),文件名称是《To张三》。可是公司并没有张三这个人,你心里就琢磨,不可能给一个不存在的人发文件呀,后面没法传呀,但是你知道有个人叫张四,于是自己把名称改为了《To张四》,然后又往别的部门传。最后出状况了,前面有人不小心把《To张王》写成了《To张三》,而张王没有收到领导的文件,领导怪罪下来,罚了传文件的每一个人,可是问题在他们么?不在,问题在于你偷偷改了文件名,并且把错误的文件发下去了。而正确的做法我们应该抛出异常告诉领导,你给我的文件名字好像有点问题,是不是前面传递资料的时候有什么地方出了差错?

还需要注意一点就是异常处理和错误处理的差别。异常要抛出,而错误我们要有相应的处理措施。在写函数的时候,只有我们自己知道什么是异常,什么是错误。

我再举个例子,比如一个打开文件的函数openFile(path),内部我们判断如果找不到path路径,就抛出异常;而错误处理是我们在准备调用openFile(path)函数的时候,先调用isFileExist(path)来判断path是否存在,不存在在else中写错误处理逻辑。这样既保证了函数的单一性,又强化了逻辑性。

3.函数名字和所做事情要匹配

这个和第一点有点像,就是说你函数做了什么事,你函数名就要体现出做的事,如果做了两件事(比如”开”和”关”),就要有两个动词体现(onAndOff)

一般来说函数名都是动词或者动宾结构,这样读者比较好理解函数作用,反而言之如果你放个名词(比如door(),门),会让人感到匪夷所思。不过这一点像我这种初级程序员都很少犯的。

4.注释不要重复代码字面意思;当你的读者需要一个why时,给个注释,而不是解释how

看我之前的博文,可以看到我经常在代码中写注释,而且每一步还标号呢。如下:

       //1.动画逻辑
       animLogic();
       //2.绘制图像
       postInvalidate();
       //3.延迟,不然会造成执行太快动画一闪而过
       try {
           Thread.sleep(30);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

不要以为我是为了教程故意加上的,我自己平时确实是这么干的 [笑cry],我还经常和别人说我注释量占代码量的百分之三十呢。

这么做可能是受了老师教学影响——很多教学视频都是这样注释加深理解的。

我觉得这么做也无可厚非,只不过宋宝华老师提出说高手写代码,代码就是最好的注释,其他文字注释都是多余,我们如果代码写得清晰明了,层次结构清楚,有些分步式注释就不需要了,特别是解释代码含义的注释是最低等级的注释,比如

    //x减10
    x -= 10;
    //y加10
    y += 10;

这种注释不但没必要还让人觉得有负担,注释里说的内容从代码看更直观,而且上面注释的内容并没有加深我们对代码的理解——为什么x要减10,y要加10?如果我们写了一段极其复杂的算法,在注释上标明计算弧长公式,就非常明白这段算法是做什么的了。

5.提交代码时的注释很重要,要清楚详细,并且提交代码是以修改或增加一个功能为单位,不论修改文件多少和时间多少。

之前在学校老师没教过版本控制管理,SVN还是我在实习的时候才接触的,那时候导师让我们每天提交一次代码,第二天更新下来再继续修改。我的意识里对为什么提交代码变得很模糊,好像只是为了和其他成员合并每天做的东西,那如果只有自己一个人开发,是不是可以想什么时候提交就什么时候提交?

首先必须理解为什么要用SVN(或者现在都用Git),版本控制是为了自己每次增加删除或修改代码后,能够自由地回溯到原来的状态,如果我只有一份文件,是不是删除了某段代码,保存后就找不回来了?那为了保留这段代码我是不是得新建一个副本(副本1)作为原来的版本?如果副本建多了(副本1234567)是不是我们管理起来也很费劲了?版本控制就是帮我们解决这个问题的。(可以看看一篇介绍Git的文章《史上最浅显易懂的Git教程!》,里面由浅入深地解释了版本控制系统的作用)

好了,回到主题,既然我们已经理解了版本控制系统的作用,那么我们提交代码时,就要按照功能划分去提交,哪怕我改了一条语句,使一个功能增加,也应该提交,同样哪怕改了一万条语句,只为实现同一个功能,也等改完一万条提交,这样的好处是当我们需要回溯版本时能很清楚要回溯的分割点在哪,而且每回溯到一个版本都是可用的。而为了更好地区分那么多版本,我们在提交的注释中就应该尽可能详细:今天在什么模块,解决了什么bug,bug的原因,怎么解决的……这样你团队的人也能很清楚你做了什么修改,比“副本1234567”看起来要爽多了。

6.代码最重要的是可读性,优雅的代码指的是可读性高的代码,结构清晰,语句简单,但是设计地高内聚,低耦合,复用高

这个理论有点抽象,据说真正的高手写代码,用的都是极其简单的语句构成的函数,而且结构简单明了,也就是只要懂语法的新手都能看得懂,但是高手能设计代码框架设计地很好,容易复用,每个函数都是单一的功能,低耦合。

我也不是高手,所以只能意会,不可言传,慢慢体会吧。

7.尽量少用全局变量,大部分全局变量都可以依附于一个对象,函数内尽量用参数,而不是全局变量

我经常说自己使用的是面向对象的思想,可是我却也经常使用全局变量,一个Activity中经常有很多记录状态的全局变量。

其实这里已经可以体现我并没有完全理解面向对象的思想,用的还是面向过程的方法,要把所有事物想象成对象,哪怕是一个“命令”(不是有命令模式嘛)。

大部分全局变量都可以依附于一个它属于的对象,比如我在Activity中记录当前菜单页数 curPage当前菜单位置 curPos,很明显这两个全局变量是属于菜单这个类的。

然后我接着在刷新函数 update()里使用全局变量进行刷新

    public void update() {
        curPage ...
        curPos  ...
        ...
    }

如果不直接使用全局变量而是传入参数的话,会更好复用这个函数一点

    public void update(int page, int pos) {
        page ...
        pos  ...
        ...
    }

如果还不懂,就慢慢意会吧……

8.有时候可以用空间换时间

又要说到在上学的时候了,在上学的时候老师会布置作业,经常让我们自己把各种算法实现一遍,而且明确规定不准使用已有API,我这么乖(sha)的孩子就乖(sha)乖(sha)地自己慢慢想算法,也不敢去瞄别人是怎么实现的,有时候想破头都想不出来。

这个习惯到现在就变成了,有些可能有一定规律可以通过算法算出来,也可以通过死记的方式得到固定答案值的,我更倾向于用算法,显得高大上。

宋宝华老师举了个例子说

char[] c = {0123456789abcdef};

但是很抱歉我忘记他怎么讲的了(:з」∠)

于是我自己想了个例子来说明:假如我们现在需要最快速度得到100以内的质数,然后从当中随机得到一个值进行下一步运算,怎么写?

好,你闭着眼睛开始想算法了,终于想到如下算法求质数

public class ZhiShu {
    public static void main(String[] args) {
        // 求100以内的质数

        for (int i = 2; i <= 100; i++) { // 质数
            for (int k = 2; k <= i; k++) { // 除数
                // 排除所有在 i=k 之前 能被k整除(余数为0)的数
                if (i % k == 0 && i != k) {
                    break;
                }
                // 输出所有在 i=k 且 i%k=0的数
                if (i % k == 0 && i == k) {
                    System.out.println(i);
                }
            }
        }
    }
} //以上代码来自百度,本人不保证正确性

这个算法还不是最优的,你可以不断优化,到最优……然而都不是最快的。

最快的是怎样?

//100以内的所有质数(25个)
public static final int[] PRIME_100={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};

/**
 * 从100以内的质数中得到一个随机数
 */
public int randomPrime100() {
    int index = new Random.nextInt(PRIME_100.length);
    return PRIME_100[index];
}

你可能会笑,这个方法太傻了,没学过编程的人才会这么想。可是不可否认,这样是最快的。有时候,在一些特定情况下,而且要求我们极其严格的性能优化,不妨可以考虑一下还有这种做法。


11.21更新

9.关于初始化

如果是成员变量,不用初始化为0或者为null,因为成员变量默认就会初始化为0null。但是如果是函数内部变量,一定记住要初始化!函数内部的变量如果不初始化直接用,比如int型,得到的可能是随机数。

你可能感兴趣的:(*,个人小结,*,实用技巧)