为什么《程序员修炼之道》评分能到 9.1?
大家好,我是 方圆。开始接触到《程序员修炼之道:通向务实的最高境界》这本书是在豆瓣图书的高分榜单上,它的评分高达 9.1,其中有条蛮有意思的书评非常吸引我:“这本书我读过 5 遍信不信,每个字都磨出了感情… 爱看技术书的程序员,看看可以往上走走;不爱看技术书的程序员,看看可以轻松刷出阅读成就感”。所以,本着刷阅读成就感并希望磨炼技术的态度便开始了本书的阅读,抽业余时间读完,其中有部分收获能和大家分享,当然更希望大家去看原书。
1. DRY 原则
DRY 是书中强调和多次出现的原则,它的中文释义是“干的;干燥的”。在文中强调的是:在系统中,对于每一处的知识都要保持单一和明确。初看上去这个原则是值得坚持和肯定的,但其中提到了关于注释不要重复代码实现的想法,我对此并不是很同意,我觉得在接口层加入详细的注释还是有必要的。
以较为复杂的查询接口为例,如果不添加详细的注释的话,那么调用者需要深入到接口实现中去找、去看必要了解的知识,如果这个查询接口比较复杂,那么便需要花费比较多的时间。反之,如果有详细的注释,那么便节省了翻看实现的时间,比如说如下查询推荐延保的接口,简略注释只是对方法名的翻译,至于返回值是什么,请求参数需要哪些赋值需要去具体实现中寻找;详细注释则注明了这些内容。
/*** 简略注释:查询推荐的延保* 详细注释:结果中推荐延保数量至多为 2;入参中分页查询信息 ...
**/
Result queryRecommand(Request req);
但书中为什么反对注释重复方法的实现呢?因为它担心修改实现时,忘记维护注释使得注释过时。虽然有理,但是我觉得如果 将注释看成代码的一部分,并约束修改代码时,同时修改注释,是能够避免这个问题的,这样不仅仅是提高可读性,还能提高接口的抽象程度。
不过,DRY 原则也有值得坚持的地方,以如下线段类定义为例:
class Line {Point start;Point end;double length;
}
第一眼看上去貌似没什么问题,线段有起点、终点和长度。但是实际上出现了重复:长度是由起点和终点定义而来的,改变其中之一那么便将引起长度的变化,最好是把长度的定义变成方法,如下:
class Line {Point start;Point end;public double length() {return end.distanceTo(start);}
}
这样消除了重复。但是如果该计算非常耗费性能,这样定义可能并不合适,还是需要为长度计算的结果,冗余出字段来保存,如下:
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. 继承税
“少用继承,多用组合” 是之前在学习设计模式时接触到的原则,但是当时我对此并没有什么感触,甚至觉得继承蛮好用的,比如在应用模板方法模式时,使用抽象类来定义方法模板。不过在书中又提到了这个原则,它称之为“继承税”,并做出了一段蛮有意思的描述:
你想要一根香蕉,但得到的却是一只拿着香蕉的大猩猩,甚至还有整个森林
其表达的意思也不难理解:强调继承带来的父类与子类之间的 耦合太深 了,父类中通用字段、方法的变更对子类来说可能带来意想不到的后果:
以如上继承关系为例,如果最高父类中某些内容发生变更,子类中对其使用的话,那么可能会引起子类行为的改变,而这种改变没有导致编译时异常,可能是没办法发现的,这样使得代码的可维护性大大降低,而且维护在每个类中的知识会在继承关系之间波动,暴露了太多的知识出来,做不到抽象和信息隐藏。
那么不用继承该怎么办呢?
-
使用 接口实现来代替类的继承,保证多态性又不会造成信息的紧耦合
-
使用 组合代替继承:比如想要香蕉,那么直接将包含香蕉的类注入进来,不再通过继承去获取了
从这也能理解为什么 C++ 语言中的多继承被诟病。此外,我觉得继承也并不能被一票否决,在 Java 源码中常用容器的实现里,都是有抽象层的(AbstractList
, AbstractMap
等等),通过继承它们,实现了大量代码复用,为各种不同容器的具体实现提供了很多方便之处。但是我觉得继承能被这么应用需要具备前提条件:一是 抽象出来的父类不会或很少再变动;二是 开发者变动的前提是对此有清晰的了解。
如果不是这样,在业务代码中引入继承树,那带来的复杂性就太高了。
3. 重构
先前我对重构的观点是:如果能用,尽量避免重构,当代码实在难以满足需求时,再推翻它重新来。但是书中提出的观点则不同:代码需要演化,它不是静态的东西。 当遇到绊脚石或是注意到有两件事的确需要合并,又或是被其他什么事情触动而心生悔意时,那么请不要犹豫,去改掉它。并且它主张的重构是一项日复一日、小步快走的工作,并不是“大厦的倒塌重建”,这样低风险小步骤进行改造有助于使代码更易于维护和更改。
不过理想总是好的,在现实中重构总会面临一些问题:
-
时间压力:这个需求预计 3 天能开发完,但是为了优化代码设计和逻辑,需要增加 2 天时间。增加出的额外时间,可能并不会被接受
-
改动带来的风险:如何才能保证重构的影响全部在可控的范围内非常值得思考,如果重构会引发 Bug,那么开发者会宁愿重构并没有开始
事实上,时间压力并不太能站得住脚,因为随着功能增加,复杂度会不断累积,那么未来便需要投入更多的时间来修复,并且将引发更大规模的改动,带来 Bug 的风险也会增加;而对于改动带来的风险,我觉得本书强调“小步、多次”重构也是想将此风险降低,它更像是一项慢慢地、有意地、仔细地进行的活动,除此之外使重构安全的方法是在重构之后有良好的测试,如果我们有 完善的业务场景的单元测试用例,在重构完能及时发现问题所在,也不至于对重构这件事情畏手畏脚了。
此外,不对代码进行重构往往会触发开发者心中的“破窗效应”,在已经很难维护的代码上继续叠加功能,而不是对其进行改善,使得代码更加难以维护,还会拿“这段代码已经很烂了”作为“合理的借口”。所以,重构该成为日常开发中,需要注意和进行的活动。
破窗效应:一种社会心理学理论,它表明环境的恶化会导致人们行为的恶化。该理论认为,如果一个社区的某个小问题没有及时得到修复,那么这个小问题的存在就会给人一种信号,即这个地方被忽视或者管理不善。这种信号会诱使人们模仿这种不良行为,从而导致更多的窗户被破坏,最终可能导致整个社区的秩序崩溃。
4. 命名
每次提到命名或者在为接口命名时,我都会有一种非常强烈的让它自解释的想法,但是我最近这种想法的欲望逐渐降低,原因有二:
-
阅读习惯:对国人来说,可能大多数人没有先去读英文的习惯,更倾向于读中文相关的内容,比如注释
-
英语水平参差:可能有时候想要自解释的初心是好的,但是如果使接口名变成了长难句,可读性将降低
即使是这样,也并不能降低命名的标准,应该有一个适度的折中:不引入长难句,将其中难以表达的内容考虑使用注释来补充。此外,我觉得命名保持一致性也非常重要,比如在项目中对于补购已经命名为 AddBuy
,那么便不要再引入 SupplementaryPurchase
和 Replenishment
等命名,团队内成员将知识统一才是最好的,并不在于它在英文语境下是否表达准确。但是对于这一项工作,我还没有发现团队花费心血来做这件事,如果不去翻看原有代码的话,冒然的命名可能不符合系统内现有规范,所以我觉得可以创建相关的文档或者在 README
中将这些命名规范记录下来,这不光降低了命名难度,而且使得团队内成员能够统一,也方便交流。
除了在方法自解释上下功夫外,方法的表达也值得注意,比如定义一个打折的方法:
void deductPercent(double amount);
deductPercent
扣除百分比指的是 要做的事情,但是扣除什么的百分比是不明确的,其次,入参 amount
也容易让人疑惑,是绝对值呢?还是百分数?应该有几位小数?所以,换一种方式会更好一些:
void applyDiscount(Percentage discount);
applyDiscount
方法名表达了折扣的意图,并且将 double
类型换成了对象类型,在对象中进行准确的定义,也是一种方法。当然,如果仍然采用 double
类型的入参也没有问题,在注释中注明容易让人迷惑的部分也是不错的方案。
5. 终
我觉得这本书更多的是在传达一种 务实的工程师精神和责任,不只是要编写好的代码,还要为你编写的代码负责,积极地为编码添加上 @author
的标志,当开发者看到你的名字时,他们能联想这段逻辑是可靠的、可读性好的和易于维护的,也是专业性的体现。
除了本文中提到的内容之外,其中还有关于基础工具的使用、编码的习惯、需求管理和人生哲学等内容,我觉得用它的结尾来作为本文的结尾也再合适不过:
你要为自己的人生做主,精心营造,与人分享,并为之喝彩!
相关文章:

为什么《程序员修炼之道》评分能到 9.1?
大家好,我是 方圆。开始接触到《程序员修炼之道:通向务实的最高境界》这本书是在豆瓣图书的高分榜单上,它的评分高达 9.1,其中有条蛮有意思的书评非常吸引我:“这本书我读过 5 遍信不信,每个字都磨出了感情…...

接口自动化测试框架中动态参数接口,加密接口,签名接口你们是怎么处理的?
动态参数:可通过热加载形式(在代码执行过中自动去yaml里面执行外部的函数) 接口测试加密解密简介: 对称加密(私钥加密,只有一个密钥)AES,DES,BASE64 特点是:加密和解密有相同的密钥…...

【hadoop】常用命令
集群信息 查看hadoop版本 hadoop version查询hdfs系统中的namenode # 方式一 hdfs getconf -namenodes# 方式二 hdfs getconf -confKey dfs.namenode.http-address获取NameNode restful接口 hdfs getconf -confKey dfs.namenode.http-address hdfs getconf -confKey dfs.na…...

时间同步--- ntp与ptp
时间同步 1. 什么是NTP时间?什么是PTP时间? NTP时间(Network Time Protocol 时间): NTP即网络时间协议(Network Time Protocol),它是一种用于同步计算机时间的网络协议。NTP可以将所有参与的计…...

CSDN 僵尸粉 机器人
CSDN 僵尸粉 机器人 1. 前言 不知道什么时候开始每天创作2篇就有1500流量爆光,每次都能收获一些关注和收藏,感觉还是挻开心的感觉CSDN人气还是挻可以的以前各把月一个收藏和关注都没有写的动力了。 2. 正文 后面又连接做了2天的每日创建2篇任务&…...

【Material-UI】File Upload Button 组件详解
文章目录 一、基础实现1. component"label"2. 隐藏的输入元素 二、样式和交互增强1. 自定义按钮样式2. 交互提示 三、支持多文件上传四、无障碍性(Accessibility)1. 提供 aria-label 或 aria-labelledby2. 支持键盘导航 五、高级用法和集成1. …...

计算机组成原理 - 中央处理器
中央处理器 考纲内容 CPU的功能和基本结构指令执行过程数据通路的功能和基本结构控制器的功能和工作原理异常和中断机制 异常和终端的基本概念;异常和中断的分类;异常和中断的检测与响应指令流水线 指令流水线的基本概念;指令流水线的基本实…...

C++笔试练习笔记【5】:最小花费爬楼梯(有题目链接) 初识动态规划
文章目录 题目思路代码 动态规划简介**一、什么是动态规划****二、动态规划的应用场景****三、动态规划的基本步骤****四、动态规划的优缺点** 题目 题目链接:https://www.nowcoder.com/practice/9b969a3ec20149e3b870b256ad40844e?tpld230&tpld39751&ru/…...

数据结构----------贪心算法
什么是贪心算法? 贪心算法(Greedy Algorithm)是一种在问题求解过程中,每一步都采取当前状态下最优(即最有利)的选择,从而希望导致最终的全局最优解的算法策略。 贪心算法的核心思想是做选择时&…...

C++初学(11)
不知不觉就第11篇了QWQ 11.1、指针和自由存储空间 之前提到了计算机程序在存储数据时必须跟踪的3个基本属性: (1)信息存储在何处; (2)存储的值为多少; (3)存储的信息…...

Vba选择cad中不同类型图元(Select Case True语句和like用法)
Select Case True 是一个常见的VBA编程技巧,用于在多个条件之间进行选择。具体来说,Select Case True 语句的每个 Case 语句都包含一个布尔表达式,这些表达式会逐个与 True 进行比较。当其中一个表达式的结果为 True 时,对应的代码…...

Kafka基本讲解
Kafka基本讲解 一:Kafka介绍 Kafka是分布式消息队列,主要设计用于高吞吐量的数据处理和消息传输,适用于日志处理、实时数据管道等场景。Kafka作为实时数仓架构的核心组件,用于收集、缓存和分发实时数据流,支持复杂的…...

thinkphp6项目初始化配置方案二次修正版本
数据返回统一格式 app/BaseController.php新增文件内容在末尾,并在构造函数中实例化数据模型类 // 成功统一返回格式 function Result($data, $msg , $code 200, $httpCode 200): \think\response\Json {$res [code > $code,msg > $msg,data > $data];return j…...

XXE靶机教学
arp-scan -l主机发现 arp-scan -l 端口扫描 nmap -p- 192.168.48.139 服务探测 nmap -p80,5355 -sT -sC -sV 192.168.48.139 目录扫描 dirsearch -u http://192.168.48.139 访问robots.txt 发现两个可访问路径 burp抓包 测试是否存在xxe漏洞 <?xml version "1.…...

干货 | 2024步入数字化转型深水区,云原生业务稳定性如何保障(免费下载)
云原生业务的稳定性保障是一个涉及多个层面的复杂任务,以下是一些关键措施和策略,以确保云原生业务的高效稳定运行: 一、平台安全性评估与加固 云原生平台安全评估:对云原生平台(如Kubernetes、Docker等)…...

for(char c:s),std::vector<int> numbers 和std::int numbers[],.size()和.sizeof()区别
在C中当需要对某个容器或数组进行遍历时我们可以使用以下语句,c将会被赋值为s中的元素 for(char c:s)://s可以是任何满足条件的容器或数组for(int c:s):for(double c:s):for(float c:s):在C中我们来区分std::vector numbers {1, 2, 3, 4, 5};和std::int numbers[] …...

桌面云备份可以删除吗?安不安全
桌面云备份可以删除吗?答案是可以的。如果用户不需要这些备份或者想要释放存储空间,桌面云备份是可以进行删除的,并且删除桌面云备份是一个相对安全的过程,但需要注意以下几点来确保操作的安全性和数据的完整性。 一、桌面云备份…...

【爬虫实战】利用代理爬取电商数据
文章目录 前言工具介绍实战获取网站数据编写代码数据展示 推荐总结 前言 当今电商平台正经历着快速的转型与升级。随着技术的进步和用户需求的多样化,电商不仅从简单的在线购物演变为综合性的购物生态系统,还融合了人工智能、大数据和云计算等先进技术。…...

python如何统计列表中元素出现的次数
在 Python 中,可以使用多种方法来统计列表中元素出现的次数。以下是一些常用的方法: 方法 1: 使用 count() 方法 list 对象有一个内置的 count() 方法,可以直接统计某个元素在列表中出现的次数。 my_list [1, 2, 3, 2, 1, 4, 2] count_of…...

【算法】山脉数组的峰顶索引
难度:中等 题目描述: 给定一个长度为 n 的整数 山脉 数组 arr ,其中的值递增到一个 峰值元素 然后递减。 返回峰值元素的下标。 你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。 示例 1: 输入:arr [0,1,0]…...

牛客 JZ31.栈的压入,弹出序列 C++写法
牛客 JZ31.栈的压入,弹出序列 C写法 思路🤔: 创建一个栈,push压入序列,然后用栈顶跟弹出序列比,如果一样就出栈并且继续比较,不一样就再次push入栈,直到压入序列走完,如果…...

PageHelper在Mybatis的一对多表关联时total数错误
最近在学习PageHelper遇到一个bug记录一下: 在Mybatis的一对多表中,PageHelper获取的total是所有的记录数,而不是我想要的第一次sql的记录数。 解决方案1: 不要在mapper层获取一对多关联,在service层先获取一&#…...

(20240806)硫氧镁 / 碱式硫酸镁-混凝土
一、目录 一篇博士论文,5篇硕士论文,南京航空航天大学双一流211,60。余红发团队 具体涉及到 (1) 碱式硫酸镁水泥的混凝土应用 、(一篇博士论文) 有微观分析 (2)混…...

string类的模拟实现(C++)
一、前言 想要模拟实现一个库中的类,那就要首先要熟悉如何使用这个类。建议通过下面博客,完成对Cstring类的学习。 C的string类-CSDN博客 二、模拟实现 我们将从string的成员函数即成员变量入手,模拟实现string类。 成员变量 string类的…...

C++_sizeof的相关知识点
1.指针的大小永远是固定的,取决于处理器位数,32位就是 4 字节,64位就是 8 字节 2.数组作为函数参数时会退化为指针,大小要按指针的计算 int func(char array[]) {printf("sizeof%d\n", sizeof(array));printf("s…...

Istio Proxy的Envoy代理架构中,Upstream提供的功能是:
Istio Proxy的Envoy代理架构中,Upstream提供的功能是: A. 接收来自Envoy连接和请求的主机,并返回响应 B. 连接的一组逻辑相同的上游主机 C. 将下游主机连接到Envoy的主机,用来发送请求并接受响应 选择A Istio Proxy的Envoy代理架…...

LeetCode 热题 HOT 100 (015/100)【宇宙最简单版】
【栈】No. 0155 最小栈【中等】👉力扣对应题目指路 希望对你有帮助呀!!💜💜 如有更好理解的思路,欢迎大家留言补充 ~ 一起加油叭 💦 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持! …...

【HarmonyOS】鸿蒙应用实现截屏
【HarmonyOS】鸿蒙应用实现截屏 组件截屏 通过componentSnapshot的get函数,将需要截图的组件设置id传进去即可。 import { componentSnapshot } from kit.ArkUI; import { image } from kit.ImageKit;/*** 截图*/ Entry Component Preview struct SnapShotPage {S…...

Conda包依赖侦探:conda inspect命令全解析
Conda包依赖侦探:conda inspect命令全解析 在Conda环境中,管理包及其依赖关系是一项重要任务。conda inspect命令是一个强大的工具,它可以提供包的详细信息,包括依赖关系、链接、版本等。这对于诊断环境问题、理解包的依赖结构以…...

数模——灰色关联分析算法
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 文章目录 前言 一、基本概念了解 1.什么是灰色系统? 2.什么是关联分析? 二、模型原理 三、建模过程 1.找母序列(参考序列&am…...