1、有意义的命名
看到这个标题,大家大概也都知道,一个程序是否直观易懂,命名首当其冲。不过良好的命名是一个很难做的事情,至少工作半年来是深有体会的。一个良好的命名需要有好的描述能力、文化背景和英文能力。但目前我好像都欠缺,除了有道翻译稍微的帮我提高了英文能力之外,其余的还处于低谷。不过这个章节提供了一些技巧来提高命名的规范。
- 名如其物,都要有实际意义;
- 名字不宜过长且相似,如果造成误导;(比如:XYZControllerForEfficientHandlingOfStrings、XYZControllerForEfficientStorageOfStrings)
- 程序中尽量不要存在魔数,应该用常量去替换,易于搜索;
- 类名和对象应该是名词或名词短语,不应该是动词;
- 方法名应当是动词或动词短语;
- 类中的属性添加不用必要的语境,如果这个类自带语境了,便不用添加。
这一章节有一个不同意的地方:2.7.3节中接口和实现,里面说命名接口时,使用前导字母I被泛滥使用了,我不同意这种观点,接口就要使用I开头,来告诉别人这是一个接口,项目并不只是一个人在开发,也需要让被人知道。
2、函数
函数是所有程序中的第一组代码,是代码的主体部分。在这章节的总结中,有一句话写得特好:大师级程序员把系统当做故事来讲,而不是当做程序来写。
- 短小,20行最佳(个人觉得看情况,如果函数过于短小,则小函数就会变多);
- if语句、else语句、while语句等,其中的代码块应该只有一行,该行应该是一个函数调用语句。函数的缩进层级不该多余一层或两层;
- 每个函数一个抽象层级。
- switch语句通常用多态进行实现
- 函数参数
- 最理想的参数数量是0,越多越不好。从测试的角度看,参数的增加将使测试用例指数性增长;
- 函数参数尽量少,绝对不要多于2个,多于2个就要进行封装。
- 避免使用输出参数 如果函数要修改某种状态,应去修改所属对象的状态。
- 分隔指令与询问
- 使用异常替换返回错误码(错误处理就是一件事 错误处理函数不应该再做其他事,这就意味着,如果try在某个函数中存在,它就应该是该函数的第一个单词,而catch代码块后面也不该再有其他内容,即“前无古人,后无来者”。)
3、注释
以前认为注释越多越好,最好是每一句都能写一个注释,搞得整个代码中文比代码还多,其实这是一种错误的方式,乱七八糟的注释可能会搞乱一个模块。写注释也是有一定的方法和技术的。
- 如果代码能够解释的,请把注释删掉;
- 如果你想要用注释来掩饰你糟糕的代码,那么你可以洗洗睡了,注释并不能美化糟糕的代码;
- 直接把代码注释掉,不删除是讨厌的做法;(至少我是这么认为的)
4、格式
格式很重要,格式很重要,格式很重要,重要的事说三遍。代码格式是关于沟通的,而沟通是专业开发者的头等大事。个人感觉这一章节讲得很详细,不过代码都是Java的,那格式,我一个学C#的看起来就特别别扭,我感觉我有强迫症,好想把大括号移到下一行。
- 垂直水平方向的区别、靠近和距离;
- 顺序,被调用的函数应该放在执行调用的函数下面;
- 团队规则至上。(每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,那就是团队说了算)。
好的软件系统是由一系列读起来不错的代码文件组成的。它们需要拥有一致和顺畅的风格。如果团队成员中使用不同的风格来编写源代码,只会增加复杂度。
5、对象和数据结构
- 数据抽象
- 这个小节真的是让我大开眼界,作者主张变量的实现是不能暴露的,不能暴露数据细节,而是要以抽象形态表述数据。
- 数据、对象的反对称性
- 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象(多态)代码便于在不改动既有函数的前提下添加心类。
- 过程式代码难以添加新数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。
- 德墨忒尔律:模块不应该了解它所操作对象的内部情形。准备的说类C的方法f只能调用以下对象的方法:
- C
- 由f创建的对象
- 作为参数传递给f的对象
- 由C的实体变量持有的对象
- 方法不应调用由任何函数返回的对象的方法
- 火车失事:最好不要链式编程,我是这么来理解的,最好每一个方法有一个切分,不要一直.下去
- 数据结构:数据结构是数据的载体,暴露数据,而几乎没有意义的行为,像我们最常见到的DTO(Data Transfer Objects)
- 对象:对象作为面向对象的产物,必须封装隐藏数据,而暴露出行为接口。DDD中领域模型倾向于对象不仅在数据更多暴露行为操作自己或者关联状态。
6、错误处理
如果能将错误处理隔离看待,独立于主要逻辑之外,就能写出强固而整洁的代码。做到这样,就能单独处理它,也极大地提升了代码的可维护性。
- 使用异常而非返回码
- 先写Try-Catch-Finally语句
- 使用不可控异常
- 给出异常发生的环境说明
- 依调用者需要定义异常类
- 定义常规流程
- 别返回null值、别传递null值(重点)
7、边界
我们很少控制系统中的全部软件。因为有时我们会购买第三方程序包或使用开源插件,或者是某个组件是公司中其他团队打造的。这时候,我们都得将外来代码干净利落地整合进自己的代码中。
- 学习第三方代码很难,整合更难,同时做两件事,难上加难。建议,不要在生产代码中试验新东西,而是编写测试来遍览和理解第三方代码,这叫学习型测试,这方面部门的欣哥做得就特别好。
- 学习性测试的好处:当第三方发布了新版本,我们就可以运行学习性测试,看看程序包的行为有没有改变。
- Adapter模式
8、单元测试
这一章节是我最欠缺的,单元测试,可能到现在还没有养成习惯。
- TDD三定律:
- 定律一:在编写不能通过的单元测试前,不可编写生产代码。
- 定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过。
- 定律三:只可编写刚好足以通过当前失败测试的生产代码。
- 这三条定律将限制在大概30秒一个循环中。测试与生产代码一起编写,测试只比生产代码早些几秒钟。
- 保持测试整洁,测试代码和生产代码一样重要
- 整洁测试三个要素:可读性,可读性,还是可读性。
- 整洁的测试还遵循以下5条规则:First
- 快速(Fast)
- 独立(Independent)
- 可重复(Repeatable)
- 自足验性(Self-Validating)
- 及时(Timely)
9、类
- 类应该短小
- 单一职责原则
- 内聚
- 隔离修改(开闭原则)
- 依赖倒置原则,类应该依赖于抽象而不是依赖于具体细节
10、系统
- 这一张给我印象最深的就是依赖注入了,因为最近一直都在看关于Ioc的文章,Ioc只是一种思想,DI是这种思想的实践
- 将系统的构建和使用分开
11、迭进
这一章节阐述了简单设计的四条规则,按重要性排序如下:
- 运行所有测试:遵循有关编写测试并持续运行测试的简单、明确的规则,系统就会更贴近OO低耦合度、高内聚度的目标
- 不可重复:重复是拥有良好设计系统的大敌,如有重复,利用手段将程序进行代码层面上的重构
- 表达了程序员的意图:选用好名称、类保持短小
- 尽可能少的类和方法:这可能会与前面所讲得SRP等基础原则相悖,为了遵循原则,我们造出了很多细小类和方法。不过请记住,这在四条规则中是优先级最低的一条。所以,尽管使类和函数的数量尽量少是很重要的,但更重要的却是测试、消除重复和表达力
12、并发编程
并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开。并发是一种时间(When)和目的(What)的解耦,提高应用程序的吞吐量,提高cpu利用率。但事情总是有两面性的,并发容易造成死锁等问题,所以,编写优质的并发代码是一件难度极高的事情。
客观的认识一下并发:
- 编写并发程序会在代码上增加额外的开销
- 正确的并发是非常复杂的,即使对于很简单的问题
- 并发中的缺陷因为不易重现也不容易被发现
- 并发往往需要对设计策略从根本上进行修改
如果要进行并发编程,尽量遵循一下并发防御原则:
- 单一职责:并发已经足够复杂,我们更需要代码分离,分离线程相关代码和非线程相关代码,尽可能降低其复杂度
- 谨记数据封装:严格限制对可能被共享的数据的访问
- 线程应尽可能地独立
13、逐步改进
这一章都是代码。(吐槽一下)
代码能工作还不够,能工作的代码经常会严重崩溃。满足于仅仅让代码能工作的程序员不够专业。我一直认为没有什么能比糟糕的代码给开发项目带来更深远和长期的损害了。如果不及时清理,随着代码腐坏下去,模块之间互相渗透,出现大量隐藏纠结的依赖关系,这时候,时间清理代码成本就很高了。所以解决之道就是保持代码持续整洁和简单。用不让腐坏有机会开始。