代码整洁之道学习笔记

目录

1、整洁代码

2、有意义的命名

3、函数

4、注释

5、格式 

6、对象和数据结构

7、错误处理

8、边界

9、单元测试

10、类

11、系统


1、整洁代码

勒布朗法则:稍后等于永不

简单代码,依其重要顺序:能通过所有测试;没有重复代码;体现系统中的全部设计理念;包括尽量少的实体,比如类、方法、函数等

减少重复代码,提高表达力,提早构建简单抽象

让营地比你来时更干净

2、有意义的命名

(1)名副其实

变量、函数或类的名称应该已经答复了所有的大问题,它该告诉你,它为什么会存在,它做什么事,应该怎么用

(2)避免误导

程序员必须避免留下掩藏代码本意的错误线索,应该避免使用与本章相悖的词;提防使用不同之处较小的名称;

(3)做有意义的区分

避免数字系列命名;避免废话来做区分

(4)使用读得出来的名称 

避免糟糕的命名

(5)使用可搜索的名称

避免使用单字母和数字常量。若变量或常量可能在代码中多处使用,则应赋其以便于搜索的名称。

(6)避免使用编码

把类型或作用域编进名称里面,徒然增加了解码的负担。没理由要求每位新人都在弄清要应付的代码之外,还要再搞懂另一种编码"语言"。这对于解决问题而言,纯属多余的负担。带编码的名称通常也不便发音,容易打错

不必使用匈牙利语标记法;不必使用成员前缀;接口前不要使用Ixxx,直接使用xxx,实现类使用xxxImpl

(7)避免使用思维映射

不应当让读者在脑中把你的名称翻译为他们熟知的名称,这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语。

(8)类名

类名和对象名应该是名词或名词短语

(9)方法名

方法名应该是动词或动词短语;重载构造器时,使用描述了参数的静态工厂方法名。

(10)别扮可爱

避免使用俗语或俚语

(11)每个概念对应一个词

给每个抽象概念选一个词,并且一以贯之。

(12)别用双关语

避免将同一单词用于不同目的,同一术语用于不同概念,基本上就是双关语了。

(13)使用解决方法领域名称

尽量使用那些计算机科学术语、算法名、模式名、数学术语。

(14)使用源自所涉问题领域的名称

如果不能使用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称。

(15)添加有意义的语境

用有良好命名的类、函数或名称空间来放置名称。

(16)不要添加没用的语境

只要短名称足够清楚,就要比长名称好,别给名称添加不必要的语境。

3、函数

(1)短小

规则要求短小。if语句、else语句、while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。

(2)只做一件事

函数应该只做一件事,对于相同抽象层级。如果函数只是做了该函数名下同一抽象层上的步骤,则函数还是只做了一件事。

(3)每个函数一个抽象层级

要确保函数只做一件事,函数中的语句都要在同一抽象层级上。

(4)switch语句

将switch语句埋到抽象工厂底下,该工厂使用switch语句为类型的派生类创建适当的实体。

(5)使用描述性的名称

长而具有描述性的名称,要比短而令人费解的名称好。使用某种命名约定;能理清你关于模块的设计思路,并帮你改进之;命名方式要保持一致,使用与模块名一脉相承的短语、名词和动词给函数命名

(6)函数参数

最理想的参数数量是零,基次是一,再次是二,应尽量避免三。

避免使用输出参数;避免使用标识参数(即布尔类型参数);参数过多时,使用参数对象;给函数取个好名字,能较好的解释函数的效果图,以及参数的顺序和意图。

(7)无副作用

函数承诺只做一件事。

(8)分隔指令与询问

函数要么做什么事,要么回答什么事,但二者不可兼得。函数应该修改某对象的状态,或是返回该对象的有关信息。

(9)使用异常代替返回错误码

从指令式函数返回错误码轻微违反了指令与询问分隔的规则,同时会导致更深层次的嵌套结构。使用异常代替返回错误码,能从主路径代码中分离出来,得到简化。

使用try/catch结构时,把try和catch代码块的主体部分抽离出来,另外形成函数。

错误处理就是一件事,处理错误的函数不该做其他事。意味着try应该是函数中的第一个单词,catch/finally代码块后面不该有其它内容。

返回错误码通常暗示某处有个类或是枚举,定义了的所有错误码,会存在依赖,其它类都得导入使用它,有修改时,需要重新编译和部署。使用异常,新异常可以从异常类派生出来,无需重新编译或部署。

(10)别重复自己

许多原则与实践规则都是为控制与消除重复而创建。

(11)结构化编程

遵循Edsger Dijkstra的结构化编程规则:每个函数、函数中的每个代码块都应该有一个入口,一个出口。遵循这些规则,意味着每个函数中只该有一个return语句,循环中不能有break或者continue语句,而且永远不能有任何goto语句。

便是对于小函数,这些规则助益不大,只有在大函数中,才会有明显的好处。所以,只要函数保持短小,偶尔出现的return,break,continue语句没有坏处,甚至还比单入单出原则更有表达力,另外一方面,goto只在大函数中才有道理,所以应该尽量避免使用。

4、注释

程序员应当负责将注释保持在可维护、有关联、精确的高度。更应该把力气用在写清楚代码上,直接保证无须编写注释。

(1)注释不能美化糟糕的代码

写注释的动机之一是糟糕代码 存在。注释应该少量并且有表达力

(2)用代码来阐述

使用代码来解释大部分的意图。

(3)好注释

好的注释是想办法不用去写注释

法律信息:只要有可能,就指向一份标准许可或其它外部文档,不要把所有条款放到注释中

提供信息的注释:更好的方式是尽量使用表达力的命名来传达信息

对意图的解释:来解释原因

阐释:更好的方法是尽量让参数或返回值自身就足够清楚,如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助阐释其含义的代码就会有用。

警示:警告其它程序员会出现某种后果

TODO注释:放置要做的工作列表。

放大:可以用来放大某种看来不合理之物的重要性。

公共API中的Javadoc:应该编写良好的Javadoc

(4)坏注释

喃喃自语:如果觉得应该或者因为过程需要就添加注释,那就是无谓之举。

多余的注释:不能比代码本身提供更多的信息。

误导性注释:与实际意图相反

循规式注释:所谓每个函数都要有Javadoc或每个变量都要有注释的规矩全然是愚蠢可笑的。

日志式注释:很久之前,在模块开始处创建并维护这些记录还算有些道理,那时,还没有源代码控制系统。

废话注释、可怕的废话、能用函数或者变量时就别用注释:命名可以准确表达时就不需要再加注释

位置标记:尽量少用标记栏,只在特别有价值的时候用

括号后面的注释:尽管对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱。如果你发现自己想标记右括号,其实应该做的是缩短函数。

归属与署名:源代码控制系统非常善于记住是谁在何时添加了什么,没必要用那些小小的签名搞脏代码。

注释掉的代码:应该删除

HTML注释:应该由工具而非程序员来负责给注释添加合适的HTML标签

非本地信息:确保它描述了离它最近的代码。

信息过多:别在注释中添加有趣的历史话题或者无关的细节描述

不明显的联系:注释及其描述的代码之间的联系应该显而易见

函数头:短函数不需要太多描述,为只做一件事的短函数选个好名字,通常要比写函数头注释要好。

非公共代码中的Javadoc:对于非公共用途的代码不需要。

5、格式 

关乎沟通,是专业开发者的头等大事。代码的可读性却会对以后可能发生的修改行为产生深远影响。

(1)垂直格式 

文件大小可能大多数为200行,最长500行的单个文件,短文件通常比长文件易于理解。

向报纸学习:名称应当简单且一目了然,名称本身应该足够告诉我们是否在正确的模块中,源文件最顶部应该给出高层次概念和算法,细节应该往下渐次展开,直至找到源文件中最底层的函数和细节。

概念区间垂直方向上的区隔:用行来区分思路,标记出新的独立概念。

垂直方向上的靠近:紧密相关的代码应该互相靠近

垂直距离:对于那些关系密切、放置于同一源文件中的概念,它们之间的区隔应该成为对相互的易懂度有多重要的衡量标准,应避免迫使读者在源文件和类中跳来跳去;变量声明就尽可能靠近其使用位置,循环中的控制变量应该总是在循环语句中声明。

垂直顺序:自上而下顺序,被调用的函数应该放在执行调用的函数下面

(2)横向格式

行字符上限80,100或120个字符。

水平方向上的区隔与靠近:使用空格字符将彼此紧密相关的事物连接到一起,也用空格字符把相关性较弱的事物分隔开。

水平对齐:不要做无意义的对齐。

缩进:源文件是一种继承结构,其中的信息涉及整个文件、文件中每个类、类中的方法、方法中的代码块,也涉及代码块中的代码块,这种继承结构中的每一层级都圈出一个范围。例如大多数的类声明,根本不缩进,类中的方法相对该类缩进一个层级,方法的实现相对方法声明缩进一个层级,代码块的实现相对于其容器代码块缩进一个层级。

空范围:while或for语句的语句体为空,这种情况尽量不使用。如果需要 ,确保空范围体的缩进,用括号包围起来。

(3)团队规则

一组开发者应当认同一种格式风格,每个成员都应该采用那种风格

6、对象和数据结构

(1)数据抽象

隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象。类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据主体。

(2)数据、对象的反对称性

对象把数据隐藏于抽象之后,曝露操作数据的函数。数据结构曝露其数据,没有提供有意义的函数。

过程式代码难以添加数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。

(3)得墨忒耳律

模块不应了解它所操作对象的内部情形,即类C的方法f只应该调用以下对象的方法:由f创建的对象;作为参数传递给f的对象;由C的实体变量持有的对象。 方法不应调用由任何函数返回的对象的方法。

(4)数据传递对象

只有公共变量,没有函数的类

bean结构,拥有赋值器和取值器操作的私有变量。

Active Record形式,拥有公共变量的数据结构 ,通常也会拥有类似save和find这样的可浏览方法。一般是对数据库表或其它数据源的直接翻译。

7、错误处理

(1)使用异常而非返回码

遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱

(2)先写Try-Catch-Finally语句

执行try-catch-finally语句中try部分的代码时,你是在表明可随时取消进行,并在catch语句中接续。try代码块就像是事务,catch代码块将程序维持在一种持续状态,无论try代码块中发生了什么均如此,所以,在编写可能抛出异常的代码时,最好先写出try-catch-finally语句。

(3)使用不可控异常

可控异常指的是在每个方法的签名都列出它可能传递给调用者的异常,而且,这些异常就是方法类型的一部分。

可控异常违反开放封装原则,如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处之间的每个方法签名中声明该异常。这意味着对软件中较低层级的修改,都将波及较高层级的签名。修改好的模块必须重新构建、发布,即便它们自身所关注的任何东西都没改动过。

(4)给出异常发生的环境 说明 

你抛出的每个异常,都应当提供足够的环境说明 ,以便判断错误的来源和处所。

应创建信息充分的错误信息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给catch块,并记录下来。

(5)依调用者需要定义异常类

通过打包调用 api,确保它返回通用异常类型,从而简化代码。

将第三方api打包是个良好的实践手段,可以降低对第三方的依赖,有助于模拟第三方调用。不必绑死在某个特定厂商的api设计上。

(6)定义常规流程

采用特例模式(special case pattern)创建一个类或配置一个对象,用来处理特例。异常行为封装到特例对象中。

(7)别返回null值

返回特例对象或者空列表。

(8)别传递null值

一种方案是使用断言

8、边界

指的是第三方程序包或使用开放源代码,或者公司中其他团队打造组件或子系统与自己代码之间的关系

(1)使用第三方代码 

在接口提供者和使用者之间,存在与生俱来的张力。第三方程序包和框架提供者追求普适性,这样就能在多个环境中使用,吸引广泛的用户。而使用者则想要集中满足特定需要的接口,这种张力会导致系统边界出现问题。

避免从公共api中返回边界接口,或将边界接口作为参数传递给公共api

(2)浏览和学习边界

使用学习性测试来遍览和理解第三方代码

(3)学习性测试的好处不只是免费

确保第三方程序按照我们想要的方式工作。

(4)使用尚不存在的代码 

这种边界是将已知和未知分隔开的边界。先设计出接口,然后使用适配器模式来适配依赖方的接口。

(5)整洁的边界

需要良好的软件设计,无需巨大的投入和重写即可进行修改。在使用我们控制不了的代码时,必须加倍小心保护资源,确保未来的修改不至于代码太大。

需要清晰的分割和定义了期望的测试。应该避免我们的代码过多地了解第三方代码中的特定信息。

通过代码中少数几处引用第三方边界接口的位置来管理第三方边界,可以使用 adapter模式将我们的接口转换为第三方提供的接口

9、单元测试

(1)TDD三定律

  • 在编写不能通过的单元测试前,不可编写生产代码
  • 只可编写刚好无法通过的单元测试,不能编译也算不通过
  • 只可编写刚好足以通过当前失败测试的生产代码

(2)保持测试整洁

测试代码和生产代码一样重要,需要 被思考、被设计和被照料,也该像生产代码一般保持整洁

(3)整洁的测试

要有可读性。保持同一抽象层级,不需要暴露细节,遵循构造-操作-检验(build-operate-check)模式。每个测试都清晰地拆分为三个环节,第一个环节构造测试数据,第二个环节操作测试数据,第三个环节部分检测操作是否得到期望的结果。

  • 面向特定领域的测试语言

打造一套包装这些api的函数和工具代码,这样就能方便地编写测试,写出来的测试也更便于阅读。

  • 双重标准

应当简单、精悍、足具表达力,但它该和生产代码一般有效。

(4)每个测试一个断言

单个测试中的断言数据应该最小化

每个测试一个概念。

(5)F.I.R.S.T

快速(Fast):测试应该够快。

独立(Independent):测试应该相互独立

可重复(Repeatable):测试应当可以任何环境中重复通过

自足验证(Self-Validating):测试应该有布尔值输出。

及时(Timely):测试应及时编写

10、类

(1)类的组织

类应该从一组变量列表开始,顺序:公共静态变量、私有静态变量、私有实体变量、公共变量。公共函数应跟有变量列表之后,私有工具函数紧随在该公共函数后面。

封装,保持变量和工具函数的私有性。

(2)类应该短小

类的名称应当描述其权责。

  • 单一职责原则

类或模块应有且只有一条加以修改的理由。

鉴别权责常常帮助我们在代码中认识到并创建出更好的抽象。

  • 内聚

方法操作的变量越多,就越黏聚到类上。如果一个类中的每个变量都被每个方法把使用,则该类具有最大的内聚性。

  • 保持内聚性就会得到许多短小的类

(3)为了修改而组织

将系统打造成在添加或修改特性时尽可能少惹麻烦的架子。在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性。

借助接口和抽象类来隔离这些细节带来的影响。

11、系统

(1)将系统的构造与使用分开 

软件系统应将启始过程和启始过程之后的运行逻辑分离开,在启始过程中构建应用对象,也会存在互相缠结的依赖关系。

每个应用程序都该留意启始过程,将关注的方面分离开,是软件技艺中最古老也最重要的设计技巧。

  • 分解main

将构造与使用分开的方法之一是将全部构造过程搬迁到main或者被称为main的模块中,设计系统的其余部分时,假设所有对象都已正确构造和设置

  • 工厂

有时应用程序也要负责确定何时创建对象,可以使用抽象工厂模式让应用自行控制何时创建,但构造的细节却隔离于应用程序代码之外。

  • 依赖注入

实现分离构造与使用的强大机制是依赖注入。控制反转将第二权责从对象中拿出来,转移到另一个专注于此 对象中,从而遵循单一权责原则。

(2)扩容 

架构可以递增式增长,需要持续恰当的切分关注面

(3)Java代理

JDK提供的动态代理仅能与接口协同工作,对于代理类得使用字节码操作库,比如 CGLIB、ASM或者Javassist

(4)纯Java AOP框架

使用描述性配置文件或者 API,你把需要 的应用程序架构组合起来,包括持久化、事务、案例、缓存、恢复等横贯性问题。只是指定Spring或者Jboss类库,框架以对用户透明的方式处理使用Java代理或字节代码库的机制。这些声明驱动了依赖注入和(DI)容器,DI容器再实体化主要 对象,并按需将对象连接起来。

(5)AspectJ的方面

通过方面来实现关注面切分的功能最全的工具是Aspect语言,一种提供一流的将方面作为模块构造处理支持的 Java扩展。在80%-90%用到方面特性的情况下,Spring AOP和JBoss AOP提供的纯Java实现手段足够使用,然而AspectJ却提供了一套用以切分关注面的三宝面强有力的工具。

(6)测试驱动系统架构

从简单自然但切分良好的架构开始做软件项目,快速交付可工作的用户故事,随着规模的增长添加更多基础架构。

最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java对象实现。不同的领域之间用最不具有侵害性的方面或类方面工具整合起来,这种架构能测试驱动。

(7)优化决策

模块化和关注面切分成就了分散化管理和决策。提前决策是一种预备知识不足的决策。拥有模块化关注面的系统提供 的敏捷能力,允许我们基于最新的知识做出优化的、时机刚好的决策。

(8)明智使用添加了可论证价值的标准

有了标准,就更易利用想法和组件、雇用拥有相关经验的人才、封装好点子,以及将组件连接起来。

(9)系统需要领域特定语言

DSL是一种单独的小型脚本语言或以标准语言写就的API,领域专家可以用它编写读起来像是组织严谨的散文一般的代码。

优秀的DSL填平了领域概念和实现领域概念的代码之间的壕沟,就像敏捷实践优化了开发团队和甲方之间的沟通一样。如果你用与领域专家使用的同一种语言来实现领域逻辑,就会降低不正确地将领域为实现的风险。

DSL在有效使用时能提升代码惯用法和设计模式之上的抽象层次,它允许开发者在恰当的抽象层级上直指代码的初衷

领域特定语言允许所有抽象层级和应用程序中的所有领域,从高级策略到底层细节。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kgduu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值