代码好坏的评估分为四个层次,可用,可读,可维护,可扩展。这篇文章就从可读这个角度看一看如何编写出高质量的代码。
变量命名
代码大全中很详细的阐述了如何给变量取一个好的名称,但开发中我们可能无法记住那么多条,我选择了部分很关键的条目进行举例阐述。
变量名应该很清晰的描述出该变量代表的事物包括大部分的临时变量,以下举几个例子
- 当前日期 好名字:currentDate 坏名字:date - 每页行数 好名字:linesPerPage 坏名字:x1,num
给布尔变量赋予暗含“真/假”含义的名字
- 好名字:done,success,error 坏名字:status
给枚举类型赋予所在组的前缀
enum today{ today_morning, today_afternoon }
常量命名大写并传递自身含义
- 好名字:CYCLE_NEEDED 坏名字:FIVE
团队开发中你代码被阅读的次数远大于编写的次数,为变量取合适的名称是增强可读性的一个关键步骤,工作中要尽力避免随意给变量的命名。
防御式编程
防御式编程分为两个部分
- 对所以的外来数据(从文件、网络等外部接口的读入的数据,子程序的输入参数值)进行检查(包括SQL注入,过滤HTML,整数溢出等)。
- 对错误数据进行处理
断言
断言的作用就是让程序运行时自我检验。
int testData=0;
assert testData!=1 :"testData is unexpectedly equal to 0";
assert testData==1 :"testData is unexpectedly equal to 1";
输出 testData is unexpectedly equal to 1
断言为真表明程序正常运行,错误表示出现异常。
用错误来处理预期会发生的错误,用断言来处理绝不应该发生的错误。
可以将断言理解为注解,但是它更主动的对程序进行判断。
断言可以开启和关闭,关闭后编译器直接忽略断言代码,因此不能将执行程序放在断言中。
上面四句出自代码大全,第二句很重要,所谓预期错误就是源于外部的数据,不在程序员自己编码范围内出现的错误。
错误
错误的一大特征就是我们可以预料,常用的处理方式有返回错误码等,异常则是子程序遇到了自己无法处理的错误将它抛出。
高质量的子程序
本章主要说明什么时候该创建子程序,又该如何创建子程序
编写子程序可以让我代码更精简,更容易理解,但编写时有几个准则需要我们核对
- 程序中所有适于单独提出的部分是不是都提到了子程序中?
- 子程序的名字是否描述了它做的事情?
- 子程序是否有功能上的内聚性?
- 是否用到了每一个参数?
另外何时使用过程也值得我们注意,当一个子程序的主要用途就是返回它名字指明的返回值就使用
书上说的读起来有点绕,举个例子就知道了
第一段
if(report(formattedReport)==success) then ...
第二段
status = report(formattedReport)
if(status==success) then ...
这样把对程序的判断,和函数的调用分开了,使得程序更好阅读
表驱动法
一句话就是用查询表来代替复杂的逻辑判断
直接访问表
int [] dayOfmonth=new int []{31,39,31,30..........};
int month=1;
//查询1月份的天数
//表查询
int day=dayOfmonth[month-1];
//逻辑查询
if(month==1){
...
}else if(month){
...
}
可以看到查询表代替了复杂的逻辑判断
阶梯访问表
int [] range=new int []{30,50,70,90};
char [] rank=new char[]{'A','B','C','D'};
int score=98;
for(int i=0;i<range.length;i++){
if(score<30){
return rank[i];
}
}
我们不能直接将数字转化为字母,因此根据分数的范围返回评级,采用阶梯表法比较区间的上/下限。
组织直线型代码
所谓直线型就是将前后依赖的代码组织在一起,目的仍是使代码能被自上而下的阅读,更容易被理解。
核对准则
- 代码间的依赖关系明显
- 代码中子程序的参数/名字使得依赖关系明显
- 把相对独立的代码放进各自的子程序中
- 依赖关系不明显是否用注释进行了说明?
若没有依赖关系,那相关语句被组织在一起了吗?