clean code-代码整洁之道 阅读笔记(第十七章 终章)

大纲

第十七章 味道与启发

17.1 注释

C1:不恰当的信息

C2:废弃的注释

C3:冗余注释

C4:糟糕的注释

C5:注释掉的代码

17.2 环境

E1:需要多步才能实现的构建

E2:需要多步才能做到的测试

17.3 函数

F1:过多的参数

F2:输出参数

F3:标识参数

F4:死函数

17.4 一般性问题

G1:一个源文件中存在多种语言

G2:明显的行为未被实现

G3:不正确的边界行为

G4:忽视安全

G5:重复

G6:在错误的抽象层级上的代码

G7:基类依赖于派生类

G8:信息过多

G9:死代码

G10:垂直分隔

G11:前后不一致

G12:混淆视听

G13:人为耦合

G14:特性依恋

G15:选择算子参数

G16:晦涩的意图

G17:位置错误的权责

G18:不恰当的静态方法

G19:使用解释性变量

G20:函数名称应该表达其行为

G21:理解算法

G22:把逻辑依赖改为物理依赖

G23:用多态替代lf/Else或Switch/Case

G24:遵循标准约定

G25:用命名常量替代魔术数

G26:准确

G27:结构甚于约定

G28:封装条件

G29:避免否定性条件

G30:函数只该做一件事

G31:掩蔽时序耦合

G32:别随意

G33:封装边界条件

G34:函数应该只在一个抽象层级上

G35:在较高层级放置可配置数据

G36:避免传递浏览

17.5 Java

J1:通过使用通配符避免过长的导入清单

J2:不要继承常量

J3:常量vs.枚举

17.6 名称

N1:采用描述性名称

N2:名称应与抽象层级相符

N3:尽可能使用标准命名法

N4:无歧义的名称

N5:为较大作用范围选用较长名称

N6:避免编码

N7:名称应该说明副作用 

17.7 测试

T1:测试不足

T2:使用覆盖率工具

T3:别略过小测试

T4:被忽略的测试就是对不确定事物的疑问

T5:测试边界条件

T6:全面测试相近的缺陷

T7:测试失败的模式有启发性

T8:测试覆盖率的模式有启发性

T9:测试应该快速

17.8小结


第十七章 味道与启发

17.1 注释

C1:不恰当的信息

        注释只应该描述有关代码和设计的技术性信息。

C2:废弃的注释
C3:冗余注释
C4:糟糕的注释

        使用正确的语法和拼写。别闲扯,别画蛇添足,,保持简洁。

C5:注释掉的代码

        看到注释掉的代码,就删除它!别担心,源代码控制系统还会记得它。

17.2 环境

E1:需要多步才能实现的构建

        构建系统应该是单步的小操作。不应该从源代码控制系统中一小点一小点签出代码。不应该需要一系列神秘指令或环境依赖脚本来构建单个元素。不应该四处寻找额外的 JAR、XML文件和其他系统所需的杂物。你应当能够用单个命令签出系统,并用单个指令构建它。

E2:需要多步才能做到的测试

        你应当能够发出单个指令就可以运行全部单元测试。能够运行全部测试是如此基础和重要,应该快速、轻易和直截了当地做到。

17.3 函数

F1:过多的参数

        函数的参数量应该少。没参数最好,一个次之,两个、三个再次之。三个以上的参数非常值得质疑,应坚决避免。

F2:输出参数

        输出参数违反直觉。如果函数非要修改什么东西的状态不可,就修改它所在对象的状态。

F3:标识参数

        布尔值参数大声宣告函数做了不止一件事。它们令人迷惑,应该消灭掉。

F4:死函数

        永不被调用的方法应该丢弃。

17.4 一般性问题

G1:一个源文件中存在多种语言

        理想的源文件包括且只包括一种语言。现实上,我们可能会不得不使用多于一种语言但应该尽力减少源文件中额外语言的数量和范围。

G2:明显的行为未被实现

        遵循"最小惊异原则"(The Principle of Least Surprise),函数或类现其他程序员有理由期待的行为。例如,考虑一个将日期名称翻译为表示该日期的枚举的函数。

Day day = DayDate.StringToDay(String dayName);

        我们期望字符串 Monday 翻译为 Day.MONDAY。我们也期望常用缩写形式也能被翻译出来,我们还期待函数忽略大小写。
        如果明显的行为未被实现,读者和用户就不能再依靠他们对函数名称的直觉。他们不再信任原作者,不得不阅读代码细节。

G3:不正确的边界行为

        没什么可以替代谨小慎微。每种边界条件、每种极端情形、每个个异常都代表了某种可能搞乱优雅而直白的算法的东西。别依赖直觉。追索每种边界条件,并编写测试。

G4:忽视安全

        忽视安全相当危险。手工控制serialVersionUID可能有必要,但总会有风险。关闭某些编译器警告(或者全部警告!)可能有助于构建成功,但也存在陷于无穷无尽的调试的风险。关闭失败测试、告诉自己过后再处理,这和假装刷信用卡不用还钱一样坏。

G5:重复

        本书提到的最重要的规则之一。

        每次看到重复代码,都代表遗漏了抽象。

        重复最明显的形态是你不断看到明显一样的代码,可以用单一方法来替代之。

        较隐蔽的形态是在不同模块中不断重复出现、检测同一组条件的switch/case或if/else链。可以用多态来替代之。

        更隐蔽的形态是采用类似算法但具体代码行不同的模块。这也是一种重复,可以使用模板方法模式或策略模式来修正。

G6:在错误的抽象层级上的代码

        创建分离较高层级一般性概念与较低层级细节概念的抽象模型。

        所有较低层级概念放在派生类中,所有较高层级概念放在基类中。

        要点是不能就错误放置的抽象模型撒谎。孤立抽象是软件开发者最难做到的事之一,而且一旦做错也没有快捷的修复手段。

G7:基类依赖于派生类

        通常来说,基类对派生类应该一无所知。

        有时,派生类数量严格固定,而基类中拥有在派生类之间选择的代码 。解决方法:把派生类和基类部署到不同的jar文件中。

G8:信息过多

        优秀的软件开发人员学会限制类或模块中暴露的接口数量。类中的方法越少越好。函数知道的变量越少越好。类拥有的实体变量越少越好。
        隐藏你的数据。隐藏你的工具函数。隐藏你的常量和你的临时变量。不要创建拥有大量方法或大量实体变量的类。不要为子类创建大量受保护变量和函数。尽力保持接口紧凑。通过限制信息来控制耦合度。

G9:死代码

        死代码就是不执行的代码。可以在检查不会发生的条件的if语句体中找到。可以在从不抛出异常的try语句的catch块中找到。可以在从不被调用的小工具方法中找到,也可以在永不会发生的switch/case条件中找到。
        死代码的问题是过不久它就会发出臭味。时间越久,味道就越酸臭。这是因为,在设计改变时,死代码不会随之更新。它还能通过编译,但并不会道循较新的约定或规则。它编写的时候,系统是另一番模样。如果你找到死代码,就体面地地埋葬它,将它从系统中删除掉。

G10:垂直分隔

        变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明,垂直距离要短。本地变量不该在其被使用之处几百行以外声明。

        私有函数应该刚好在其首次被使用的位置下面定义。

G11:前后不一致

        小心选择约定,一旦选中,就小心持续遵循。

G12:混淆视听

        没有实现的默认构造器、没有用到的变量、从不调用的函数、没有信息量的注释等等,这些都是应该移除的废物。保持源文件整洁,良好地组织,不被搞乱。

G13:人为耦合

        一般来说,人为耦合是指两个没有直接目的之间的模块的耦合。其根源是将变量、常量或函数不恰当地放在临时方便的位置。这是种漫不经心的偷懒行为。花点时间研究应该在什么地方声明函数、常量和变量。不要为了力便随手放置,然后置之不理。

G14:特性依恋

        类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。当方法通过某个其他对象的访问器和修改器来操作该对象内部数据,则它就依恋于该对象所属类的范围。它期望自己己在那个类里面,这样就能直接访问它操作的变量。

G15:选择算子参数

        算子可能是布尔类型、枚举元素、整数或任何一种用于选择函数行为的参数。使用多个函数,通常优于向单个函数传递某些代码来选择函数行为。

G16:晦涩的意图

        代码要尽可能具有表达力。联排表达式、匈牙利语标记法和魔术数都遮蔽了作者的意图。
例如,下面是overTimePay函数可能的一种表现形式:

public int m_otCalc(){return iThsWkd * iThsRte+ (int)Math.round(0.5 * iThsRte * Math.max(0, iThsWkd-400));
}


        它既短小又紧凑,但实际上不可捉摸。值得花时间将代码的意图呈现给读者。

G17:位置错误的权责

        代码应该放在读者自然而然期待它所在的地方。

G18:不恰当的静态方法

        通常应该倾向于选用非静态方法。如果有疑问,就是用非静态函数。如果的确需要静态函数,确保没机会打算让它有多态行为。

G19:使用解释性变量

        让程序可读的最有力方法之一,就是将计算过程打散成在用有意义的单词命名变量中放置中间值

        这事很难做过火。解释性变量多比少好。只要把计算过程打散成一系列良好命名的中间值,不透明的模块就会突然变得透明,这很值得注意。

G20:函数名称应该表达其行为

        如果你必须查看函数的实现(或文档)才知道它是做什么的,就该换个更好的函数名,或者重新安排功能代码,放到有较好名称的函数中。

G21:理解算法

        在你认为自己完成某个函数之前,确认自己理解了它是怎么工作的。通过全部测试还不够好。你必须知道解决方案是正确的。
        获得这种知识和理解的最好途径,往往是重构函数,得到某种整洁而足具表达力、清楚呈示如何工作的东西。

G22:把逻辑依赖改为物理依赖

        如果某个模块依赖于另一个模块,依赖就该是物理上的而不是逻辑再上的。依赖者模块不应对被依赖者模块有假定(换言之,逻辑依赖)。它应当明确地询问后者全部信息。

G23:用多态替代lf/Else或Switch/Case

        "单个switch"规则:对于给定的选择类型,不应有多于一个switch语句。在那个switch语句中的多个case,必须创建多态对象,取代系统中其他类似switch语句。

G24:遵循标准约定

        每个团队都应遵循基于通用行业规范的一套编码标准。编码标准维应指定诸如在何处声明实体变量,如何命名类,方法和变量,在何处放置括号等等。团队不应用文档描述这些约定,因为代码本身提供了范例。

G25:用命名常量替代魔术数
G26:准确

        在代码中做决定时,确认自己足够准确。明确自己为何要这么做,如果遇到异常情况如何处理。别懒得理会决定的准确性。如果你打算调用可能返回null的函数,确认自己检查了null值。如果查询你认为是数据库中唯一的记录,确保代码检查不存在其他记录。如果要处理货币数据,使用整数,并恰当地处理四舍五入。如果可能有并发更新,确认你实现了某种锁定机制。
        代码中的含糊和不准确要么是意见不同的结果,要么源于懒惰。无论原因是什么,都要消除。

G27:结构甚于约定

        坚守结构甚于约定的设计决策。命名约定很好,但却次于强制性的结构。

G28:封装条件

        如果没有if或while语句的上下文,布尔逻辑就难以理解。应该把解释了条件意图的函数抽离出来。

//例如:
if(shouldBeDeleted(timer))
//要好于
if(timer.hasExpired() && !timer.isRecurrent())
G29:避免否定性条件

        否定式要比肯定式难明白一些。所以,尽可能将条件表示为肯定形式。

G30:函数只该做一件事
G31:掩蔽时序耦合

        常常有必要使用时序耦合,但你不应该掩蔽它。排列函数参数,好让它们被调用的次序显而易见。

 =》

G32:别随意

        构建代码需要理由,而且理由应与代码结构相契合。如果结构显得太随意,其他人就会想修改它。如果结构自始至终保持一致,其他人就会使用它,并且遵循其约定。

G33:封装边界条件

        边界条件难以追踪。把处理边界条件的代码集中到一处,不要散落于代码中。

G34:函数应该只在一个抽象层级上

        函数中的语句应该在同一抽象层级上,该层级应该是函数名所示操作的下一层。

G35:在较高层级放置可配置数据

        如果你有个已知并该在较高抽象层级的默认常量或配置值,不要将它埋藏到较低层级的函数中。把它作为较高层级函数调用较低层级函数时的一个参数。

G36:避免传递浏览

        正确的做法是让直接协作者提供所需的全部服务。不必逛遍系统的对象全图,搜寻我们
要调用的方法。

17.5 Java

J1:通过使用通配符避免过长的导入清单
J2:不要继承常量
J3:常量vs.枚举

        别再用public static final int,而是使用枚举。

17.6 名称

N1:采用描述性名称
N2:名称应与抽象层级相符

        不要取沟通实现的名称;取反映类或函数抽象层级的名称。

N3:尽可能使用标准命名法
N4:无歧义的名称
N5:为较大作用范围选用较长名称
N6:避免编码

        不应在名称中包括类型或作用范围信息。

N7:名称应该说明副作用 

        名称应该说明函数、变量或类的一切信息。不要用名称掩蔽副作用。不要用简单的动词来描述做了不止一个简单动作的函数。

17.7 测试

T1:测试不足
T2:使用覆盖率工具
T3:别略过小测试
T4:被忽略的测试就是对不确定事物的疑问
T5:测试边界条件
T6:全面测试相近的缺陷
T7:测试失败的模式有启发性
T8:测试覆盖率的模式有启发性
T9:测试应该快速

17.8小结

        这份启发与味道的清单很难说已完备无缺。我不能确定这样一份清单会不会完备无缺。但或许完整性不该是目标,因为该清单确实给出了一套价值体系那套价值体系才该是目标,也是本书的主题所在。整洁代码并非遵循一套规则写就。学习一系列启发并不足以让你成为软件匠人。专业性和技艺来自于驱动规程的价值观。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/373343.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51单片机嵌入式开发:2、STC89C52操作GPIO口LED灯

STC89C52操作GPIO口LED灯 1 芯片介绍1.1 芯片类型1.2 芯片系列说明 2 GPIO引脚寄存器说明3 GPIO操作3.1 GPIO输入3.2 GPIO输出3.3 GPIO流水灯3.4 Protues仿真 4 总结 1 芯片介绍 1.1 芯片类型 芯片采用宏晶科技品牌下的STC89C52RC单片机 选择STC89C52RC系列STC89C58RD系列单片…

基于Java的学生选课系统

第1章 系统概述 1.1概述 背景:随着计算机网络技术的发展,Web 数据库技术已成为应用最为广泛的网站架构基础技术。学生选课系统作为教育单位不可缺少的部分,其内容对于学校的决策者和管理者至关重要。传统的人工管理方式存在效率低、保密性差等…

LabVIEW平台从离散光子到连续光子的光子计数技术

光子计数技术用于将输入光子数转换为离散脉冲。常见的光子计数器假设光子是离散到达的,记录到来的每一个光子。但是,当两个或多个光子同时到达时,计数器会将其记录为单个脉冲,从而只计数一次。当连续光子到达时,离散光…

ceph存储

1 存储简介 存储的三种方式包括:块存储、文件存储、对象存储1。此外,还有内存存储、硬盘存储和闪存存储2。 内存存储:临时性数据存储方式,存储速度快,容量有限,通常用来存储正在使用的程序和数据。硬盘存…

测试几个 ocr 对日语的识别情况

测试几个 ocr 对日语的识别情况 1. EasyOCR2. PaddleOCR3. Deepdoc(识别pdf中图片)4. Deepdoc(识别pdf中文字)5. Nvidia neva-22b6. Claude 3.5 sonnet 识别图片中的文字7. Claude 3.5 sonnet 识别 pdf 中表格8. OpenAI gpt-4o 识…

操作系统:信号究竟是什么?如何产生?

OS信号 一、信号的概念二、信号的产生1)终端按键产生信号1、 前台进程、后台进程2、验证终端按键是否产生信号 2)调用系统函数向进程发信号3)硬件异常产生信号1、浮点数溢出,CPU产生信号2 浮点数溢出,产生信号原理3. 空…

神经网络构成、优化、常用函数+激活函数

Iris分类 数据集介绍,共有数据150组,每组包括长宽等4个输入特征,同时给出输入特征对应的Iris类别,分别用0,1,2表示。 从sklearn包datasets读入数据集。 from sklearn import darasets from pandas impor…

【密码学】分组密码概述

一、分组密码的定义 分组密码和流密码都是对称密码体制。 流密码:是将明文视为连续的比特流,对每个比特或字节进行实时加密,而不将其分割成固定的块。流密码适用于加密实时数据流,如网络通信。分组密码:是将明文数据…

GuLi商城-商品服务-API-品牌管理-OSS获取服务端签名

新建第三方服务: 引入common 把common中oss的依赖都拿到第三方服务中来 配置文件: 加上nacos注解:<

windows USB 设备驱动开发-USB带宽

本文讨论如何仔细管理 USB 带宽的指导。 每个 USB 客户端驱动程序都有责任最大程度地减少其使用的 USB 带宽&#xff0c;并尽快将未使用的带宽返回到可用带宽池。 在这里&#xff0c;我们认为USB 2.0 的速度是480Mbps、12Mbps、1.5Mbps&#xff0c;这分别对应高速、全速、低速…

【QML之·基础语法概述】

系列文章目录 文章目录 前言一、QML基础语法二、属性三、脚本四、核心元素类型4.1 元素可以分为视觉元素和非视觉元素。4.2 Item4.2.1 几何属性(Geometry&#xff09;:4.2.2 布局处理:4.2.3 键处理&#xff1a;4.2.4 变换4.2.5 视觉4.2.6 状态定义 4.3 Rectangle4.3.1 颜色 4.4…

《植物大战僵尸杂交版》2.2版本:全新内容与下载指南

《植物大战僵尸杂交版》2.2版本已经火热更新&#xff0c;带来了一系列令人兴奋的新玩法和调整&#xff0c;为这款经典的塔防游戏注入了新的活力。如果你是《植物大战僵尸》系列的忠实粉丝&#xff0c;那么这个版本绝对值得你一探究竟。 2.2版本更新亮点 新增看星星玩法 这个新…

宏碁F5-572G-59K3笔记本笔记本电脑拆机清灰教程(详解)

1. 前言 我的笔记本开机比较慢&#xff0c;没有固态&#xff0c;听说最近固态比较便宜&#xff0c;就想入手一个&#xff0c;于是拆笔记本看一下有没有可以安的装位置。&#xff08;友情提示&#xff0c;在拆机之前记得洗手并擦干&#xff0c;以防静电损坏电源器件&#xff09…

ChatTTS使用

ChatTTS是一款适用于日常对话的生成式语音模型。 克隆仓库 git clone https://github.com/2noise/ChatTTS cd ChatTTS 使用 conda 安装 conda create -n chattts conda activate chattts pip install -r requirements.txt 安装完成后运行 下载模型并运行 python exampl…

Python酷库之旅-第三方库Pandas(013)

目录 一、用法精讲 31、pandas.read_feather函数 31-1、语法 31-2、参数 31-3、功能 31-4、返回值 31-5、说明 31-6、用法 31-6-1、数据准备 31-6-2、代码示例 31-6-3、结果输出 32、pandas.DataFrame.to_feather函数 32-1、语法 32-2、参数 32-3、功能 32-4、…

【计算机毕业设计】基于Springboot的IT技术交流和分享平台【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

14-56 剑和诗人30 - IaC、PaC 和 OaC 在云成功中的作用

介绍 随着各大企业在 2024 年加速采用云计算&#xff0c;基础设施即代码 (IaC)、策略即代码 (PaC) 和优化即代码 (OaC) 已成为成功实现云迁移、IT 现代化和业务转型的关键功能。 让我在云计划的背景下全面了解这些代码功能的当前状态。我们将研究现代云基础设施趋势、IaC、Pa…

MATLAB备赛资源库(1)建模指令

一、介绍 MATLAB&#xff08;Matrix Laboratory&#xff09;是一种强大的数值计算环境和编程语言&#xff0c;特别设计用于科学计算、数据分析和工程应用。 二、使用 数学建模使用MATLAB通常涉及以下几个方面&#xff1a; 1. **数据处理与预处理**&#xff1a; - 导入和处理…

MacOS如何切换shell类型

切换 shell 类型 如果你想在不同的 shell 之间切换&#xff0c;以探索它们的不同之处&#xff0c;或者因为你知道自己需要其中的一个或另一个&#xff0c;可以使用如下命令&#xff1a; 切换到 bash chsh -s $(which bash)切换到 zsh chsh -s $(which zsh)$()语法的作用是运…

VSCode无法连接网络安装插件-手动安装插件

手动安装插件&#xff1a; 你可以尝试从 Visual Studio Code Marketplace 下载 .vsix 文件&#xff0c;然后在VSCode中手动安装。 手动安装的步骤如下&#xff1a; 1.访问插件页面&#xff0c;下载 .vsix 文件。 Extensions for Visual Studio family of products | Visual S…