软件工程 — 程序员修炼之道

程序员修炼之道:通向务实的最高境界(第2版)

相比第一版,书中大约三分之一的主题是全新的,而其余的大部分都被部分或全部重写了。作者再版的目的是,让内容变得更清晰、更贴切,并在某种程度上不受时间的影响。所以可以直接阅读第二版了。

务实的哲学 #

务实的程序员的特质是什么?是他们面临问题时,在解决方案中透出的态度、风格及理念。他们总是越过问题的表面,试着将问题放在更宽泛的上下文中综合考虑,从大局着想。他们的另一个成功点是他们为所做的一切负责。

务实的编程源于务实思考的哲学。

人生是你的 #

软件开发在任何职业列表中,绝对是你自己最能掌控的职业之一。

我们的技能供不应求,我们的知识不限于地域,我们可以远程工作。

我们收入颇丰。我们真的能做我们想做的任何事情。

你有权选择

你的工作环境很糟糕?你的工作很无聊?尝试纠正它。不过不要一直尝试下去,你还可以改变组织,或是让自己换一个组织。

如果你的技术过时了,安排时间学习一些看起来有趣的新东西,这是一种自我投资,只有为此加班才是合理的。

想要远程工作?要求过了吗?如果他们说不行,就去找个说行的人。

我的源码被猫吃了 #

一切行为都要对自己负责,也意味着自己要承担责任。

提供选择,别找借口

给出选择,而不是找借口。不要说搞不定;解释一下要做些什么才能挽回这个局面。

软件的熵 #

不要放任破窗

破窗理论:如果一间房子的一扇窗户破了,运转良好的干净系统会迅速恶化,紧接着,一扇又一扇窗户都会因缺乏维护而破掉,从而使得整个房子变得破败不堪。

不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个就赶紧修一个。如果没有足够的时间完全修好,那么就把它钉起来。也许你可以注释掉那些糟糕的代码,显示一行“尚未实现”的信息,或用假数据先替代一下。采取行动,预防进一步的损害发生,表明一切尽在你的掌握中。

石头做的汤和煮熟的青蛙 #

做推动变革的催化剂

学习士兵。

牢记全景

够好即可的软件 #

将质量要求视为需求问题

知道何时止步。

知识组合 #

对知识组合做定期投资

  • 每年学习一门新语言
  • 每月读一本技术书
  • 还要读非技术书
  • 上课
  • 加入本地的用户组和交流群
  • 尝试不同的环境
  • 与时俱进

批判性地分析你读到和听到的东西

问自己几个值得思考的问题:

  • ask 5 why
  • 谁从中受益
  • 有什么背景
  • 什么时候在哪里可以工作起来
  • 为什么这是个问题

交流 #

英语就是另一门编程语言

说什么和怎么说同样重要

把文档嵌进去,而不要拴在表面

注释源码。

务实的方法 #

优秀设计的精髓 #

优秀的设计比糟糕的设计更容易变更

关注易于变更的能力。

DRY – 邪恶的重复 #

在一个系统中,每一处知识都必须单一、明确、权威地表达。

Don’t repeat yourself

重复的现象有很多:

  • 代码中的重复
  • 文档中的重复
  • 表征的重复
  • 内部 API 的重复
  • 外部 API 的重复
  • 数据源引起的重复
  • 开发人员间的重复

让复用变得更容易

正交性 #

“正交性”是从几何学中借用来的术语。若两条直线相交后构成直角,它们就是正交的。在计算科学中,这个术语象征着独立性或解耦性

排除不相关事物之间的影响

有几种技术可以用来保持正交性:

  • 保持代码解藕
  • 避免全局数据
  • 避免相似的函数

可逆性 #

不设最终决定

放弃追逐时尚

曳光弹 #

你肯定看过那些人们用机关枪射击的电影、电视节目和电子游戏?在这些场景中,会经常看到子弹在空中留下明亮的轨迹线。这些线条来自曳光弹。

和普通弹药间隔着一起被压入弹夹。当曳光弹发射时,上面的磷就会被点燃,在枪和击中物之间留下一道烟火轨迹。如果曳光弹击中了目标,那么之后的常规子弹也会击中。士兵们使用这些曳光弹来调整他们的瞄准:这是一种务实的方法,可以在真实条件下提供实时反馈。

曳光弹式开发和项目不会结束这种理念是一致的:总有东西需要改,总有新功能需要加。这是一个逐步递增的方法。为了在编码中获得相同的效果,我们会找一些东西,能让我们快速、直观、可重复地从需求中得到最终系统的某个方面。

使用曳光弹找到目标

使用曳光弹代码有很多优势:

  • 用户可以更早地获得能工作的东西
  • 开发者构造了一个可以在其中工作的框架
  • 你有了一个集成平台
  • 你有可以演示的东西
  • 你对进度有更好的感觉

原型与便签 #

使用原型学习

当制作一个原型时,哪些细节可以忽略:

  • 正确性:你可以在适当的地方使用替代数据。
  • 完整性:原型只需要满足有限的功能,可能只有一个预先选好的输入数据片段及单个菜单选项。
  • 健壮性:错误检查可以不完整,甚至完全没有都行。如果你偏离了预定的航线,原型机很可能烧毁在绚丽的烟火中——那又如何!
  • 格式:原型代码可能并不需要太多注释和文档(尽管围绕从原型中获取的经验,可能会产生大量文档,但是相对而言,原型系统本身的文档要少得多)。

领域语言 #

靠近问题域编程

估算 #

通过估算来避免意外

根据代码不断迭代进度表

这可能不受管理人员的欢迎,他们通常在项目开始之前就想要一个简单可靠的数字。你必须帮助他们去理解,进度是由团队、团队的生产力和环境综合决定的。明确了这一点,把提炼进度表作为每次迭代的一部分,你就可以估算出能力范围内最精确的进度安排。

基础工具 #

纯文本的威力 #

将知识用纯文本保存

Shell 游戏 #

发挥 shell 命令的威力

加强编辑能力 #

游刃有余地使用编辑器

版本控制 #

共享目录绝非版本控制!

永远使用版本控制

调试 #

去解决问题,而不是责备

最容易欺骗的人就是自己。

Don’t Panic

遇事不要慌。。

修代码前先让代码在测试中失败

读一下那些该死的出错信息

“select” 没出问题

不要假设,要证明

文本处理 #

学习一门文本处理语言

工程日记 #

写日记

务实的偏执 #

你无法写出完美的软件 #

契约式设计 #

通过契约进行设计

契约驱动测试。

死掉的程序不会说谎 #

尽早崩溃

断言式编程 #

使用断言去预防不可能的事情

如何保持资源的平衡 #

有始有终

在局部行动

不要冲出前灯范围 #

我们无法看到遥远的未来,离照射轴越远,就越黑暗。

小步前进 —— 由始至终

避免占卜

很多时候,明天看起来会和今天差不多,但不要指望一定会这样。

宁弯不折 #

如何编写只弯曲却不会折断的代码。

解藕 #

解藕代码让改变更容易

只管命令不要询问

这个原则说的是,不应该根据对象的内部状态做出决策,然后更新该对象。

不要链式调用方法

当你访问某样东西时,尽量不要超过一个“.”。

“一个点”规则有一个很大的例外:如果你链式调用的东西真的不太可能改变,那么这个规则就不适用。

避免全局数据

如果全局唯一非常重要,那么把它包装到 API 中

在现实世界抛球杂耍 #

有限状态机 #

观察者模式 #

发布/订阅 #

响应式编程与流 #

变换式编程 #

所有程序其实都是对数据的一种变换:将输入转换成输出。然而,当我们在构思设计时,很少考虑创建变换过程。相反,我们操心的是类和模块、数据结构和算法、语言和框架。

编程讲的是代码,而程序谈的是数据

不要囤积状态,传递下去

继承税 #

你想要一根香蕉,但得到的却是一只拿着香蕉的大猩猩,甚至还有整个丛林。– 乔·阿姆斯特朗

有点“智商税”的意思。

继承就是耦合。

不要付继承税

更好的替代方案:

  1. 接口与协议
  2. 委托
  3. mixin 与特征

尽量用接口来表达多态

用委托提供服务:“有一个”胜过“是一个”

利用 mixin 共享功能

Django-Rest-Framework 是比较典型的例子。

配置 #

使用外部配置参数化应用程序

并发 #

打破时域耦合 #

时间对我们来说有两个重要的方面:并发性(在同一时刻发生的多件事情)以及次序(事情在时间轴上的相对位置)。

通过分析工作流来提高并发性

共享状态是不正确的状态 #

你坐在最喜欢的餐厅。吃完主菜,问男服务员还有没有苹果派。他回头一看——陈列柜里还有一个,就告诉你“还有”。点到了苹果派,你心满意足地长出了一口气。与此同时,在餐厅的另一边,还有一位顾客也问了女服务员同样的问题。她也看了看,确认有一个,让顾客点了单。

总有一个顾客会失望的。

随机故障通常是并发问题

角色与进程 #

用角色实现并发性时不必共享状态

黑板 #

使用黑板来协调工作流

当你编码时 #

听从蜥蜴脑 #

蜥蜴脑即本能。

倾听你内心的蜥蜴

首先,停止正在做的事情。给自己一点时间和空间,让大脑自我组织。远离键盘,停止对代码的思考,做一些暂时不需要动脑筋的事情——散步、吃午饭、和别人聊天,或是先睡一觉。让想法自己从大脑的各个层面渗透出来:对此不用很刻意。最终这些想法可能会上升到有意识的水平,这样你就能抓住一个“啊哈!”的时刻。

如果这不起作用,就试着把问题外化。把正在写的代码涂画到纸上,或者向你的同事(最好不是程序员)解释一下是怎么回事,向橡皮鸭解释一下也行。

但也许在试过这些方法后,还是无法脱困。那么就该行动了。我们需要告诉大脑:打算要做的事情,并没有那么重要。可以用做原型的方式来干这件事。

巧合式编程 #

不要依赖巧合编程

首先判别什么是巧合,并对此形成识别模式,其次决策依据里不能有巧合和假设。

算法速度 #

评估算法的级别

对估算做测试

重构 #

何时该重构:

  • 重复,发现了一处违背 DRY 原则的地方。
  • 非正交设计
  • 过时的知识
  • 使用:当系统在真实的环境中被真实的人使用时,你会意识到,与以前的认识相比,一些特性现在看来更为重要,反而“必须拥有”的特性可能并不重要。
  • 性能
  • 通过了测试

尽早重构,经常重构

对编码测试 #

测试与找 Bug 无关

测试是代码的第一个用户

既非自上而下,也不自下而上,基于端对端构建

为测试做设计

要对软件做测试,否则只能留给用户去做

基于特性测试 #

使用基于特性的测试来校验假设

出门在外注意安全 #

务实的程序员有相当多的偏执。我们知道自己有缺陷和限制,外部攻击者会抓住每个我们留下的漏洞去破坏系统。特定的开发和部署环境,将有其自己的围绕安全性的需求,但是你应该始终牢记一些基本原则。

将攻击面的面积最小化

系统攻击面的面积指,攻击者可以在其中输入数据、提取数据或调用服务执行的所有访问点的总和。

保持代码简洁,让攻击面最小,代码复杂性滋生攻击载体

输入数据是一种攻击载体

未经身份认证的服务成为攻击载体

经过身份认证的服务成为攻击载体

输出数据成为攻击载体

调试信息成为攻击载体

最小特权原则

在最短的时间内使用最少的特权。换句话说,不要自动获取类似root 或 Administrator 这样的最高级别权限。如果真需要这么高级别的权限,那就去申请,获得权限后只做最少量的工作,然后迅速放弃权限,这样可以降低风险。

安全的默认值

例如,密码输入的默认设置可能是隐藏输入,用星号替换每个字符。

敏感数据要加密

不要将个人身份信息、财务数据、密码或其他凭据,以纯文本的形式保存在数据库或其他外部文件中。如果数据被泄露,加密提供了额外的安全级别。

维护安全更新,尽早打上安全补丁

了解一些密码学常识。

事物命名 #

名字有着深刻的含义。

在计算机科学中只有两件难事:缓存失效和命名。

好好取名;需要时更名

项目启动之前 #

需求之坑 #

无人确切知道自己想要什么

程序员帮助人们理解他们想要什么

需求是从反馈循环中学到的

和用户一起工作以便从用户角度思考

策略即原数据

使用项目术语表

如果用户和开发者使用不同的名称来称呼相同的事物,或者更糟糕的是,使用相同的名称来指代不同的事物,那么项目就很难取得成功。

处理无法解决的难题 #

不要跳出框框思考:找到框框

携手共建 #

康威定律:设计系统的架构受制于产生这些设计的组织的沟通结构。

结对编程

群体编程

不要一个人埋头钻进代码中

敏捷的本质 #

敏捷不是一个名词,敏捷有关于你如何做事。

敏捷软件开发宣言

我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:

个体和互动 高于 流程和工具

工作的软件 高于 详尽的文档

客户合作 高于 合同谈判

响应变化 高于 遵循计划

也就是说,尽管右项有其价值,我们更重视左项的价值。

务实的项目 #

务实的团队 #

维持小而稳定的团队

团队的工作不应仅致力于开发新功能,还可能包括:

  • 旧系统的维护
  • 流程的反思与精炼
  • 实验新技术
  • 学习和提升技能

派上日程以待其成

组织全功能的团队

椰子派不上用场 #

人们容易受到诱惑,掉入货物崇拜的陷阱:通过投资去造出神奇的外观,希望吸引来潜在有效的魔法。但与最初发生在美拉尼西亚的货物崇拜一样,用椰子壳制成的赝品是替代不了真机场的。

做能起作用的事,别赶时髦

在用户需要时交付

务实的入门套件 #

使用版本控制来驱动构建、测试和发布

尽早测试,经常测试,自动测试

直到所有的测试都已经运行,编码才算完成

使用破坏者检测你的测试

测试状态覆盖率,而非代码覆盖率

每个 Bug 只找一次

一个 Bug 一旦被人类测试员发现,这就应该是它被该人类测试员发现的最后一次。要立即修改自动化测试,以便这个特定的 Bug,从此往后每次都被检查到 —— 不能有任何例外,无论它多么琐碎,也无论开发者有多少抱怨,或是不停唠叨“哦,永远不会再发生了”。

不要使用手动程序

人不像电脑那样具有可重复性。我们也不应对此抱有期望。shell 脚本或程序将一次又一次地以相同的顺序执行相同的指令。

一切都要依赖自动化。

取悦用户 #

作为开发者,我们的目标是取悦用户。

用户真正要的不是代码,他们只是遇到某个业务问题,需要在目标和预算范围内解决。他们的信念是,通过与你的团队合作,能够做到这一点。

取悦用户,而不是只交付代码

如果你想取悦客户,就和他们建立起某种关系,这样即可积极地帮助他们解决问题。

傲慢与偏见 #

在作品上签名

保持匿名会滋生粗心、错误、懒惰和糟糕的代码,特别是在大型项目中 —— 很容易把自己看成只是大齿轮上的一个小齿,在无休止的工作汇报中制造蹩脚的借口,而不是写出好的代码。

#

先勿伤害

请问自己:我自己愿意成为这个软件的用户吗?我希望分享自己的详细信息吗?我希望自己的行踪被交给零售店吗?我愿意乘坐这辆自动驾驶汽车吗?做这件事我能心安吗?

有一些创造性的想法,开始打道德行为界限的擦边球,如果你参与了这类项目,就和出资者一样负有责任。

不要助纣为虐

你正在为自己和子孙后代建设未来——这是你的职责所在,去创造一个让所有人心向往之的宜居未来。当你做的事情违背了这个理想时,要敢于承认,并有勇气说“不!”对可以拥有的未来充满憧憬,才有动力去创造它。即使是空中楼阁,也要每天为它添砖加瓦。

我们都有精彩的人生。

本文共 5525 字,上次修改于 Apr 8, 2023
相关标签: 软件工程, 读书笔记