代码整洁之道读书笔记(只从前端方面)
本书作者罗伯特·C. 马丁(江湖人称鲍勃大叔),鲍勃大叔主要著作有架构整洁之道、敏捷整洁之道、代码整洁之道、程序员的职业素养等书,Clean Coders网站的创始人。本书以精炼的语言辅以生动有趣的漫画(虽然很多图我没看懂,但是感觉还是不错的),虽然本书比较老,但是还是不错的,思想是不会变的,推荐阅读
下面来分享本文章核心内容,读书笔记:
读书中感悟最深的名言
-
原文:整洁的代码力求集中,每个函数、每个类和每个模块都全神贯注于一事不受四周细节的干扰和污染
感悟: 一份整洁的代码就应该是指责单一的,不让它被其他事情所累
-
原文:让营地比你来时更干净
感悟:著名的营地法则,被很多人引用,也昭示了我们应该时刻保证代码一次比一次好
-
原文:你该明白了。花费读与写时间的比例超过10:1。写新代码时,我们一直在读旧代码
既然比例如此之高,我们就想让读的过程变得轻松,即便那会使编写过程更难。不可能光写不读,所以使之易读实际也就是使之易写。这事概无例外。不读周边代码的话就没法写代码。编写代码的难度,取决于读周边代码的难度。要想干得快,要想早点做完,要想轻松写代码,先让代码易读吧。感悟:以读者角度去审核自己的代码
-
原文:尽管讨论了这么多关于代码语句及由代码语句形成的函数的表达力,但是,除非我们将注意力放到代码组织的更高层面,否则始终不能得到整洁的代码。
感悟:点使得模块内的代码通俗易懂,面使得代码整体变得通俗易懂,两者都不可以忽略。
-
原文:复杂要人命。它消磨开发者的生命,让产品难以规划、构建和测试”
感悟: 出自微软公司首席技术官,Ray Ozzie,凡事应当从简,不要把你的一个模块搞成一大坨,小模块的代码不一定是好代码,但一大坨的代码一定不是好代码
5s原则
整理(Sei),或谓组织(想想英语中的sort一词)。搞清楚事物之所在通过恰当地命名之类的手段—至关重要。觉得命名标识无关紧要?读读后面的章节吧
整顿(Seiton),或谓整齐(想想英文中的 systematize一词)。有句美国老话说:物皆有其位,而后物尽归其位( A place for everything, and everything in its place)。每段代码都该在你希望它在的地方一一如果不在那里,就需要重构了。
清楚( Seiso),或谓清洁(想想英文中的 shine-词)。就像清理工作地的拉线、油污和边角废料,对于那种四处遗弃的带注释的代码及反映过往或期望的无注释代码,本书作者怎么说的来着?除之而后快。
清洁(Seiketsu),或谓标准化。有关如何保持工作地清洁的组内共识。本书有没有里来提到在开发组内使用一贯的代码风格和实践手段?这些标准从哪来?读读看
身美(Shitsuke),或谓纪律(自律)。在实践中贯彻规程,并时时体现在个人工作读读看中,而且要乐于改进
本书中列举的几位大佬对整洁代码的定义
Bjarne(c++之父)
我喜欢优雅和高效的代码。代码逻辑应当直截了当,令缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事
Grady Booch(面向对象分析与设计作者)
整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句
Dava Thomas(eclipse战略教父)
整洁的代码应可由作者之外的开发这阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰的、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需的信息均可通过代码自身清晰表达
MIchael Feathers(修改代码艺术的作者)
我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的。整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码的作者什么都想到了,如果你企图改进它,总会回到原点,赞人留下的代码赞叹某人留给你的代码
ron Jeffries(极限编程实施、c#极限编程探险的作者
- 能通过所有测试
- 没有重复代码
- 体现系统中的全部设计理念;
- 包括尽量少的实体,比如类、方法、函数等
(因为过长,只截取一部分)
Ward Cunningham(wiki发明者,极限编程创始人之一)
如果每个例程都让你感到深合已意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在的,就可以称之为漂亮的代码
感悟:不同的知名程序员对于整洁代码的定义,虽然略有不同,但基本都是透露着以下几个字眼
1、细节
2、单一
3、易读
4、维护
5、测试
6、依赖
7、渐进式重构
好的命名的规则
-
名副其实
能用名字将程序说明白
-
避免误导的名字
如多义词、如非数组使用list、如同一个类内使用外形很相似的单词、难以理解的缩写等
-
避免无意义的区分
如使用a1、a2,userData,userInfo,等来进行变量的对比。 使用name,namestring,来做无意义的赘述。
-
避免使用读不出来的长串名称
这类的名称不方便交流也不方便记忆,更对解释毫无意义
-
定义可搜索的名称
找 MAX CLASSES_ PER STUDENT很容易,但想找数字7就麻烦了,避免单字母、关键字、通用规约的常见单词(如e),名称长短应该与其作用于大小相对应
-
避免思维映射
不要声明那些让读者需要先将他们翻译之后,才能往下阅读的名字,如单字母变量
-
类名应该是名词或名词短语,而不应该是动词
-
方法名应该是一个动词
-
别起那些俗语或者搞笑名字,要言到意到
-
别用一词多义的词、要多想想读者会不会曲解
-
尽量使用程序员专业的词,避免阅读者需要去找各种专业领取的词汇,如果实在找不到,就选择涉及问题领域的单词
-
不要添加无意义语境,如你整个项目都是汽车项目,你在所有函数名字前都增加car前缀
如何写好函数
-
短小精悍(最多不要超过20行)
-
**if/else/while/for中的代码都应该只有一行(调用函数,个人观点:当然实际开发中不一定严格,但是一旦逻辑复杂,行数较多就将他们抽出去)这也会使得一块代码锁进最多两次
** -
函数应该只做一件事,做好这件事,只做一件事
-
函数应该有一个具有描述性的名称,哪怕这个名称稍微长一点,但总好过短但是难以理解的名称好
-
函数参数尽可能少,可以将其声明为一个this. xxx或者通过计算得出,可以参考我重构笔记中的做法
-
不应该出现标示参数,标示参数意味着函数不只在做一件事
-
尽量不要传入非当前类,如果传入非当前类,不如将其移动到那个类
-
如果函数看起来需要2个、3个或3个以上参数,就说明其中一些参数应该封装为类(特定情况除外,比如point就是天生就要俩参数)
-
函数应该无副作用,比如:不能再checkpassword中调用clearsession,应该将clear抽取出去,哪怕实在不能抽出,也应该在函数名字上告知,这个函数会清空session
-
应该将函数设置值和函数读取值分割开来
-
使用异常来代替返回错误码,并要将正确逻辑和错误逻辑区分开。
-
一个函数应该只有一个返回,并且严格禁止使用goto
注释应该怎么写
-
应当尽量使用良好的命名来表达意思,而不是过多的依靠注释,当依靠注释过多时,则意味着你现在所写有问题
-
注释应该整洁而有表达力
-
**如果参数或者返回值是某个标准库的一部分,或是你不能修改的代码,提供注释其含义的阐述
** -
用于警示可能会造成某种后果,或是解释为什么要这么做(某种不合常理的特殊场景)
-
不要写那些读代码,比看注释时间还短的注释,这样的注释只会耽误时间
-
不要写不够精准,甚至误导的注释
-
所谓每个函数都要有 Javadoc(或者jsdoc)或每个变量都要有注释的规矩全然是愚盘可笑的。这类注释徒然让代码变得散乱,满口胡言,令人迷惑不解。
-
不要在头部写那些日志式注释(每一次修改的备注)
-
不要写废话注释,如在catch内写:这里是报错处理
-
不要写语句结束符号后面增加xxxend来标记一个模块结束,如果这么写则意味着,函数过长,应该做的是函数细化
-
不要写那些xxx编辑了xxx的注释,依赖版本管理
-
不要保留那些废弃的代码,具体做法可以想重构中说的那样。
如果真的未来很大可能会有用,在删除的地方留下版本号即可,更多情况下我们不需要这样
-
注释应该在需要的地方,不要让注释离需要注释的地方差十万八千里
-
不要写出注释本身还需要解释或者翻译的注释
-
如果是非公共库或者需要文档的类,就没必要写jsdoc/javadoc这样的头
编写代码的格式/风格建议
-
团队内应该一致采用一套格式规则
-
垂直格式(单文件行数)不要过大
-
每个空白行都是一条线索,标识出新的独立概念。往下读代码时,你的目光总会停留于空白行之后的那一行。反之则是将相同关联的代码聚合
-
除非有足够的理由,否则不要吧相同关联的概念,放到不同的文件中
-
变量声明. 函数调用应该尽可能靠近使用的地方
-
被调用的函数应该放在调用函数下面(从顶上往下阅读,但是如果是在js中,会有未定义一说,这个时候就应该在上面声明,但是在类中则无这个问题)
-
横向代码应该尽可能短,至少应该不能超过100个字符
-
横向上适当位置增加空格来分隔,如赋值语句,将左右两部分分开
-
在每个代码块内部增加缩进,标示不同代码块
错误处理
-
使用异常来让程序结束,而不是使用状态码
-
异常应当在当前执行,而不要波及到他外面的的上级函数
-
应该创建可以追踪的错误. 消息
一个整洁的代码应该有单元测试
-
TDD定律
第一定律 在编写不能通过的单元测试前,不可编写生产代码。
第二定律 只可编写刚好无法通过的单元测试,不能编译也算不通过。
第三定律 只可编写刚好足以通过当前失败测试的生产代码。
-
测试代码应该和生产代码一样重视,因为测试是随着生产代码的演进而改变
-
测试代码应当简单、精悍、足具表达力
-
每个测试一个断言
-
每个测试应该只测试一个概念
类应该怎么写
-
公共静态变量、私有静态变量、私有实体变量这样的顺序来编写变量
-
类应该短小,权责不宜过多,一个类应该只有一个权责,并以类名来表达他的权责(权责=职责)
-
当类的数目过多时,应当对其进行分组管理,方便当我们想要查找某一个功能时,知道去哪个组找
-
高内聚:一个类应该只有少数实体变量,通常来说所有的方法中操作的变量越多,内聚就越强
-
如果一组子集方法所用的实体变量数量增加,往往意味着至少一个类要从大类中挣扎出来了,应当尝试将这些变量和方法分拆到两个或多个类中,以达到更为内聚
-
应该符合开放封闭原则,对扩展开放,对修改封闭
-
依赖倒置原则,类应该依赖抽象而不是具体细节(也就是降低各个类之间的连接度,低耦合)
如何在系统层级保持整洁
-
将系统的构造和使用分开
软件系统应将起始过程和起始过程之后的运行时逻辑分离开,在起始过程中构建应用对象,也会存在互相缠结的依赖关系
方案:
将全部的构造过程搬迁至main,或称之为main的模块
如果需要控制何时创建子对象,则可以通过工厂模式进行控制。即:在main中构造,而在工厂中负责创建子对象
-
我们应该只去实现令天的用户故事(需求),然后重构,明天再扩展系统、实现新的用户故事。这就是迭代和增量敏捷的精髓所在
扩展的基础在于,你能对架构有恰当的切分关注面,即每个类既不会重复,也不会缺损
模块化和关注面切分成就了分散话管理和决策
ps:由于这是一本java书籍,对js有很多的作者的点,在js中不通用,如:注册表、数据库的等。 所以我只列出与我(前端)有关的点
迭进
-
运行所有测试
只有所有测试都通过,才意味着你实现的东西是符合需求的东西。但是测试在前端,其实很多公司都是缺乏的。 但是有好的测试,确实可以保证我们在修改和清理代码时不必恐惧
-
不可重复
重复是拥有良好设计的系统的大敌。它代表着额外的工作、额外的风险和额外且不必要的复杂度。重复有多种表现。极其雷同的代码行当然是重复。类似的代码往往可以调整得更相似,这样就能更容易地进行重构。
-
表达了程序员的意图
- 好的名称
- 保持函数和类的短小
- 采用标准的命名法(说的是设计模式的标准,如使用命令模式,内部可以用command)
- 良好的单元测试
- 编写完成后的回顾
-
尽可能减少类和方法的数量。
避免因为实现某种模式或者某中思想而增加很多的无用类或者函数。 但是这条的优先级和上面相比,并没有他们重要。
以上规则按其重要程度排列。
逐步改进
-
当我们在维护过程中,嗅到了它将会变得难以理解,那我们就该要对其进行重构,变得更利于扩展和理解
-
避免以改进之名,大动其结构
-
修改之前确保测试涵盖完全
-
确保改进过程中代码始终行为一致,如果不一致要先修复他。
-
小步幅修改和保持测试通过
鲍勃大叔觉得的坏气味(扩展自马丁福勒的重构1)
注释篇
-
不恰当的信息
通常,作者、最后修改时间、SPR数等元数据不该在注释中出现。注释只应该描述有关代码和设计的技术性信息。
-
废弃的注释
过时、无关或者不正确的注释就是废弃的注释,最好发现它就尽快清理或者更新
-
冗余注释
如果注释描述的是某种充分自我描述了的东西,那么注释就是多余的,例如你给i++加一个注释为:i自增
-
糟糕的注释
如果要编写一条注释,就花时间保证写出最好的注释,字斟句酌,使用正确的语法和拼写,别闲扯,别画蛇添足,要保持简洁。
-
注释掉的代码
它污染了所属的模块,分散了想要读他的人的注意力,看到注释掉的代码,就删除它!别担心,源代码控制系统还会记得它有人真的需要,可以签出较旧的版本。
函数篇
-
过多的参数
无参数最好,一个参数次之,二个三个在次之,有三个以上就要谨慎了
-
输出参数
输岀参数违反直觉,因为读者期望参数用于输入而非输出。如果函数非要修改什么东西的状态,就修改它所在对象的状态好了。
-
标示参数
他的出现就意味着你这个函数干了不止一件事
-
死函数
永不被调用的函数应该被丢弃。保留死函数纯属浪费,和注释掉的代码一样,我们有版本管理
一般性问题
-
一个源文件多种语言
-
明显的行为未被实现
函数或类应该实现其他程序员有理由期待的行为
-
不正确的边界行为
不要信赖自己的直觉,而不努力去证明代码在所有的所有角落和边界情形下都真正能工作
-
忽视安全
任何的报错和安全问题都应该得到重视
-
重复
这个没必要说了,只要是一本讲代码、讲模式的书都会有这一条(DRY原则、极限编程核心原则之一)
-
在错误的抽象层级上的代码
良好的软件设计要求分离位于不同层级的概念将它们放到不同容器中
-
基类依赖派生类
通常来说,基础类应该对派生类一无所知才对
-
信息过多
设计良好的接口并不提供许多需要依赖的函数,所以耦合度也较低。设计低劣的接口提供大量你必须调用的函数,耦合度较高
-
死代码
死代码就是不执行的代码,各种用不到的文件、用不上的条件判断等都算
-
垂直分割
变量和函数应该在靠近被使用的地方定义。本地变量应该正好在其首次被使用的位置上面声明
-
前后不一致
从一而终,一旦坚决贯彻,就能让代码更加易于阅读和修改
-
混淆视听
没有用到的变量、函数,没有信息量的注释,等等,这些都是应该移除的废物。保持源文件整洁,组织良好,不被搞乱。
-
人为耦合
人为耦合是指两个没有直接目的的模块之间的耦合。其根源是将变量、常量或函数不恰当地放在临时方便的位置,这是种漫不经心的偷懒行为
-
特性依赖
类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数
-
选择算子参数
就是说一个参数标记了不同的算法运算。
-
晦涩的意图
代码要尽可能具有表达力。联排表达式、匈牙利语标记法和魔术术都遮蔽了作者的意图。
-
位置错误的权责
软件开发者做出的最重要决定之一就是在哪里放代码。代码应该放在读者自然而然期待它所在的地方
-
不恰当的静态方法
要结合实际情况,并且从调用者角度思考,是否定义为静态方法(当然也要根据场景)
-
使用解释性变量
让程序可读的最有力方法之一就是将计算过程打散,再用有意义的单词命名的变量中放置的中间值
-
函数名称应该表达其行为
也不多说了。同样是一本写代码的书都会这么写。 也是为了职责单一以及便于理解
-
理解算法
在你认为自己完成某个函数之前,确认自己理解了它是怎么工作的。通过全部测试还不好,你必须知道解决方案是正确的
-
把逻辑依赖改为物理依赖
-
用多态替代if/else或switch/case
对于给定的选择类型,不应有多于一个的 switch语句。在那个 switch语句中的多个case,必须创建多态对象,取代系统中其他类似的switch语句,if就是一种坏气味!
-
遵循标准约定
标准都不统一还谈什么好代码
-
用常量取代魔术数
最古老的规则之一,等于用好的命名帮助理解代码。但是如果每个人都能理解这个数的意思。 就没必要常量来隐藏了。例如3.14。
-
准确
在代码中做决定时,确认自己足够准确。明确自己为何要这么做,如果遇到异常情况如何处理。代码中的含糊和不准确,要么是意见不同的结果,要么源于懒惰。无论原因是什么都要消除。
-
结构甚于约定
结构甚于约定的设计原则。命名约定很好,但却次于强制性的结构
-
封装条件
应该把解释了条件意图的函数抽离出来。
-
避免否定性条件
否定的表达式一定是比肯定是要难理解一些。所以尽可能写肯定表达式
-
函数只该做一件事
老掉牙的一个规则了。不需要赘述了吧。
-
掩蔽时序耦合
常常有必要使用时序耦合,但你不应该掩蔽它。排列函数参数,好让它们被调用的次序显而易见
-
别随意
不作为类工具的公共类,不应该放到其他类里面惯例是将它置为 public,并且放在代码包的顶部。
-
封装边界条件
边界条件难以追踪。把处理边界条件的代码集中到一处,不要散落于代码的每一处
-
在较高的层级放置可配置数据
自顶而上去写可配置的数据。 位于较高层级的配置性常量易于修改,它们向下贯穿应用程序。应用程序的较低层级并不拥有这些常量的值。
-
避免传递浏览
德墨忒耳律,确保模块只了解其直接协作者,而不了解整个系统的游览图,让直接协作者提供所需的全部服务,而不必逛遍系练的对象全图。搜寻我们要调用的方法。
名称
-
采用描述性名称
软件中的名称对于软件可读性有90%的作用。你要花时间明智地起名,保持名称有关,名称太重要了,不可随意对待
-
名称应该与抽象层级相符
个人理解为名称应该结合当前模块的语境来针对性的起名字。
-
尽可能使用标准命名法。
如果名称基于既存约定或用法,就出易于理解,例如将对象转换为字符串的函数通常命名为toString。最好是遵循这些约定,而不是自己创造命名法
-
无歧义的名称
就是选用不会混淆的名称。也包含那些多义词
-
为较大作用范围选用较长的名称
就和编写可读代码的作者所说一致,变量作用域越大,变量命名就应该越长
-
避免编码
这个编码说的是不应在名称中包括类型或作用范围,不要用匈牙利语命名法污染你的名字
-
名字应该说明副作用
说的也一样。就是取一个确切可靠能描述整个函数的名字。 有多个动作的函数就不应该用一个动词
测试篇
-
针对测试,多少才叫足够?
只要还有没被测试探测过的条件,或是还有没被验证的计算。测试就不算足够
-
使用覆盖率工具,更好的找出测试不足的地方
-
别略过小测试
-
被忽略的测试就是对不确定事物的疑问
-
测试边界条件特别注意测试边界条件。
算法的中间部分正确但边界判断错误的情形很常见。
-
全面测试相近的缺陷
在某个函数中发现一个缺陷时,最好全面测试那个函数。你可能会发现缺陷不止
-
测试失败的模式有启发性
-
测试覆盖率的模式有启发性
-
测试应该快速
注: 鲍勃大叔的书中,相当于对重构中所提到的一些坏气味做了一些详细的说明。这块赶紧读的还是比重构中要更透一些。毕竟更细了,对重构感兴趣的,可以参考我的重构笔记
读后感
虽然作为一本java的2011年的书籍,但是鲍勃大叔一贯的幽默以及代码编写的深厚内功还是让我收获了不少,我相信如果本书出一版针对js的 我会吸收更多知识,不过很知足,虽然只读了通用的一些知识,也是收获颇丰。希望学习如何写js代码的人可以作为辅助书籍查看