Clean Code读书笔记

Clean Code读书笔记

一.整洁代码

如何做到整洁
怎么做为什么
代码逻辑直接了当便于理解
有意义的命名便于理解
减少依赖关系便于维护
分层战略完善错误缺陷难以隐藏
有单元测试和验收测试风险控制
尽量“少”单一职责原则

如果每个例程都让你感到深合己意,那就是整洁代码,如果代码让编程语言看上去像是专为解决那个问题而存在,就可以称之为漂亮的代码。

二.有意义的命名

  1. 不要以数字系列命名
  2. 相似的类要加以区分:如:Product类、ProductInfo类、ProductData、ProductObject类的区别
  3. 使用读的出来的名称,可使用谐音,如2,4
  4. 使用可搜索的名称,不要过短或者过普遍
  5. 方法名最好是动词或动词短语
  6. 方法中, 可增加局部变量, 以增加代码的可读性

每段代码都有其实际应用的场景,一段代码如果难以理解,很多时候并不是因为代码复杂,而是代码的模糊度较高,难以明确代码的意图。

三.函数

基本规范
  1. 短小
  2. 只做一件事
public static String RenderPageWithSetupAndTeardowns(PageData pageData, Boolean isSuite) throws Exception{
    if ( isTestPage( pageData ) ) {
        includeSetupAndTeardownPages( pageData ,  isSuite );
    }
    return pageData.getHtml( );
}

/*
看起来像是做了三件事:
判断是否为测试页面;如果是,则包含设置和分析步骤;返回Html。
Bob 提供了”To”(要)原则,也就是以To起头段落来描述这个函数。
要(To) RenderPageWithSetupAndTeardowns,检查页面是否为测试页,如果是测试页,就包含设置和分析步骤,无论是不是测试页,都返回HTML。
这三件事情都处于一个抽象层次上,所以RenderPageWithSetupAndTeardowns做了一件事。
判断函数只做了一件事还有另一个方法:看能否再拆出一个函数
*/
函数参数
  1. 参数越少,困难越少
  2. 参数超过三个封装成类
  3. 禁止将boolean类型作为参数:宣布函数不止做一件事
函数的抽象层
  1. 函数的抽象层级以功能来划分,比如去买书,抽象层级:买书>去书店>走路
  2. 函数的抽象层级的最高层类似于人类自然语言,使代码清晰易懂,简洁。
  3. 确保,每个函数,一个抽象层级,也就是说一个函数中的语句都要在同一抽象层级上。我们想要让代码拥有自顶向下的阅读顺序,因此让每个函数后面都跟着下一抽象层级的函数,此举方便了以后修改代码和阅读代码。
函数编写细则
  1. 不要重复自己
  2. 函数应该只做一件事
  3. 函数要么“做什么事”,要么“回答什么事”。即是将指令和询问分隔开来。
  4. 抽离try/catch代码块。把其主体部分抽离出来,另外写成函数。错误处理的代码应该就是包含它的函数的所有代码

沃德原则:如果每个历程都让你感到深和己意,那就是整洁的代码

四.注释

  1. 提供信息的注释。例如解释某个抽象方法的返回值,规定参数的顺序和个数。
  2. 对意图的解释。使别人更清楚一段复杂代码是在干什么。

注释不能美化糟糕的代码
用代码来阐述

五.格式

垂直格式
  1. 变量声明尽可能靠近其使用位置。
  2. 最上面的代码应该是最抽象的,底部细节应该在下面实现
  3. 若某个函数调用另一个,就应当把他们放在一起。
横向格式

1.适当对齐,间隔

团队规则
  1. 一组开发者应当认同一种模式风格,每个成员都应该采用那种风格
  2. 好的软件系统是由一系列读起来不错的代码文件组成的,需要拥有一致和顺畅的风格

代码格式关乎沟通,而沟通是专业开发者的头等大事

六.对象和数据结构

对象和数据结构的区别
  1. 数据结构中的对象只是数据,面向对象中的对象包括了数据和行为。
  2. 数据结构暴露其数据,没有提供有意义的函数;对象把数据隐藏于抽象之后,暴露操作数据的函数。
  3. 数据结构难以添加新的的数据类型,因为需要改动所有函数,面向对象的代码则难以添加新的函数,因为需要修改所有的类。
得墨忒耳律
  1. 得墨忒耳律(The Law of Demeter):模块不应了解它所操作对象的内部情形,意味着对象不应通过存取器曝露其内部结构,因为这样更像是曝露而非隐藏其内部结构

    • 类C的方法f只应调用以下对象的方法:
      1. C
      2. 由f创建的对象
      3. 作为参数传递给f的对象;
      4. 由C的实体变量持有的对象;

方法不应调用由任何函数返回的对象的方法,换句话说,只和朋友说话,不和陌生人说话。以下就是违反该法则的一段代码:

七.错误信息

使用异常而非返回码
  1. 遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱
先写Try-Catch-Finally语句
  1. 异常的妙处之一是,它们在程序中定义了一个范围。执行try-catch-finally语句中try部分的代码时,你是在表明可随时取消执行,并在catch语句中接续
  2. try代码块就像是事务,catch代码块将程序维持在一种持续状态
  3. 在编写可能抛出异常的代码时,最好先写try-catch-finally语句,能帮你定义代码的用户应该期待什么,无论try代码块中执行的代码出什么错都一样
使用不可控异常
  1. 可控异常,就是指在方法签名中标注异常。但有时候会产生多层波及,有时候你对较底层的代码修改,可能会波及很多上层代码
给出异常发生的环境说明
  1. 抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所
  2. 应创建信息充分的错误消息,并和异常一起传递出去
定义常规流程
  1. 特例模式(SPECIAL CASE PATTERN,[Fowler]),创建一个类或配置一个对象,用来处理特例,异常行为被封装到特例对象中
别返回null值
  1. 在方法中返回null值,不如抛出异常或返回特例对象

错误处理很重要,但如果它搞乱了代码逻辑,就是错误的做法

八.边界(使用第三方代码)

学习性测试
  1. 学习性测试毫无成本,编写测试是获得这些知识(要使用的API)的容易而不会影响其他工作的途径
  2. 学习性测试确保第三方程序包按照我们想要的方式工作
整洁的边界
  1. 边界上的改动,有良好的软件设计,无需巨大投入和重写即可进行修改
  2. 边界上的代码需要清晰的分割和定义了期望的测试。依靠你能控制的东西,好过依靠你控制不了的东西,免得日后受它控制
  3. 可以使用ADAPTER模式将我们的接口转换为第三方提供的接口

九.单元测试

TDD三定律
  1. 在编写能通过的单元测试前,不可编写生产代码(测试先行)
  2. 只可编写刚好无法通过的单元测试,不能编译也算不通过(测试一旦失败,开始写生产代码)
  3. 只可编写刚好足以通过当前失败测试的生产代码(老测试一旦通过,返回写新测试)
保持测试整洁
  1. 脏测试等同于没测试,测试必须随生产代码的演进而修改,测试越脏,就越难修改
  2. 测试代码和生产代码一样重要,它需要被思考、被设计和被照料,它该像生产代码一般保持整洁
  3. 如果测试不能保持整洁,你就会失去它们,没有了测试,你就会失去保证生产代码可扩展的一切要素
单元测试的固定模式

构造-操作-检验模式写成一个函数

  1. 第一个环节构造测试数据
  2. 第二个环节操作测试数据的
  3. 第三个环节,检验操作是否得到期望的结果。
每个测试一个断言
  1. JUnit中每个测试函数都应该有且只有一个断言语句
  2. 最好的说法是单个测试中的断言数量应该最小化
  3. 更好一些的规则或许是每个测试函数中只测试一个概念
  4. 最佳规则是应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念
F.I.R.S.T
  1. 快速(Fast)测试应该够快
  2. 独立(Independent)测试应该相互独立
  3. 可重复(Repeatable)测试应当可在任何环境中重复通过
  4. 自足验证(Self-Validating)测试应该有布尔值输出,自己就能给出对错,而不需要通过看日志,比对结果等方式验证
  5. 及时(Timely)测试应及时编写

十.类

类的组织
  1. 类应该从一级变量列表开始,如果有公共静态变量,应该先出现,然后是私有静态变量,以及实体变量,很少会有公共变量
  2. 公共函数应该跟在变量列表之后
  3. 保持变量和工具函数的私有性,但并不执着于此
类应该短小
  1. 类名应该精确,职责单一,并且能够描述其权责
  2. 内聚性强,方法操作的变量越多,就越黏聚到类上,如果一个类的每个变量都被每个方法所使用,则该类具有最大的内聚性
  3. 保持函数和参数列表短小的策略,有时会导致为一组子集方法所用的实体变量数量增加。出现这种情况时,往往意味着至少有一个类要从大类中挣扎出来。你应当尝试将这些变量和方法分拆到两个或多个类中,让新的类更为内聚
  4. 将大函数拆为许多小函数,往往也是将类拆分为多个小类的时机
为了修改而组织
  1. 在整洁的系统中,我们对类加以组织,以降低修改的风险
  2. 开放-闭合原则(OCP):类应当对扩展开放,对修改封闭
  3. 在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性
  4. 依赖倒置原则(Dependency Inversion Principle,DIP),类应该依赖于抽象而不是依赖于具体细节

我们知道编写一个类不是一触而就的,而是通过了无数次修进的。而系统的每处修改(添加功能,改变逻辑方法等)都让我们冒着系统会出现问题的风险。这时候我们要对类加以修进(组织和重构),以降低修改所面临的风险。

十一.系统

如何建造一个城市
  1. 每个城市都有一组人管理不同的部分,有人负责全局,其他人负责细节
  2. 深化出恰当的抽象等级和模块,好让个人和他们所管理的“组件”即便在不了解全局时也能有效地运转
将系统的构造与使用分开
  1. 构造与使用是非常不一样的过程
  2. 软件系统应将启始过程和启始过程之后的运行时逻辑分离开,在启始过程中构建应用对象,也会存在互相缠结的依赖关系
  3. 将构造与使用分开的方法之一是将全部构造过程搬迁到main或被称为main的模块中,设计系统的其余部分时,假设所有对象都已正确构造和设置
  4. 可以使用抽象工厂模式让应用自行控制何时创建对象,但构造的细节却隔离于应用程序代码之外
  5. 控制反转将第二权责从对象中拿出来,转移到另一个专注于此的对象中,从而遵循了单一权责原则。在依赖管理情景中,对象不应负责实体化对自身的依赖,反之,它应当将这份权责移交给其他“有权力”的机制,从而实现控制的反转
扩容
  1. “一开始就做对系统”纯属神话,反之,我们应该只去实现今天的用户故事,然后重构,明天再扩展系统、实现新的用户故事,这就是迭代和增量敏捷的精髓所在。测试驱动开发、重构以及它们打造出的整洁代码,在代码层面保证了这个过程的实现
  2. 软件系统与物理系统可以类比。它们的架构都可以递增式的增长,只要我们持续将关注面恰当地切分
  3. 持久化之类关注面倾向于横贯某个领域的天然对象边界
Java代理AOP
  1. 通过方面式(AOP)的手段切分关注面的威力不可低估。假使你能用POJO编写应用程序的领域逻辑,在代码层面与架构关注面分离开,就有可能真正地用测试来驱动架构
  2. 没必要先做大设计(Big Design Up Front,BDUF),BDUF甚至是有害的,它阻碍改进,因为心理上会抵制丢弃即成之事,也因为架构上的方案选择影响到后续的设计思路
  3. 我们可以从“简单自然”但切分良好的架构开始做软件项目,快速交付可工作的用户故事,随着规模的增长添加更多基础架构
  4. 最佳的系统架构由模块化的关注面领域组成,每个关注面均用纯Java(或其他语言)对象实现,不同的领域之间用最不具有侵害性的方面或类方面工具整合起来,这种架构能测试驱动,就像代码一样
优化决策
  1. 模块化和关注面切分,成就了分散化管理和决策
  2. 延迟决策至最后一刻也是好手段,它让我们能够基于最有可能的信息做出选择
  3. 拥有模块化关注面的POJO系统提供的敏捷能力,允许我们基于最新的知识做出优化的、时机刚好的决策,决策的复杂性也降低了
明智使用添加了可论证价值的标准
  1. 有了标准,就更易复用想法和组件、雇用拥有相关经验的人才、封装好点子,以及将组件连接起来。不过,创立标准的过程有时却漫长到行业等不及的程度,有些标准没能与它要服务的采用者的真实需求相结合
系统需要领域特定语言
  1. 领域特定语言(Domain-Specific Language, DSL)是一种单独的小型脚本语言或以标准语言写就的API,领域专家可以用它编写读像是组织严谨的散文一般的代码
  2. 领域特定语言允许所有抽象层级和应用程序中的所有领域,从高级策略到底层细节,使用POJO来表达

城市能运转,还因为它演化出来恰当的抽象等级和模块,好让个人和他们所管理的“组件”在不了解全局时也能有效运转。

十二.迭代

简单规则:
  1. 通过所有测试
  2. 不可重复
  3. 表达了程序员的意图
  4. 尽可能减少类和方法的数量
  5. 以上规则按其重要程序排列

十三.并发编程

为什么要并发
  1. 并发是一种解耦策略,它帮助我们把做什么(目的)和何时(时机)做分解开
  2. 解耦目的与时机能明显地改进应用程序的吞吐量和结构
  3. 单线程程序许多时间花在等待web套接字I/O结束上面,通过采用同时访问多个站点的多线程算法,就能改进性能
常见的迷思和误解
  1. 并发总能改进性能:只在多个线程或处理器之间能分享大量等待时间的时候管用
  2. 编写并发程序无需修改设计:可能与单线程系统的设计极不相同
  3. 在采用web或ejb容器时,理解并发问题并不重要
有关编写并发软件的中肯的说法
  1. 并发会在性能和编写额外代码上增加一些开销
  2. 正确的并发是复杂的,即使对于简单的问题也是如此
  3. 并发缺陷并非总能重现,所以常被看做偶发事件而忽略,未被当做真的缺陷看待
  4. 并发常常需要对设计策略的根本性修改
并发防御原则
  1. 单一权责原则
  2. 限制数据作用域
  3. 使用数据副本
  4. 线程应尽可能独立
了解Java库
  1. 使用类库提供的线程安全群集
  2. 使用executor框架(executor framework)执行无关任务
  3. 尽可能使用非锁定解决方案
  4. 有几个类并不是线程安全的
了解执行模型

警惕同步方法之间的依赖
保持同步区域微小

很维编写正确的关闭代码
  1. 平静关闭很难做到,常见问题与死锁有关,线程一直等待永远不会到来的信号
  2. 建议:尽早考虑关闭问题,尽早令其工作正常
测试线程代码
  1. 建议:编写有潜力曝露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。如果测试失败,跟踪错误。别因为后来测试通过了后来的运行就忽略失败
  2. 将伪失败看作可能的线程问题:线程代码导致“不可能失败的”失败,不要将系统错误归咎于偶发事件
  3. 先使非线程代码可工作:不要同时追踪非线程缺陷和线程缺陷,确保代码在线程之外可工作
  4. 编写可插拔的线程代码,能在不同的配置环境下运行
  5. 编写可调整的线程代码:允许线程依据吞吐量和系统使用率自我调整
  6. 运行多于处理器数量的线程:任务交换越频繁,越有可能找到错过临界区域导致死锁的代码
  7. 在不同平台上运行:尽早并经常地在所有目标平台上运行线程代码
  8. 装置试错代码:增加对Object.wait()、Object.sleep()、Object.yield()、Object.priority()等方法的调用,改变代码执行顺序,硬编码或自动化

十四.逐步改进

  1. 要编写清洁代码,必须先写肮脏代码,然后再清理它
  2. 毁坏程序的最好方法之一就是以改进之名大动其结构
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值