0%

201 principles of software development

软件开发中的201条原则

本文翻译自《201 principles of software development》

基本原则

  1. 质量第一。
  2. 不同的人对软件质量的定义不同。对于开发者来说,质量意味着优雅的设计和编码;对于高压环境下的用户来说,质量意味着响应时间和容量;对某些客户来说,质量意味着满足所有已知的和未知的需求…一个项目的质量取决于它需要优先考虑的事以及各参与方。
  3. 产量和质量不可兼得。产量越高,则质量越低。贝尔实验室发现,要想实现每千行代码只有1~2个bug,那么每人月只能产出150~300行代码。
  4. 高质量的软件是可以实现的。大型的软件系统是能以很高的质量构建的,但必须以很高的价格:每行代码$1000。作为开发者,要知道那些能够提高软件质量的技术:多与用户沟通,构建原型,保持设计简单,检查,雇佣最优秀的人…
  5. 不要试图改造软件质量。就好比不要试图把一个测试原型(throwaway prototype)转化成产品一样。
  6. 可靠性差比效率低更可怕。
  7. 尽早交付产品。可以尽快构建一个原型,让用户体验,一方面能早日收集到反馈,一方面可以验证需求,便于后续开发出令用户满意的产品。
  8. 与客户/用户沟通。人和环境都可能发生变化,跟进变化的唯一方法就是交流。
  9. 目标对齐:开发人员与客户的目标需要一致,尤其是在产品功能的优先级上。
  10. 先做一个测试原型(throwaway prototype), 不要试图第一次就做好一个完整的系统,尤其是面对一个新的产品和领域时。
  11. 13.
  12. 27.
  13. 35.
  14. 37.

需求分析

软件设计

  1. 把需求文档转变成设计文档并不是一件容易的事儿
  2. 跟踪每一个需求,记录每一个需求的设计及实现情况(可以用表格来记录)
  3. 评估每一种方案,从中选出最好的
  4. 没有文档的设计不算真正的设计
  5. 封装,便于维护以及隔离错误
  6. 不要重复造轮子
  7. KISS(Keep It Simple, Stupid)原则,即 保持简洁和单纯
  8. 避免过多的特殊情况:如果需要考虑的特殊情况太多,说明设计或算法有问题
  9. 最小化计算机世界与现实世界的距离,即 在选择模型和方法时尽可能地模拟现实世界
  10. 保持设计的智能可控性,本质上是倡导分层设计,并提供多种视角 (->80)
  11. 保持概念完整性,包括数据结构的组织、模块间的通信方式、错误警告等等的一致性。即使是由多个人开发的系统,看起来也像一个人开发的一样。
  12. 概念错误比语法错误更重要、更难以排查。
  13. 松耦合,高内聚
  14. 为改变而设计,即 所作的设计必须能够适应变化的需求:模块化、可移植、可扩展、贴近现实(69)、智能可控(70)、概念完整(71)
  15. 设计需要考虑后期维护。就可维护性而言,架构的选择比算法和代码更重要。
  16. 设计需要考虑错误情况。尽量做到:
    1. 不引入错误
    2. 引入的错误很容易检测到
    3. 部署后依然存在的错误要么不重要,要么能够避免执行过程中出现灾难性问题
    
    有些具体方法可以帮助提高设计的鲁棒性:
    1. 不要遗漏任何一种状态,例如一个变量有4种取值,不要只检查其中的3种
    2. 预测尽可能多的“impossible”的情况,并给出恢复策略
    3. 为了排除引发灾难的情况,使用故障树(fault tree)分析预测不安全的情形
    
  17. 构建通用性软件。通用性强的软件/组件一般运行会稍微慢一些。
  18. 构建灵活性软件。灵活性强的组件一般比通用性强的组件运行更高效。
  19. 使用高效的算法。要求设计者了解各种算法并能够进行算法复杂度分析。
  20. 模块规格说明应该包含用户需要的所有信息,不要加入用户不需要的任何信息。即模块化。
  21. 设计是多维度的,包括 打包(packaging, what is part of what), 分层(hierarchy, who needs whom), 调用(invocation, who invokes whom), 流程(processes)…
  22. 伟大的设计来自于伟大的设计者
  23. 了解你的应用程序,例如压力环境下的预期行为、输入频率、响应时间、天气的影响…等等
  24. 可以复用一些模块或组件,因为复用的成本小且效率高。
  25. 对于软件来说”garbage in, garbage out”是不正确的。对于无效的输入,软件应该给出智能的回应,指出为什么无效;并且不应该进行处理,而是返回错误码,避免错误向后扩散。
  26. 可以通过冗余来实现软件可靠性:
    1. 并行策略,例如mr任务,每个分片的job都会起多个相同的、互不影响的task,如果其中一个跑完了,就可以kill掉其他的了
    2. 冷备,当主控机器出现硬件故障时,可以启动备用机器继续提供服务
      软件的高可靠性需要很高的代价,有时针对一套需求可能需要提供两套设计方案。

编码原则

87. 避免花招(avoid tricks)

- 许多程序员喜欢在编码时使用一些奇淫技巧以显示他们的聪明才智,但这无疑降低了代码的可读性和可维护性

88. 避免全局变量
89. 自上而下写代码

- 人们习惯从上到下阅读代码,因此编码时要能够便于读者理解。自上而下编码包括:
    1. 文件头部要添加详细的注释,以说明程序的用途和用法
    2. 预先指定外部访问的程序、变量、算法
    3. 采用结构化编程方法

90. 避免副作用(avoid side-effect)

- 程序的副作用是指程序基本功能之外的一些效果,且在程序外部是可见可感知的 ???副作用是软件中很多微小错误的来源,是最难发现的一类错误。

91. 使用有意义的名字

- 无意义的缩写命名,例如"N_FLT","F",看似减少了按键次数,其实是降低了开发效率:
    1. 测试和维护风险增加,因为人们需要花很多时间去猜测和理解命名
    2. 对于无意义的缩写,因为需要添加注释来解释命名,总的按键次数反而增加了
- 好的程序员应该只用10~15%的时间来打字(typing),其他大部分时间用来思考(thinking).

92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.

测试原则

管理原则

管理包含软件开发周期中的一系列活动,包括规划、控制、监督、汇报等。

127. 好的管理比好的技术更重要

1. 好的管理可以激励人们做到最好;相反,差的管理会使人们失去动力
2. 并没有普遍适用的正确的管理风格;管理风格可以因时因地因人而异,重点在于必须适应其所在的环境。

128. 采用合适的解决方法:技术问题需要技术方案,政治问题需要政治解决,管理问题需要合适的管理方法。
129. 不要对你读到的所有内容都深信不疑

1. 当一个人相信某个哲理时,他倾向于寻找能够支撑这个哲理的数据,而丢弃那些不支持该哲理的数据
2. 当你读到“你可以用方法X得到93%的提高(产量or质量)”时,这个方法或许能够达到预定的结果,但也可能存在例外。很大可能是,大部分工程都达不到如此戏剧性的结果;甚至,有些工程使用方法X后产量还降低了。

130. 理解客户的优先级

1. 你可以从需求分析中获知一些用户的优先级信息,难的是如何理解那些可能会发生变化的优先级
2. 另外,要理解用户所说的基本的(essential)、期望的(desirable)、可选的(optional)的需求。试想,如果只完成了基本需求,而没有完成任何期望的或者可选的需求,用户会满意吗? 

131. 人才是成功的关键

1. 有经验、有才华的高技术人才是按时按预算完成软件的关键。正确的人选,即使在工具/语言/方法有限的情况下也能成功;而错误的人选,即使在工具等资源充足的情况下也可能失败。
2. 根据COCOMO模型,最好的人选有4倍于其他人的效率;所以及时那个人需要4倍于他人的薪水,你也没有吃亏;如果他要的更少,你不仅节约了成本还得到了好的产品。

132. 少量有技术的人比大量没有技术经验的人要好

1. 与其投入大量没有经验的工程师,不如分配给几个好的、有经验的工程师
2. 另外,为了避免几个好的工程师都离开,应该对人员配置进行适当的混合,尤其注意不要太偏向于任何一个人

133. 倾听你的队员:如果你和团队成员之间没有信任,那么你们的项目必将失败。信任的第一原则就是倾听。
134. 相信你的队员

1. 相互信任是成功的管理的基本要素。如果你相信你的队员,他们就会值得你信任;如果你表现出不信任他们,他们也会给你不信任他们的理由。信任是相互的,当你相信他们,并且让他们没有理由不相信你时,他们就会信任你。
2. 当你的一个员工跟你说:“我今天下午可以2点离开吗?本周的后面几天我会多工作几个小时补回来的”,此时你应该说“可以”。你什么都没有失去,反而赢得了员工的忠诚和尊敬。你扮演坏人的机会要远多于扮演好人的机会,因此抓住每一个可以做好人的机会。说不定哪一天,你就需要那个员工加几个小时的班以便帮忙完成任务。

135. 期望优秀和卓越

1. 当你对员工抱有很高的期望时,他们会做的更好!在很多实验中,将一群人分成两个组,给他们定下相同的目标,对第一组表现出很高的期望,同时表现出视第二组为平庸之辈,这种情况下,第一组总是会比第二组做的更好!
2. 如何表现出你对他们有很高的期望呢?你可以以身作则,树立榜样(如努力工作,不在工作期间玩游戏...);给员工提供教育福利以帮助他们取得最好的成绩;嘉奖优秀的行为;鼓励表现差的员工养成更好的习惯、作出更好的产品,即使没能引导他们做的更好,也可以帮他们寻找其他合适的机会(公司内外)。你不能允许他们一直待在不合适的岗位上,但你也要表现出同情和理解。

136. 沟通技巧很重要:招聘过程中不能低估沟通和协作的重要性
137. 帮忙打水

1. 当你的员工正在加班完成一个项目时,你应该和他们工作相同的时间。
2. 即使无法提供工程上的帮助,也要让他们知道你与他们同在:帮忙订披萨,买苏打水,带水等,一切他们需要的事情...

138. 给不同的员工以不同的激励

1. 不同的人想要的激励可能是不同的,有的想要加薪,有的想要升职,有的或许只想要一台高性能的电脑...
2. 想要发现激励每个人的因素并不是一件简单的事,一个比较好的方法就是倾听,剩下的就是去尝试。但不管怎样,都不要因为怕选择错误的激励方式而放弃提供嘉奖。

139. 保持办公环境的安静

- 高效的员工和公司都会提供安静的、私有的办公环境;与之对应的是大部分企业都是开放的景观办公室,降低了物理设备成本,说是方便沟通,但其实是方便了噪音和干扰,严重影响了生产力和质量

140. 人力和时间是不能互换的

- 布鲁克斯法则(Brook's Law)指出,投入更多的人来开发一个紧急的项目只会让进度更慢,更多并不意味着更好。投入新的人力时,要考虑训练和沟通成本。

141. 不同软件工程师之间的差别是很大的

- 最好的软件工程师和最差的软件工程师在产量(每人月代码行数)上能相差25倍,软件质量(每1000行代码的bug数)上能相差10倍

142. 可以优化任何方面

- 当你告诉员工 排期、软件大小、软件性能、可维护性、用户友好性都同等重要时,那么很可能哪方面都没有得到优化。当你告诉他们其中的一项或者两项比较重要时,可能只有重要的因素得到了优化。事实上在软件开发的过程中,这些因素的优先级可能会不断调整,需要进行不同的折中(trade-offs),你应该让员工理解这些优先级变化以及用户需求。

143. 自动收集工程师数据

- 收集工程师相关的数据有助于将来进行成本预测、评估项目状态和进展、评估改变(管理/技术/..)方案的影响。但数据收集应该尽可能自动化,最主要的是不唐突、不要让开发人员感到困扰。

144. 考虑每行代码的成本是没有意义的

- 相同的需求,可以使用不同的编程语言来完成。通常,使用高级语言会比低级语言节省开发时间,且缩短代码量;但由于用户文档、需求文档、设计文档等成本是固定的,最终会导致前者的单行代码成本增加。

145. 没有完美的衡量开发效率的方法

1. SLOC(source lines of code)代码行估算法:大家普遍认为代码产出越多越好,但有时候却并非如此。实现相同的功能,代码量肯定越少越好。
2. FP(function points)功能点估算法:大家可能认为功能点越多表示产出越多、效率越高,但所要解决的问题的复杂程度或难度不同也是会影响产出率的。
3. 没有哪一种方法是完美的,也不要单独依赖一种方法进行考量。

146. 调整成本估算方法

- 市面上有很多成本估算方法,每个方法都是基于大量已经完成的项目提炼出来的。你可以用这些方法估算大致的成本,但若想获得更准确的结果,则需要对模型或方法进行调整,使其适应你的工作环境(包括你的团队、你的产品等各种因素)。

147. 不要设定不切实际的deadline

- 不切实际的deadline会消解士气、降低员工对你的信任、导致员工离职等,会产生很多负面影响。这类deadline基本都无法达到,即使达到了,产品质量也无法保证,最终导致团队在整个软件领域的信誉度下降。

148. 避免不可能

- 从写需求分析文档到软件交付所需要的时间不会少于人月立方根的2.15倍,即 $T > 2.15 * \sqrt[3]{PM}$,因此不要制定不可能完成的计划

149. 使用之前必须了解

- 例如,在你在选择一套度量指标之前,你必须确保它能够度量你所想测的东西并能够达到你的目标

150. 收集生产数据:

- 没有历史生产数据就没有办法进行成本估算模型的调整和训练,也就不能进行准确的成本估计,因此数据收集要从平时做起。

151. 不要忘记团队生产力

- 相比团队生产力来说,个人生产力比较容易衡量。但是,提高每个成员的生产力并不一定能够提高整个团队的生产力。例如一个篮球队,每个球员的投篮命中率都很高,但并不意味着这个球队就能赢。可以从一段时间内团队解决突出问题的能力、所解决问题的难度等方法来考察团队效率。

152. 代码行数取决于编程语言

- 使用高级语言的效率一般会明显高于低级语言,例如500行Ada代码比500行汇编代码能实现更多的功能

153. 相信设定的时间表

1. 一旦制定好了计划和排期,就需要所有员工相信这个时间表的可行性,否则就很难成功。
2. 一个建议就是让工程师们来设定时间表,这种方法并非总是可行;另一个建议就是让工程师参与到时间表的制定中来,尤其需要在功能点、排期、放弃项目之间作出选择和折衷时。

154. 精心计算的成本估计也不是万无一失的

- 即使一个精心设计的估算模型得出的估算结果也未必准确。这里面有三个因素:(1)你 (2)假设条件 (3)概率。你的领导能力对结果影响很大,你可以在5秒钟摧毁你们团队1年建立起来的文化;你在进行估算时所作的前提假设可能会使结果不准确,如果你的需求变更了呢,如果你的核心成员生病了呢;估算结果只是整个概率分布中的一个峰值,其发生也是需要一定概率的。

155. 定期评估时间表

- 每个阶段的工作完成后都应该重新评估时间表,如果有delay的话,则需要及时调整时间表,不要妄图在后面的阶段追赶上来,否则只会让delay升级。试想如果每个阶段的期限都错过了,那么最后留给测试的时间就会越来越少,这样就可能出现两种不可避免的结果:(1)交付一个质量没有保障的产品,(2)客户在项目后期才了解到产品出现了很大的delay。因此,应该及时与业务方或上级领导沟通,告知他们排期的变动,并商讨对策,以降低损失。

156. 轻微的低估并非总是坏事

- 轻微的低估导致的轻微的delay能够督促队员更加努力赶上进度,从而提高生产力。注意,不能是明显的低估成本!

157. 合理分配资源

- 人为约束的时间表和不合理的预算都会导致项目的失败,不要试图压缩时间表和预算,否则最终可能需要付出比正常情况更多的成本。

158. 详细地计划项目

- 每个项目都需要一个计划,计划的详细程度取决于项目的复杂程度。项目越复杂,则需要计划越详细。
    - 用PERT(Program Evaluation and Review Technique)图来表示各任务之间的依赖关系
    - 用GANTT甘特图来显示每个任务的开始时间、结束时间及中间进度
    - 设立一系列可实现的阶段性目标(milestones)
    - 设定文档和代码编写规范
    - 合理的人员及任务分配

159. 及时更新你的计划
160. 避免驻波

- 出现问题(delay、需求变更)及时调整计划和排期,不要等问题越积越多,越变越大

161. 了解十大风险

1. 人员短缺
2. 不切实际的时间表
3. 不理解需求
4. 构建了一个糟糕的用户界面
5. 试图增加用户不想要的feature
6. 没有把握需求变化
7. 缺少可重用的接口或组件
8. 缺少可外部执行的任务
9. 响应时间太差
10. 试图超过当前的计算机技术

162. 预先了解风险

- 项目初期,可以列出那些可能出现的比较大的风险,并量化其影响以及其出现的概率。可以构建一棵决策树来描述可能的风险以及规避/降低风险的方法。

163. 选择合适的开发模型

- 软件开发模型有很多:瀑布模型、抛弃型原型、增量开发、螺旋模型、业务原型等,但没有哪种模型能适用于所有项目。模型的选择可以基于公司文化、风险意愿、应用领域、需求波动以及对需求的理解程度等因素。

164. 方法并不能拯救你

- 方法并不是万能的。如果一个团队以前使用结构化的方法无法开发出高质量的软件,那么现在用面向对象的方法还是无法开发出高质量的软件,因为方法并不是问题。

165. 没有奇迹般地提高生产力的秘诀

- 不要轻信市面上那些宣称能够将生产力提高50%、75%甚至100%的工具、语言或方法。

166. 理解进度的含义

- BCWP(Budgeted cost of work performed),衡量目前已经完成的工作的预算
- ACWP(Actual cost of work performed),衡量目前的实际开销
- BCWE(Budgeted cost of work expected),衡量你预期的开销
- (BCWP-BCWE)/BCWE, 技术状态,大于0表示进度提前,小于0表示进度delay
- (BCWP-ACWP)/BCWP, 预算状态,大于0表示低于预算,小于0表示高于预算

167. 通过偏差进行管理

- 很多项目经理在汇报进度时总是花很多时间讲述他们做的有多好,这些话应该留在项目结束后的荣誉时刻再说。项目进行中,应该着重汇报实际进展与初始计划的偏差,这样注意力和资源才会被用于解决问题。

168. 不要过度紧张你的硬件

- 硬件的确会对软件产生很大的影响,当你有充足的硬件资源时,可以忽略这条原则;当你需要争取每一块内存、每一个cpu时,记得要相应调整软件开发的时间表。

169. 乐观看待硬件的发展
170. 悲观看待软件的发展
171. 认为灾难是不可能的想法往往导致灾难

- 过度自信往往是很多灾难的原因。

172. 要进行项目的事后分析

- 每个项目进行过程中都会遇到各种各样的问题,项目结束后应该及时检讨、总结经验教训,以期下次做的更好!

refer:《201 principles of software development》