1. 代码应当易于理解
一、表面层次的改进
2. 把信息装到名字里
把信息塞入名字中。读者仅通过读到名字就可以获得大量信息。
- 使用专业的单词一一例如,不用Get,而用 Fetchi或者Down load 可能会更好,这由上下文决定。
- 避免空泛的名字,像tmp和retval,除非使用它们有特殊的理由
- 使用具体的名字来更细致地描述事物
- Server Can Start这个名字就比 Canlistenonport更不清楚。
- 给变量名带上重要的细节
- 例如:在值为毫秒的变量后面加上ms,或者在还需要转义的、未处理的变量前面加上raw_。
- 为作用域大的名字采用更长的名字
- 不要用让人费解的或两个字母的名字来命名在几屏之间都可见的变量
- 对于只存在于几行之间的变量用短一点的名字更好。
- 有目的地使用大小写、下划线等
- 例如,你可以在类成员和局部变量后面加上"_"来区分它们
3. 不会误解的名字
不会误解的名字是最好的名字
阅读你代码的人应该理解你的本意,并且不会有其他的理解。遗憾的是,很多英语单词在用来编程时是多义性的,例如filter、length和limit。
- 在你决定使用一个名字以前,要吹毛求疵一点,想象一下你的名字会被误解成什么,最好的名字是不会误解的~
命名建议:
- 当要定义一个值的上限或下限时,max和min是很好的前缀
- 对于包含的范围,first和last是好的选择
- 对于包含/排除范围,begin 和end是最好的选择,因为它们最常用。
- 当为布尔值命名时,使用is和has这样的词来明确表示它是个布尔值
- 避免使用反义的词(例如 disable_ssl)
- 要小心用户对特定词的期望
- 例如:用户会期望get()或者size() 是轻量的方法。
4. 审美
大家都愿意读有美感的代码,通过把代码用一致的、有意义的方式“格式化”,可以把代码变得更容易读,并且可以读得更快
具体技巧
- 如果多个代码块做相似的事情,尝试让它们有同样的剪影。
- 把代码按“列”对齐可以让代码更容易浏览。
- 如果在一段代码中提到A、B和C,那么不要在另一段中说B、C和
A- 选择一个有意义的顺序,并始终用这样的顺序。
- 用空行来把大块代码分成逻辑上的“段落”
5. 该写什么样的注释:
什么地方不需要注释
- 能从代码本身中迅速地推断的事实
- 用来粉饰烂代码(例如蹩脚的函数名)的“拐杖式注释” ,应该把代码改好
你应该记录下来的想法包括:
- 对于为什么代码写成这样而不是那样的内在理由(“指导性批注”)
- 代码中的缺陷,使用像T0D0,或者XX:这样的标记。
- 常量背后的故事,为什么是这个值。
站在读者的立场上思考:
- 预料到代码中哪些部分会让读者说:“啊?”并且给它们加上 注释
- 为普通读者意料之外的行为加上注释。
- 在文件/类的级别上使用“全局观”注释来解释所有的部分是如何一起工作的。
- 用注释来总结代码块,使读者不致迷失在细节中。
6. 写出言简意赅的注释
把更多的信息装入更小的空间里
- 当像"it"和"this"这样的代词可能指代多个事物时,避免使用
- 尽量精确地描述函数的行为。
- 在注释中用精心挑选的输入/输出例子进行说明。
- 声明代码的高层次意图,而非明显的细节
- 用嵌入的注释(如 Function(/arg=/…))来解释难以理 解的函数参数。
- 用含义丰富的词来使注释简洁。
二、简化循环和逻辑
7. 把控制流变得易读
- 在写一个比较时( while( bytes expected> bytes received)),把改变的值写在左边
- 并且把更稳定的值写在右边更好一些( while( bytes received< bytes expected))。
- 也可以重新排列if/else语句中的语句块
- 通常来讲,先处理正确的/简单的/有趣的情况
- 有时这些准则会冲突,但是当不冲突时,,这是要遵循的经验法则
- 某些编程结构,像三目运算符(:?)、do/ while循环,以及goto 经常会导致代码的可读性变差。
- 最好不要使用它们,因为总是有更整洁的代替方式
- 嵌套的代码块需要更加集中精力去理解。
- 每层新的嵌套都需要读者把更多的上下文“压入栈”。
- 应该把它们改写成更加“线性”的代 码来避免深嵌套
- 通常来讲提早返回可以减少嵌套并让代码整洁
- 保护语句 (在函数顶部处理简单的情况时)尤其有用。
8. 拆分超长的表达式
很难思考巨大的表达式。
几种拆分表达式的方法
- 1、引入“解释变量”来代表较长的子表达式
三个好处:- 它把巨大的表达式拆成小段。
- 它通过用简单的名字描述子表达式来让代码文档化。
- 它帮助读者识别代码中的主要概念。
- 2、用德摩根定理来操作逻辑表达式
- 这个技术有时可以把布尔表达式用更整洁的方式重写
- (例如if(!、(a&&!b)) 变成if(!aIb))
- 这个技术有时可以把布尔表达式用更整洁的方式重写
- 3、把一个复杂的逻辑条件拆分成小的语句
- 例子:就像"if(a<b)…"
- 4.、所有的if语句内不超过两个值,这是理想情况。
- 可能不是总能做到这样,有时需要把问题“反向”或者考虑目标的对立面
9. 变量与可读性
程序中的变量是如何快速累积而变得难以跟踪的。你可以通过减少变量的数量和让它们尽量“轻量级”来让代码更有可读性。
- 减少变量,即那些妨碍的变量
- 通过立刻处理结果来消除“中间结果”变量。
- 减小每个变量的作用域,越小越好
- 把变量移到一个有最少代码可以看到它的地方。眼不见,心不烦。
- 只写一次的变量更好
- 那些只设置一次值的变量(或者 const、 final、常量)使得代码更容易理解
三、重新组织代码
10. 抽取不相关的子问题,创建通用代码
总结:就是“把一般代码和项目专有的代码分开”。其结果是,大部分代码都是一般代码。通过建立一大组库和辅助函数来解决一般问题,剩下的只是让你的程序与众不同的核心部分
这个技巧有帮助的原因是它使程序员关注小而定义良好的问题, 这些问题已经同项目的其他部分脱离。其结果是,对于这些子问题的解决方案倾向于更加完整和正确。你也可以在以后重用它们
11. 一次只做一件事
一个组织代码的简单技巧:一次只做一件事情。
- 如果你有很难读的代码,尝试把它所做的所有任务列出来。
- 其中一些任务可以很容易地变成单独的函数(或类),其他的可以简单地成为一个函数中的逻辑“段落”。
- 具体如何拆分这些任务没有它们已经分开这个事实那样重要,难的是要准确地描述你的程序所做的所有这些小事情。
12. 把想法变成代码
- 用自然语言描述程序,然后用这个描述来帮助你写出更自然的代码。这个技巧出人意料地简单,但很强大。看到你在描述中所用的词和短语还可以帮助你发现哪些子问题可以拆分出来。
- 但是这个“用自然语言说事情”的过程不仅可以用于写代码。
- 例如,某个大学计算机实验室的规定声称,当有学生需要别人帮它调试程序时,他首先要对房间角落的一只专用的泰迪熊解释他遇到的问题。 令人惊讶的是,仅仅通过大声把问题描述出来,往往就能帮这个学生找到解决的办法。这个技巧叫做“橡皮鸭技术”。
- 另一个看待这个问题的角度是:如果你不能把问题说明白或者用词语来做设计,估计是缺少了什么东西或者什么东西缺少定义。
- 但是这个“用自然语言说事情”的过程不仅可以用于写代码。
- 把一个问题(或想法)变成语言真的可以让它更具体。
13. 少写代码
冒险、兴奋一一绝地武士追求的并不是这些。 – 尤达大师
- 每行新的代码都需要测试、写文档和维护
- 另外,代码库中的代码越多,它就越“重”,而且在其上开发就越难。
- 可以通过以下方法避免编写新代码:
- 从项目中消除不必要的功能,不要过度设计。
- 重新考虑需求,解决版本最简单的问题,只要能完成工作就行
- 经常性地通读标准库的整个API,保持对它们的熟悉程度。
- 例子: 使用Unix工具而非编写代码
- 在unix中查找带有4XX或5XX响应的代码日志:
- cat access log I awk '{ print $5 " “KaTeX parse error: Expected 'EOF', got '}' at position 3: 7 }̲" I egrep "[45…”
I sort | uniq-c | sort -nr
四、精选话题
14. 测试与可读性
在测试代码中,可读性仍然很重要。
如果测试的可读性很好其结果是它们也会变得很容易写。因此大家会写更多的测试。
并且,如果你把事实代码设计得容易测试,代码的整个设计会变得更好。
如何改进测试的几个具体要点:
- 每个测试的最高一层应该越简明越好
- 最好每个测试的输入/输出可以用一行代码来描述
- 如果测试失败了,它所发出的错误消息应该能让你容易跟踪并修正这个bug
- 使用最简单的并且能够完整运用代码的测试输入
- 给测试函数取一个有完整描述性的名字
- 以使每个测试所测到的东西很明确。不要用Test1(),而用像Test_< Function Name><
Si tuation>这样的名字。
- 以使每个测试所测到的东西很明确。不要用Test1(),而用像Test_< Function Name><
最重要的是,要使它易于改动和增加新的测试。
对测试较好的开发方式:测试驱动开发TDD
测试驱动开发(TDD)是一种编程风格,你在写真实代码之前就写出测试。
- TDD的支持者相信,这种流程对没有测试的代码来讲,会做出极大的质量改进,比写出代码之后再写测试要大得多。
- 这是一个争论很激烈的话题。至少,仅通过在写代码时想着测试这件事就能帮助把代码写得更好。
但不论你是否使用TDD,其结果总是你用代码来测试另一些代码。
15 设计并改进“分钟/小时计数器”
- 首先,从编写一个幼稚的方案开始,意识到两个设计上的挑战:速度和内存使用情况。
- 接下来,尝试用“传送带”设计方案。
- 这种设计改进了速度和内存使用情况,但对于高性能应用来讲还是不够好。
- 并且,这个设计不是很灵活,让这段代码能处理其他的时间间隔需要很多工作
- 最终的设计解决了前面的问题,通过把问题拆分成子问题。下面是创建的三个类,自下向上,以及每个类所解决的子问题:
- 1、ConveyorQueue:一个最大长度的队列,可以“移位”并且维护其总和。
- 2、TrailingBucketCounter:根据过去了多少时间来移动 Conveyor Queue,并且按所给的精度来维护单一(最后)的时间间隔中的计数。
- 3、 MinutehourCounter:简单地包含两个TrailingBucketCounter,一个用来统计分钟一个用来统计小时