第二章 注重实效的途径
1. 重复的危害
a) DRY-Don’t Repeat Yourself。系统中的每一项知识都必须具有单一、无歧义、权威的表示。
b) 重复是怎样发生的
Imposed Duplication强加的重复。开发者觉得他们无可选择-环境似乎要求重复。
Inadvertent Duplication无意的重复。开发者没有意识到自己在重复信息。
Impatient Duplication无耐心的重复。开发者偷懒,因为重复似乎更容易。
Interdeveloper dumplication开发者之间的重复。同一个团队的几个人重复了同样的信息。
下面是对这四类重复的详细解释
c) 强加的重复
1) 注释。糟糕的代码才需要许多注释。要把低级的知识放在代码中,把注释保留给其它高级的说明,否则过多的注释只是在重复知识,每次改变代码,注释也需要更改,最终注释会变得过时,不可信任的注释比完全没有注释更糟。可以考虑用合理的变量命名、逻辑清晰的代码逻辑来代替低级的注释,而描述函数运作原理的注释,以及约定函数的输入、输出等,这些应该算是高级注释。
2) 文档与代码。撰写文档,然后编写代码,文档和代码在重复同样的知识,文档需要与代码保持同步,但常常得不到及时的维护。这种情况估计执行力不到位的公司都会遇到。
d) 无意的重复。这常常来自不合理的设计。比如一条线段,设计了起点、终点两个属性后,如果再加上长度属性,便是多余的。
e) 无耐心的重复。这种重复最容易检测,为了走捷径而简单复制,常常是欲速而不达,一旦需要修改代码,这种简单地复制的行为就会受到应有的惩罚。
f) 开发者之间的重复。这类重复最难检测,项目在演进过程中,随着人员的变动,方案的调整,到最后往往很难看清项目的全貌,也许正在编写的函数已经实现过了却没人能想起来。对于这类重复,最好是通过清晰的设计、强有力的技术项目领导、明确的责任划分来规避。
2. 正交性
“正交性”本是本意是指几何中相互垂直的两条直线,正交时某个点沿着一条直线移动,它投影在另一条直线的位置不变。在软件领域中,正交性指某种不相依赖或解耦性。如果一个软件模块发生变化,不会影响其它模块,那它们就是正交的。要尽量设计内聚的组件(独立,具有单一、良好定义的目的)。
a) 软件模块正交的好处
1) 提高生产率
使改动局部化,降低开发测试时间
促进复用。如果组件具有明确而具体的、良好定义的职责,就可以用最初的开发者未曾想象过的方式,把它们与新组件组合起来。
充分发挥模块的功能,A组件M件事,B组件N件事,如果A B正交,可以组合成M*N种功能,这是最大化的。可能只一点只能体现在理论上吧。
2)降低风险
正交的设计可以隔离有问题代码区域,如果某个模块有问题,在正交系统中,不会蔓延到其它模块,要更换问题模块也很容易
让系统更健壮。对某个模块的改动,所导致的任何影响都被局限在该区域内。
更方便测试,因为设计测试、并针对其组件运行测试更容易,否则为了测试一个模块还要关联测试其它模块,这就像之前单元测试描述的,复杂度会快速膨胀。
3) 不会与某个特定的供应商、产品或平台绑在一起。但如果使用的是UI控件、ORM框架,要不绑在一起估计很困难。
b) 在工作中运用正交原则的几种方式
1) 项目团队。怎样把团队划分为责任互不重叠的小组,这个没有明确的答案,据项目而定,但可以从基础设施与应用分离开始。比如按照主要的基础设施组件(数据库、通信接口、中间件等)划分,并根据具体情况进行调整。对团队的正交性衡量有一个方法:查看在讨论每个所需改动时涉及的人数,人数越多正交性越差
2) 设计。采用分层的方法是设计正交系统的强大方式。每层都只使用在其下面的层次提供的抽象,在改动一个层的实现时,可以不影响其他层,拥有极大的灵活性。而且分层也降低了模块间依赖关系失控的风险,否则根本无法驾驭模块间的互相引用。衡量设计好坏,可以考虑这个问题:如果我显著地改变某个特定功能背后的需求,有多少模块会受影响?在最理想的正交系统中,答案应该是“一个”,现实中虽然很少能做到这样,但也应该是越少越好。而且要小心地作出假设,不要依赖你无法控制的事物,比如将电话号码作为顾客的识别码
3) 编码。要努力地让代码保持解耦。作者形象地比喻为:编写“羞涩”的代码。羞涩的代码不会没有必要地向其他代码模块暴露任何事情、也不依赖其他模块的实现。此外避免重复、应对变化是设计模式的拿手好戏,需要多学习领会
4) 测试。正交地设计和实现的系统更易于测试。建议每个模块都有自己的、内建在代码中的单元测试,并让这些测试作为常规构建过程的一部分自动运行。而且构建单元测试本身就是对正交性的有趣测试,如果为了构建某个单元测试,你需要把系统中其余部分拉进来,那么正交性就很差。
3. 可撤销性
a) 如果某个想法是你唯一的想法,再没有什么比这更危险的事情了。
b) 没有什么永远不变,而如果你严重依赖某一事实,你几乎可以确定它将会变化。要把决策视为写在沙滩上的,而不要把他们刻在石头上。大浪随时可能到来,把他们抹去。
c) 除了保持代码的灵活性,还需要考虑架构、部署及供应商等领域的灵活性。
4. 曳光弹
a) 在机枪射击中,常会把曳光弹与常规弹药交错装在弹药带上,发射时曳光弹会在枪与击中的地方留下烟火般的踪迹,而如果曳光弹击中目标,常规弹药也会击中目标。在软件开发中,如果有新的项目是你从未构建过,客户也没有用过类似系统以致需求模糊不清时,可以使用类似的曳光弹方法。
1) 曳光弹与真正的子弹在相同的环境和约束下工作,枪手能够得到即时的反馈。在软件开发中,使用曳光代码可以快速、直观可重复地从需求出发,满足最终系统的要求。
2) 曳光代码并非用过就扔的代码,它含有任何一段产品代码都拥有的完整的错误检查、结构、文档、自查,只是功能不全而已。曳光开发与项目永不会结束的理念是一致的:总有改动需要完成,总有功能需要增加,这是一个渐进的过程。
b) 曳光代码的优点
1) 用户能够及早看到能工作的东西,并帮你定位目标
2) 曳光代码相当于一个有待壮大的集成平台,一旦新的代码段通过了单元测试,就可以将它加入该环境中
3) 有了用于演示的东西
4) 将更能感觉到工作进展,相当于把一个大目标分成了许多小目标来完成
c) 但是曳光弹并非总能击中目标,曳光代码也不是总能满足需求,这正是曳光弹和曳光代码的价值所在。曳光代码可以帮助在客户的不断反馈中接近目标,而小段代码的惯性也小,改变起来容易、迅速
d) 曳光代码与原型的区别。原型制作的是用过就扔的代码,而曳光代码虽然简约,却是完整的,并且构成了最终系统的骨架的一部分。可以把原型制作视为在第一发曳光弹发射之前进行的侦查和情报搜集工作。
5. 调试
a) 调试的“心理学”
最容易欺骗的人是自己
不要恐慌
如果见到Bug的第一反应是“那不可能”, 就完全错了。不要浪费时间在以“那不可能”起头的思路上,因为那不仅可能,而且已经发生了。
在调试时小心近视,要抵制只修正你看到的症状的迫切愿望,要尽可能找到其它相关的地方,找出问题的根源,而不是问题的特定具体表现。
测试时尽可能覆盖全部边界条件。
b) 跟踪。如果需要观察程序或数据结构随时间的变化情况,就需要用到跟踪的方法。比如并发编程、实时系统、基于事件的应用中,将跟踪信息打印到屏幕或文件中就是有效的方法。
c) 审视自己的代码,看看是否有一些不严密的假定
欢迎关注我的个人公众号【菜鸟程序员成长记】