阿里妹导读
入职一年后再读到这本书其实有些晚了,每一章节几乎都是在这一年的工作中所经历和摸索过后才建立的认知,每一章外延为单独的一本书大概才能成体系地深入学习,作者也确实每一章的末尾都做了相关阅读推荐。
按「道法术器」的角度看,这本书讲的应该更多是「法」和「术」的东西,虽然也可以被理解为是叭叭一堆“正确的废话”,但只要是正确的就是值得输入的,哪怕最终对自己的帮助和影响只有分毫。
此外不得不提的是,本书的翻译实在是过于“简单直接”,和Google机翻也大致无二了,尽可能还是阅读英文原版吧。
书名:程序员的README
作者:克里斯·里科米尼 德米特里·里亚博伊
译者:付裕
出版社:人民邮电出版社有限公司
出版时间:2023-07-01
ISBN:9787115599438
品牌方:人民邮电出版社有限公司
第1章 前面的旅程
技术知识
执行力
沟通能力
领导力
坎宁安定律 (Cunningham’s Law):The best way to get the right answer on the Internet is not to ask a question, it's to post the wrong answer. 在谈话中,我们也能利用“说错话”来激发更多的对话。
自行车棚效应 (Bike-shed Effect) / 帕金森琐碎定律(Parkinson's Law of Triviality):A common phenomenon in group decision-making where people give disproportionate weight to trivial issues. 一个被指派到发电厂对该发电厂的设计方案进行评审的委员会,因为发电厂的设计方案过于复杂,以至于无法讨论出什么实际的内容,所以他们花了几分钟就批准了这些计划。然后,他们又花了45分钟来讨论发电厂旁边的自行车棚的材料问题。
第2章 步入自觉阶段
前置学习:入职后学习开发流程和项目知识,参与设计讨论、On-Call轮换、解决运维问题和评审代码。 在实践中学习:上手编写并发布代码,适当谨慎,不要过于害怕造成问题。 执行代码:在生产环境外运行项目,多调试,多打日志。 阅读:团队文档、设计文档、代码、积压的tickets、书籍、论文和技术网站。 观看讲座:观看教程、技术讲座和阅读会议简报。 适度地参加会议和聚会:学术会议、草根兴趣小组聚会和科技展览会,偶尔参加。 跟班学习并同有经验的工程师结对:在另一个人执行任务时跟着他,他做笔记并提出问题;结对编程(pair programming),两名工程师一起写代码,轮流编程。 用副业项目实践:贡献导向,参与开源项目;兴趣导向,找到有兴趣解决的问题,用想学习的工具来解决。
尝试自己寻找答案:不要直接请教同事,可以尝试互联网、文档、内部论坛、聊天记录、源代码等地方。 设置时间限制:设定好研究一个问题预期花费的时间,防止收益递减。 记录全过程:向别人提出问题时,要描述背景、自己的尝试和发现。 别打扰:注意提问的时机,不要打扰别人工作的专心状态。 多用“非打扰式”交流:组播(multicast)是指将消息发送到一个组而不是个人目标;异步(asynchronous)是指可以稍后处理消息,而不需要立即响应。即多使用群聊,邮件或论坛。 批量处理你的同步请求:面对面的交流是“高带宽”和“低延迟”的,所以在聊天和邮件外,预约时间或安排会议集中处理问题更好,但事前要做好功课,事中要多记录,事后要有反馈。
冒充者综合征:觉知(awareness),意识到是自己主动在寻找“冒充”的证据,而自己是真真切切取得了成就;重塑(awareness),把悲观的、消极的自我暗示转化为乐观的、积极的自我鼓励;反馈(feedback),与导师、敬重的同事交流,明确自己做得怎么样。 邓宁-克鲁格效应:有意识地培养好奇心;对犯错持开放态度;向受人尊敬的工程师取经;讨论设计决策并倾听建议;培养一种权衡利弊而不是非黑即白的心态。
推荐阅读:
《软件开发者路线图:从学徒到高手》(Apprenticeship Patterns: Guidance for theAspiring Software Craftsman) 《你要做的全部就是提问:如何掌握成功最重要的技能》(All You Have toDo Is Ask:How to Master the Most Important Skill forSuccess) 《解析极限编程——拥抱变化》(ExtremeProgramming Explained: Embrace Change) 《高能量姿势:肢体语言打造个人影响力》(Presence: Bringing Your Boldest Self to Your BiggestChallenges)
马丁·M.布罗德威尔在其文章《为学而教》(“Teaching forLearning”)中定义了能力的4个阶段:“无意识的无能力”(unconscious incompetence)、“有意识的无能力”(consciousincompetence),“有意识的有能力”(conscious competence)和“无意识的有能力”(unconscious competence)。具体说来,无意识的无能力意味着你无法胜任某项任务,并且没有意识到这种差距。有意识的无能力意味着你虽然无法胜任某项任务,但其实已经意识到了其中的差距。有意识的有能力意味着你有能力通过努力完成某项任务。最后,无意识的有能力意味着你可以很轻松地胜任某项任务。
冒充者综合征(冒名顶替症候群,Impostor syndrome):a behavioral health phenomenon described as self-doubt of intellect, skills, or accomplishments among high-achieving individuals.
邓宁-克鲁格效应(Dunning-Kruger effect):a cognitive bias in which people with limited competence in a particular domain overestimate their abilities.
第3章 玩转代码
定义:为了修复现有的代码不足而欠下的未来工作,是造成软件的熵的主要原因之一。与金融债务一样,技术债也有“本金”和“利息”,本金是那些需要修复的原始不足,利息是随着代码的发展没有解决的潜在不足。因为实施了越来越复杂的变通方法,随着变通办法的复制和巩固,利息就会增加,复杂性蔓延开来,就会造成bug。 技术债矩阵: 谨慎的、有意的:在代码的已知不足和交付速度之间进行务实的取舍,团队要有规划地解决。
鲁莽的、有意的:在团队面临交付压力的情况下产生的,“就...”,“只是...”这样的形容就是鲁莽债务。
鲁莽的、无意的:不知道自己不知道,需要通过做方案评审、代码评审和持续学习来最大限度减少。
谨慎的、无意的:成长经验积累的自然结果,有些教训只有在事后才会被吸取。 解决: 不要等到世界都停转一个月了才去解决问题,要边做边解决,着手去做小幅的重构。 有时增量重构还不够,需要大型的重构。短期看,偿还技术债会拖慢交付特性的速度,而承担更多的技术债会加速交付,但长期看情况则相反,平衡点在很大程度上取决于环境。如果有大规模重构或重写某些模块的建议,可以向团队说明情况:按事实陈述情况,描述技术债的风险和成本,提出解决方案,讨论备选方案(不采取行动也是备选之一),权衡利弊。要注意的是以书面形式提出建议,不要把呼吁建立在价值判断上(“这段代码又老又难看”),要足够具体,将重点放在技术债的成本和修复它带来的好处上。
善于利用现有代码:定位需要改变的代码即变更点并阅读,然后找到测试点即修改代码的入口,这也是测试用例需要调用和注入的区域。有时候不得不打破现有依赖结构,比如拆分方法、引入接口,这可以更方便地编写测试用例。 过手的代码要比之前更干净:在不影响整个项目持续运转的情况下要持续地重构工程,这样重构的成本就会平摊在多次的版本更迭中。 做渐变式的修改:不要一次提交就“翻天覆地”,保持每次重构代码的体量很小,要尽量将重构代码的提交和新特性的提交分开。 对重构要务实:团队的工作有优先事项和交付时效,重构需要花费时间,成本也可能超过其价值。正在被替换的旧的、废弃的代码,低风险或很少被触及的代码,都是不需要重构的。 善用IDE:重构时多使用IDE的功能,比如抽取方法和字段、更新方法签名等。 善用Git:尽早并频繁地commit,但一定要规范自己的commit message,要压缩但清晰。
保守一些的技术选型:新技术的问题是它不太成熟,缺乏成熟的稳定性、兼容性、框架、文档、社区。只有新技术的收益超过其成本时,才值得考虑。 不要特立独行:不要因为个人喜好就忽视公司或行业标准,自定义的方案必然会付出代价。 不要只fork而不pr:没有及时贡献到上游代码库的小变更也会随着时间的推移而变得复杂。 克制重构的冲动:把重构看作最后的手段,因为其成本和风险都巨大,但又很难评估价值。
推荐阅读:
《修改代码的艺术》(Working Effectively with Legacy Code) 《处理遗留代码的工具箱:软件专业人员处理遗留代码的专业技能》(The Legacy Code Programmer’sToolbox: Practical Skills for Software Professionals Workingwith Legacy Code) 《重构:改善既有代码的设计》(Refactoring: Improving the Design of Existing Code) 《人月神话》(The Mythical Man-Month)
本·霍洛维茨在他的《创业维艰:如何完成比难更难的事》(TheHard Thing About Hard Things)一书中说:任何技术创业公司必须做的主要的事情是建立一个产品,这个产品在做某件事情时至少要比目前流行的方式好十倍。两倍或三倍的改进不足以让人们快速或大量地转向新事物。
丹·麦金利在他的演讲“选择保守的技术”中指出“在保守技术上出现的故障模式很好理解”。所有的技术都会发生故障,但旧的东西以可预测的方式发生故障,新东西往往会以令人惊讶的方式发生故障。
弗雷德里克·布鲁克斯在他的名作《人月神话》(The Mythical Man-Month)中创造了“第二系统综合征”这一短语,描述了简单系统如何被复杂系统所取代。第一个系统的范围是有限的,因为它的创造者并不了解可能会出问题的地方。这个系统完成了它的工作,但它是笨拙的和有限的。现在有经验的开发者清楚地看到了他们的问题所在,他们开始用他们的一切聪明才智来开发第二个系统。新系统是为灵活性而设计的,所有东西都是可配置和可注入的。可悲的是,第二个系统通常是一个臃肿的烂摊子。
第4章 编写可维护的代码
避免空值:使用空对象模式(null object pattern)或可选类型(option type)来避免空指针异常;使用@NotNull注解替代手动空值检查让代码更干净。 保持变量不可变:将变量声明为不可变可以防止意外修改,比如Java的final。 使用类型提示和静态类型检查器:限制变量可以被赋的值。 验证输入:永远不要相信你的代码接收的输入,一定要进行校验。 善用异常:不要使用特殊的返回值来标识错误类型(如null、0、−1等),尽量使用异常,它可以被命名,有堆栈跟踪和错误信息。 异常要有精确含义:尽可能地使用内置的异常,避免创建通用的异常。使用异常处理来应对故障,而不是控制应用程序的运行逻辑。 早抛晚捕:“早抛”意味着在尽可能接近错误的地方引发异常,这样开发人员就能迅速地定位相关的代码。“晚捕”意味着在调用的堆栈上传播这个异常,直到你到达能够处理异常的程序的层级。 智能重试:单纯的重试方法是捕捉到一个异常马上就进行重试,但可能会影响系统性能;谨慎的做法是backoff,非线性地增加休眠时间(通常使用指数如(retry number)^2);不要盲目地重试所有失败的调用,最好是让应用程序在遇到其在设计时没有预想到的错误时崩溃,即fail-fast。 构建幂等系统:处理重试的最好方法是构建幂等系统,一个幂等的操作是可以被进行多次并且仍然产生相同结果的操作,比如往一个Set里添加一个值,客户端单独为每个请求提供一个唯一ID。
给日志分级:通过全局配置和对包或类级别的覆写来控制,设置后所有处于该级别或高于该级别的日志都会被发出来,常见分级有TRACE、DEBUG、INFO、WARN、ERROR、FATAL。 日志的原子性:原子日志就是指在一行消息中包含所有相关的信息,不能原子化输出时考虑在每一条日志中加上Trace ID。 关注日志性能:用参数化的日志输入及异步附加器避免日志对性能的损害。 不要记录敏感数据:日志信息不应该包括任何私人数据,如密码、安全令牌、信用卡号码或电子邮件地址。
常见系统指标形式:计数器、仪表盘和直方图(百分比),汇总到一个集中式可视化系统,在聚合指标之上提供面板和监控工具,跟踪SLO(service level objective,服务等级目标),根据指标值触发警告,或扩容缩容。 使用标准的监控组件:不要推出你自己的系统指标库,非标准库是“维护噩梦”,而标准库可以与其他一切“开箱即用”的东西集成。 测量一切:监测的性能开销很低,要尽可能地监测各种数据结构、操作和行为,比如资源池(线程池、连接池)的大小、缓存的命中数和失误数、数据结构的大小、CPU密集型操作的性能开销、I/O密集型操作的处理时间、异常和错误的数量、远程请求和响应的数量和耗时。
堆栈跟踪:异常。 分布式调用跟踪:Trace ID。
配置的形式和方式:形式有JSON或YAML文件,环境变量,命令行参数,领域特定语言(DSL),应用程序所使用的语言;方式可以是静态配置或动态配置。 记录并校验所有的配置:在程序启动时立即记录所有(非秘密的)配置,并加载配置的值时立即对其进行类型和逻辑意义的校验。 不要玩花活:配置方案越聪明,bug就越奇怪,尽量使用最简单、有效的方法,理想状态应该是单一标准格式的静态配置文件;如果不得不使用变量替换、条件配置或动态配置,也要日志跟踪配置的变化和记录实际生效的配置值。 提供默认值:提供良好的默认值能让应用开箱即用,避免用户上手就配置大量参数。 给配置分组:可以使用YAML的嵌套将相关属性分组。 配置即代码(configuration as code,CAC):配置应该受到与代码同样严格的要求,要进行版本控制、评审、测试、构建和发布。 保持配置文件清爽:删除不使用的配置,使用标准的格式和间距,不要盲目地从其他文件中复制配置。 不要编辑已经部署的配置:避免在生产环境中手动编辑某台特定机器上的配置文件。
多了解并尽量使用公司或团队的通用运维工具。 如果是自己编写的脚本,一定要注意规范和测试,有价值的工具可以尝试抽象为共享库或服务。
推荐阅读:
《代码大全:软件构造之实践指南》(CodeComplete: A Practical Handbook of Software Construction) 《代码整洁之道》(Clean Code: A Handbook of Agile Software Craftsmanship) 《Google系统架构解密:构建安全可靠的系统》(BuildingSecure & Reliable Systems:Best Practices for Designing, Implementing, and Maintaining Systems) 《SRE:Google运维解密》(Site Reliability Engineering: How Google Runs ProductionSystems)
第5章 依赖管理
相依性是指你的代码所依赖的代码,依赖关系是在软件包管理或构建文件中声明的。 好的版本管理方案要有唯一性(版本不应该被重复使用),可比性(推断版本的优先顺序),信息性(区分预发布和正式发布)。 语义版本管理(semantic versioning,SemVer):主版本号.次版本号.补丁版本号,还可以添加一个-来定义预发布版本。 传递依赖:软件包管理或构建文件揭示了项目的直接依赖关系,直接依赖又依赖于其他类库,形成依赖传递。
循环依赖:A依赖B,B依赖C,C依赖A。一个库间接性地依赖它自己 钻石依赖:A依赖B和C,B和C依赖D。一个底层库被多个路径依赖。 版本冲突:A直接或间接依赖了B的2个版本。一个项目中存在同一个库的多个版本。
隔离依赖项:不必把依赖管理交给构建和打包系统,直接复制依赖相关的必要代码,Java还可以使用包路径的区分来遮蔽依赖。 按需添加依赖项:将你使用的所有类库显式声明为依赖项,不要使用来自横向依赖的方法和类。 指定依赖项的版本:明确设定每个依赖项的版本号。 依赖范围最小化:依赖范围指在构建生命周期的编译、运行、测试阶段何时使用某个依赖,对每个依赖项指定尽可能精确的依赖范围。 保护自己免受循环依赖的影响:使用构建工具检测,不要引入循环依赖。
推荐阅读:
SemVer的官方主页 Python官方主页的版本管理说明
帕累托法则(Pareto principle):也被称为二八定律,由意大利经济学家维尔弗雷多·帕累托在20世纪初提出,他发现意大利约有80%的土地由20%的人口所有。在编程领域,80%的软件缺陷来源于代码中的20%部分;80%的性能提升可以通过优化20%的代码来实现;80%的开发时间被用来完成项目中20%的功能点。
第6章 测试
检查代码是否正常工作,验证软件的行为是否符合预期。 编写测试迫使开发人员思考他们程序的接口和实现过程,尽早地暴露出笨拙的接口设计和混乱的实现过程。 测试代码可以是另一种形式的文档,是开始阅读并了解一个新的代码库的入口。
单元测试:测试某个单一的方法或行为。 集成测试:验证多个组件集成在一起之后是否还能正常工作。 系统测试:验证整个系统的整体运行情况。 性能测试:负载测试和压力测试并监控系统性能。 验收测试:验证交付的软件是否符合验收标准。
模拟库:用于单元测试,特别是面向对象的代码中,可以模拟外部依赖的系统、类库或对象。 测试框架:管理测试的setup和teardown,管理测试执行和编排,提供工具,生成结果报告和代码覆盖率。 代码质量工具:运行静态分析并执行代码风格检查,报告复杂度和测试覆盖率等指标。
编写干净的测试:测试代码也要注意质量,专注于测试基本功能而不是实现细节,将测试的依赖项与常规代码的依赖项分开。 避免过度测试:编写有意义的、对代码影响最大风险点的测试,不要为了覆盖率而去写无效测试。
查看原图 58K