公司在11.1-11.2两天请来了宋宝华老师来为我们培训,虽然讲的都是基于C语言的有关Linux内核的知识,我听不太懂,不过一些设计相关的常识技巧还是挺适用的。用自己的语言理解记录一下。
我经常会写出一个类似show(boolean isShow)
函数,传入一个boolean类型的参数判断显示或者隐藏。
这样对于程序逻辑性来说并不好,函数名称既然叫show
,那么这个函数就应该只做show
的事,隐藏的事就再写一个hide()
函数去完成。
如果非要传入boolean变量,可以把参数名改成showOrHide(boolean isShow)
。在Android源码中也可以看到view.setEnabled(boolean enabled)
,我们知道传入一个值可以改变enabled
属性,可是如果把函数名改成view.enabled(boolean enabled)
,会不会觉得很奇怪?既然函数名是enabled
,为什么还可以传一个参数使得函数变成了完全相反功能的disabled
。就是这个道理。
我经常写函数的第一句话就是判断传入的参数是否为空,如果为空直接return;
或者赋给一个缺省值。如果参数是int
型的,还会判断是否小于0
或者大于最大范围值,如果超过了值域范围,我还会“强行”把参数的值“拉回”正确的范围内。
这样其实是错误的处理方式!
如果出现异常数据,说明程序到这已经错了,应该尽早报告出来,哪怕是程序崩溃,所谓早死早超生。我们经常会遇到一个问题,崩溃的地方就是原因所在,也就是“所见即所得”,这样的问题一般很容易解决;我们有时候还会发现出问题的地方没有一点错误,即不是问题发生的地方,也就是“所见不是所得”。我们可以假设一下,前面运行了很多步,到了我们写的这个函数,已经出现了错误的参数,可是我们对异常的参数进行了强行转换,然后用正确范围的参数继续下面的步骤,这中间发生了很可怕的事有木有!就是不对接!
举个例子,就好像领导给了你一个文件让你给张三(通过了很多层传到你这了),文件名称是《To张三》。可是公司并没有张三这个人,你心里就琢磨,不可能给一个不存在的人发文件呀,后面没法传呀,但是你知道有个人叫张四,于是自己把名称改为了《To张四》,然后又往别的部门传。最后出状况了,前面有人不小心把《To张王》写成了《To张三》,而张王没有收到领导的文件,领导怪罪下来,罚了传文件的每一个人,可是问题在他们么?不在,问题在于你偷偷改了文件名,并且把错误的文件发下去了。而正确的做法我们应该抛出异常告诉领导,你给我的文件名字好像有点问题,是不是前面传递资料的时候有什么地方出了差错?
还需要注意一点就是异常处理和错误处理的差别。异常要抛出,而错误我们要有相应的处理措施。在写函数的时候,只有我们自己知道什么是异常,什么是错误。
我再举个例子,比如一个打开文件的函数openFile(path)
,内部我们判断如果找不到path
路径,就抛出异常;而错误处理是我们在准备调用openFile(path)
函数的时候,先调用isFileExist(path)
来判断path
是否存在,不存在在else
中写错误处理逻辑。这样既保证了函数的单一性,又强化了逻辑性。
这个和第一点有点像,就是说你函数做了什么事,你函数名就要体现出做的事,如果做了两件事(比如”开”和”关”),就要有两个动词体现(onAndOff)
一般来说函数名都是动词或者动宾结构,这样读者比较好理解函数作用,反而言之如果你放个名词(比如door()
,门),会让人感到匪夷所思。不过这一点像我这种初级程序员都很少犯的。
看我之前的博文,可以看到我经常在代码中写注释,而且每一步还标号呢。如下:
//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?如果我们写了一段极其复杂的算法,在注释上标明计算弧长公式
,就非常明白这段算法是做什么的了。
之前在学校老师没教过版本控制管理,SVN还是我在实习的时候才接触的,那时候导师让我们每天提交一次代码,第二天更新下来再继续修改。我的意识里对为什么提交代码变得很模糊,好像只是为了和其他成员合并每天做的东西,那如果只有自己一个人开发,是不是可以想什么时候提交就什么时候提交?
首先必须理解为什么要用SVN(或者现在都用Git),版本控制是为了自己每次增加删除或修改代码后,能够自由地回溯到原来的状态,如果我只有一份文件,是不是删除了某段代码,保存后就找不回来了?那为了保留这段代码我是不是得新建一个副本(副本1)作为原来的版本?如果副本建多了(副本1234567)是不是我们管理起来也很费劲了?版本控制就是帮我们解决这个问题的。(可以看看一篇介绍Git的文章《史上最浅显易懂的Git教程!》,里面由浅入深地解释了版本控制系统的作用)
好了,回到主题,既然我们已经理解了版本控制系统的作用,那么我们提交代码时,就要按照功能划分去提交,哪怕我改了一条语句,使一个功能增加,也应该提交,同样哪怕改了一万条语句,只为实现同一个功能,也等改完一万条提交,这样的好处是当我们需要回溯版本时能很清楚要回溯的分割点在哪,而且每回溯到一个版本都是可用的。而为了更好地区分那么多版本,我们在提交的注释中就应该尽可能详细:今天在什么模块,解决了什么bug,bug的原因,怎么解决的……这样你团队的人也能很清楚你做了什么修改,比“副本1234567”看起来要爽多了。
这个理论有点抽象,据说真正的高手写代码,用的都是极其简单的语句构成的函数,而且结构简单明了,也就是只要懂语法的新手都能看得懂,但是高手能设计代码框架设计地很好,容易复用,每个函数都是单一的功能,低耦合。
我也不是高手,所以只能意会,不可言传,慢慢体会吧。
我经常说自己使用的是面向对象的思想,可是我却也经常使用全局变量,一个Activity
中经常有很多记录状态的全局变量。
其实这里已经可以体现我并没有完全理解面向对象的思想,用的还是面向过程的方法,要把所有事物想象成对象,哪怕是一个“命令”(不是有命令模式嘛)。
大部分全局变量都可以依附于一个它属于的对象,比如我在Activity
中记录当前菜单页数 curPage
和当前菜单位置 curPos
,很明显这两个全局变量是属于菜单
这个类的。
然后我接着在刷新函数 update()
里使用全局变量进行刷新
public void update() {
curPage ...
curPos ...
...
}
如果不直接使用全局变量而是传入参数的话,会更好复用这个函数一点
public void update(int page, int pos) {
page ...
pos ...
...
}
如果还不懂,就慢慢意会吧……
又要说到在上学的时候了,在上学的时候老师会布置作业,经常让我们自己把各种算法实现一遍,而且明确规定不准使用已有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更新
如果是成员变量,不用初始化为0
或者为null
,因为成员变量默认就会初始化为0
或null
。但是如果是函数内部变量,一定记住要初始化!函数内部的变量如果不初始化直接用,比如int
型,得到的可能是随机数。