第5章:当你编码时
有人认为,项目一旦进入编码阶段,便是机械劳动,这是错误的。
编码不是机械劳动,程序员每分钟都需要进行思考做出决策。
本章讲的是实际编码时要注意的细节:
- 主动思考代码为何能工作,而不是靠巧合编程。
- 评估算法速率。
- 不断地在代码设计中看到改进的余地,进行重构。
- 记住你未来要对代码进行测试,所以你需要编写易于测试的代码。
- 小心那些帮你写大量代码的工具,比如邪恶的向导。
31《靠巧合编程》
我们应该避免靠巧合编程——依靠运气和偶然的成功。
而是应该深思熟虑地编程。
巧合包括:
- 实现的偶然。你可能依靠没有计入文档的错误,或是边界条件。
- 语境的偶然。例如你假定现在在GUI中,没有考虑非GUI的情况。
- 隐含的假定。
如何深思熟虑地编程?
- 总是意识到你在做什么。
- 不要试图构建你完全不理解的应用,或者你有疑问的技术。
- 依靠可靠的事物。
- 为你的假定建立文档。
- 不要只是测试你的代码,还要测试你的假定。
- 为你的工作划分优先级,把时间花在最重要的地方,确保你的基本原则和基础设施是正确的。
- 不要做历史的奴隶。一旦你发现过去的代码有问题,请修改它,甚至是进行重构。
32《算法速率》
评估算法速率有时很重要,它将决定你采取什么决策。
如果关系总是线性的,那么这一节就无关紧要了。但大多数时间不是。
有时,你确实需要更严格的评估,那么就需要用到 O()表示法了
常见的O():
- O(1):常数型。例如数组元素,简单语句。
- O(lg(n)):对数型。例如二分查找。
- O(n):线性。比如顺序查找。
- O(nlg(n)):比线性差,但是不会差太多。例如快速排序的平均时间
- O(n^2):平方。(选择和插入排序)
- O(n^3):立方。(2n x n 矩阵相乘)
- O(C^n):指数型。(旅行商问题,集合划分)
常识估算:
- 简单循环。很可能是O(n)。
- 嵌套循环。O(m x n)。
- 二分法。O(lg(n))
- 分而治之。典型例子是快速排序。尽管技术上是O(n^2),但是其行为在馈入时是排过序的输入时会退化,因此平均时间是O(nlg(n))。
- 组合。运行时间可能会失去控制。
估算你算法的阶数。
尝试替换成阶数更低的算法。
不过,也要注重实效。理论上最快的不一定是实际最快的。因为不同的输入数据会导致更快的算法不同。
33《重构》
很多人喜欢把软件开发比喻为建筑:
- 建筑设计师绘制蓝图。
- 承包商挖掘地基、修建上层建筑、布设管线、最后装修。
- 最后房客开心来入住,如果有问题就找人来修。
商人喜欢建筑的比喻,因为它看起来更科学、可控、可复用。
但遗憾的是,软件开发与上述的流程不怎么相似。
软件开发比起建筑更像是园艺——它比混凝土更有机:
- 你有个初步计划在花园里种下许多植物。
- 有些茁壮成长,但有些注定成为肥料。
- 你可能会改变植物的位置以有效利用光影和风雨的交互。
- 过度生长的植被会被修剪。你还需要拔除野草。
- 你不断关注着花园的兴旺,并按照需要做出任何调整。
重写、重做和重新架构代码合起来,称为“重构”。
你该在何时重构?
- 重复
- 非正交的设计
- 过时的知识
- 需求变化
- 性能
现实情况很复杂,你的重构想法不一定会得到认可,但还是应该——
早重构,常重构
关于重构的建议:
- 在重构的同时不要试图增加功能。
- 在开始重构之前,确保你拥有良好的测试。
- 采取短小而深思熟虑的改动。
34《易于测试的代码》
芯片在设计时就考虑了测试。
我们可以在软件中做同样的事——从一开始就把可测试性构建进软件中。
单元测试:
在隔离状态下,用人为的条件对模块进行测试。
单元测试也可以被看作是“对合约的测试”。
单元测试不应被放在遥远的角落,而是放在容易被找到的地方。
你可以使用一些工具进行测试。
测试是技术,但也是一种文化。
测试你的软件,否则你的用户就得测试。
35《邪恶的向导》
类似VisualStudio的IDE,可以在创建项目时轻轻一点就以模板生成代码。
我们不是反对这样的向导。相反,我们用了整整一节来说明《代码生成器》的好处。
我们反对的是使用那些你不理解的向导。
有人觉得这是一种极端的想法,毕竟我们每天都在依赖于不完全理解的事物——比如集成电路的量子力学。
但是向导不是:
- 向导生成的代码变成了你的应用的完整组成部分。
- 它没有被隐藏在整洁的接口之后,而是和你自己编写的代码交织在一起。
- 最后,它变为了你自己的代码。然而没人应该制作那些他们不完全理解的代码。