Clean Code
- 神在细节之中
- 整洁的代码只做好一件事
- 如何在意代码
- 让营地比你来时更干净
简洁代码
- 能通过所有测试
- 没有重复代码
- 体现系统中全部设计理念
- 包括尽量少的实体,比如类,方法,函数等
如果每个里程都让你感到深合己意,那就是整洁代码。
如果代码让编程语言看起来像是专为解决那个问题而存在,就可以称之为漂亮的代码
-- Ward Cunningham
有意义的命名
变量名应该明确它为什么会存在,它做什么事,应该怎么用
如果名称需要注释来补充,那就不算是名副其实
代码要关联上下文
没有关联,不知道这个方法是干什么的
public List getThem() {
List list1 = new ArrayList
- theList中是什么类型的东西
- theList零下标条目的意义是什么
- 值4的意义是什么
- 我怎么使用返回的列表
比方说我们在开发一种扫雷游戏,我们发现,盘面是名为theList
的单元格列表,那就将其名称改为gameBoard
。
盘面上每一个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值
,而该状态值为4
表示已标记
。只要改为有意义的名称,代码就会得到相当程度的改进
public List getFlaggedCells() {
List flaggedCells = new ArrayList
不用int表示单元格,新写一个类,类包括一个名副其实的函数(isFlagged),从而掩盖住那个魔术数
。
public List getFlaggedCells() {
List flaggedCells = new ArrayList();
for (Cell cell : gameBoard)
if (cell.isFlagged)
flaggedCells.add(cell);
return flaggedCells;
}
| |
不要以a1,a2,aN这样的命名
不要无意义的区分:
Product
ProductInfo
ProductData //与ProductInfo类名称虽然不同,意思却无区别
以读者能区别不同之处来区分名称
- 使用读得出来的名称
- 使用可搜索的名称
- 避免使用编码
明确是王道
类名
类名和对象名应该是名词或名词短语
类名不应该是动词
Such as: Customer WikiPage Account AddressParser
Not as : Manager Processor Data Info
函数是语言的动词,类是名词
方法名
方法名应当是动词或动词短语
Such as :
postPayment deletePage save
属性访问器,修改器和断言应该根据其值命名,并依Javabean标准加上get set is前缀
重载构造器时,使用描述了参数的静态工厂方法名
Such as :
Complex fulcrumPoint = Complex.FromRealNumber(23.0)
通常好于
Complex fulcrumPoint = new Complex(23.0)
可以考虑将相应的构造器设置为private, 强制使用这种命名手段
每个概念对应一个词 --一以贯之
如果在同一堆代码中有controller,又有manager,还有driver,就会令人疑惑
别用双关语 --一词一义
避免将同一单词用于不用目的,同一术语用于不同概念
使用解决方案领域的名称
只有程序员会读你的代码
添加有意义的语境
添加add前缀之类的
不要添加没用的语境
只要短名称足够清楚,就要比长名称好。
函数
函数的第一规则是短小,第二规则是更短小
函数就应该只有5,6行
- 每个函数都只说一件事
- 而且每个函数都依序把你带到下一个函数
代码块和缩进
if语句,else语句,while语句等,其中的代码快应该只有一行。该行大抵应该是一个函数调用语句
- 函数不应该大到足以容纳嵌套结构
- 函数的缩进层级不该多于一层或两层
函数应该做好一件事。做好这件事,只做一件事
函数中的语句都要在同一个抽象层级上
判断函数是否不止做了一件事,可以看是否还能拆出一个函数
向下规则
每个函数后面都跟位于下一抽象层级的函数
程序就像是一序列TO起头的段落,没一段都描述当前抽象层级,并引用位于下一抽象层级的后续啊TO起头的段落
如果每个例程都让你感到深合己意,那就是整洁代码
- 长而具有描述性的名称,要比短而令人费解的名称好
- 长而具有描述性的名称,要比描述性的长注释好
最理想的参数数量是零,其次是一,再次是二
尽量避免三参数函数。无论如何最好别出现三个以上参数的函数
输出参数比输入参数还要难理解,最好通过返回值从函数中输出
向函数传递参数的情况:
1.获取参数的信息
boolean fileExists("MyFile")
2.操作该函数,将其转换为其他东西
InputStream fileOpen("MyFile")
把String类型的文件名转换为InputStream类型的返回值
3.事件
有输入参数而无输出参数
使用该函数修改系统状态
void passwordAttemptFailedNtimes(int attempts)
反例:
void includeSetupPageInto(StringBuffer pageText)
对于转换,应该使用返回值
transform(StringBuffer out)
标识参数
不要向函数传入布尔值
应该用把函数一分为二,分别处理true 和 false部分
尽量把二元函数转换成一元函数
- 把一元函数写成二元函数的成员
- 把二元函数其中的一个参数写成类的成员变量
参数对象
把参数封装成类来减少参数的数量
Circle makeCircle(double x, double y, double radius)
Circle makeCircle(Point center, double radius)
当一组参数共同被传递,往往就是该有自己名称的某一个概念的一部分
参数列表
可变参数和传递类型为List的单个参数没什么两样
动词与关键字
把参数名称编码成函数名
assertEqual
qssertExpectedEqualsActual(expected, actual)
应避免使用输出参数
如果函数必须要修改某种状态,就修改所属对象的状态
分隔指令与询问
函数要么回答什么事,要么做什么事,二者不可得兼
Such as:
public boolean set(String attribute, String value);
会导致:
if (set("username","unclebob")) ...
应该:
if (attributeExists("username")) {
setAttribute("username","unclebob")
}
把指令和询问分隔开来
使用异常替代返回错误码
防止因if导致的更深层次的嵌套结构
抽离TryCatch代码块
把try和catch代码块的主题部分抽离出来,另外形成函数
public void delete(Page page) {
try {
deletePageAndAllReference(page);
}
catch (Exception e) {
logError(e);
}
}
private void deletePageAllReference(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
错误处理就是一件事
处理错误的函数不该做其他事
这意味着如果关键字try在某个函数中存在,它就该是这个函数的第一个单词,而且在catch/finally代码块后面也不该有其他内容
结构化编程
每个函数、函数中的每个代码块中只该有一个return语句,循环中不能有break或continue语句,永远不能有任何的goto语句。
注释
与其花时间编写解释你糟糕的代码的注释,不如花时间清洁糟糕的代码
- 对意图的解释
- 阐释
- 把晦涩难明的参数或返回值的意义翻译为某种可读形式
- 警示
- 警告其他程序员会出现某种后果
TODO注释
在源码中放置要做的工作列表
是程序员认为应该做,但是还没做的工作
//TODO-MdM these are not needed
// We except this to go away we do the checkout meodel
protected VersionInfo makeVersion() throws Excpetion {
return null;
}
不必每个函数都要有Javadoc或每个变量有要有注释
能用函数或变量时就别用注释
不要直接把代码注释掉
代码格式
垂直格式
文件长度在200行到500行之间比较合适
概念间垂直方向上的区隔
在封包声明,导入声明和每个函数之间,都有空白行隔开
垂直方向上的靠近
紧密相关的代码应该相互靠近
- 多个实体变量的定义
尽量不把关系密切的概念放到不同的文件中
变量声明
变量声明应该尽可能靠近其使用的位置
本地变量应该在函数的顶部出现
实体变量
应该在类的顶部声明
应该被该类的所有方法或大部分方法所用
让大家知道在哪儿可以看到这些声明
相关函数
若某个函数调用另外一个,就应该把它们放在一起
调用者应该尽可能的放在被调用者上面
横向格式
无需拖动滚动条原则
水平方向上的区隔与接近
使用空格字符将彼此紧密相关的食物连接到一起
把相关性较弱的食物分割开
- 在赋值符周围加上空格
- 函数参数前加空格
- 不在函数名和左圆括号之间加空格
乘法因子之间可以不加空格(优先级较高),加减法之间加空格,优先级较低
过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数
面向对象代码便于在不改动既有函数的前提下添加新类
方法不应调用由任何函数返回的对象的方法
final String outputDir = ctxt.getOptions().getScratchDir();
数据传送对象DTO
Data Transfer Object
只含有公共变量,没有函数的类
public class Address {
private String street;
private String streetExtra;
private String city;
public Address(String street, String streetExtra, String city) {
this.street = street;
this.streetExtra = streetExtra;
this.city = city;
}
public String getStreet() {
return street;
}
public String getStreetExtra() {
return streetExtra;
}
public String getCity() {
return city;
}
}
错误处理
依调用者需要定义异常类
public class LocalPort {
private ASMEPort innerPort;
public LocalPort(int portNumber) {
innrPort = new ACMEPort(portNumber);
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e) {
throw new PortDeviceFailure(e);
} catch (ATM1212UnlockedException e) {
throw new PortDeviceRailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
}
不要返回null值
若需要返回空列表,可以用Collections.emptyList()
别传递null值
尽量不要将null值传递给其他方法
如果有人传入null值,可以使用断言来处理
assert : <错误信息表达式>
如果
如果为false,则程序抛出java.lang.AssertionError,并输入<错误表达式>
public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
assert p1 != null : "p1 should not be null";
assert p2 != null : "p2 should not be null";
return (p2.x = p1.x) * 1.5;
}
}
将错误隔离看待,独立于主要逻辑之外,防止错误处理终端主体代码
类
类的组织
类应该从一组变量列表开始
- 公共静态常量
- 私有静态常量
- 私有实体变量
- 公共变量
- 公共函数
- 私有工具函数(紧跟调用其的公共函数)
类应该短小
类的第一条原则是应该短小
单一权责原则SRP
类或模块应有且只有一条加以修改的理由
类应该只负责一块
系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为
内聚性
类应该只有少量实体变量
每个方法都应该操作一个或多个这种变量
方法操作的变量越多,就越粘聚到类上
如果一个类的每个变量都被每个方法所用,则该类具有最大的内聚性
系统
分离构造和使用过程
- 运行所有测试
- 不可重复
- 表达了程序员的意图
- 尽可能减少类和方法的数量
Java AOP
对象是过程的抽象。线程是调度的抽象