您的位置:首页 > 编程语言

编写可读代码的艺术读书整理

2017-07-24 15:32 253 查看

好代码的各种衡量

之前看过一些**编码规范,作者一般上来都是这样子的,先说明自己来自某大厂,职位什么的,然后下面就开始逐条规定,对于什么样的情况,我该怎么写代码。其实,这过程中是有些内容被忽视掉了的,那就是什么是好代码?在整理编码规范前,我觉得有必要去想想这个问题的。

运行效率高的?比如:a/2写成a<<1这种,把每句代码的取址-译码-执行的时间降低到最低?nono,这是个标准,但是不是这个时代的标准,这个标准放到纸带打孔机那个时代还行,在这个时代,那百万分之一毫秒的差别,几乎不能引起关注。

还有N多假设在此省略。。。自行脑补吧。。

代码应当”让人“易于理解

这里特别强调是“人”易于理解,而不是让机器易于理解;因为,只要你语法正确,机器都能理解,相反,可能你周围的小伙伴理解起来,就未必能那么顺畅了。

说明原因之后,我们一下的内容,都会围绕这一原则进行具体描述。

可读性基本定理

代码的写法应当使别人理解它所需时间最小化

你可能遇到过这种情况,一个项目,来来回回好几拨人,可能当时的管理各方面也比较混乱吧,项目中的code style都乱的不成样子了,这时候,你忽然被告知,要在这个摇摇欲坠的项目里面,做点儿新需求,请问你什么感觉。

理解代码所需时间是否与其他标准有冲突

遇到这种问题时候,这里给出最简单的方法,先问自己,这个代码别人好理解吗?如果回答是肯定的,那么,就这样吧;否则,fix it.

例如,两个变量互换:

A=A^B;
B=B^A;
A=A^B;


偶尔猛一看,略微蒙,倒不如这种傻瓜式的写法:

int x = 10;
int y = 20;
int temp = x;
x = y;
y = temp;


表面层次上的改进

首先我们对代码的改进,先从最简单的开始:选择好的名字;写好的注释;以及把代码写成更好的格式。

关于命名

key words:把信息装进名字中

选择专业的词

先来看个bad name:

public void statisticOrderOfDay();

public void statisticOrderOfDay(String str);

public void statisticStationOrderOfDay();


这个是我从一个service接口里面找到的,丝毫没改动,然后拿了过来,猛一看,这三个方法连在一起,感觉萌萌的,再仔细读,会发现更蒙,因为从名字里面,看不出有什么特别大的区别。

好点儿的的方法命名:

int updateByExampleSelective(@Param("record") BizRescueWorkOrder record, @Param("example") BizRescueWorkOrderExample example);

int updateByExample(@Param("record") BizRescueWorkOrder record, @Param("example") BizRescueWorkOrderExample example);

int updateByPrimaryKeySelective(BizRescueWorkOrder record);

int updateByPrimaryKey(BizRescueWorkOrder record);


key words:尽量使用精确的单词描述你要表达的事情!

- 避免使用空泛的名字

常见单词,temp,retVal,foo,bar,baz,qux,i,j这种。一个个来说,temp只能读出这个是个临时变量,retVal看出了这个东西是个返回值,至于foo,已经后面连这的这几个词,能用这些的,你站出来说说你是有多无聊。

好的名字应该尽可能的去描述变量的目的和它所承载的值。

例如:

/* What will be run. */
private Runnable target;


不恰当的用法这里也举几个例子,比如,在两个变量交换值的时候,此时使用temp是合适的,说明此时正在使用的是个临时变量,但是,比如去拼接一个很长的字符串时候,如果一直是temp+=*,就不大合适了;

再比如,嵌套循环中:







使用这种没有意义的参数进行循环,很容易把下标写错,倒不如换用有意义的迭代索引:





tips:多去给自己的变量重命名,会发现命名能力会逐渐提升。

- 用具体的名字代替抽象的名字





- 名字的长度

System.Windows.Media.TextFormatting.GetTextEffectCharacterIndexFromTextSourceCharacterIndex()


上面的方法来自 .net framework via MSDN(够长吧,放出来吓吓你!)

到底名字长度多少合适,其实也并没标准,但是还是提供了一些指导性的原则:

在小的作用域里面,尽量使用简短名字;想反,大的作用域内,你可能就需要更多的单词去描述这个东西了。

缩写不要太过分啊:之前跟朋友聊天,说他最近在做的 项目,一个类名怎么也看不懂,然后就去问原作者,之后原作者告诉他,这个缩写是自己女儿名字的缩写。之后我就呵呵了,这个是真事儿。想说的是,缩写可以用,但是尽量让大家都能看懂,比如doc,str这种,还是可以接受的。

丢掉没用的单词:比如,convertToString这个方法,完全可以这样子:toString().尽量简化。

利用名字的格式来传递含义:

例如,常见的常量值:

/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;


使用“_”区分类成员和局部变量。

- 使用不容易被误解的名字:表示上限时使用min_和max_作为前缀;对于包含范围的选择,使用firset和last;对于包含排除的范围,使用begin和end;对于布尔值,尽量使用has,is,来表示,避免使用否定的词比如disable_ssl;

其他格式规范:对于不同的语言,都会有自己的一些特色性的东西,比如,jquery对象,命名加上$标记。

段落

跟我们小学时候学写作文一样,学了词汇,我们就要学习一些段落的知识了。

首先来说关于段落的几条原则:

- 使用一致的布局,让读者很快习惯这种style.

- 让相似的代码看上去更相似。

- 把相关代码进行分组,形成代码块。

一些小策略:

- 1,重新安排换行来保持一致跟紧凑




这样看起来三个方法是不是很相似了,如果觉得空间占用太大,还可以调整下:





- 2,用方法来整理不规则的东西




感觉assert测试除了参数不一样,其他都是一个意思,但是有的都换行了,不太一致,调整下:




3,在需要时候使用列对齐




这样写,我们很容易一眼看出,第二个参数,第三个参数的位置。

- 4,选择一个有意义的顺序,始终使用它

比如,在接收表单参数的时候:





跟填写的顺序保持一致;从重要到不重要排序;

- 把声明按照块组织起来

如果类的声明是这样纸的:





是不是略微茫然,如果按照用途调整下:





个人风格:之前有个笑话,说两个大括号的位置就能让两个程序猿打起来。其实风格这个东西,就是个规范,就像是剪草坪,政府怎么规划,你就怎么剪,一致的风格比你自己 的style更重要。

注释

注释的原则:注释的目的是尽量帮助读者了解的跟原作者一样多

什么时候不需要注释

有时候,知道不做什么跟知道要做什么是一样有意义的。

//this is a class for Resource class
class Resource{
//this is a constructor method
Resource(){}
}


上面这个注释,其实是没有意义的,反而看上去有点儿可笑。

key words:不要用能从代码中[ 快速 ]推断出来的事实去注释,这样有点儿stupid!

- 不要为了注释去注释

之前有个理论说是注释比例跟代码比例是什么3:2这种,我就郁闷了。感觉其实没有这个必要。

- 不要去为名字起得不好的东西加上好注释

如果为了弥补名字不好,而去加上更多的注释去解释这个东西,那么还是请你先跳回去读,怎么给一个东西起个好名字。

- 加入comments

我们有时候在读word文档的时候,会加入审阅,这个就是这个意思。

**
* When I wrote this, only God and I understood what I was doing
* Now, God only knows
*/
/**
* 写这段代码的时候,只有上帝和我知道它是干嘛的
* 现在,只有上帝知道
*/


为代码中的瑕疵写注释

// I am not sure why this works but it fixes the problem.
// 虽然我不知道为什么这样管用,但它却是修复了问题


给常量加上注释

公布可能的陷阱

全局性的总结性的注释

写好注释的how-to-do and not to do

让注释保持紧凑

//
// 摘要:
//     Initializes a new instance of the System.IO.Ports.SerialPort class using the
//     specified port name and baud rate.
//
// 参数:
//   portName:
//     The port to use (for example, COM1).
//
//   baudRate:
//     The baud rate.
//
// 异常:
//   T:System.IO.IOException:
//     The specified port could not be found or opened.
public SerialPort(string portName, int baudRate);


避免使用不必要的动词

比如,this,it这类词,鬼才知道你指的是什么。

润色粗糙的句子





改为下面的,是不是更好:





- 精确的描述函数的行为





因为并没有描述出,怎么才算是一行,改为下面的:





使用输入输出来说明特别情况




声明代码的意图





使用信息含量高的词





改为下面一行:





简化循环逻辑

在顺序,选择,循环中,尽量将选择和循环逻辑处理成顺序逻辑,这个比较符合我们大脑的处理方式的。




把控制流变得容易读

条件语句




if(age>18){

}


关于尤达表示法:

if(null==obj){}


对于上述写法,首先来描述下为什么会有这么写的,这个是从上古流传下来的写法,当时编译器对于if(obj=null)这种if条件判断是可以通过,此时的obj=null会被处理成赋值运算,而不是条件判断,为了避免出现这种错误,所以倒过来写成null==obj,但是今天的编译器会对obj=null这种写法给出warn,所以尽量少用这种可读性不强的写法。

- if/else语句块顺序





- 谨慎使用三目运算符

var data = new[]
{
new []{"v1",  db.V1.ToString("0.000"),db.V1>singleV_min && db.V1<singleV_max  ? "√" : "×"},
new []{"v2",  db.V2.ToString("0.000"),db.V2>singleV_min && db.V2<singleV_max ? "√" : "×"},
new []{"v3",  db.V3.ToString("0.000"),db.V3>singleV_min && db.V3<singleV_max ? "√" : "×"},
new []{"v4",  db.V4.ToString("0.000"),db.V4>singleV_min && db.V4<singleV_max ? "√" : "×"},
new []{"v5",  db.V5.ToString("0.000"),db.V5>singleV_min && db.V5<singleV_max ? "√" : "×"},
new []{"v6",  db.V6.ToString("0.000"),db.V6>singleV_min && db.V6<singleV_max ? "√" : "×"},
new []{"v7",  db.V7.ToString("0.000"),db.V7>singleV_min && db.V7<singleV_max ? "√" : "×"},
new []{"v8",  db.V8.ToString("0.000"),db.V8>singleV_min && db.V8<singleV_max ? "√" : "×"},
new []{"v9",  db.V9.ToString("0.000"),db.V9>singleV_min && db.V9<singleV_max ? "√" : "×"},
new []{"v10",  db.V10.ToString("0.000"),db.V10>singleV_min && db.V10<singleV_max ? "√" : "×"},
new []{"v11",  db.V11.ToString("0.000"),db.V11>singleV_min && db.V11<singleV_max ? "√" : "×"},
new []{"v12",  db.V12.ToString("0.000"),db.V12>singleV_min && db.V12<singleV_max ? "√" : "×"},
new []{"v13",  db.V13.ToString("0.000"),db.V13>singleV_min && db.V13<singleV_max ? "√" : "×"},
new []{"v14",  db.V14.ToString("0.000"),db.V14>singleV_min && db.V14<singleV_max ? "√" : "×"},
new []{"v15",  db.V15.ToString("0.000"),db.V15>singleV_min && db.V15<singleV_max ? "√" : "×"},
new []{"v16",  db.V16.ToString("0.000"),db.V16>singleV_min && db.V16<singleV_max ? "√" : "×"},
new []{"v17",  db.V17.ToString("0.000"),db.V17>singleV_min && db.V17<singleV_max ? "√" : "×"},
new []{"v18",  db.V18.ToString("0.000"),db.V18>singleV_min && db.V18<singleV_max ? "√" : "×"},
new []{"v19",  db.V19.ToString("0.000"),db.V19>singleV_min && db.V19<singleV_max ? "√" : "×"},
new []{"温度1", db.Temprature1 == 215 ? "null" : db.Temprature1.ToString(), db.Temprature1 == 215 ? "--" : db.Temprature1 > temprature_min && db.Temprature1 < temprature_max ? "√" : "×"},
new []{"温度2", db.Temprature2 == 215 ? "null" : db.Temprature2.ToString(), db.Temprature2 == 215 ? "--" : db.Temprature2 > temprature_min && db.Temprature2 < temprature_max ? "√" : "×"},
new []{"温度3", db.Temprature3 == 215 ? "null" : db.Temprature3.ToString(), db.Temprature3 == 215 ? "--" : db.Temprature3 > temprature_min && db.Temprature3 < temprature_max ? "√" : "×"},
};


避免do/while循环

从函数中提前返回

别用goto,不然揍你呦

最小化嵌套

//TODO:检查电池组压差
if (!isDisplay)
{
if (isBefore == 0)
{
if (double.Parse(DataAnalysis.getBatteryGroupPress(batteryDataList)) * 1000 >= double.Parse(config.getStringValueByKey(IniConfig.BEFORE_BATTERY_GROUP_VOLUMN_DIFF,"")))
{
db.IsPass = false;
checkMsg += " 电池组压差告警,应小于:" + config.getStringValueByKey(IniConfig.BEFORE_BATTERY_GROUP_VOLUMN_DIFF,"") + " mv";
}

}
else if (isBefore == 1)
{
if (double.Parse(DataAnalysis.getBatteryGroupPress(batteryDataList)) * 1000 >= double.Parse(config.getStringValueByKey(IniConfig.AFTER_BATTERY_GROUP_VOLUMN_DIFF,"")))
{
db.IsPass = false;

checkMsg += " 电池组压差告警,应小于:" + config.getStringValueByKey(IniConfig.AFTER_BATTERY_GROUP_VOLUMN_DIFF,"") + " mv";

}
}


对于这种嵌套情况,可以使用在内层中,提前返回来减少嵌套,让代码尽量顺序可读。

拆分超长表达式

keywords:把你的超长表达式拆分成更容易理解的小块。

- 用作解释的变量




变量与可读性

变量的问题:

1,变量越多,越容易跟踪它的东西;

2,变量的作用域越大,越难跟踪它的问题;

3,变量的改动越频繁,越难跟踪它的问题;

总结起来就是:操作一个变量的地方越多,它越容易出问题。

- 减小变量

减少无用的中间变量;

减小控制流变量;

- 缩小变量的作用域

key workds:让你的变量对尽量少的代码行可见。

一些方法:

1,将成员变量降格成局部变量;

2,将方法变为静态的;

3,把大类拆分成小类;

4,大方法拆分成小方法;

重新组织代码

基本的重新组织代码的方式:

1,抽取出那些与程序主要目的“不相关的子问题”;

2,重新组织代码使它只做一件事情;

3,先用自然语言描述代码,然后用这个描述来帮助你找到

积极地发现并抽取出不相关的子逻辑:

- 1.看看某个函数或代码块,问问你自己,这段代码更高层次的目标是什么

- 2.对于每一行代码,问一下:它是直接为了目标而工作的吗?这段代码更高层次的目标是什么

- 3.如果足够的行数在解决不相关的子问题,抽取代码到独立的函数中。

具体策略:

- 创建纯工具代码

- 创建大量通用代码

- 简化已有方法

一次只做一件事:

同时在做几件事的代码很难理解,一个代码块可能初始化对象,清除数据,解析数据,然后应用业务逻辑,所有的这些同时进行。如果这些代码都纠缠到一起,对于每个任务都很难靠其自身理解它从哪里开始,到哪里结束。

关键思想:应把代码组织的一次只做一件事。

一次只做一件事所用到的流程:

1.列出代码中的所有任务。这里的任务没有很严格的定义——它可以小的如“确保这个对象有效”,或者含糊得“遍历树中所有结点”。

2.尽量把这件任务拆分到不同的函数中,或者至少是代码中不同的段落中。

把想法变成代码:

用自然语言描述问题和解决办法,然后用这些自然语言来帮助你写出更自然的代码。

少写代码:

你所写的每一行代码都是需要测试和维护的,通过common lib和减少功能,可以节约时间并让代码保持精简。

key words:最好读的代码就是没有代码。

- 别费神实现不需要的功能

- 保持小的代码库

- 熟悉周围的代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: