Skip to content

程序员的README

内容提要

对于刚刚成为软件工程师的新手来说,知道如何编写代码只是成功了一半。你可能很快就会发现,学校并没有教授在现实世界中至关重要的技能和工作中必要的流程。本书恰恰填补了这一环节,它是作者十多年来在大型公司指导初级工程师工作的教程,涵盖软件工程的基础知识和最佳实践。

本书第1~2章讲解当你在公司开启你的职业生涯时会发生什么;第3~11章会扩展你的工作技能,教你如何使用现有代码库、解决和防止技术债、编写生产级软件、管理依赖关系、有效地测试、评审代码、交付软件、处理On-Call时的事故和构建可演进的架构等;剩余章节涵盖管理能力和职业阶梯的提升等相关内容,例如敏捷计划、与管理者合作以及成长为资深工程师的必经之路。本书中非常重要的一部分内容是教你如何应对糟糕的管理,以及如何调整自己的节奏。

本书内容不仅浅显易懂,还覆盖整个软件开发周期,是一本技术主管希望每名新入行的工程师在开始工作之前都能阅读的书。

${200+}^{-}$

推荐序

就职于一家软件公司,对于刚毕业的学生或已经拥有几年工作经验的软件工程师来说,都可能是令人望而生畏的。

对于刚毕业的学生来说,他们可能只经历过一些规模较小的项目,所以大概会对技术概念和大规模的企业代码感到不知所措。这本书是一本极好的入门级教程,有助于他们从软件工程专业的学生转变为独当一面的工程师,通过公司特定的流程,使用特定的工具来编写高质量、生产级、可测试的代码。

对于拥有几年工作经验的软件工程师来说,他们可能会在新的工作方式、工具和流程上遇到挑战,更不用说与新人协作时的社交难题了。随着软件开发行业的与时俱进,工程师之间也可能存在技术上的差距。这本书是一本非常好的用于自我精进的教程,尤其除第1章和第14章外,每章末尾都有“升级加油站”,读者可以按图索骥,找到更匹配自己的内容。

人类是具有习惯的生物。虽然软件工程师可能会被一些人视为特殊人群,但他们也可能会陷入习惯中。作为一名软件工程师,不断地学习和发展至关重要,不管当前的经验或职位怎样,这本书对每一名志在从事软件开发工作的人员都很有用。一名成熟的软件开发者的标志是打破固有的习惯,批判性地回顾旧代码,发现瑕疵,做到自省,且为没多做些什么而感到羞愧。

在本书的前几章中,“提问”就被高度重视,这让我感到非常高兴。向同事提问和学习是快速成长和习得新技能的有效方式。为你的工作成果感到自豪通常是件好事,但当自己在持续改进和交付中比以前做得更好时,你应该优先感到自豪。

这本书针对如何改进、如何学习、如何推进职业生涯发展,以及如何成为一名更好的开发者提供不同的方法和步骤。这本书包含适应团队的工作流程、处理会议、如期交付、善用学习工具和技术领域的最佳实践,并指导人们如何成为团队中有价值的成员。
向资深工程师寻求帮助可能会让人感到一丝畏惧,因为打断他们的工作通常是不妥的。资深工程师往往在聚精会神工作时会因为被打扰而失去在脑海中已构建的系统的短期记忆,但大多数资深工程师都很愿意提供帮助,优秀的资深工程师更是以指导和协助他人为荣。如果你在寻求帮助时感受到了敌意,大可不必心烦意乱,因为这在每个 人身上都可能发生。不要让某一次糟糕的遭遇阻断了你再次寻求帮助的欲望。但总是打断他人的工作并非是合适的,这时就可以使用这本书中涵盖的其他策略和原则,它们都可以指导你将职业生涯提升到新的阶段。

无论你处于职业生涯的哪个阶段,这本书都非常实用。请保持开放的心态,好学深思,渴望提高,不惧破旧习,不惧提问题。

雅克·奥洛夫松(Jerker Olofsson)

索尼移动公司前核心架构师

译者序

本书的两位作者在最后一章中说:“你可以为任何行业做出贡献,从科学到农业、健康、娱乐,甚至是太空探索。”我一直都认为软件工程师的工作带来的成就感并不亚于小说家、建筑师或音乐家。软件开发本身带来的欣喜是一种“尤里卡时刻”的幸福体验,尤其是在无数个面对调试器“狂按”F10键的夜晚,自己提交的特性成功上线时的喜悦,抑或是眼看着自己设计的技术架构方案从纸面上的框图到代码实现再到最终交付的满足感。

当然在软件行业工作也有艰辛,变动的需求、阻滞的沟通、糟糕的管理和紧迫的工期,总有些时刻会令人产生倦怠。毕竟真实世界中并不存在只和机器进行沟通的工作,更多的还是人与人之间的交流。本书能够很好地帮助刚刚踏入这个行业的工程师去认识真实世界。似乎从来都是程序员为他人撰写README,从来没有人为程序员撰写过README。

由于工作的关系,我个人不仅参与过采用瀑布流模型的传统项目,也参与过极度灵活多变的研发项目,以及采用改良后的Scrum框架的敏捷项目。虽然各自采用的开发模型不尽相同,但是遇到的问题都很相似。在实际的工作中,成功地交付软件并不是只有唯一的路径可走。如何衡量与之匹配的需求、成本和风险才是考验团队的地方。本书中,两位作者提到了软件开发过程中的诸多困境,在我曾经的工作中也遇到过类似的情况。至今我还记得曾经有一次由于共享的数据层竞合而引发的生产环境故障,整个团队高负荷地工作了一周才最终解决。而在应对这起故障的过程中,团队明明在非常积极地处理,却引起了客户的极大不满。让客户不满的并不是故障本身,而是出了故障之后得不到应急方案导致了长时间的服务器宕机。如果那个时候能采用本书中提到的事故处理的标准流程(分流、协同、应急方案、解决方案和后续行动),那么很多弯路就可以避免了。
翻译完本书的最后一章时,正好是我踏入软件行业的12年整,此时的我却觉得自己的职业生涯好像才刚刚开始。

在翻译本书期间,首先要感谢我的朋友托老师,在我犹豫要不要接受翻译这本书带来的挑战时,是托老师“一脚把我踹进了翻译的大门”,并帮我辨析了许多汉语和英语在表意细节上的差异。接下来要感谢Stacy分享了许多关于专业的商业文案写作的经验,快速地纠正了我在书面语言表达中的不良习惯。然后要感谢就职于硅谷“大厂”的移动端工程师Lide,他提供了一种兼容东西方软件工程文化的视角,每当我遇到技术领域的专有名词没有办法贴切地翻译时,他总是能提出非常精妙的思路。最后要感谢有多年海外生活经验的Allen和ARK,译文中有多处涉及美式俚语,是他们耐心地向我解答这些俚语的日常使用场景。另外Lide、Allen、ARK都是我从小逮蜻蜓、丢口袋、“摇街机”时就认识的好朋友。没想到在这个年纪,我们会因为我翻译了一本书而隔空聚在一起。仔细想想这是我的幸运。

最后要着重感谢几位同样优秀的工程师Jeff、Hobbes、Silver和Jerker,他们现在依然奋斗在各自的业务一线,他们的工作场景几乎涵盖了本书的各个章节,同时也意味着涵盖了软件工程的全部流程。他们用各自业务领域的鲜活案例和经验为我解答了诸多疑惑。尤其是Jeff让我看到了科学和人文融合的领域会散发出多么强烈的人格魅力。

前言

此时的你刚接任新的工作,已经做好了万全的准备,正蓄势待发地迎接一切难题,希望通过优雅的代码施展才华。多么激动人心!祝贺你!我们希望你能轻松地应对有趣的挑战,与优秀、机敏且激情满满的同事一起工作,并构建出有用的东西。

但你很快就会发现,或者说你已经发现了,知道如何编写代码——也就是如何使用计算机去解决问题,仅仅是“战斗的一半”。它是你技能包中一个关键的部分,然而要成为一名高效的软件工程师,你还需要那些学校里没有教授过的技能。而本书将会教你这些技能。

我们将解释构建、测试和运行生产软件的现代实践,并阐释那些可以使团队更强大和使队友更默契的行为和方法。我们会给你一些实用的建议,诸如如何获得帮助、如何撰写设计文档、如何维护旧代码、如何On-Call(待命)、如何规划你的工作以及如何与你的管理者和团队互动。

本书并不包含你需要知道的所有内容,因为这是一项不可能完成的任务,还会让你读起来很疲劳。相反,我们把重点放在那些计算机科学课程并没有涉及的主题上。这些主题通常都需要深入了解。如果你想了解更多信息,我们在许多章的结尾放置的“升级加油站”包含推荐的内容,以供阅读。

本书第1~2章介绍你初入一家公司开启职业生涯时应该知道的内容。第3~11章拓展你的职业技能:编写生产级别的代码、高效地测试、代码评审、持续集成和部署、撰写设计文档和进行技术架构上的最佳实践。第12~14章涉及软技能,诸如敏捷计划、与你的管理者协作以及职业生涯规划。

这是一本有态度的书。本书中构建团队的经验取自那些快速成长的、由风险投资公司资助的或者准上市的硅谷公司。你所在的环境可能会有所不同,但是没关系。公司与公司之间总会有些差异,但基本原理总是相通的。
本书是我们希望你在职业生涯刚起步时就拥有的书,同时也是一本送给那些新加入团队的工程师们的书。读到最后,你就会知道成为一名专业的软件工程师都需要什么。让我们开始吧!

致谢

由衷地感谢我们的编辑Athabasca Witschi。没有她,本书就不会是现在的样子。感谢Kim Wimpsett在文字编辑上提供的帮助,感谢Jamie Lauer的校对,感谢Bill Pollock、Barbara Yien、KatrinaTaylor以及来自No Starch的其他成员在本书的写作过程中对两名新手提供的指导。

感谢我们的评审人员:Joy Gao 、Alejandro Crosa 、JasonCarter、Zhengliang (Zane) Zhu以及Rachel Gita Schiff。你们的反 馈非常珍贵。感谢Todd Palino 对运维相关章节的反馈,感谢Matthew Clower对第6章进行了详尽的校对并提出了坦诚的意见,感 谢Martin Kleppmann和Pete S kom or och 供指导,感谢Tom Hanley、Johnny Kinder和Keith Wood对管理相关章节提出的反馈。

如果没有我们的雇主和管理者的支持,我们不可能完成本书。感谢Chris Conrad、Bill Clerico、Aaron Kimball和Duane Valz让我们得以在这个项目上一展身手。

第1章 前面的旅程

你作为一名软件工程师的旅程将跨越你的整个职业生涯,沿途将有许多站点——学生、工程师、技术负责人,甚至可能是管理者。大多数新入行的工程师在开始时都有技术基础,但没有什么实质上的经验。本书前面的章节将会引导你走向职业生涯的第一个里程碑。当你能够安全地交付代码并与你的团队无缝协作时,你就会到达这个里程碑。

到达第一个里程碑是很困难的。因为你需要的信息会散落在互联网上,或者更糟糕的是,隐藏在某人的脑袋里。本书整合了成功所需的关键信息。但是一名成功的软件工程师究竟是什么样子的呢?以及如何成为一名成功的软件工程师呢?

1.1 你的目的地

每个人都是从入门级工程师开始做起的。如果想晋级,你就需要具备下面几个核心领域中所需要的能力。

技术知识:你知道计算机科学的基础知识。你知道如何使用集成开发环境(IDE)、构建系统、调试代码和测试框架。你熟悉持续集成、系统指标和监控、配置和打包系统。你积极主动地创建和改进测试代码。在做架构决策时,你会考虑到长期运维。

执行力:你通过用代码解决问题来创造价值,并且你了解你的工作和业务之间的联系。你已经可以构建并部署中小型的特性。你会编写、测试和评审代码。你分担On-Call的职责,调试运维问题。你是积极主动并且可靠的。你参加技术讲座、阅读小组、面谈和路演。

沟通能力:你能同时以书面和口头的形式进行清晰的沟通。你能够有效地给予和接受反馈。在模棱两可的情况下,你会主动寻求帮助并得到明确的结果。你能以建设性的方式提出问题和定义课题。你在可能的情况下可以提供帮助,并开始影响同事。你会文档化你的工作。你撰写清晰的设计文档并征求反馈意见。在与他人打交道时,你富有耐心和同理心。

领导力:你能在指定的工作范围内独立地完成工作。你能迅速地从错误中学习。你能很好地处理变动和模糊的问题。你积极参与到项目和季度的规划中。你能帮助新的成员融入团队。你可以向你的管理者提供有意义的反馈。

1.2 你的旅程地图

要想到达你的终点,你需要一张地图。本章的其余部分将帮助你导览全书和你的职业生涯初期。我们将从“新手营”开始,因为那里是所有新手出发的地方。之后沿着“试炼之河”漂流,来到编写代码并学习规范和流程的地方。接下来去“贡献者之角”,在这里发布一些有意义的特性。发布特性意味着你将不得不在“运维之海”的风暴中扬帆航行。最后,我们会在“胜任之湾”这个安全港着陆。

我们对很多段落都提供了注释。你可以从头至尾地通读本书,也可以直接跳到你最关心的章节。我们有意让许多注释在下文中多次出现。各章是按照主题来进行分类的,但我们所涉及的主题将会贯穿你的整个职业生涯,常读常新。

1.2.1 新手营

你以一名新手的身份开启了你的旅程。你需要熟悉公司、团队,以及如何完成本职工作;参加入职会议;设置你的开发环境和系统权限,并弄清楚团队的常规流程和会议;阅读文档并与队友进行讨论。如果你在入职过程中发现了漏洞,你可以在文档中做出一些补充。

为了帮助你顺利地开展工作,你的公司可能会有一个新人入职培训。这些培训课程会让你了解公司是如何运转的,带领你参观整个组织,并介绍一些公司领导。新人入职培训还会向你介绍其他部门的员工,那些是你未来的同事。如果你的公司没有安排新人入职培训,那需要你自己去向你的管理者要一份公司的“组织架构图”,了解清楚谁负责什么,谁向谁汇报,都有哪些不同的部门,以及它们之间的关系。记得做好笔记。

坎宁安定律和自行车棚效应

我们建议你在团队中用文档记录下会议的内容、入职流程和其他口口相传的东西。你会得到很多的评论和纠正。不要把这些评论和纠正当作针对你个人的批评。重点并不是要写一份完美的文档,而是要写得足够多,以引发讨论,充实细节。这是坎宁安定律的一个应用,该定律认为:“在互联网上获得正确答案的最好方法并不是提出问题,而是发布错误的答案。”

过度集中在细枝末节上的讨论总是会很冗长,这种现象被称为“自行车棚”(bike-shedding)效应。自行车棚效应是西里尔·诺思科特·帕金森的一则寓言故事,该寓言描述了一个被指派到发电厂对该发电厂的设计方案进行评审的委员会的故事。对该委员会来说,因为发电厂的设计方案过于复杂,以至于无法讨论出什么实际的内容,所以他们花了几分钟就批准了这些计划。然后,他们又花了45分钟来讨论发电厂旁边的自行车棚的材料问题。“自行车棚效应”在技术类的工作中经常出现。

有些公司会有额外的新手教程来帮助你获得系统权限、构建开发环境、检出和编译代码。如果没有这样的教程,正好借此机会去创建一份。写下所有你在构建环境时所做的事情。(参见第2章“步入自觉阶段”。)

你应该被分配到一个小任务去学习如何修改代码并将其安全地发布到生产环境。如果没有的话,就主动寻找或要求去做一些有用的小幅改动。这些改动一定要小,甚至可以小到只更新一行注释。这样做的目的是让你去了解那些步骤,而不是去打动谁。(参见第2章“步入自觉阶段”和第8章“软件交付”。)

设置你的代码编辑器或IDE。IDE要和你团队的保持一致。如果你还不了解团队所使用的IDE,就去网上找一个教程。学习IDE将为你以后节省出大量的时间。配置你的IDE去适配团队的代码规范,搞清楚代码规范里都有什么以及怎样才能符合这些规范。(参见第3章“玩转代码”。)
确保你的管理者邀请你参加团队和公司的会议,诸如站会、迭代计划会、项目总结会、全员会议等。如果你的管理者有一对一面谈的习惯的话,提醒他们给你安排一个。(参见第12章“敏捷计划”和第13章“与管理者合作”。)

1.2.2 试炼之河

一旦你完成了新手任务,你将开始为团队分担真正的工作。你可能会在一个现有的代码库上工作。你发现的东西可能会使你感到困惑或害怕。多提问,并经常让团队评审你的工作成果。(参见第3章“玩转代码”和第7章“代码评审”。)

在你的成长过程中,持续学习是至关重要的。了解如何编译、测试和部署代码。阅读那些提交代码的请求和代码评审意见。不要害怕询问更多的信息。多报名参加技术讲座、午餐会、阅读小组、导师计划,诸如此类。(参见第2章“步入自觉阶段”;第5章“依赖管理”;第6章“测试”和第8章“软件交付”。)

现在是时候与你的管理者建立一些联系了。了解他们的工作风格,理解他们的期望,并与他们谈谈你的目标。如果你的管理者有一对一面谈的习惯,那么你要期待有几场这样的谈话。管理者通常都希望跟踪事情的进展,所以问问他们后续如何沟通。(参见第13章“与管理者合作”。)

你第一次被邀请参加的计划会议,通常是迭代计划会议。你也可能参加项目总结会或全员会议。要了解一下路线图和开发计划的全貌。(参见第12章“敏捷计划”。)

1.2.3 贡献者之角

一旦你着手开发大一些的任务和特性就意味着你进入了“贡献者之角”。团队会信任你能更独立地完成工作。学习如何编写生产级别的代码,使这些代码对运维者友好。恰当地管理组件间的依赖关系,并进行完备的测试。(参见第3章“玩转代码”;第4章“编写可维护的代码”;第5章“依赖管理”和第6章“测试”。)
现在你也应该去帮助队友。参与到代码评审中去,做好队友会询问你的想法和反馈的准备。因为你的团队可能会忘记你是最近才加入的,所以你一旦感到困惑,就请提出问题。(参见第2章“步入自觉阶段”;第7章“代码评审”和第10章“技术设计流程”。)

大多数公司都会有季度规划和目标设定的周期。参与到团队的计划中去,并与你的管理者一同制定目标或OKR(目标和关键成果)。(参见第12章“敏捷计划”和第13章“与管理者合作”。)

1.2.4 运维之海

当你参与到更大的任务中时,你将会学到如何向客户交付代码。在交付过程中会发生很多事情:测试、构建、发布、部署和展开。完善这个过程需要一些技巧。(参见第8章“软件交付”。)

在你的修改生效之后,你将不得不去运维这个软件。运维工作中你会面临很大的压力并且需要勇气。客户会因为软件的不稳定而受到影响。你需要使用监控指标、日志和跟踪工具来实时调试软件。这时你也可能需要参与轮流的On-Call。接触运维工作会让你清楚地了解那些代码如何在客户的手中发挥作用。同时你也要学会保护你的软件。(参见第4章“编写可维护的代码”和第9章“On-Call”。)

1.2.5 胜任之湾

你的团队现在将依靠你来负责一个小项目。你需要撰写一份技术设计文档并帮助团队进行项目规划。设计软件将迫使你面临全新级别的复杂度。不要满足于你的第一版设计。反复斟酌,要随时做好准备,因为你的系统会随着时间的推移而不断变化。(参见第10章“技术设计流程”;第11章“构建可演进的架构”以及第12章“敏捷计划”。)
你工作中的早期光芒已经消散。你在系统架构、编译环节、部署环节以及测试环境中都看到了不足之处。你要开始学习在必要的维护和重构中间寻找平衡。不要试图重构一切。(参见第3章“玩转代码”。)

你可能也对团队的工作流有见解。写下你的观察,哪些是有效的,哪些是无效的,然后与你的管理者一对一地谈谈你的想法。(参见第13章“与管理者合作”。)

现在是设定长期目标和评估绩效的时候了。同管理者一起理解这个过程,并从同事那里获得反馈。同管理者谈谈职业上的志向、未来的工作、项目和想法。(参见第13章“与管理者合作”和第14章“职业生涯规划”。)

1.3 前进!

你现在已经有了初学者旅程的地图和目的地。在“胜任之湾”着陆后,你将成为一名成熟的软件工程师,此时的你能够与你的团队合作,贡献价值。本书中的其他部分将引导你的职业生涯。接下来,我们的旅程即将开始。

第2章 步入自觉阶段

马 丁·M.布罗德威尔在其文章《为学而教》( “Teaching for Learning”) 中定义了能力的4 个阶段:“ 无意识的无能力”(unconscious incompetence) 、“ 有意识的无能力”(conscious incompetence),“有意识的有能力”(conscious competence)和“无意识的有能力”(unconscious competence)。具体说来,无意 识的无能力意味着你无法胜任某项任务,并且没有意识到这种差距。有意识的无能力意味着你虽然无法胜任某项任务,但其实已经意识到了其中的差距。有意识的有能力意味着你有能力通过努力完成某项任务。最后,无意识的有能力意味着你可以很轻松地胜任某项任务。

所有的工程师都是从前两个阶段开始的。即使你对软件工程了如指掌(这不太可能),你也必须学习公司的那些具体操作流程和规范。你还必须学习实用的技能,正如本书中所涉及的那些。你的目标是尽快到达第三个阶段。

本章的大部分内容将阐释如何自主学习和如何获得帮助。校外学习是一种技能。我们会为如何养成独立自主的学习习惯提供一系列建议。我们还将准备一些提示,方便你在“万事都求人”和“独行侠”之间取得平衡。本章的最后将讨论冒充者综合征和邓宁-克鲁格效应,这可能会导致新工程师感到自信不足或自信爆棚,而这两种情况都会限制他们的成长。我们将解释如何自省并遏制这两种极端情况。在避免落入自我怀疑和过度自信的陷阱的同时,练习独立学习并提出有效的问题将会使你迅速地到达第三个阶段。

2.1 学习如何学习

学习将帮助你成为一名合格的工程师,并在未来的日子里持续进步。软件工程领域在不断地发展。无论你是一名刚毕业的学生还是一名经验丰富的老手,如果你不学习,你就会落后。

本节将列举各种各样的学习方法。切勿试图同时去做本章中列出的所有事情!因为那样会让你感到倦怠。切记善用个人的时间——虽然说持续进步非常重要,但是把所有清醒的时间都花在工作上是不健康的。应该根据你的现实情况和自然倾向,从下面的方法中选择。

2.1.1 前置学习

在工作的前几个月里,你要学习一切如何运作。这将有助于你参与设计讨论、On-Call轮换、解决运维问题和评审代码。前期的学习会让你感到不舒服,因为你总会希望尽快将软件上线,而花时间阅读文档和摆弄工具却会让你慢下来。别担心,大家都预料到你会需要些时间来成长。前置学习是一项有价值的投资,许多公司会专门为新员工设计学习课程。Facebook公司就有一个著名的为期6周的被称作“boot camp”的新手工程师训练营。

2.1.2 在实践中学习

前置学习并不意味着要整天坐在那里阅读文档。在实践中学到的。你应该上手编写并 且发布代码。第一次发布代码很可怕。要是你弄坏了什么东西怎么办?但是管理者们通常不会把你放在一个可以造成严重破坏的环境中(尽管有时新员工需要在别无选择的情况下从事高风险的任务)。尽你所能去理解你的工作会造成的影响,并以适当的谨慎程度行事。与变更高流量数据库上的索引相比,编写单元测试可以不那么谨慎,从而更快。

克里斯删除所有代码的那一天

在克里斯的第一次实习中,他与一名高级工程师搭档做项目。克里斯完成了一些修改,并需要部署它们。这名高级工程师向他演示了一下如何将代码并入他们所使用的版本控制系统(VCS)。克里斯按照说明盲目地执行了涉及分支(branch)、标记(tag)和合并(merge)的相关步骤。紧接着他继续完成了当天的其他工作,然后就回家了。第二天早上,克里斯兴高采烈地逛了一圈,和大家打招呼。大家尽了最大的努力做出善意的回应,可情绪却很低落。当克里斯问起发生了什么事时,他们告诉克里斯,他破坏了整个VCS代码库。公司的所有代码都丢了。大家整晚都没睡,拼命地恢复那些能恢复的东西,最终找回了大部分的代码(除了克里斯提交的和其他几个)。克里斯被整件事吓坏了。他的管理者把他拉到一边,告诉他不要担心:克里斯和高级工程师做了正确的事情。错误难免会发生。每名工程师都有类似的故事。尽你所能,努力理解你在做什么,但要知道这种事情总会发生。

错误是不可避免的。成为一名软件工程师的路途艰辛,我们有时会失败。这几乎是所有人都知道的事情。降低系统风险并使这些错误不那么致命是你的管理者和团队的工作。如果你失败了,也不要被击垮:写下经验教训,然后继续前行。

2.1.3 运行实例代码

运行实例代码可以真正地了解代码的工作原理。文档可能会过期,同事们也会忘记某些事情,但是实例代码是安全的,因为你可以在生产环境之外运行它们,而且非生产环境的实例会允许使用一些具有侵入性的技术。例如,你知道某个方法被调用了,但无法确定它是如何触达的。你可以通过抛出一个异常,输出一串堆栈跟踪信息,或者附加一个调试器来查看调用层级。

调试器是你运行实例代码时最好的朋友。你可以用它来暂停正在运行的代码,然后查看运行中的线程、堆栈信息和变量的实际值。添加一个调试器,触发某个事件,并单步执行代码,就可以查看该事件是如何被处理的。
虽然调试器很强大,但有时了解一个软件行为的最简单的方法是在关键位置输出几行日志或在控制台输出。你可能对这种方法很熟悉,只是要注意,在复杂的情况下,特别是在多线程的应用程序中,输出调试信息可能会产生误导。因为操作系统会使用缓冲的方式将内容写到标准输出接口,这样会让你在控制台中看到的信息有延迟。而且多个线程都在向这个接口写入信息,这样输出的信息就会交织在一起。

一个看起来有些笨拙但非常实用的方法是在程序执行的入口处输出一行特殊的语句。这样你就可以很容易地知道现在运行的程序究竟是你修改过的,还是原来的旧版本。你会节省出那些跟踪程序“谜之行为”的时间。“谜之行为”通常都是由正在被调用的程序是旧版且你修改的内容没有生效造成的。

2.1.4 阅读

请每周都花一部分时间去阅读。可供阅读的内容有很多:团队文档、设计文档、代码、积压的任务票、书籍、论文和技术网站。不要试图一下子把所有东西都读完。请从团队文档和设计文档入手。这些 文档会就事情是如何组合在一起的给你一个整体的概念。要特别注意那些关于如何权衡取舍和背景的讨论。接下来你就可以深入研究那几个与你最初任务相关的子系统了。

正如罗恩·杰弗里斯所说“代码从不说谎。注释有时却会”(Code never lies. Comments sometimes do,更多细节可以参见作者的个人博客),去读源代码,因为它并不总是与设计文档相吻合!不要只读你自己的代码库,还要去阅读高质量的开源项目,特别是那些你使用的类库。不要像阅读小说一样从前到后地通读代码:请利用你的IDE来浏览代码。为关键的操作绘制控制流和状态图。仔细研究代码的数据结构和算法。注意那些临界值的处理。留意那些惯用写法和风格,也就是去学习“本地方言”(local dialect)。

在“任务票”(ticket)或“问题点”(issue)中跟踪未完成的工作。阅读团队的任务票,看看每个人都在做的事情以及即将发生的事情。
那些积压的工作也是寻找新手任务的好地方。旧的任务票大概分为三大类:不再相关的,有用但次要的,以及过于重大且无法立刻解决的。弄清楚你正在看的任务票属于这几类中的哪一类。

出版物和在线资源是互补关系。阅读书籍和论文是深入研究某个主题的很棒的途径。出版物大多很可靠,只是有些过时。在线资源则正好相反,不那么可靠,但很能跟上潮流。在实施黑客新闻(hackernews)中的最新想法之前要记得“踩下刹车”,因为采用保守一些的技术选型是有益处的。(在第3章中有更多关于这个问题的介绍。)

加入一个阅读小组来跟进学术界和工业界的最新进展。一些公司可能有内部的阅读小组,去问问看。如果你的公司没有,可以考虑成立一个。你也可以加入当地的“Papers We Love”组织,这些组织会定期地阅读和讨论计算机科学方面的论文。

学习阅读代码

在职业生涯的早期,德米特里被分配了一个传统的Java应用程序,并被要求“搞定它”。他是团队中唯一对Java比较熟悉的人,而他的管理者想改进一些特性。源代码中充满了……奇特之处。所有的变量都使用了类似于a、b、c这样的命名。更糟糕的是,一个方法中的a会在另一个方法中变成d。这份源代码既没有更新履历,也没有测试代码。最初的开发者早已离开。不用说,这肯定是一个雷区。

每当德米特里需要改变代码库中的某些东西时,他都会屏蔽所有的杂念,仔细阅读代码,重新命名变量,跟踪代码逻辑,在纸上画一些东西,并进行实验。这是个缓慢的过程。德米特里越来越欣赏这个代码库了。该代码库正在做复杂的事情。他开始对写这个东西的人有些敬畏,因为这个人可以在没有合理的变量命名的情况下,把所有的东西都记在脑子里。最后,在午餐时,德米特里流露出了敬佩之情。他的同事看着他,仿佛他长出了第二个脑袋。“德米特里,我们没有原始的代码。你正研究的东西是反编译器的输出结果。正常人是不会这样写代码的!”我们不建议用这种方式来学习阅读代码。但是,孩子们,这种经历让德米特里学会了慢慢来,为了理解而阅读,而且永远都不会相信变量名称。

2.1.5 观看讲座

你可以从一个优秀的讲座中学到许多东西。从过去录制的视频演示开始,包括公司内部演示和外部的YouTube视频。观看教程、技术讲座和阅读会议简报(conference presentations)。四处打听打听,找到好的内容。你通常可以用1.5倍速甚至2倍速观看视频,以节省时间,但不要被动地观看。你需要做笔记来帮助记忆,并学习任何不熟悉的概念或术语。

如果你的公司提供午餐会(brown bag)和技术讲座,就去参加。这些非正式的演讲一般是在现场举办的,所以很容易就可以参加。它们也是你公司内部的活动,所以你会获得真正有价值的信息。

2.1.6 适度地参加会议和聚会

会议和聚会非常有利于建立联系和发现新的想法。它们值得偶尔参加,但不要过度。那些有价值的内容与所有内容的比例——也就是信噪比,通常都很低,而且许多会议之后都可以在网上获得。

会议大致有3种类型:学术会议、草根兴趣小组聚会和供应商展示会。学术会议有很棒的内容,但阅读论文和参加小型的、更有针对性的聚会通常会更好。对于想获得实用技巧和想会见有经验的从业者的人来说,那些基于兴趣的聚会非常好,可以找几个这样的聚会去参加一下。供应商展示会一般是较大和较吸引眼球的。它们是大型科技公司的营销工具,但不适合学习。与你的同事一起参加这种展示会很有趣,但每年超过一次就可能是在浪费时间了。问问周围的人,找到那些比较好的。请记住,有些雇主会支付门票、旅行和住宿的费用。

旁听学术聚会

几年前,德米特里和他的同事彼得·阿尔瓦罗正在努力地使他们的数据仓库发挥作用,他们认为将聚合任务分发给廉价服务器集群是一个好办法。在他们的研究中,彼得发现谷歌最近发布了关于MapReduce的论文。强大的谷歌公司正在做彼得和德米特里想做的事情!他们发现了更多有趣的论文,并想找人深入探讨一下。德米特里发现加州大学伯克利分校的数据库小组在举办技术方面的午餐会并对公众开放。彼得和德米特里成了那里的常客(他们小心翼翼地不吃免费的比萨饼,直到所有的学生都吃饱了)。他们甚至可以偶尔地参与到对话中。

他们的学习进入了高速发展阶段。最终,彼得永远留在了这里,他开始学习这里的博士课程,现在是加州大学圣克鲁兹分校的教授。德米特里也跳到了加州大学的研究生院。在他们离开两年后,昂贵的数据仓库被Hadoop所取代,这是一个开源的分布式聚合系统。

如果你开始觉得你不再有学习进展了,可以去当地大学看看。他们有大量向公众开放的项目。扩大你的圈子,接触新的想法。去上研究生是一个可选项。

2.1.7 跟班学习并同有经验的工程师结对

跟班学习是指在另一个人执行任务时跟着他。跟随者是一个积极的参与者:他做笔记并提出问题。跟随一名高级工程师是学习新技能的好方法。为了获得更大的收益,你应该在整个跟班学习过程的前后安排时间进行计划和回顾。

当你准备好了,就把角色调换过来。让一名高级工程师跟随你。和你一样,他应该提供反馈。如果出了问题,他也会充当一个安全网。这是一种温和的方式,它可以帮助你轻松面对可怕的情况,例如面试。

结对编程(pair programming)也是一种很好的学习方式。两名工程师一起写代码,轮流打字。这需要一些时间来适应,但这是相互学习最快的方式之一。这种技术的倡导者还声称,它可以提高代码质量。如果你的队友愿意,我们强烈建议你尝试一下。结对编程也不仅仅是针对初级工程师的,所有级别的队友都可以从中受益。
一些公司也鼓励向非工程角色进行跟班学习。跟随客户支持部门和销售演示部门学习是一种可以开阔眼界的方式,这样做可以了解你的客户。写下并分享你的观察,与你的管理者和高级工程师一同优先考虑由经验激发出来的想法。

2.1.8 用副业项目实践

从事副业项目会让你接触新的技术和想法。当你只有自己工作时,你可以跳过那些被称为“软件工程”的环节(测试、运维、代码评审等)。忽略这些方面可以让你快速地学习新技术,只是不要忘记在工作中还有那些“真实的”环节。

你也可以参与开源项目。大多数开源项目欢迎所有人贡献力量。这是一种学习和建立职业联系的好方法。你甚至可以通过开源社区找到未来的工作。请记住,这些项目通常是由志愿者运营的。不要指望你能获得和工作中同样的周转速度。有时人们会很忙,他们会消失一段时间。

不要根据你认为你需要学习的领域来选择项目。找到你有兴趣去解决的问题,并使用你想学习的工具来解决这些问题。一个可以从内激励你的目标会让你更长时间地参与,你也会学到更多。

公司一般会对外部的工作有规定,询问你公司的政策。不要使用公司资源(你的公司提供的笔记本计算机)来从事副业项目,不要在工作中从事副业项目,避免那些与你公司有竞争的副业项目。确认你是否可以在工作中或在家里为开源项目做贡献,有些公司会希望你只用指定的工作账户提交代码,有些公司则会希望你只使用个人账户。了解你是否保留对你的副业项目的所有权。问问你的管理者,你是否需要得到批准。从长远来看,获得明确的信息将保护你免受挫折。

2.2 提出问题

所有的工程师都应该提出问题,这是学习的一个重要部分。新手工程师会担心打扰队友而试图自己解决所有问题,这样做既慢又没有效果。有效地提出问题将帮助你快速地学习,而不会烦扰其他人。使用这3个步骤:做研究,提出明确的问题,并恰当地安排解决你的问题所需的时间。

2.2.1 动手调查一下

尝试自己寻找答案。即使你的同事知道答案,你也要付出努力,这样你会学到更多。如果你没有找到答案,当你寻求帮助时,你的调查仍然会成为你的起点。

不要只是在互联网上搜索。信息还存在于文档、内部论坛、自述文件(README)、源代码和错误跟踪器中。如果你的问题是关于代码的,试着把它变成一个可以演示的单元测试。你的问题有可能曾经被别人问过:查看邮件列表或聊天记录。你收集的信息将会引领你到那些可测试的方案中去。如果你找不到任何线索,试着自己通过实验来解决它。记录下你在哪里寻找过,你做了什么,为什么这么做,发生了什么,以及你学到了什么。

2.2.2 设置一个时间限制

限制你研究一个问题时预期花费的时间。在你开始研究之前就应该设定好时间限制,这样可以鼓励你遵守这个限制,防止收益递减(研究最终会拖累生产性)。考虑你最终何时需要知道答案,然后留出足够的时间来提出问题,得到回答,并根据你学到的东西采取行动。

一旦你到达了设定的时间限制,就需要请人帮忙。只有在你取得良好进展的情况下才可以超过之前的时间限制。如果你已经超过了第一个时间限制,那么需要再设定一个。如果你在第二个时限之后仍然找不到确定的答案,就应该及时止损并寻求帮助。及时止损需要自律和练习,因为你要对自己负责。

2.2.3 写下全过程

在提出问题时描述你已经知道的情况。不要只是分享你的原始笔记。简要地描述你所做的尝试和发现,这表明你已经花了很多时间去试图自己解决这个问题。这样做也会给别人一个回答你的起点。

下面的例子是一种糟糕的提问方式。

嗨,艾丽斯。

你知道为什么testKeyValues在TestKVStore中会失败吗?要重新运行这个真的拖慢了我们的构建速度。

谢谢!

潘卡

这让艾丽斯几乎没什么可说的。这听起来隐约像是潘卡在责怪艾丽斯,这可能不是他的本意。这种表述有一点儿懒惰。将上面的表述与下面的表述进行比较。

嗨,艾丽斯。

我在调查为什么testKeyValues在TestKVStore中会失败时遇到了一些麻烦(在DistKV的代码库中)。肖恩建议我来问问你。希望你能帮助我。

在我看来,这个测试差不多每执行3次就会失败1次。这似乎是随机的。我试着单独运行它,但还是失败了,所以我认为这不是测试用例与测试用例之间的问题。肖恩在他的计算机上循环运行了这个测试,但仍无法重现它。我在源代码中没有看到任何明显的东西能解释这个测试失败的原因。这似乎是某种竞争条件。有什么想法吗?
有人告诉我这不太可能影响到生产环境,所以并不十分紧急。但是,每次发生这种情况,拍打测试都会花费我们20到30分钟,所以我很想知道如何解决这个问题。我附上了显示测试失败的日志和我当前所有的环境设置,以备不时之需。

谢谢!

潘卡

在第二个例子中,潘卡给出了一些背景,描述了问题,告诉艾丽斯他已经尝试了什么,然后才请求帮助。他还指出了影响和紧急程度。第二个例子非常简洁,但又附有详细的信息,所以艾丽斯不需要主动去捕获这个问题。艾丽斯会帮助潘卡解决问题。她同时会记住,潘卡是可靠的。像这样的请求将建立潘卡在同事眼中的可信度。

写第二种邮件需要更多的努力,但这是值得的。请把它应用到工作中。

2.2.4 别打扰别人

就像你一样,其他人也在努力完成工作,他们需要专注。当他们进入状态时,不要打扰他们——即使问题很简单,即使你心里清楚他们知道答案,即使你的工作被卡住了。除非有重大问题发生,真的,请不要打扰他们。

公司有不同的惯例来标识“请勿打扰”。耳机、耳塞或耳罩是通用的标识。关于“休息区”(lounge space)的解读有一些混乱,有些人认为在办公桌以外的地方工作是“神圣不可侵犯”的,他们不想被人发现;有些人则将在共享空间内的工程师理解为“可以打扰”。请确保你了解你公司的惯例!
走上前去与人交谈会迫使他们做出反应。即使他们只是回答说很忙,你也已经打断了他们,使他们失去了注意力。如果你需要的人很忙,同时你又不想自己的工作进程被卡住,这时候你就需要找到一种异步的沟通方式。

2.2.5 多用“非打扰式”交流

在网络通信中,组播(multicast)是指将消息发送到一个组而不是个人目标;异步(asynchronous)是指可以稍后处理的消息,而不需要立即响应。这些概念也适用于人们之间的通信。

发出你的问题,方便大家可以在自己的位置上(异步)做出回应(组播)。以一种大家都能看到的方式来发出问题,这样当你得到帮助的时候就很显眼。解决办法也会变成可被发现的,所以其他人以后也能找到当时讨论的内容。

这通常意味着需要使用群发邮件列表或群聊(例如,德米特里的公司有一个叫作#sw-helping-sw的频道)。即使你需要一个特定的人来回答问题,也要使用共享论坛。你可以在帖子中提到他们的名字。

2.2.6 批量处理你的同步请求

聊天和电子邮件对简单的问题很实用,但复杂的讨论很难异步进行。面对面的交流是“高带宽”和“低延迟”的。你可以快速地解决很多问题。不过,这依然有代价。打断你的同事会影响他们的工作效率。要想避免出现这种情况,可以同你的技术领导或管理者约定专门的时间来解决非紧急的问题。

安排一次会议,或者使用“办公室答疑时间”(如果他们预留了的话)。写下你的问题并保留到会议上。在此期间,你可以继续做你的调查。随着其他问题的出现,你的清单也会越来越长,这很好。在你设置的会议议程中应包括这个清单,不要只靠脑袋来记问题,也不要事前不做功课就来参加。
如果你已经没有问题了,就请取消会议。如果你发现自己反复地取消会议,就自省一下这种会议是否还有用。如果已经没用了,就不要再安排。

2.3 克服成长的障碍

知道如何学习以及如何提出问题还不够。你还必须避开那些会减缓你成长的障碍。一般有两个常见的障碍会影响许多工程师,即“冒充者综合征”和邓宁-克鲁格效应。如果你了解这些现象是什么,以及如何克服它们,你会成长得更快。

2.3.1 冒充者综合征

大多数新手工程师在开始工作时处于“有意识的无能力”阶段。有很多东西需要学习,而其他人似乎早已遥遥领先。你可能会担心你不属于这个行业,或者找到工作只是运气好。对自己苛责很容易,我们也有过这样的经历。无论我们多么频繁地告诉工程师他们做得很好,有些人就是不相信。即使他们升职了,还是不信!这让他们感到很不舒服。他们说他们只是很幸运,他们不值得别人认可,或者是升职标准太宽松了。这就是冒充者综合征。保利娜·罗斯·克朗斯博士和苏珊娜·阿门特·艾姆斯博士在1978年的一篇名为《高成就女性中的冒充者现象:动态与治疗干预》( “The Impostor Phenomenon in High Achieving Women: Dynamics and Therapeutic Intervention” )的 研究文章中首次描述了这种现象。

尽管有着杰出的学术和职业成就,经历着冒充者现象的女性仍然坚持认为她们真的不聪明,而且还愚弄了任何不这么想的人。众多的成就似乎并不影响冒充者的信念,而这些成就本身就是能充分证明智力水平卓越的客观证据。

如果这能引起你的共鸣,你就知道自我怀疑很常见。只要努力,这些感觉就会过去。你可以用几种策略来推动事情的发展:觉知(awareness)、重塑(reframing)以及与同事交谈。

冒充者综合征会自我强化。每一个错误都会被看作能力匮乏的证明,而每一项成功都是优秀“冒充者”冒充的证据。一旦某个人进入了这个循环,就很难摆脱它。觉知会在下面的场景里帮助你。如果你注意到了上文的循环,你可以有意识地打破它。当你取得一些成就的时候,那是因为你真真切切地做到了,你并不只是运气好。
不要忽视赞美和成就。即使是小事情,也要把它们写下来。你的同行都是有能力的人,如果他们说一些积极的话,那是因为他们确实有充分的理由这样做。练习重塑消极的想法:“我不得不求助达里亚来帮助解决软件上的竞争条件难题”变成“我联系了达里亚,现在我知道了如何解决竞争条件难题!”。规划你要完成的任务,并注意你实现了目标的时候。这将帮你建立信心。

获得反馈也有助于缓解冒充者综合征。请你尊敬的人来告诉你,你做得怎么样。这个人可以是你的管理者、导师,或者只是你仰慕的工程师。重要的是你要信任他们,并觉得与他们谈论自我怀疑是安全的。

治疗可能也会有帮助。可以利用治疗来获得你的优势,并克服短期的挑战。冒充者综合征,以及可能随之并发的焦虑和抑郁,是一个复杂的话题。如果你仍在苦苦挣扎,考虑接触几名治疗师,找到一个 适合你的方法。

2.3.2 邓宁-克鲁格效应

与冒充者综合征相反的是邓宁-克鲁格效应。这是一种认知偏见,人们认为自己比实际情况更有能力。处于“无意识的无能力”阶段的工程师不知道自己不知道什么,所以他们不能准确地评估自己和他人的表现。他们太自信了。他们总是到处批判公司的技术栈,抱怨代码的质量,贬低设计。他们确信自己的想法是正确的。他们的默认模式是直接回绝或无视反馈。拒绝所有的建议会亮起一盏巨大的红灯:完全自信标志着盲点。

幸运的是,邓宁-克鲁格效应在新手工程师中并不常见。有许多方法可以对抗它:有意识地培养好奇心;对犯错持开放态度;找到一位受人尊敬的工程师,询问他你做得怎么样,并真正地倾听;讨论设计决策,尤其是那些你不同意的决策,问问为什么会做出这样的决策;培养一种权衡利弊的心态,而不是非黑即白的心态。

2.4 行为准则

2.5 升级加油站

戴夫·胡佛和阿德瓦莱·奥希尼亚合著的《软件开发者路线图:从学徒到高手》( Apprenticeship Patterns: Guidance for the Aspiring Software Craftsman,已由机械工业出版社于2010年引进出版)是一个伟大的“模式”(pattern)集合,人们可以利用这些模式在新的环境中起步,从中寻求指导,深入地学习技能,并克服常见的障碍。

关于如何提问的更多信息,我们推荐韦恩·贝克写作的《你要做的全部就是提问:如何掌握成功最重要的技能》(All You Have to DoIs Ask:How to Master the Most Important Skill for Success,由Currency出版社于2020年出版),这本书分为两部分:第一部分讨论了提出问题的价值以及为什么它很难;第二部分是一个有效提问的工具箱。

关于结对编程的更多信息,一本经典的书是由肯特·贝克和辛西娅·安德烈斯写作的《解析极限编程—— 拥抱变化》(ExtremeProgramming Explained: Embrace Change,已由机械工业出版社于2011年引进出版)。这本书涵盖的内容远远超过了结对编程。如果你只对较短的介绍感兴趣,比吉塔·贝克勒和尼娜·西塞格的文章《论结对编程》(“On Pair Programming”)是一篇优秀的使用指南。

如果冒充者综合征或邓宁-克鲁格效应的部分引起了你的共鸣,请查看埃米· 卡迪的《高能量姿势:肢体语言打造个人影响力》(Presence: Bringing Your Boldest Self to Your BiggestChallenges,已由中信出版集团于2019年引进出版)。该书涉及许多工作焦虑和过度自信的常见原因。

第3章 玩转代码

在法国的阿尔勒有一个建于古罗马时代的竞技剧场。当时它能容纳多达2万名观众,供观众观看战车比赛和角斗。罗马灭亡后,一个小镇就建在竞技剧场内。这是有道理的,因为竞技剧场既有墙壁,又有排水系统。后来的居民可能发现这种搭配既奇怪,又很不方便。他们可能会去批评圆形剧场的建筑师,因为是建筑师的那些选择使它现在难以彻底地转变为一个城镇。

代码库就像阿尔勒的那个圆形剧场一样。一代人写了几层,然后又改了改。很多人都接触过这些代码。测试缺失或强制执行已经过时的设定,不断变化的需求扭曲了代码的使用。与代码打交道很艰难,但也是你首先必须要做的几件事情之一。

本章将告诉你如何处理现有的代码。我们将介绍混乱的根源,即“软件的熵”和“技术债”,然后给你一些视角。接着,我们将就如何安全地修改代码给出实用的指导。最后我们将给出一些提示来避免意外地造成代码混乱。

3.1 软件的熵

当你浏览代码时,你就会注意到它的缺点。混乱的代码是变化的自然副作用,不要把代码的不整洁归咎于开发者。这种走向无序的趋势被称为软件的熵(software entropy)。

很多事情都会导致软件的熵,比如开发者误解了其他人的代码或风格上的差异,再如不断进步的技术栈和不断发展的产品需求会导致混乱(参见第11章),以及bug修复和性能优化带来的复杂性。

幸运的是,软件的熵可以被管理。代码风格和bug检测工具有助于保持代码的整洁(参见第6章),代码评审有助于传播知识和减少 不一致(参见第7章),持续的重构可以减少熵(参见3.3节)。

3.2 技术债

技术债(technical debt)是造成软件的熵的一个主要原因。技术债是为了修复现有的代码不足而欠下的未来工作。与金融债务一样,技术债也有“本金”和“利息”。本金是那些需要修复的原始不足。利息是随着代码的发展没有解决的潜在不足,因为实施了越来越复杂的变通方法。随着变通办法的复制和巩固,利息就会增加。复杂性蔓延开来,就会造成bug。未支付的技术债很常见,遗留代码里有很多这样的债务。

你不同意的技术决策并不是技术债,你不喜欢的代码也不是。要成为技术债,这个问题必须迫使团队“支付利息”,或者代码必须冒着触发严重问题的风险,因为严重问题需要紧急支付。不要滥用这个专有名词。经常说“技术债”会削弱这个说法的严重程度,使解决重要的债务变得更加困难。

我们知道债务的存在令人沮丧,但它并不全然是坏事。马丁·福勒将技术债划分为一个 $2!\times!2$ 的矩阵(见表3-1)。

表3-1 技术债矩阵

谨慎的、有意的技术债(右上)是技术债的典型形式:在代码的已知不足和交付速度之间进行务实的取舍。只要团队有规划地解决这个问题,这就是好的债务。
鲁莽的、有意的技术债(左上)是在团队面临交付压力的情况下产生的。出现“……就……”或“只是”这种词就是在暗示讨论中的内容是鲁莽的债务:“我们稍后就会添加结构化日志”,或者“只是增加了超时等待的时长”。

鲁莽的、无意的技术债(左下)来自“不知道自己不知道”。你可以通过事前写下实施计划并获得反馈的方式,以及进行代码评审的方式来减轻这种债务的危险。持续学习也可以最大限度地减少这种无意的鲁莽行为。

谨慎的、无意的技术债(右下)是成长经验积累的自然结果。有些教训只有在事后才会被吸取:“我们应该为那些没有完成注册流程的人创建账户。市场部需要拿到那些失败的注册行为,现在我们不得不添加额外的代码。如果它是核心数据模型的一部分,我们就不用这么费劲了。”与谨慎的、有意的技术债不同,团队不会知道自己正在承担债务。与鲁莽的、无意的技术债不同,这种类型的债务更像是在出问题的领域反思学习或作为软件架构师成长的必经之路,而不是未做功课这么简单。健康的团队使用诸如项目回顾等做法来发现无心之债,并讨论何时以及是否偿还。

从这个矩阵中得到的一个重要启示是,技术债总是不可避免的,因为你无法防止无意中的错误。技术债甚至可能是成功的标志:项目只有存活了足够长的时间,才会变得无序。

解决技术债

不要等到世界都停转一个月了才去解决问题。相反地,要边做边解决,着手去做小幅的重构。在小幅的、独立的提交(commit)和拉动请求(pull request)中推动问题的修改。

你可能会发现,增量重构还不够,你需要更大的改动。大型重构是一项重大的投入。在短期内,偿还技术债会拖慢交付特性的速度,而承担更多的技术债会加速交付。长期来看,情况正好相反:偿还技术债会加快交付的速度,而承担更多的债务则会减缓交付。产品经理会得到鼓励去推动实现更多的特性(当然,这意味着更多的技术债)。正确的平衡点在很大程度上取决于环境。如果你有关于大规模重构或重写某些模块的建议,请先向你的团队说明情况。下面是讨论技术债的一个优秀的模板:
1.按事实陈述情况;

2.描述技术债的风险和成本;

3.提出解决方案;

4.讨论备选方案(不采取行动也是备选方案);

5.权衡利弊。

以书面形式提出你的建议。不要把你的呼吁建立在价值判断上(“这代码又老又难看”),将重点放在技术债的成本和修复它带来的好处上。要具体,如果有人要求你证明这种改动会带来哪些好处,不要感到惊讶。

大家好,

我认为现在是时候把登录服务分成两个服务了:一个用于认证,另一个用于授权。

登录服务的不稳定性占了我们On-Call问题的30%以上。这种不稳定性似乎主要来自认证和授权逻辑的交织。目前的设计使得我们真的很难测试所有我们需要提供的安全相关的特性。我们保证我们客户数据的安全,但是现在的登录服务使得这个承诺越来越难以兑现。我还没有同合规审计部门谈过,但我担心当我们进行下次审计时,他们会提出这个问题。
我认为访问控制逻辑被放在服务中主要是权宜之举,因为当时有各种时间和资源的限制,并没有一个总体的架构原则导致了这个决策。现在为了解决这个问题,意味着需要重构登录服务并将授权代码移出,这会是一个大工程。不过,在我看来,为了解决稳定性的问题和应对正确性的挑战,这些付出还是值得的。

减少工作量的一个方法是利用后端团队的授权服务来替代创建我们自己的。我不认为这是个正确的方法,因为这两种服务要应对的是不同的应用场景。我们处理的是面向用户的授权,而他们解决的是系统间的授权。但也许会有一种更好的方法可以同时满足这两种情况,还不会相互影响。

大家觉得怎么样?

谢谢!

约翰娜

3.3 变更代码

变更代码和在新代码库中写代码完全不一样,你必须在不破坏现 有行为的情况下进行这些修改。你必须理解其他开发者的想法,坚持原有的代码风格和设计模式。而且,你必须在工作中温和地改进代码库。

无论是添加新特性、重构、删除代码还是修复bug,变更代码的技术大体上一致。事实上,不同类型的变更经常被结合起来使用。所谓重构,是指在不改变软件行为的情况下改进内部代码结构。它经常发生在添加新特性的时候,因为它使新特性可以更容易地被添加。而在修复bug的过程中,则经常删除代码。

改变现有的大型代码库是一项需要经过多年甚至几十年锤炼的专业技能。下面的小技巧在你开始的时候就会帮助到你。

3.3.1 善于利用现有代码

迈克尔·C. 费瑟斯在他的《修改代码的艺术》(WorkingEffectively with Legacy Code,已由人民邮电出版社于2007年引进出版)一书中,对如何安全地在现有代码库中修改代码提出了以下步骤:

1.定义变更点;2.寻找测试点;3.打破依赖关系;4.编写测试;5.进行修改和重构。
如果将第5步比作播撒种子,那么前4个步骤就可以被看作在田地周围清理空地并建立栅栏。在栅栏建好之前,野生动物可以闯入并挖走你的作物。找到你需要修改的代码,并想出如何测试它。如果需要的话,为了让测试成为可能,可以对代码进行重构。针对现有的软件行为也要添加测试用例。一旦竖起栅栏,你的修改点周围的区域就得到了很好的保护,然后就可以在里面修改代码了。

首先,使用第2章中的策略定位需要改变的代码,即变更点:阅读代码,进行实验并提出问题。在我们的园艺比喻中,变更点就是你要种下种子的地方。

一旦你定位了代码,就要找到它的测试点。测试点是你想要修改的代码的入口,也就是测试用例需要调用和注入的区域。测试点揭示了代码在被你变更之前的行为,你需要使用这些测试点来测试你自己的变更。

如果你很幸运,测试点很容易被找到;否则,你就需要打破依赖 关系才能找到它们。在这里,依赖关系不是指类库或服务的依赖关系,而是指测试你的代码时所需要的对象或方法。打破依赖关系意味着改变代码结构,使其更容易测试。你只有改变代码,才能将你的测试挂起来,并提供合成的输入。这些代码变更一定不要改变原有的代码行为。

使用重构以打破依赖关系是工作中风险最大的部分。它甚至可能涉及改变预先设定好的测试用例,这使得它更难以检测到原有的行为是否改变。采取小步前进的方式,在这个阶段不要引入任何新特性。确保你能快速地运行测试用例,这样你就能频繁地测试。

有各种各样的技术手段可以打破依赖关系,包括以下几种:

将一个大的、复杂的方法拆分成多个小的方法,这样就可以分别去测试独立的特性片段;
引入一个接口(或其他中介),为测试提供一个复杂对象的简单实现——不完整,但要满足测试需要;

注入明确的控制点,允许你模拟难以控制的执行的切片,如时间的推移。

不要为了方便测试去改变访问声明。将私有(private)方法和变量公开以让测试用例访问代码,但同时也破坏了封装,这是一种糟糕的方式。破坏封装会增加你在项目的生命周期内必须保证的原有行为一致性的覆盖面积。我们将在第11章中进一步讨论这个问题。

当你重构和打破依赖关系时,应该添加新的测试来验证旧的行为。在迭代过程中要频繁地运行测试套件,包括新的和旧的测试用例。考虑使用自动测试工具来生成捕获现有行为的测试用例。关于测试编写的更多内容,请参见第6章。

一旦打破了依赖关系,同时还有良好的测试用例,就应该去“真正地”变更代码了。添加测试用例来验证这些变更,然后重构代码以进一步改善其设计。你可以进行大胆的改变,因为你已经确保了代码的边界。

3.3.2 过手的代码要比之前更干净

互联网上的编程传说经常引用童子军的原则:“住过的营地要比住之前更干净”。就像营地一样,代码库是共享的,如果能继承一个清爽的代码库,善莫大焉。将同样的哲学——“过手的代码要比之前更干净”应用于代码会帮助你的代码随着时间的推移而变得更好。在不影响整个项目持续运转的情况下要持续地重构工程,这样重构的成本就会平摊在多次的版本更迭中。

当你修复错误或增加新的特性时,只清理有关联性的代码。不要不顾一切地去找“脏”代码,要“随缘”一些。尽量将清理代码的提交和改变行为的提交各自分开。分开提交可以让你在不会丢失针对代码清理的提交的基础上,更容易地去恢复代码变更。较小的提交也更容易针对变更的部分进行评审。
重构并不是清理代码的唯一方法。有些代码“闻起来”就很糟糕。在你的工作中,要随时定位有异味的代码。代码异味(codesmell)是一个术语,专指那些不一定是bug,但采用了已知会导致问题的代码模式,通常“闻起来很怪”,如代码清单3-1所示的代码片段。

代码清单3-1

if $(\sf a\phi<\phi)$ a $+=~,,\perp$ ;

这段代码是完全正确的。在Java中,一个单一的语句可以跟在一个条件后面,并不完全需要在它周围加上大括号。然而,这段代码非常糟糕,因为它会使人们很容易犯如代码清单3-2所示的错误。

代码清单3-2

if $(\sf a\phi<\phi)$ )

a $+=~,,\perp$ ;

 $\textrm{\boldmath{a}}=\textrm{\boldmath{a}}\,^{\star}\,\textrm{\boldmath{2}};$  

与Python不同,Java忽略了缩进,而是依靠大括号来分组语句。所以不管if条件是什么,a的值都会被翻倍。如果在编写原始代码时使用了大括号包围住a $+=1,$ ;,就更难犯这个错误了。缺少大括号就是一种代码异味。

许多编译器和代码质量检查工具会检测到这个问题,以及其他的代码异味,比如过长的方法或类、重复的代码、过多的分支或循环或过多的参数。如果没有可供参照的工具和经验,就很难识别更多、更微妙的反模式的代码。

3.3.3 做渐变式的修改

重构经常会采用下面两种方式中的一种。第一种方式是那种巨大的“翻天覆地”式的代码变更,一次性地修改几十个文件。第二种方式是在一个混乱的拉动请求中既有重构也有新特性。这两种方式的修改都很难进行代码评审。这种合并方式使得只想恢复特性而不影响那些用以重构的代码的回滚操作变得非常困难。相反,保持你每次重构代码的体量很小。为代码变更算法中的每个步骤提交单独的拉动请求(参见3.3.1小节)。如果这些代码变更难以理解,那就使用较小规模的提交。最后,在你着手重构的时候,要得到你的团队的支持。因为你正在修改你的团队的代码,他们也应该参与进来。

3.3.4 对重构要务实

重构并不总是明智的选择。因为团队的工作有截止日期和排他的优先事项,重构需要花费时间。你的团队可能会决定忽略重构,而去开发新特性,正是这样的决定增加了团队的技术债,但这可能同时也是正确的决定。重构的成本也可能超过其价值。正在被替换的旧的、废弃的代码不需要被重构,同理,低风险或很少被触及的代码也不需要。在重构的时候要务实。

3.3.5 善用IDE

很多“精英程序员”会认为使用集成开发环境(IDE)是一种耻辱。他们认为从编辑器中获得“帮助”是一项弱点,并迷恋Vim或Emacs,所谓“一种更文明时代的优雅武器”。这是无稽之谈。利用一切你所拥有的工具。如果你的编程语言有一个优秀的IDE,就去使用它。

IDE在重构时特别有帮助。它们拥有可以方便地重命名和移动代码、提取方法和字段、更新方法签名,以及进行其他常见的操作的工具。在大型代码库中,简单的代码操作既烦琐又容易出错。IDE会自动浏览代码并更新它以反映新的变化。(为了防止有人抬杠:我们知道如何让Vim和Emacs也这样做。)
只是不要被工具牵着鼻子走,IDE使重构变得如此容易,以至于通过一些简单的调整就能创造出巨大规模的代码变更。开发者仍然需要自己检查一下IDE做的自动修改。自动重构也有其局限性,如果通过反射或元编程来调用一个已经重命名的方法,这种调用就可能不会被自动调整。

3.3.6 请使用VCS的最佳实践

代码变更都应该被提交到版本控制系统(VCS),如Git。VCS可以跟踪代码库的历史:谁做了哪些修改(提交了哪些代码改动)以及何时修改的。每次提交都需要附一份提交信息(commit message)。

在开发过程中,尽早并频繁提交你的修改。频繁地提交可以显示出代码随着时间的推移而发生的变化,方便你撤销修改,并将之作为一份远程备份。然而,频繁地提交往往会导致无意义的信息,如“哎呀”或“修复测试错误”。当你在写代码的时候,简略地提交信息并没有错,但对其他人来说,这些信息是没有价值的。重置你的分支,压缩你的提交,并在提交代码修改供评审之前写一份清晰的提交信息。

你压缩后的提交信息应该遵循你的团队的惯例。一个常见的做法是在提交信息前加上一个问题编号作为前缀:“[MYPROJ-123]让后端与数据库的处理生效”。将提交信息与问题联系起来,可以让开发人员找到更多的背景,并允许使用脚本和工具。如果没有既定的规则,可以遵循克里斯·比姆斯的建议。

用一个空行将标题与正文分开。

标题行限制在50个字符以内。

标题行要大写。

● 不要以句号结束标题行。
● 在标题行中使用命令式语气。

● 将正文限制在72个字符之内。

● 用正文解释修改的内容和原因,而不解释如何修改。克里斯的帖子值得一读,它描述了良好的代码规范。

3.4 避“坑”指南

现有的代码都多多少少地背负历史包袱。类库、框架和模式都已经摆在那里了,有些标准会困扰你。想用干净的代码和现代技术栈来工作是很自然的想法,但重构代码或忽视标准的诱惑是危险的。重构代码如果做得不好,会破坏代码库的稳定性,而且重构会以牺牲发布新特性为代价。继承原有的代码标准可以保持代码的可读性,但前后的不统一将使开发人员难以理解代码。

本·霍洛维茨在他的《创业维艰:如何完成比难更难的事》(TheHard Thing About Hard Things,已由中信出版集团于2015年引进出版)一书中说:

任何技术创业公司必须做的主要的事情是建立一个产品,这个产品在做某件事情时至少要比目前流行的方式好十倍。两倍或三倍的改进不足以让人们快速或大量地转向新事物。

本说的“产品”是初创产品,但同样的想法也适用于现有代码。如果你想重构代码或重定义标准,你的改进就必须是一个数量级层面的改进。小的收益是远远不够的,因为成本太高了。大多数工程师低估了惯例的价值,而高估了忽视惯例的收益。重构、打破惯例或在技 术栈中添加新技术时要谨慎。把重构代码的机会留给高价值的情况。在可能的情况下使用保守一些的技术。不要忽视惯例,即便你不同意它,也要避免对代码硬分叉。

3.4.1 保守一些的技术选型

软件是一个快速发展的领域。新的工具、语言和框架不断地出现。与网上“大火”的东西相比,现有的代码看起来有些过时。然而,成功的公司留用旧的代码——比如旧的类库和旧的模式——是有原因的:成功需要时间,而在技术上大动干戈会让人分心。

新技术的问题是,它不太成熟。丹·麦金利在他的演讲“选择保守的技术”中指出“在保守技术上出现的故障模式很好理解”。所有的技术都会发生故障,但旧的东西以可预测的方式发生故障,新东西往往会以令人惊讶的方式发生故障。缺乏成熟度意味着更小的社区、更低的稳定性、更少的文档,以及更差的兼容性。新技术甚至在StackOverflow上有更少的答案。

有时新技术会解决你公司的问题,有时则不会。要辨别何时使用新技术,需要明确的规则和经验。新技术的收益必须超过其成本。每一项使用新技术的决定都要花费一枚“创新代币”,丹用这个概念表明,花在新技术上的精力也可以花在开发新的软件特性上。公司拥有这种代币的数量很有限。

为了平衡成本和收益,应该把代币花在服务于公司高价值领域(即核心竞争力)的技术上,以解决广泛的难题,并能被多个团队复用。如果你的公司专门从事抵押贷款的预测分析,并拥有一支博士级的数据科学家的团队,那么采用前沿的机器学习算法就有意义;如果你的公司只有10名工程师,正在开发iOS游戏,这种情况就要使用一些现成的东西。如果新技术能使你的公司更有竞争力,它就有更大的技术红利。如果它能被广泛采用,并且更多的团队可以从中受益,那么你的公司将分摊更少的软件整体维护的成本。

为一个项目选择一种新的编程语言会产生特别深远的影响。使用一种新的语言会将整个技术栈拉入你公司的生态系统。新的构建系统、测试框架、IDE和类库都必须得到支持。一种语言可能具有很大的优势:一种特定的编程范式,更易于实验,或消除某些类型的代码错误。一种语言的优势必须与它的劣势保持平衡。如果说使用一个新的框架或数据库需要花费一枚创新代币,那么使用一种新的语言可能就需要花费3枚。

围绕一种新语言的生态系统的成熟度尤其关键。构建和打包系统是否考虑周全?IDE的支持情况如何?重要的类库是否由经验丰富的开发者维护?测试框架是否可用?如果你需要技术支持,你能支付费用吗?你能雇用到具有相关技能的工程师吗?该语言是否容易掌握?
该语言的性能如何?该语言的生态系统是否可以与公司现有的工具集成在一起?对这些问题的回答与语言本身的特点一样重要。价值数十亿美元的公司都是建立在成熟但有些无聊的编程语言之上的,伟大的软件基本都是用C、Java、PHP、Ruby和.NET编写的。除非某种语言正在消亡,否则它的年龄和缺乏吸引力都很难成为反对使用它的理由。

SBT和Scala

2009年,LinkedIn的开发人员发现了Scala。与LinkedIn广泛使用的Java相比,用Scala写代码更令人愉快。这种语言拥有一个强大的、富有表现力的类型系统。它不那么啰唆,融合了函数式编程技术。此外,Scala可以在Java虚拟机(JVM)上运行,所以运维团队可以使用他们习惯的JVM工具来运行Scala。这也意味着Scala代码可以与现有的Java类库进行互操作。有几个大型项目采用了Scala,其中包括LinkedIn的分布式图数据库和它的新日志系统Kafka。

克里斯为Kafka创建了一个流处理系统,叫作Samza。他很快就发现,Scala宣称的自己可以与JVM轻松整合的理念并没有被实践所证实。许多Java类库在使用Scala的集合库时都很笨拙。最重要的是,Scala的构建环境也很麻烦。LinkedIn正在使用Gradle作为它的构建系统,这也是一项新生的技术。Gradle根本不支持Scala,Scala社区使用的则是Scala自身的构建工具(SBT)。

SBT是一个建立在Scala本身之上的构建系统。它定义了一种用于创建构建文件的领域特定语言(domain-specific language,DSL)。克里斯了解到,SBT有两种完全不同的DSL,这取决于你所运行的版本。大多数互联网上的实例都使用了较早的、被放弃的语法,而他完全无法理解新版语法的说明文档。

在随后的几年里,Scala仍然是他的“眼中钉”:不同版本之间的二进制不兼容,JVM分段故障,不成熟的类库,以及缺乏与LinkedIn内部工具的整合。团队开始隐藏Scala,将其从客户端类库中剥离。对像克里斯这样更专注于流处理而非语言细节的人来说,在2011年Scala被证明是一个糟糕的选择,因为它使他在语言和工具本身花费了大量的时间。

3.4.2 不要特立独行

不要因为你不喜欢你公司(或行业)的标准就忽视它们,编写非标准的代码意味着它将无法适应公司的环境。因为持续集成检查、IDE插件、单元测试、代码校验、日志聚合工具、指标仪表盘和数据管道都已经集成在一起了,你自定义的方案肯定会代价高昂。

你的喜好可能真的更好,但特立独行仍然不是一个好主意。在短期内,大家要做一样的事情。试着去理解标准做法的理由,它有可能是在解决一个不显眼的问题。如果你不能找出一个好的理由,就去四处打听一下。如果你仍然找不到答案,就与你的管理者和负责该技术的团队交流一下。

在改变标准时,会有许多方面需要考虑:优先权、所有权、成本和实施细节。说服一个团队终结他们自己的东西并不容易,一定会有很多意见。你需要实事求是。

与重构一样,改变被广泛采用的东西肯定进展很缓慢,但并不意味着不值得这样做。如果你通过恰当的方式展开行动,这会对你有益。你会接触到组织中的其他部分,这对于协同工作和晋升都大有裨益。你还将成为新解决方案的早期采用者,因为你可以首先使用这个新东西。通过贡献力量,你会得到你想要的东西。

但不要从你的日常工作中分心,并确保你的管理者了解你正花时间在这些项目上。

3.4.3 不要只分叉而不向上游提交修改

分叉(fork)是对一个代码库进行完整的、独立的复制,分叉之后的代码库有自己的主干、分支和标签。在GitHub这样的代码共享平台上,在向上游代码库提交拉动请求之前,就可以分叉上游代码库。分叉操作可以让那些对主代码库没有写入权限的人仍然可以对项目做出贡献,这是一种正常而健康的做法。
不太健康的做法是只分叉代码库而不打算回馈修改。这种情况发生在对项目的方向有分歧的时候,原来的项目被废弃了,或者是很难把修改的代码合并到主代码库里。

分叉公司内部的代码库并进行维护特别有害。开发人员会告诉对方,他们会在“稍后”回来贡献一些修改,但这很少发生。没有及时贡献到上游代码库的小调整会随着时间的推移而变得复杂。最终,你将运行一个完全不同的软件。特性和bug修复会变得越来越难以向上游合并。团队会发现,它已经隐含地签署了维护整个项目的协议。有些公司甚至把他们自己的开源项目分叉了,因为他们自己内部也没有贡献修改。

3.4.4 克制重构的冲动

重构工作常常升级为全方位的重写。重构现有的代码令人望而生畏,为什么不扔掉旧系统,从头开始重写一切呢?把重构看作最后的手段——这是从多年的经验中艰难得出的建议。

有些重构值得去做,但许多重构不值得。对你的重构欲望要诚实,那些代码使用了你不喜欢的语言或框架并不是一个好理由,只有在收益大于成本的情况下才应该进行重构。重构是有风险的,成本也很高。工程师们总是会低估重构花费的时间,尤其是迁移花费的时间往往很可怕。数据需要被转移,上游和下游的系统都需要同步更新,这可能需要几年甚至几十年。

重构也不总是一个更好的选择。弗雷德里克·布鲁克斯在他的名作《人月神话》(The Mythical Man-Month,已由清华大学出版社于2002年引进出版)中创造了“第二系统综合征”这一短语,描述了简单系统如何被复杂系统所取代。第一个系统的范围是有限的,因为它的创造者并不了解可能会出问题的地方。这个系统完成了它的工作,但它是笨拙的和有限的。现在有经验的开发者清楚地看到了他们的问题所在,他们开始用他们的一切聪明才智来开发第二个系统。新系统是为灵活性而设计的,所有东西都是可配置和可注入的。可悲的是,第二个系统通常是一个臃肿的烂摊子。如果你要着手重构一个系统,要小心过度扩张。

迁移Duck Duck Goose

Twitter公司的内部A/B测试工具被称为Duck Duck Goose,缩写为DDG。DDG的第1版是在公司历史的早期所创建的。在公司飞速发展了几年后,它开始显示出它的垂垂老矣。几名最有经验的工程师开始动了重建它的想法。他们想改进系统架构,使其变得更加可靠和可维护,将编程语言从Apache Pig改为Scala,并解决其他的限制(这是在克里斯的Samza故事发生之后的几年,Twitter为使Scala可以在公司稳定运行付出了多年努力)。德米特里被拉去管理一个围绕这项工作而成立的团队。这个团队由老的开发人员、有新想法的工程师和其他的一些人组成。他们预计用一个季度的时间建立并发布新的系统,用第二个季度的时间去淘汰旧的系统。

在实践中,他们却花了一年的时间才使DDG第2版稳定到足以成为公司A/B测试的默认选择,又花了六个月时间退役旧的DDG。在这个过程中,旧的代码库赢得了很多尊重。代码中的复杂分层开始变得有意义。最后,新的工具在很多方面都优于其前身。它经受住了时间对自己的考验:现在它比第1版被取代时的使用年限还长。但是,有经验的工程师和管理者们预估的6个月的项目周期最终膨胀到了18个月,这远远超过了100万美元的额外开发成本。于是他们不得不与副总们进行很艰难的交涉:“不,说真的,老板,这将是更好的选择。我只是再需要一个季度,就一个季度。”在最初负责重构原型的3名开发人员中,有两人离开了公司,第三人刚完成重构就调离了团队。不要以为重构工作会很轻松,这将是一个艰难的过程。
3.5 行为准则

3.6 升级加油站

我们大量引用了迈克尔·C.费瑟斯的《修改代码的艺术》的内容。这本书的内容远比我们在几页纸上所写的要详细得多。如果你发现自己在处理庞大而混乱的代码库,我们推荐迈克尔的书。你可能还会发现乔纳森·博卡拉的书《处理遗留代码的工具箱:软件专业人员处理遗留代码的专业技能》( The Legacy Code Programmer’s Toolbox: Practical Skills for Software Professionals Working with Legacy Code,由LeanPub于2019年出版)很有帮助。

马丁·福勒写了很多关于重构的文章。如果你想阅读短一些的内容,他的博客是一个寻找这些内容的好地方。如果你对关于重构的经典图书感兴趣,他的代表作为《重构:改善既有代码的设计》( Refactoring: Improving the Design of Existing Code ,已由人民 邮电出版社于2010年引进出版)。

最后,我们必须提到弗雷德里克·布鲁克斯的《人月神话》。这是一本每名软件工程师都应该阅读的经典作品,讲述了软件项目在实践中如何运转。你会惊讶于这本书在很大程度上适用于你在工作中的日常经验。

Released under the MIT License.