您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
为什么《程序员修炼之道》评分高达 9.1?
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
为什么《程序员修炼之道》评分高达 9.1?
wy****
2024-12-05
IP归属:北京
100浏览
开始接触到《程序员修炼之道:通向务实的最高境界》这本书是在豆瓣图书的高分榜单上,它的评分高达 9.1,其中有条蛮有意思的书评非常吸引我:“这本书我读过 5 遍信不信,每个字都磨出了感情... 爱看技术书的程序员,看看可以往上走走;不爱看技术书的程序员,看看可以轻松刷出阅读成就感”。所以,本着刷阅读成就感并希望磨炼技术的态度便开始了本书的阅读,抽业余时间读完,其中有部分收获能和大家分享,当然更希望大家去看原书。 ### 1\. DRY 原则 DRY 是书中强调和多次出现的原则,它的中文释义是“干的;干燥的”。在文中强调的是:在系统中,对于每一处的知识都要保持单一和明确。初看上去这个原则是值得坚持和肯定的,但其中提到了关于注释不要重复代码实现的想法,我对此并不是很同意,我觉得在接口层加入详细的注释还是有必要的。 以较为复杂的查询接口为例,如果不添加详细的注释的话,那么调用者需要深入到接口实现中去找、去看必要了解的知识,如果这个查询接口比较复杂,那么便需要花费比较多的时间。反之,如果有详细的注释,那么便节省了翻看实现的时间,比如说如下查询推荐延保的接口,简略注释只是对方法名的翻译,至于返回值是什么,请求参数需要哪些赋值需要去具体实现中寻找;详细注释则注明了这些内容。 ```java /** * 简略注释:查询推荐的延保 * 详细注释:结果中推荐延保数量至多为 2;入参中分页查询信息 ... **/ Result queryRecommand(Request req); ``` 但书中为什么反对注释重复方法的实现呢?因为它担心修改实现时,忘记维护注释使得注释过时。虽然有理,但是我觉得如果 **将注释看成代码的一部分,并约束修改代码时,同时修改注释**,是能够避免这个问题的,这样不仅仅是提高可读性,还能提高接口的抽象程度。 不过,DRY 原则也有值得坚持的地方,以如下线段类定义为例: ```java class Line { Point start; Point end; double length; } ``` 第一眼看上去貌似没什么问题,线段有起点、终点和长度。但是实际上出现了重复:长度是由起点和终点定义而来的,改变其中之一那么便将引起长度的变化,最好是把长度的定义变成方法,如下: ```java class Line { Point start; Point end; public double length() { return end.distanceTo(start); } } ``` 这样消除了重复。但是如果该计算非常耗费性能,这样定义可能并不合适,还是需要为长度计算的结果,冗余出字段来保存,如下: ```java class Line { private Point start; private Point end; private double length; public void setStart(Point p) { this.start = p; calculateLength(); } private void calculateLength() { this.length = end.distanceTo(start); } public double length() { return length; } } ``` 所以,DRY 原则还是需要被辩证地看待。当然,程序中最明显的代码重复还是非常值得去处理的,将它们抽象提出来,能够 **让复用变得更容易**。 ### 2\. 继承税 **“少用继承,多用组合”** 是之前在学习设计模式时接触到的原则,但是当时我对此并没有什么感触,甚至觉得继承蛮好用的,比如在应用模板方法模式时,使用抽象类来定义方法模板。不过在书中又提到了这个原则,它称之为“继承税”,并做出了一段蛮有意思的描述: > 你想要一根香蕉,但得到的却是一只拿着香蕉的大猩猩,甚至还有整个森林 其表达的意思也不难理解:强调继承带来的父类与子类之间的 **耦合太深** 了,**父类中通用字段、方法的变更对子类来说可能带来意想不到的后果**: ![继承税.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-08-06-20-35yJXGzl35EliEajkO.png) 以如上继承关系为例,如果最高父类中某些内容发生变更,子类中对其使用的话,那么可能会引起子类行为的改变,而这种改变没有导致编译时异常,可能是没办法发现的,这样使得代码的可维护性大大降低,而且维护在每个类中的知识会在继承关系之间波动,暴露了太多的知识出来,做不到抽象和信息隐藏。 那么不用继承该怎么办呢? 1. 使用 **接口实现来代替类的继承**,保证多态性又不会造成信息的紧耦合 2. 使用 **组合代替继承**:比如想要香蕉,那么直接将包含香蕉的类注入进来,不再通过继承去获取了 从这也能理解为什么 C++ 语言中的多继承被诟病。此外,我觉得继承也并不能被一票否决,在 Java 源码中常用容器的实现里,都是有抽象层的(`AbstractList`, `AbstractMap` 等等),通过继承它们,实现了大量代码复用,为各种不同容器的具体实现提供了很多方便之处。但是我觉得继承能被这么应用需要具备前提条件:一是 **抽象出来的父类不会或很少再变动**;二是 **开发者变动的前提是对此有清晰的了解**。 如果不是这样,在业务代码中引入继承树,那带来的复杂性就太高了。 ### 3\. 重构 先前我对重构的观点是:如果能用,尽量避免重构,当代码实在难以满足需求时,再推翻它重新来。但是书中提出的观点则不同:**代码需要演化,它不是静态的东西。** 当遇到绊脚石或是注意到有两件事的确需要合并,又或是被其他什么事情触动而心生悔意时,那么请不要犹豫,去改掉它。并且它主张的重构是一项日复一日、小步快走的工作,并不是“大厦的倒塌重建”,这样低风险小步骤进行改造有助于使代码更易于维护和更改。 不过理想总是好的,在现实中重构总会面临一些问题: 1. **时间压力**:这个需求预计 3 天能开发完,但是为了优化代码设计和逻辑,需要增加 2 天时间。增加出的额外时间,可能并不会被接受 2. **改动带来的风险**:如何才能保证重构的影响全部在可控的范围内非常值得思考,如果重构会引发 Bug,那么开发者会宁愿重构并没有开始 事实上,时间压力并不太能站得住脚,因为随着功能增加,复杂度会不断累积,那么未来便需要投入更多的时间来修复,并且将引发更大规模的改动,带来 Bug 的风险也会增加;而对于改动带来的风险,我觉得本书强调“小步、多次”重构也是想将此风险降低,它更像是一项慢慢地、有意地、仔细地进行的活动,除此之外使重构安全的方法是在重构之后有良好的测试,如果我们有 **完善的业务场景的单元测试用例**,在重构完能及时发现问题所在,也不至于对重构这件事情畏手畏脚了。 此外,不对代码进行重构往往会触发开发者心中的“破窗效应”,在已经很难维护的代码上继续叠加功能,而不是对其进行改善,使得代码更加难以维护,还会拿“这段代码已经很烂了”作为“合理的借口”。所以,重构该成为日常开发中,需要注意和进行的活动。 > 破窗效应:一种社会心理学理论,它表明环境的恶化会导致人们行为的恶化。该理论认为,如果一个社区的某个小问题没有及时得到修复,那么这个小问题的存在就会给人一种信号,即这个地方被忽视或者管理不善。这种信号会诱使人们模仿这种不良行为,从而导致更多的窗户被破坏,最终可能导致整个社区的秩序崩溃。 ### 4\. 命名 每次提到命名或者在为接口命名时,我都会有一种非常强烈的让它自解释的想法,但是我最近这种想法的欲望逐渐降低,原因有二: 1. **阅读习惯**:对国人来说,可能大多数人没有先去读英文的习惯,更倾向于读中文相关的内容,比如注释 2. **英语水平参差**:可能有时候想要自解释的初心是好的,但是如果使接口名变成了长难句,可读性将降低 即使是这样,也并不能降低命名的标准,应该有一个适度的折中:不引入长难句,将其中难以表达的内容考虑使用注释来补充。此外,我觉得命名保持一致性也非常重要,比如在项目中对于补购已经命名为 `AddBuy`,那么便不要再引入 `SupplementaryPurchase` 和 `Replenishment` 等命名,团队内成员将知识统一才是最好的,并不在于它在英文语境下是否表达准确。但是对于这一项工作,我还没有发现团队花费心血来做这件事,如果不去翻看原有代码的话,冒然的命名可能不符合系统内现有规范,所以我觉得可以创建相关的文档或者在 `README` 中将这些命名规范记录下来,这不光降低了命名难度,而且使得团队内成员能够统一,也方便交流。 除了在方法自解释上下功夫外,方法的表达也值得注意,比如定义一个打折的方法: ```java void deductPercent(double amount); ``` `deductPercent` 扣除百分比指的是 **要做的事情**,但是扣除什么的百分比是不明确的,其次,入参 `amount` 也容易让人疑惑,是绝对值呢?还是百分数?应该有几位小数?所以,换一种方式会更好一些: ```java void applyDiscount(Percentage discount); ``` `applyDiscount` 方法名表达了折扣的意图,并且将 `double` 类型换成了对象类型,在对象中进行准确的定义,也是一种方法。当然,如果仍然采用 `double` 类型的入参也没有问题,在注释中注明容易让人迷惑的部分也是不错的方案。 ### 5\. 终 我觉得这本书更多的是在传达一种 **务实的工程师精神和责任**,不只是要编写好的代码,还要为你编写的代码负责,积极地为编码添加上 `@author` 的标志,当开发者看到你的名字时,他们能联想这段逻辑是可靠的、可读性好的和易于维护的,也是专业性的体现。 除了本文中提到的内容之外,其中还有关于基础工具的使用、编码的习惯、需求管理和人生哲学等内容,我觉得用它的结尾来作为本文的结尾也再合适不过: **你要为自己的人生做主,精心营造,与人分享,并为之喝彩!**
上一篇:移动端设备上稀奇古怪的前端问题收集(一)
下一篇:AIGC项目中的【模板进程】方案的设计实践
wy****
文章数
26
阅读量
2476
作者其他文章
01
高性能MySQL实战(一):表结构
最近因需求改动新增了一些数据库表,但是在定义表结构时,具体列属性的选择有些不知其所以然,索引的添加也有遗漏和不规范的地方,所以我打算为创建一个高性能表的过程以实战的形式写一个专题,以此来学习和巩固这些知识。1. 实战我使用的 MySQL 版本是 5.7,建表 DDL 语句如下所示:根据需求创建 接口调用日志 数据库表,请大家浏览具体字段的属性信息,它们有不少能够优化的点。CREATE TABLE
01
分布式服务高可用实现:复制
1. 为什么需要复制我们可以考虑如下问题:当数据量、读取或写入负载已经超过了当前服务器的处理能力,如何实现负载均衡?希望在单台服务器出现故障时仍能继续工作,这该如何实现?当服务的用户遍布全球,并希望他们访问服务时不会有较大的延迟,怎么才能统一用户的交互体验?这些问题其实都能通过 “复制” 来解决:复制,即在不同的节点上保存相同的副本,提供数据冗余。如果一些节点不可用,剩余的节点仍然可以提供数据服务
01
高性能MySQL实战(三):性能优化
这篇主要介绍对慢 SQL 优化的一些手段,而在讲解具体的优化措施之前,我想先对 EXPLAIN 进行介绍,它是我们在分析查询时必要的操作,理解了它输出结果的内容更有利于我们优化 SQL。为了方便大家的阅读,在下文中规定类似 key1 的表示二级索引,key_part1 表示联合索引的第一部分,unique_key1 则表示唯一二级索引,primary_key 表示主键索引。高性能MySQL实战(一
01
从2PC和容错共识算法讨论zookeeper中的Create请求
最近在读《数据密集型应用系统设计》,其中谈到了zookeeper对容错共识算法的应用。这让我想到之前参考的zookeeper学习资料中,误将容错共识算法写成了2PC(两阶段提交协议),所以准备以此文对共识算法和2PC做梳理和区分,也希望它能帮助像我一样对这两者有误解的同学。1. 2PC(两阶段提交协议)两阶段提交 (two-phase commit) 协议是一种用于实现 跨多个节点的原子事务(分布
wy****
文章数
26
阅读量
2476
作者其他文章
01
高性能MySQL实战(一):表结构
01
分布式服务高可用实现:复制
01
高性能MySQL实战(三):性能优化
01
从2PC和容错共识算法讨论zookeeper中的Create请求
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号