clean code

Clean code note

正在走向代码的终结,代码会自动产生出来。 扯淡,永远抛不掉代码,因为代码呈现了需求的细节。在某些层面上,这些细节无法被忽略或者抽象。是否也为糟糕的代码深深困扰,沼泽 wading.花时间保持代码整洁不但有关效率,还有关生存。

第二章 有意义的命名

规则:

  1. 名副其实

问题不在于代码的整洁度,而是在于代码的模糊度:即上下文在代码中未被明确体现的程度。

  1. 避免误导

1)hp, aix, sco都不该用作变量名,因为是UNIX平台或者类UNIC平台的专有名称

 

称ICping整洁度,而是在于代码的模糊度。糟糕的代码深深困套2) 别用accountList来指称一组账号,除非它真的是List类型

3)333)避免使用差别较小的名称 XYZControllerForEfficientHandlingOfStrings 和 XYZControllerForEfficientStorageOfString

4) 小写字母l 和大写字母O

3.做有意义的区分

1)光是添加系列名a1,a2…an 远远不够,即便这样足以让编译器满意

2)废话是另一种没意义的区分,假设有个Product类 还有个 ProductInfo 或者ProductData,名字虽然不同,意思却无区别。Info 和Data 就像a\an\the一样是意义含糊的废话

4.使用读得出来的名字

5.使用可搜索的名称,单字母和数字常量很难

 

6.避免使用编码

Java程序员不需要类型编码,对象是强类型的,代码编辑环境已经先进到在编译开始前就侦测到类型错误的程度。

也不必用m_前缀来表明成员变量

 

第三章 函数

函数参数

1.最理想的参数数量是零。从测试的角度看,参数甚至更叫人为难。要编写能确保参数的各种组合运行正常的测试用例,是多么困难的事。如果参数多于两个,测试覆盖所有可能值得组合简直让人生畏。

2. 无副作用:“只做一件事”规则

3. 分隔指令与询问

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

  1. 使用异常替代返回错误码
  2. 别重复自己:如果有个算法在SetUp,SuiteSetUp,teardown和SuiteTearDown 中总共被重复了4次,代码因此显得臃肿,且当算法发生改变时需要修改4处地方,而且也会增加4次放过错误的可能性。尽管识别重复不容易,因为这4处重复与其他代码混在一起,而且也不完全一样
  3. 结构化编程

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

只有在大函数中才有明显好处.

写函数时,一开始都冗长而复杂,配上一套单元测试,覆盖每行丑陋的代码。然后打磨代码分解函数,修改名称,消除重复。缩短和重新安置方法,有时拆散类,同时保持测试通过。

 

第四章 注释

注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。用代码来阐述

  1. 好注释

1)  法律信息

2)  提供信息的注释

3)  对意图的解释

4)  警示

5)  Todo

  1. 坏注释

 

第五章 格式

  1. 概念间垂直方向上的区隔

在封包声明、导入声明和每个函数之间,都有空白行隔开

  1. 垂直方向上的靠近
  2. 垂直距离:关系密切的概念应该互相靠近。对于那些关系密切、放置于同一源文件中的概念,它们之间的区隔应该成为对相互的易懂度有多重要的衡量标准

1)  变量声明:应尽可能靠近其使用位置。本地变量应该在函数的顶部

2)  实体变量:类的顶部4\4

3)  相关函数:若某个函数调用了另外一个,就应该把它们放到一起。而且调用者应该尽可能放在被调用者上面。

4)  概念相关:相关的代码应该放在一起,相关性来自于执行相似操作的一组函数

  1. 水平方向上的区隔与靠近

空格字符把相关性较弱的事务分开

1)  赋值语句有两个确定而重要的要素:左边和右边。空格字符加强了分隔效果

2)  不在函数名和左圆括号之间加空格:函数与其参数密切相关,如果隔开,就会显得互无关系

3)  乘法因子之间没加空格,因为他们具有较高的优先级,加减法运算项之间用空格隔开,因为优先级较低

  1. 团队规则

好的软件系统是由一系列读起来不错的代码文件组成的。它们需要拥有一致和顺畅的风格。

 

第六章 对象和数据结构

隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象

  1. 数据、对象的反对称性:过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数;面向对象代码便于在不改动既有函数的前提下添加新类。
  2. 得墨忒耳律

The law of Demeter, 模块不应了解它所操作对象的内部情形。对象隐藏数据,曝露操作。对象不应通过存取器暴露其内部结构。更准确地说,类c的方法f只应该调用以下对象的方法:

C, 由f创建的对象,作为参数传递给f的对象,由c的实体变量持有的对象

火车失事: 连串的调用通常被认为是肮脏的风格,应该避免

  1. 数据传送对象

最为精练的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfer Objects)。在应用程序代码里一系列将原始数据转换为数据库的翻译过程中,他们往往是头排兵。

Active Record是一种特殊的DTO形式,他们是拥有公共变量的数据结构,但通常也会拥有类似save 和find这样的可浏览方法。

 

第七章 错误处理

错误处理与整洁代码的关系,许多程序完全由错误处理所占据,几乎无法看明白代码所做的事。错误处理很重要但如果它搞乱了代码逻辑,就是错误的做法。

1. 先写Try-Catch-Finally语句

Try 代码块就像是事务,catch代码块将程序持续在一种持续状态

使用不可控异常(Unchecked exception):可控异常的代价就是违反开放/闭合原则。如果你在方法中抛出可控异常,而catch语句在三个层级以上,你就得在catch语句和抛出异常处之间的每个地方签名中声明该异常。

2.别返回null值:容易引发错误的做法,每行代码都在检查null值的应用程序。如果从应用程序深处抛出的NullPointerException异常,到底该作何反应?可以敷衍说上例代码的问题少做了一次null检查,其实问题多多。如果你打算在方法中返回Null值,不如抛出异常,或是返回特例对象。

3. 别传递null值

 

第八章 边界

浏览和学习边界

第三方代码帮助我们在更少时间内发布更丰富的功能。我们没有测试第三方代码的职责,但为要使用的第三方代码编写测试。不要在产生代码中试验新东西,而是编写测试来遍览和理解第三方代码,Jim Newkirk把这叫做学习性测试(learning tests)

 

第九章 单元测试

1.TDD 三定律

定律一:在编写不能通过的单元测试前,不可编写生产代码

定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过

定律三:只可编写刚好足以通过当天失败测试的生产代码

正是单元测试让你的代码可扩展、可维护、可复用

  1. 每个测试一个断言;每个测试一个概念
  2. 整洁的测试还应遵守以下5条规则(FIRST):快速,独立,可重复,自主验证,及时

使用覆盖工具,测试边界条件,全面测试相近的缺陷。

 

第十章 类

遵循标准的java 约定,类应该从一组变量列表开始。如果有公共静态常量,应该先出现,然后是私有静态变量,以及私有实体变量。公共函数应跟在变量列表之后,我们喜欢把由某个公共函数调用的私有工具函数紧随在该公共函数后面。这符合了自顶向下原则。

  1. 类应该短小

对于函数,我们通过计算代码行数衡量大小,对于类,我们采用不同的衡量方法,计算权责(responsibility)

1)  单一权责原则(SRP)

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

2)  内聚

通常而言,方法操作的变量越多,就越黏聚到类上。如果与各类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。内聚性高,意味着类中的方法和变量相互依赖、相互结合成一个逻辑整体。

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

 

第十一章 系统

讨论如何在较高的抽象层级——系统层级——上保持整洁。

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

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

1)分解main

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

2)  工厂

有时应用程序也有负责确定何时创建对象。

3)  依赖注入

有一种强大的机制可以实现分离构造与使用,那个就是依赖注入(Dependency Injection),控制反转(Inversion of Control, IoC)。在依赖管理情形中,对象不应该负责实体化对自身的依赖。反之,他应当将这份权责移交给其他有权力的机制,从而实现控制的反转。

类并不直接分解其依赖,而是完全被动的,它提供可用于注入依赖的赋值器方法或者构造器参数,在构造过程中,DI容器实体化需要的对象(通常按需创建),并使用构造器参数或赋值器方法将依赖连接到一起。

Spring框架提供了最有名的java DI 容器,用户在XML配置文件中定义互相关联的对象,然后对Java代码请求特定的对象。

 

第十三章 并发编程

 

并发防御原则:

1)单一权责原则(srp):方法、类、组件应当只有一个修改的理由。并发设计自身足够复杂到成为修改的理由,所以也该从其他代码中分离出来。

2)推论:限制数据作用域 两个线程修改共享对象的同一字段时,可能互相干扰,解决的方案之一是采用synchronized关键词在代码中保护一块使用共享对象的临界区(critical section

3)推论:使用数据副本。一开始就避免共享数据。

4)推论:线程尽可能独立 ,不与其他线程共享数据

第十七章 味道与启发

边界行为:代码在所有的角落和边界情形下真能工作。追追随每种边界条件,并编写测试。考虑封装边界条件,不想随处可见的+1,-1字样

替代public static final int, 用enum,隶属于有名称的枚举

 

 

 

修改代码的艺术

第一章   修改软件

修改软件的四个起因:

1)  添加新特性

2)  修正bug

3)  改善设计

4)  优化资源使用

在不改变软件行为的前提下改善其设计的举动称为重构(refactoring).

一般而言,当对一个系统进行修改的时候,其三个方面可能会发生改变:结构、功能以及资源使用。

 

第二章   带着反馈工作

对系统进行改动有两种主要方式:

1) 编辑并祈祷(edit and pray)

2)覆盖并修改(cover and modify):在我们改动软件的时候开一张安全网,以确保糟糕的改动不会泄露出去并感染到软件的其他部分。覆盖软件即意味着用测试来覆盖它。回归测试(regression test):我们周期性地运行测试来检验已知的良好行为。

Unit test: 它们由一组独立的测试构成,其中每个测试针对一个单独的软件组件。组件是指一个系统最为原子的行为单位。一般来说指的就是函数,类。难以孤立的测试函数,类。测试的隔离性是单元测试的一个重要方面。

高层测试:是覆盖了某个应用中的场景和交互的测试。高层测试可以用来一下子就确定一组类的行为。

第三章   感知和分离

 

 

 

 

Refactoring – improving the design of existing code

The key to keeping code readable and modifiable is refactoring.

Refactoring is risky. It requires changes to working code that can introduce subtle bugs. Refactoring must be done systematically. Understanding the mechanics of such refactorings is the key to refactoring in a disciplined way. The refactoring s in this book will help you change your code one small step at a time, thus reducing the risks of evolving your design.

Refactoring is a disciplined way to clean up code that minimizes the chances of introducing bugs.

中文演讲笔记:

一个很简单的例子。计算一个顾客在音像租赁店的费用,费用取决于借的时间和类型(regular, children’s, new releases)除了计算费用,还要计算frequent renter points(算积分).

Compiler doesn’t care whether the code is ugly or clean. But when we change the system, there is human involved, and humans do care. A poorly designed system is hard to change, because it is hard to figure out where the changes are needed.

假设修改:

  1. HTML, statement web enable. 几乎不可能重用现在的statement method for HTML. 可以重写一个新方法,当然可以复制粘贴。。。
  2. 计算费用的规则变化了,更改电影分类。考虑statement 和 htmlStatement一致性
  3. The first Step in Refactoring

The first step is always the same. I need to build a solid set of tests for that section of code. The tests are essential because even though I follow refactorings structured to avoid most of the opportunities for introducing bugs, I’m still human and still make mistakes.

编造几个用户以及他们的租借信息比较statement 的输出

  1. Decomposing and Redistributing the Statement Method

将statement方法分解成几小块。小块的更容易管理

1)          首先的话可以把计算费用的部分分解出来amountFor

每进行一步修改需要测试

2)          然后发现分解出来的amountFor 只用rental object 没有用到customer的信息 所以把这个方法移到rental.

3)          分解Frequent Renter Point

4)          Removing Temps: 临时变量会导致更长更复杂的问题。在这里的话有totalAmount(总价),frequentRentalPoints(积点),可以采用query 方法。修改后发现相比之前的重构这次让代码更复杂了,起码原来while loop只有一次,这次有三次。Don’t worry about this while refactoring, you will then be in a much better position to do something about it, and you will have more options to optimize effectively.

5)          回到getCharge 方法,原来用的switch based on movie pricecode来区分. It is a bad idea to do a switch based on an attribute of another object, if you must use a switch statement, it should be on your own data. getCharge移到movie

6)          继承

几种类型的电影有不同的方式回答同一个问题,子类。但是一部电影可能会改变类,但一个object 不能改变它的class, 所以用state pattern (状态模式)

 

Duplicated Code:提取方法

Long method: 引起要传递很多参数和临时变量,长长的参数列表很难管理,可能因为你需要更多的数据而一直在变这个列表。可以采用query, 或者创建parameter object

Feature envy: 对象的话更关注所在的类,而不是其中某几个方法。如果当一个方法频繁基于另一个、多个对象计算值时果断 应该移位值了,或者提取移位值结合,分成若干个方法

Lazy class: 每个被创建的类都要花代价去维护, 无用的子类,collapse hierarchy合并继承结构,无用的组件使用内联,如果一个类很简单没有做什么事情,可以将其中的字段和方法都合并到另一个类中,并删除原来的类。

什么时候重构

In my view refactoring is not an activity you set aside time to do. Refactoring is something you do all the time in little bursts. 不是你决定重构,而是重构能帮到你做些别的你想做的事。

1)          当你第三次重复做相似的事的时候

2)          Refactor help me understand some code I need to modify (written by someone else, or I have written it); design that does not help me add a feature easily.

3)          重构使代码看上去更整洁易于发现bug

4)          Code reviews 能够在开发团队中分享经验,能够使更多的人理解一个大的软件系统的不同方面。

 

Refactoring and Performance

为了让软件能够更好理解,你的一些修改可能使程序运行得更加慢。先要写出和谐的软件然后再调整速度

 

Building Tests:

Tip: 1)测试最好是自动的能自己检查结果的

2)一系列的测试是发现bug的利器

标准的java 测试习惯是test main: 每个类应该有一个main方法,测试类。另一个方法是建一个独立的test class

 

JUnit test framework

任何包含测试的类必须是testcase class的子类。利用组合模式对测试组进行打包。可以包含:raw test case 或者 另外的suite of test cases.这使得建立大量的test suite 和自动测试成为可能

 

Unit test是用来提高程序员产量的,顺便让quality assurance department happy。它非常本地性,每个测试类在单个package 中工作,它测试其他package的interface,其余的默认都是在正常工作。

Functional test 是为了保证软件作为一个整体是工作着的。是黑盒测试。也可以用JUnit但不是很高效。

 

黑盒测试:数据驱动的测试或输入、输出驱动的测试。测试目标与程序内部机制结构无关。测试数据完全来源于软件规范,判定标准是“穷举输入测试

白盒测试:逻辑驱动,允许检查程序的内部结构。对程序的逻辑结构进行检查,从中获取测试数据。穷举路径测试。

posted on 2013-02-25 18:16  lauraxia  阅读(294)  评论(0编辑  收藏  举报

导航