为什么《程序员修炼之道》评分能到 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]…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...