软件开发中的201条原则
本文翻译自《201 principles of software development》
基本原则
- 质量第一。
- 不同的人对软件质量的定义不同。对于开发者来说,质量意味着优雅的设计和编码;对于高压环境下的用户来说,质量意味着响应时间和容量;对某些客户来说,质量意味着满足所有已知的和未知的需求…一个项目的质量取决于它需要优先考虑的事以及各参与方。
- 产量和质量不可兼得。产量越高,则质量越低。贝尔实验室发现,要想实现每千行代码只有1~2个bug,那么每人月只能产出150~300行代码。
- 高质量的软件是可以实现的。大型的软件系统是能以很高的质量构建的,但必须以很高的价格:每行代码$1000。作为开发者,要知道那些能够提高软件质量的技术:多与用户沟通,构建原型,保持设计简单,检查,雇佣最优秀的人…
- 不要试图改造软件质量。就好比不要试图把一个测试原型(throwaway prototype)转化成产品一样。
- 可靠性差比效率低更可怕。
- 尽早交付产品。可以尽快构建一个原型,让用户体验,一方面能早日收集到反馈,一方面可以验证需求,便于后续开发出令用户满意的产品。
- 与客户/用户沟通。人和环境都可能发生变化,跟进变化的唯一方法就是交流。
- 目标对齐:开发人员与客户的目标需要一致,尤其是在产品功能的优先级上。
- 先做一个测试原型(throwaway prototype), 不要试图第一次就做好一个完整的系统,尤其是面对一个新的产品和领域时。
- 13.
- 27.
- 35.
- 37.
需求分析
软件设计
- 把需求文档转变成设计文档并不是一件容易的事儿
- 跟踪每一个需求,记录每一个需求的设计及实现情况(可以用表格来记录)
- 评估每一种方案,从中选出最好的
- 没有文档的设计不算真正的设计
- 封装,便于维护以及隔离错误
- 不要重复造轮子
- KISS(Keep It Simple, Stupid)原则,即 保持简洁和单纯
- 避免过多的特殊情况:如果需要考虑的特殊情况太多,说明设计或算法有问题
- 最小化计算机世界与现实世界的距离,即 在选择模型和方法时尽可能地模拟现实世界
- 保持设计的智能可控性,本质上是倡导分层设计,并提供多种视角 (->80)
- 保持概念完整性,包括数据结构的组织、模块间的通信方式、错误警告等等的一致性。即使是由多个人开发的系统,看起来也像一个人开发的一样。
- 概念错误比语法错误更重要、更难以排查。
- 松耦合,高内聚
- 为改变而设计,即 所作的设计必须能够适应变化的需求:模块化、可移植、可扩展、贴近现实(69)、智能可控(70)、概念完整(71)
- 设计需要考虑后期维护。就可维护性而言,架构的选择比算法和代码更重要。
- 设计需要考虑错误情况。尽量做到:
有些具体方法可以帮助提高设计的鲁棒性:1. 不引入错误 2. 引入的错误很容易检测到 3. 部署后依然存在的错误要么不重要,要么能够避免执行过程中出现灾难性问题
1. 不要遗漏任何一种状态,例如一个变量有4种取值,不要只检查其中的3种 2. 预测尽可能多的“impossible”的情况,并给出恢复策略 3. 为了排除引发灾难的情况,使用故障树(fault tree)分析预测不安全的情形
- 构建通用性软件。通用性强的软件/组件一般运行会稍微慢一些。
- 构建灵活性软件。灵活性强的组件一般比通用性强的组件运行更高效。
- 使用高效的算法。要求设计者了解各种算法并能够进行算法复杂度分析。
- 模块规格说明应该包含用户需要的所有信息,不要加入用户不需要的任何信息。即模块化。
- 设计是多维度的,包括 打包(packaging, what is part of what), 分层(hierarchy, who needs whom), 调用(invocation, who invokes whom), 流程(processes)…
- 伟大的设计来自于伟大的设计者
- 了解你的应用程序,例如压力环境下的预期行为、输入频率、响应时间、天气的影响…等等
- 可以复用一些模块或组件,因为复用的成本小且效率高。
- 对于软件来说”garbage in, garbage out”是不正确的。对于无效的输入,软件应该给出智能的回应,指出为什么无效;并且不应该进行处理,而是返回错误码,避免错误向后扩散。
- 可以通过冗余来实现软件可靠性:
- 并行策略,例如mr任务,每个分片的job都会起多个相同的、互不影响的task,如果其中一个跑完了,就可以kill掉其他的了
- 冷备,当主控机器出现硬件故障时,可以启动备用机器继续提供服务
软件的高可靠性需要很高的代价,有时针对一套需求可能需要提供两套设计方案。
编码原则
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》