《CleanCode代码整洁之道》笔记

CleanCode

1. 有意义的命名

  • 名副其实,无需注释,有实际意义
  • 最好不以容器类型名命名,如List等,避免误导
    • [x]accountList
    • [ ]accountGroup
    • [ ]bunchOfAccounts
  • 提防使用不同之处较小的名称
  • 有意义区分(以数字系列命名a1、a2…theMessage、message无意义)
  • 可以被读出来,用恰当的英语
  • 长名字胜于短名字,名称长短与其作用域大小相对应
  • 避免使用编码(HN、成员前缀、接口和实现(IShapeFactory不好,宁可对实现进行编码,ShapeFactoryImp或CShapeFactory)???
  • 避免思维映射(单字母变量名)

  • 类目和对象名——名词或名词短语;

  • 方法名——动词或动词短语*

  • 重载构造器时,使用描述了参数的静态工厂命名方法

    e.g. Complex fulcrumPoint = Complex.FromRealNumber(23.0);

  • 每个概念对应一个词,函数名独一无二保持一致

  • 一词一意

  • 使用解决方案领域名称(只有程序员读代码)/原子所涉问题领域的名称

  • 不要添加没用的语境,只要短名称足够清楚即好于长名称

2. 函数

  • 短小:20行、if/eles/while语句等,其代码块只有一行(调用其他函数),因此函数的缩进层不该多于一层或两层

  • 只做一件事:看是否能再拆出一个函数,该函数不仅仅单纯重新诠释其实现,只做一件事的函数无法被合理地切分为多个区间

  • 每个函数一个抽象层级(自顶向下读代码:向下规则)

  • Switch——>抽象工厂,工厂使用Switch创建不同的实体,而不同的函数由接口多态地接受派遣

  • 使用描述性名称

  • 函数参数越少越好

    注:一元函数普遍形式

    1. 关于参数的问题boolean fileExists(“MyFile”)
    2. 操作该参数,将其转换为其他东西再输出,转换结果应为返回值
    3. 事件(event)有参数输入而无参数输出,该参数修改系统状态
  • 标识参数true\false不好

  • 二元函数——>一元函数

  • 三元函数

  • 参数对象(2个、3个或以上)——>封装成类

  • 参数列表

  • 避免输出参数

  • 分隔指令与询问

  • 使用异常代替返回错误码

  • 抽离Try/Catch代码块,另外形成函数,错误处理就是一件事,如果try在某个函数中存在,它就应该是这个函数的第一个单词,且在catch/finally代码块后不能有其他内容

  • 使用异常代替错误码,新异常从异常类派生出来

  • 消除重复

  • 小函数偶尔出现return、break、continue无坏,甚至比单入单出原则更具有表达力

3. 注释

  • 没有注释最好
  • 不要注释代码

4. 格式

  • 垂直格式

    • 概念间垂直方向上的间隔,在封包声明、导入声明、每个函数之间,都有空白行隔开
    • 垂直方向上的靠近,紧密相关的代码应互相靠近
    • 变量声明尽可能靠近其使用位置;实体变量在类的顶部声明
    • 相关函数放到一起,调用者尽可能放在被调用者上面
    • 概念相关的代码放到一起
  • 横向格式

    • 上限是120个字符
    • 赋值操作符周围(左右)加上空格字符、函数调用括号中的参数一一隔开(强调逗号)、加减运算符之间用空格隔开(加减法优先级较低)
    • 函数名与参数之间不加空格、乘法因子之间不加空格
    • 无需对齐,列表太长则需被拆分
    • 依源代码行在继承结构中的相对位置对源代码行做缩进处理
    • 确保空范围体的缩进

5. 对象和数据结构

  • 数据抽象,类并不简单地用取值器和赋值器将其变量推向外间,而是暴露抽象接口,以便用户无需了解数据的实现就能操作数据本体

  • 对象与数据结构之间的二分原理:

    • 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,难以添加新数据结构,因为必须修改所有函数。
    • 面向对象代码便于在不改动既有函数的前提下添加新类,难以添加新函数,因为必须修改所有类
  • 得墨忒耳律:模块不应了解它所操作对象的内部情况。方法不应调用由任何函数返回对象的方法。

  • 数据结构只简单地拥有公共变量,没有函数;对象则拥有私有变量和公共函数

    • 数据传送对象DTO(一个只有公共变量、没有函数的类),如bean结构半封装,Active Record是一种特殊的DTO形式,他们拥有公共(或可豆式访问的)变量的数据结构,但通常也会有类似save和find这样的可浏览方法
  • 对象暴露行为,隐藏数据,便于添加新对象类型而无需修改既有行为,同时难以在既有对象中添加新行为;数据结构暴露数据,没有明显行为,便于向既有数据结构添加行为,同时也难以向既有函数添加新数据结构

6. 错误处理

  • 使用异常而非返回码

  • 先写try-catch-finally语句确定范围,**测试驱动???**构建剩余的代码逻辑

  • 使用不可控异常(可控异常Checked Execption违反开放/闭合原则)

    在JAVA中有两种异常:

    • 非运行时异常(Checked Execption):这种异常必须在方法中声明throws语句指定,或者在方法体内捕获。例如IOException和ClassNotFoundException。
    • 运行时异常(unChecked exception):这种异常不必在方法中声明指定,也不需要在方法体捕获。例如NumberFormatException。
  • 给异常发生的环境说明(创建信息充分的错误信息(包括失败的操作和类型),并和异常一起传递出去)

  • 定义异常类时,考虑它们如何被捕获。将第三方API打包,降低对它的依赖

  • 定义常规流程,可创建一个类或配置一个对象用来处理特例

  • 不要返回null值,不如返回空列表等或是抛出异常、返回特例对象

  • 不传递null值

7. 边界

  • 使用第三方代码,不要将Map(或在边界上的其他接口)在系统中传递,若使用边界接口,就把他保留在类或亲近类中,避免从公共API中返回边界接口,或者将边界接口作为参数传递给公共API???

  • 浏览和学习边界:学习性测试,编写测试来遍览和理解第三方代码

8. 单元测试

  • 模式:构造(构造测试数据)——操作(操作测试数据)——检验(检验操作是否得到了期望的结果)

  • 每个测试一个断言,单个测试中的断言数量应该最小化

  • F(快速).I(独立).R(可重复).S(自足验证).T(及时)

9. 类

  • 类顺序:

    变量列表(公共静态变量、私有静态变量、私有实体变量(很少有公共变量))

    公共函数

    某个公共函数调用的私有工具函数

  • 封装:尽量保持变量和工具函数的私有性

  • 类的名称应描述其权责

  • 单一权责类型SRP:类或模块应有且只有一条加以修改的理由,鉴别权责可创建更好的抽象

  • 系统应由许多短小的类组成,每个小类封装一个权责,只有一个修改原因,并与少数其他类一起协同达成期望的系统行为

  • 内聚:类应该只有少量实体变量,类中的每个方法都应该操作一个或多个这种变量;拆分大类使新的类更加内聚;将较大的函数切割为小函数,会有更多的类

  • 开放-闭合原则(OCP):类应当对扩展开放,对修改封闭

  • 隔离修改:具体类包含细节(代码),抽象类只呈现概念,借助接口和抽象类来隔离这些细节带来的影响;依赖倒置原则DIP应当依赖于抽象而不是依赖于细节

10. 系统

  • 将系统的构造和使用分开

    • 分解main:将全部构造过程搬迁到main或被称之为main的模块中,设计系统的其余部分时,假设所有对象都已正确构造和设置(应用程序对main或构造过程一无所知)

    • 工厂:使用抽象工厂模式让应用自行确定何时创建对象

    • 依赖注入、控制反转:将第二权责从对象中拿出来。转移到另一个专注于此的对象中,从而遵循单一权责原则。类并不直接分解其依赖,而是完全被动的。它提供可用于注入依赖的赋值器方法或构造器参数(或二者皆有),哪个依赖对象真正用得到使通过配置文件或在一个有特殊目的的构造模块中编程决定

    • 扩容(横贯式关注面)

  • Java代理:在单独的对象或类中包装方法调用,适用于简单情况,JDK的动态代理仅能与接口协同工作

  • 纯Java AOP(面向方面)框架:Spring AOP、JBoss AOP

  • AspectJ(通过方面来实现关注面切分的功能最全的工具
    测试驱动系统框架 最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现。不同的领域之间用最不具有侵害性的方面或类方面工具整合起来。这种架构能测试驱动,就像代码一样

11. 迭进

  • 简单设计原则(重要程度递减)

      1. 运行所有测试
    • ​ 重构:2. 消除重复、3. 保证表达力、4. 尽可能减少类和方法的数量

12. 并发编程

  • 并发是一种解耦策略,帮助我们把做什么(目的)和何时(时机)做分解开

  • 并发防御原则

  • 单一权责原则:并发相关代码有自己的生命周期,有自己要对付的挑战;建议建议分离并发相关代码与其他代码

  • 限制数据作用域:如采用synchronized关键字在代码中保护一块使用共享对象的临界区;建议谨记数据封装、严格限制对可能被共享的数据的访问使用数据复本:避免共享对象

  • 线程尽可能独立,每个线程处理一个客户端请求,从不共享的源头接纳所有请求数据,存储为本地变量;建议尝试将数据分解到可被独立线程(可能在不同处理器上)操作的独立子集

  • 了解Java库
    * 线程安全群集;建议检读可用的类,对于java,掌握java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks

  • 执行模型(基础定义:限定资源、互斥、线程饥饿、死锁、活锁)

    • 生产者-消费者模型(限定资源)

    • 读者-作者模型(提供合理吞吐量,避免线程饥饿)

    • 宴席哲学家

    • 警惕同步方法之间的依赖:建议避免使用一个共享对象的多个方法。有时必须使用一个共享对象的多个方法,则有以下三种手段:

      • 基于客户端的锁定——客户端代码在调用第一个方法前锁定服务端,确保锁的范围覆盖了调用最后一个方法的代码
      • 基于服务端的锁定——在服务端内创建锁定服务端的方法,调用所有方法,然后解锁,让客户端代码调用新方法
      • 适配服务端——创建执行锁定的中间层,这是一种基于服务端的锁定的例子,但不修改原始服务端代码
      • 保持同步区域微小。同一个锁维护的所有代码区域在任一时刻保证只有一个线程执行
      • 尽早考虑关闭问题
      • 测试线程代码。建议:将伪失败看作是可能的线程问题、先使非线程代码可工作、编写可插拔的线程代码、编写可调整的线程代码、运行多于处理器数量的线程、在不同平台上运行、调整代码并强迫错误发生 (装置试错代码:硬编码、自动化(使用异动策略搜出错误))

13. 逐步改进

  • 渐进(测试驱动开发)

14. JUnit内幕

15. 重构SerialDate(例子)

16. 修改原因

  • 注释(只应描述有关代码和设计的技术性信息)

    • 不恰当的信息
    • 废弃的注释
    • 冗余的注释
    • 糟糕的注释
    • 注释掉的代码
  • 环境

    • 构建系统应该是单步的小操作,能够用单个命令签出系统,并用单个指令构建它
    • 应当能够发出单个指令就可以运行全部单元测试
  • 函数

    • 参数量应该少,没有参数最好
    • 输出参数违反直觉
    • 不要标识参数(布尔型参数)
    • 丢弃永不被调用的方法
  • 一般性问题

    • 尽力减少源文件中额外语言的数量和范围

    • 遵循“最小惊异原则”,函数或类应该实现其他程序员有理由期待的行为

    • 不要依赖直觉,追索每种边界条件,并编写测试

    • 忽略安全

  • 重复

    重复的代码可能成为子程序或另一个类,将重复代码叠放进类似的抽象。重复可能为以下形态:cv可用单一方法替代、不同模块不断重复出现、检测同一组条件的switch/case或if/else链可用多态替代、类似算法但具体代码行不同可用模块方法模式或策略模式修正

    • 创建分离较高层级一般性概念与较低层级细节概念的抽象模型,创建抽象类来容纳较高层级概念,创建派生类来容纳较低层次概念。所有较低层级概念放在派生类中,所有较高层级的概念放在基类中。不存在真的无边界的堆栈
    • 一般情况,基类对派生类应该一无所知,部署到不同的jar文件中
    • 设计良好的模块有非常小的接口,设计良好的接口并不提供需要许多依靠的函数(耦合度较低),类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好,隐藏数据、工具函数、常量、临时变量,不要为子类创建大量受保护变量和函数,尽力保持接口紧凑,通过限制信息来控制耦合度。
    • 删除不执行的代码
    • 垂直分离。变量和函数应当在靠近的地方定义
    • 从一而终
    • 删除混淆视听的代码(无意义的变量、从不调用的函数等)
    • 不互相依赖的东西不应该耦合(变量、常量、函数不恰当放在临时方便的位置)
    • 类的方法只应该对其所属类的变量和函数感兴趣,消除特性依恋
    • 使用多个函数,通常优于向单个函数传递某些代码来选择函数行为
    • 代码尽可能具有表达力
    • 位置错误的权责
    • 通常应该倾向于选用非静态方法,或者非静态函数,确保无多态行为的情况下可用静态函数
    • 使用解释性变量。将计算过程打散成在用有意义的单词命名的变量中放置的中间值
    • 函数名称应该表达其行为
    • 通过重构函数理解算法
    • 把逻辑依赖改成物理依赖
    • “单个switch”规则:对于给定的选择类型,不应有多于一个switch语句。在那个switch语句中有多个case,必须创建多态对象,取代系统中其他类似switch语句
    • 遵循标准约定(团队)
    • 用命名常类替代魔术数
    • 准确
    • 结构甚于约定
    • 封装条件
    • 避免否定性条件
    • 函数只该做一件事
    • 函数应该只在一个抽象层级上
    • 在较高层级放置可配置数据
    • 避免传递浏览
  • Java

    • 通过使用通配符避免过长的导入清单
    • 不要继承常量(应该静态导入)
  • 名称

    • 使用描述性名称
    • 名称应该与抽象层级相符
    • 尽可能使用标准命名法
    • 无歧义的名称
    • 为较大作用范围选用较长名称
    • 避免编码
    • 名称应该说明副作用
  • 测试

    • 测试不足
    • 使用覆盖率工具
    • 别略过小测试
    • 被忽略的测试和就是对不确定事物的疑问
    • 测试边界条件
    • 全面测试相近的缺陷
    • 测试失败的模式、覆盖率模式有启发性
    • 测试应该快速

附录A

  • 死锁的原因:
    • 互斥
    • 上锁及等待
    • 无抢先机制
    • 循环等待
  • 解决方法
    • 规避互斥条件:使用允许同时使用的资源;增加资源数量;在获取资源前检查是否可用
    • 拒绝等待,消除死锁。但可能带来线程饥饿、活锁,导致吞吐量较差
    • 满足抢先机制:允许线程从其他线程上争夺资源
    • 不做循环等待:所有线程都认同一种资源获取次序,并按照这种次序获取资源
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值