代码整洁之道

Clean Code

  1. 神在细节之中
  2. 整洁的代码只做好一件事
  3. 如何在意代码
  4. 让营地比你来时更干净

简洁代码

  • 能通过所有测试
  • 没有重复代码
  • 体现系统中全部设计理念
  • 包括尽量少的实体,比如类,方法,函数等

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

有意义的命名

变量名应该明确它为什么会存在,它做什么事,应该怎么用  

如果名称需要注释来补充,那就不算是名副其实

代码要关联上下文

没有关联,不知道这个方法是干什么的

public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]();
    for (int[] x : theList)
        if (x[0] == 4)
            list1.add(x);
        return list1;
}
  • theList中是什么类型的东西
  • theList零下标条目的意义是什么
  • 值4的意义是什么
  • 我怎么使用返回的列表

比方说我们在开发一种扫雷游戏,我们发现,盘面是名为theList的单元格列表,那就将其名称改为gameBoard
盘面上每一个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值,而该状态值为4表示已标记。只要改为有意义的名称,代码就会得到相当程度的改进


public List<int[]> getFlaggedCells() {
    List<int[]> flaggedCells = new ArrayList<int[]();
    for (int[] x : gameBoard)
        if (x[STATUS_VALUE] == FLAGGED)
            flaggedCells.add(x);
        return flaggedCells;
}

不用int表示单元格,新写一个类,类包括一个名副其实的函数(isFlagged),从而掩盖住那个魔术数

public List<int[]> getFlaggedCells() {
    List<Cell> flaggedCells = new ArrayList<Cell>();
    for (Cell cell : gameBoard)
        if (cell.isFlagged)
            flaggedCells.add(cell);
        return flaggedCells;
}

不要以a1,a2,aN这样的命名

不要无意义的区分:

Product
ProductInfo
ProductData  //与ProductInfo类名称虽然不同,意思却无区别  

以读者能区别不同之处来区分名称

  1. 使用读得出来的名称
  2. 使用可搜索的名称
  3. 避免使用编码

明确是王道

类名

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

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行

  1. 每个函数都只说一件事
  2. 而且每个函数都依序把你带到下一个函数

代码块和缩进

if语句,else语句,while语句等,其中的代码快应该只有一行。该行大抵应该是一个函数调用语句

  1. 函数不应该大到足以容纳嵌套结构
  2. 函数的缩进层级不该多于一层或两层

函数应该做好一件事。做好这件事,只做一件事

函数中的语句都要在同一个抽象层级上
判断函数是否不止做了一件事,可以看是否还能拆出一个函数

向下规则

每个函数后面都跟位于下一抽象层级的函数

程序就像是一序列TO起头的段落,没一段都描述当前抽象层级,并引用位于下一抽象层级的后续啊TO起头的段落

如果每个例程都让你感到深合己意,那就是整洁代码

  1. 长而具有描述性的名称,要比短而令人费解的名称好
  2. 长而具有描述性的名称,要比描述性的长注释好

最理想的参数数量是零,其次是一,再次是二

尽量避免三参数函数。无论如何最好别出现三个以上参数的函数

输出参数比输入参数还要难理解,最好通过返回值从函数中输出

向函数传递参数的情况:

1.获取参数的信息

boolean fileExists("MyFile")  

2.操作该函数,将其转换为其他东西

InputStream fileOpen("MyFile")  

把String类型的文件名转换为InputStream类型的返回值

3.事件

有输入参数而无输出参数
使用该函数修改系统状态

void passwordAttemptFailedNtimes(int attempts)

反例:

void includeSetupPageInto(StringBuffer pageText)  

对于转换,应该使用返回值

transform(StringBuffer out)  

标识参数

不要向函数传入布尔值

应该用把函数一分为二,分别处理true 和 false部分

尽量把二元函数转换成一元函数

  1. 把一元函数写成二元函数的成员
  2. 把二元函数其中的一个参数写成类的成员变量

参数对象

把参数封装成类来减少参数的数量

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语句。

注释

与其花时间编写解释你糟糕的代码的注释,不如花时间清洁糟糕的代码

  1. 对意图的解释
  2. 阐释
    • 把晦涩难明的参数或返回值的意义翻译为某种可读形式
  3. 警示
    • 警告其他程序员会出现某种后果

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行之间比较合适

概念间垂直方向上的区隔
在封包声明,导入声明和每个函数之间,都有空白行隔开

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

  1. 多个实体变量的定义

尽量不把关系密切的概念放到不同的文件中

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

实体变量
应该在类的顶部声明
应该被该类的所有方法或大部分方法所用
让大家知道在哪儿可以看到这些声明

相关函数
若某个函数调用另外一个,就应该把它们放在一起
调用者应该尽可能的放在被调用者上面

横向格式

无需拖动滚动条原则

水平方向上的区隔与接近

使用空格字符将彼此紧密相关的食物连接到一起  
把相关性较弱的食物分割开  
  1. 在赋值符周围加上空格
  2. 函数参数前加空格
  3. 不在函数名和左圆括号之间加空格

乘法因子之间可以不加空格(优先级较高),加减法之间加空格,优先级较低

过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数
面向对象代码便于在不改动既有函数的前提下添加新类

方法不应调用由任何函数返回的对象的方法

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 <boolean表达式> : <错误信息表达式>

如果<boolean表达式>为true,则程序继续执行
如果为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;
    }
}

将错误隔离看待,独立于主要逻辑之外,防止错误处理终端主体代码

类的组织

类应该从一组变量列表开始

  1. 公共静态常量
  2. 私有静态常量
  3. 私有实体变量
  4. 公共变量
  5. 公共函数
  6. 私有工具函数(紧跟调用其的公共函数)

类应该短小

类的第一条原则是应该短小

单一权责原则SRP
类或模块应有且只有一条加以修改的理由

类应该只负责一块

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

内聚性
类应该只有少量实体变量
每个方法都应该操作一个或多个这种变量
方法操作的变量越多,就越粘聚到类上

如果一个类的每个变量都被每个方法所用,则该类具有最大的内聚性

系统

分离构造和使用过程

  • 运行所有测试
  • 不可重复
  • 表达了程序员的意图
  • 尽可能减少类和方法的数量

Java AOP

对象是过程的抽象。线程是调度的抽象

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • [TOC] 代码整洁之道-理论 前言 学习中、工作中遇到很多乱七八糟的糟糕代码,自己入门时也写过不少糟糕代码。在一...
    丁永辉Dave阅读 323评论 0 1
  • 一、整洁代码 A.混乱的代价 1.有些团队在项目初期进展迅速,但有那么一两年的时间却慢去蜗行。对代码的每次修改都影...
    ZyBlog阅读 2,027评论 0 2
  • 代码整洁之道 Clean Code 第一章 整洁代码 代码的重要性我们永远抛不掉代码,因为代码呈现了需求的细节。在...
    Pengzh1阅读 1,211评论 0 1
  • “相对于任何宏伟景愿,对细节的关注甚至是更为关键的专业性基础。首先,开发者通过小型实践获得可用于大型实践的技能和信...
    寻海阅读 4,717评论 1 27
  • 整洁代码 Leblanc : Later equals never.(勒布朗法则:稍后等于永不) 对代码的每次修改...
    foever_f1eb阅读 795评论 0 0