0.前言

这些原则,或者又称为启发式方法或经验法则,它们都是为了寻找一条通往问题真解的道路,是殊途同归的。这些法则可以指导你理解当前所在的系统,使你集中精力寻找正确的解决方案,并凸显出可能有潜在问题的方面。

1.一般性原则

  • 保持一个系统的运行比你破坏它之后再去修复它要容易得多
  • 尽可能将错误从运行时移至编译时
  • 没有文档的程序没有价值
  • 注释不应该重述代码显而易见的作用
  • 注释应该通过描述意图来提升可维护性
  • 头文件中的所有内容都应该被至少两个源文件引用
  • 通过消除干扰和中断,开发人员的生产力将得到显著提高
    • 小隔间工位里的开发人员可能不是很有效率,观察他们如何处理干扰和中断就知道了
  • 系统的复杂性呈指数级增长
    • 问题的难度每增加 25%,代码量就会增加 100%
    • 没有人能够吃透一个几百万行的程序
  • 自然界中的最佳状态总是处于一个平衡的零界点,最佳状态不会出现在一个极端点
  • 过去的经验可以对当下提供良好的审视,然而囿于经验无法激发一个有灵感的设计
  • 作为一个经验法则,你每花 1 个小时在预防缺陷上,就会减少 3 - 10 个小时的维护时间

2.设计

  • 复杂的系统都是由最初那个仅仅可以工作的简单系统演变而来的
    • 一开始就设计得很复杂的系统永远不会也不可能成功,你必须从一个有效的简单的系统开始
  • 如果你不能用简洁的语言来描述你的行为,那么使用代码也不能
  • 将复杂的问题分解成更小的子问题
    • 如果一个问题可被分解成 2 个或以上的可被独立解决的子问题,那就先解决子问题
    • 在实现并测试了子问题的解决方案后,将各部分组合去解决更大的问题
  • 一个函数在概念上应该只执行一个任务
  • 不要解决不存在的问题
  • 解决具体的问题,而不是一般性的问题
  • 设计一个系统复杂如航天器时需要付出无数的努力,这就是为什么要考虑系统出错时如何控制的原因
  • 设计是一个迭代的过程,必要的迭代次数总是比你已做了的迭代次数还多 1 次,在任何时候都是如此
  • 从来没有一个正确的解决方案,然而总是会有好多个错误的
  • “更好”是“足够好”的敌人
  • 改进设计的关键点是在接口交互层,同时这也往往是产生糟糕设计的地方
  • 研究发现,软件开发总成本的 40% 到 50%,都是对缺陷的需求、设计和代码进行返工
    • 在最坏的情况下,一旦软件投入使用,重新设计的成本通常是初始设计阶段的 50 倍到 200 倍

3.费用

  • 软件是昂贵的
    • 研究表明,一行商业软件代码的成本是 15-30$,如果成本增加到每行 40$ 那么你可以得到相对健壮、设计良好的业务领域代码,当你的成本为每行 15$ 时请警惕你的代码质量
  • 如果你想降低软件开发成本,请审视每一份需求文档,粗暴地砍掉功能
  • 大量的功能意味着缓慢的进度和昂贵的开发费用
  • 非经常性工程(NRE)的成本必须在每单个产品中摊销
    • 通过减少功能来节省 NRE 成本
    • 通过将软件功能卸载到硬件中来节省 NRE 成本
    • 软硬件的划分应该在设计早期进行评估
    • 通过更快的交付产品来节省 NRE 成本
  • 彻底重写那 5% 有问题的的函数比修复现有的实现更容易也更便宜
    • 维护这 5% 有问题的函数是维护其他函数成本的 4 倍
    • 也许第一次设计的时候真搞砸了,但是只要找出那些蹩脚的例程并将它们扔掉重新开始,那么我们依然能剩下一大笔钱

4.排期

  • 你似乎永远都没有足够的时间去把它作对,但却总是有足够的时间一次又一次地重做(重温大一时《计算机科学与技术的发展与展望》课上老教授的这句话!)
  • 当估算排期以天这样粗的粒度而不是以小时这样细粒度的时候,往往会产生项目的延迟
    • 当开发者不把日历时间和工程时间分开时,排期的灾难是不可避免的
  • 如果进度表幻化出的人员利用系数远远超过 50%,那么项目就会按比例的延迟(沟通成本?)
    • 数据显示,开发者平均只有 55% 左右的时间从事新产品工作,其他的常规活动几乎消耗了一半的时间
  • 我们往往没有预见开发过程中的难点问题
    • 我们对大多数项目的进度估计都很糟糕,有 80% 的嵌入式系统都会延迟交付,专家认为项目平均消耗的开发工作量是最初预算的 2 倍
  • 5% 的函数消耗了 80% 的调试时间
    • 大多数项目都沉浸在调试周期中,而调试周期往往占据进度表的一半
  • 时间线的增长速度远超于固件的大小 – 代码增加 1 倍时交付日期增加 2 倍以上
  • 前 90% 的代码占了前 90% 的开发时间,剩下的 10% 的代码占了另外 90% 的开发时间(几乎 2 倍的时间)
  • 当把旧代码移植到新项目中时,如果有超过 25% 的代码被修改,就不会有太大的进度提升(此时应该直接重写?)
  • 想要系统运行负载达到 90%,这比运行负载为 70% 需要 2 倍的开发时间,而 95% 的负载需要 3 倍的开发时间
    • 当内存只剩下几个字节的时候,即使微不足道的功能也可能需要数周时间,因为开发人员必须重写大量的代码以释放内存/CPU周期
  • 你制定的计划表就像一部完整的小说,直到你因为不能完成它而被顾客解雇之前
  • 有时候,把所有东西都扔掉重新开始是最快的方法(解释了开发人员总倾向于重新开发而不是在老系统上迭代?)
  • 当前正在稳步推进的好计划,胜过下周的完美计划

5.软件复用

  • 倾向于使用已经被他人复用验证过的、经过审查的代码
    • 使用 STL 而不是自己编写容器
  • 倾向于简单、标准的通信协议,而不是定制的通信协议
  • 遵循”三次规则“:允许复制和粘贴代码一次,但当同一代码复制三次时,应将其提取成公共函数
  • 在考虑构建一个用来复用的包时,它必须被重复使用至少 3 次
    • 软件的应用范围和领域知识是我们需要去了解的,在我们实际使用过重复的代码之前,我们不会把它泛化到可以真正复用的程度
  • 复用最好是在大段代码中进行 – 考虑复用整个驱动程序或库,而不仅是函数

6.优化

  • 过早的优化是浪费时间
    • 程序优化的第一条规则:不要做
    • 程序优化的第二条规则(只适合专家!):先别做
  • 只有在对代码进行剖析,确定问题区域后,才能对代码进行优化
    • 瓶颈也许发生在令人惊讶的地方,所以在你没有证明那是瓶颈之前最好不要试图猜测并去改动
    • 应该忘记那无关紧要的小效率提升,然而也不应该放弃那关键的 3%,一个好的程序员会仔细观察并确定关键代码
  • 80% 的资源被 20% 的业务使用
  • 90% 的程序时间用于执行 10% 的代码
  • 算法优化比微观的优化影响更大
    • 真正的效率提升来自于改变算法的复杂度
  • 不要为了感觉到的效率提升而牺牲清晰度,特别是这个效率提升还未被数据证明

7.危险信号和问题领域

  • 当你害怕改变一个函数时,就应该从头开始重写那段代码(最近我就是这样做了 :))
  • 重复的代码是设计不良或编程习惯不好的表现,它必须被消除
    • 重复使维护变得困难
    • 这个规则对代码量不多的情况依然适用
  • 尽可能地避免共享资源
  • 消除全局变量!

8.功能点(FP)经验法则

  • 一个项目中注入的 bug 的大概数量:FP^1.25
  • 手动代码检查会发生大约 65% 的 bug,对于严谨的团队这个数字要高得多
  • 项目中的人数大概是 FP/ 150
  • 与项目有关的纸质文件的大约页数:FP^1.15
  • 每个测试策略会发现存在的 30% 左右的 bug
  • 以月为单位的时间表约为:FP^0.4
  • 项目发布后维护项目所需的全职人数:FP/750
  • 从设计到编码阶段,需求增加约 2%/月
  • 将要创建的测试用例的大约数量:FP^1.2

9.硬件设施

  • 使用硬件加速器来减少 CPU 算法可以降低耗电量
  • 将讨厌的实时硬件功能分解成独立的 CPU
    • 中央控制器每秒需要处理中断太多?将各设备分区到各自的控制器
  • 只要能简化软件就增加硬件
    • 这将大大降低NRE和软件开发成本,但代价是增加BOM成本
    • 增加额外的硬件可以减少系统负载
  • 当硬件完美工作时,真正重要的访客不会出现(指系统的核心场景将会给硬件带来挑战和感知?)

参考出处