《代码整洁之道》读书笔记

大师级的程序员把系统看成故事来说,而不是看成程序来写。

有意义的命名

作有意义的区分

  • 若是同一做用范围内两样不一样的东西不能重名,那其意思也应该不一样才对。那么这两样东西应该取不一样的名字而不是以数字区分。若是如下代码参数名改成 sourcedestination,这个函数就会像样许多java

    public static void copyChars(char a1[], char a2[]){
        for(int i = 0; i < a1.length; i++){
            a2[i] = a1[i];
        }
    }
  • 以数字系列命名(a一、a二、a3...)是依意义命名的对立面。这样的名称彻底没有提供正确的信息
  • Info和Data就像a、an和the同样,是意义含糊的废话。但有时候只要体现出有意义的区分,使用a和the这样的前缀就没错
  • 废话都是冗余的。Variable一词永远不该当出如今变量名中。Table一词永远不该该出如今表名中

避免思惟映射

例如循环中的 i、j、k 等单字母名称不是个好选择;读者必须在脑中将它映射为真实概念。最好用 filter、map 等方法代替 for循环程序员

类名与方法名

  • 类名和对象名应该是名称或名词短语
  • 方法名应该是动词或动词短语

每一个概念对应一个词

例如 fetchretriveget 表达同一个意思,应该选定一个,而后在各个类中使用相同的方法名。算法

别用双关语

避免将同一单词用于不一样的目的。同一术语用于不一样概念,基本上就是双关语了。app

使用解决方案领域名称

记住,只有程序员才会读你的代码。因此,尽管用那些计算机科学(Computer Science,CS)术语、算法名、模式名等。函数

动词与关键字

给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。fetch

  • 对于一元函数,函数和参数应当造成一种很是良好的动词/名词形式。编码

    // good
    write(name)
    // better
    // 更具体,它告诉咱们,"name"是一个"field"
    writeField(name)
  • 函数名称的关键字(keyword)形式。使用这种形式,把参数的名称编码成了函数名spa

    // bad
    assertEqual(expected, actual);
    
    // good 
    // 这大大减轻了记忆参数顺序的负担
    assertExpectedEqualsActual(expected, actual);

函数

短小

  • 函数第一条规则是要短小。第二条规则不是要短小。越短小越好,20行封顶
  • ifelsewhile等语句,其中的代码应该只有一行。该行大抵应该是一个函数调用语句。由于块内的函数拥有较具体说明性的名称,从而增长了文档上的价值

只作一件事

确保函数不能被再拆分日志

参数

最理想的参数数量是零,其次是一,再次是二,应尽可能避免三code

  • 不要传递标识参数,标识参数大声宣布函数不是作一件事。若是标识为 true 将会这样,标识为 false 则会那样
  • 二元函数:有些时候两个参数正好。例如 Point p = new Point(0, 0);由于点天生拥有两个参数。但大多数状况下应该尽可能利用一些机制将二元函数转换成一元函数。例如,把writeField 方法写成outputStream的成员之一

    // bad
    writeField(outputStream, name);
    // good
    outputStream.writeFiled(name);
  • 参数对象:若是函数看起来须要两个、三个、或三个以上参数,就说明其中一些应该封装为类了

    // bad
    Circle makeCircle(double x, double y, double, radius);
    // good
    Circle makeCircle(Point center, double radius);

    从参数封装成对象,从而减小参数数量,看起来像是在做弊,但实则并不是如此。当一组参数被共同传递,就像上例中的x和y那样,每每就是该有本身名称的某个概念的一部分

无反作用

  • 确保函数功能就像函数名描述的同样,不要作函数名描述之外的事情。应该为起一个更能描述函数功能的函数名

    public UserValidator {
        private Cyptographer cyptographer;
        
        public boolean checkPassword(String userName, String password) {
            User user = UserGateway.findByName(userName);
            if(user != User.NULL) {
                String codePhrase = user.getPhraseEncodedByPassword();
                String phrase = cyptographer.decrypt(codePhrase, password);
                if("Valid Password".equals(phrase)) {
                    // 反作用在于对这个调用
                    // checkPassword函数,顾名思义,就是用来检查密码。该名称并未暗示它会
                    // 初始化该次会话。能够重命名为 checkPasswordAndIntializeSession
                    Session.initialize();
                    return true;
                }
            }
            return false;
        }
    }
  • 普通而言,应该避免使用输出参数,若是函数必需要修改某种状态,就修改所属对象的状态

    // bad
    // 读者会弄不清s是输入参数仍是输出参数
    // 也会弄不清这函数是把s添加到什么东西后面,仍是把什么东西添加到s后面
    appendFooter(s); 
    // 函数签名
    public void appendFooter(StringBuffer report){}
    
    // good
    report.appendFooter();

分隔指令与询问

函数要行作什么事( 例如 user.setName('xxx') )、要么回答什么事( 例如 user.isVip() )。一个函数里不要把两件事都干了。

如何写出好函数

  • 分解函数
  • 修更名称
  • 消除重复

注释

好的注释

  • 法律信息
  • 警示性注释
  • TODO 注释虽好,但也要按期查看,删除再也不须要的

坏的注释

  • 循规式注释。 例如每一个函数都要有Javadoc或每一个变量都要有注释的规矩全然是愚蠢好笑的。这类注释徒然让代码变得散乱
  • 注释掉的代码。 如今已经有源代码控制系统,不要的代码应该当即删掉
  • 不明显的联系。 注释及其描述的代码之间的联系应该显而易见。注释的做用是解释未能自行解释的代码。若是注释自己还须要解释,就太遗憾了
  • 切断代码间的联系

    // bad
    public class ReportConfig {
        //
        // The class name of the reporter listener
        //
        private String m_className;
        
        //
        //The properties of the reporter listener
        //
        private List<Property> m_properties = new ArrayList<Property>();
        
        public void addProperty(Property property) {
            m_properties.add(property);
        }
    }
    
    // good
    public class ReportConfig {
        private String m_className;
        private List<Property> m_properties = new ArrayList<Property>();
        
        public void addProperty(Property property) {
            m_properties.add(property);
        }
    }

格式

垂直距离

  • 变量声名。 大多数状况下变量声名应该尽量靠近其使用的位置。可是在类内,变量声名应该统一放在顶部,由于这样读者能够一眼看出这个类有什么变量。
  • 相关函数。 若某个函数调用了另外一个函数,就应该把它们放到一块儿,并且调用者应该尽量放在被调用者上面。这样程序就有个天然顺序。
  • 概念相关。 概念相关的代码应该放到一块儿。相关性越强,彼此之间的距离就该越短

    public    class Assert {
        static public void assertTrue(String message, boolean codition(){}
        static public void assertTrue(boolean codition(){}
        static public void assertFalse(String message, boolean codition(){}
        // .....
    }

    这些函数有关极强的概念相关性,由于他们拥有共同的命名模式,执行同一基础任务的不一样变种。互相调用是第二位的。即使没有互相调用。也应该放在一块儿。

更多例子查看 p79 -- 5.25 垂直顺序

错误处理

抽离Try/Catch代码块

函数应该只作一件事,错误处理就是一件事。

// bad
public void delete(Page page) {
    try{
        deletePage(page);
        registery.deleteReference(page.name);
        configKeys.deleteKey(page.name.makeKey();
    }catch(Expection e){
        logError(e);
    }
}

// good
public void delete(Page page) {
    try{
        // 将上例的操做,封装到一个方法
        deletePageAndAllReferences(page);
    }catch(Expection e){
        logError(e);
    }
}

使用非受控异常

受控异常:Checked Exception(FileException、SQLException等),这类异常必须写 try/catch,或者 throw抛出,不然编译通不过。

非受控异常:Unchecked Exception,这类异常也叫作运行时异常(与非受控异常 字数相等),这类异常不须要 try/catch,也不须要 throw抛出。即便 throw 了,上层调用函数也非必须捕获,编译能经过。

受控异常的代价就是违反开放/闭合原则。若是你在方法中抛出受控异常,这意味着每一个调用该函数的函数都要修改,捕获新异常,或在其签名中添加合适的throw语句。对于通常的应用开发,受控异常依赖成本要高于收益成本,尽可能 try/catch 处理,不要抛出。

给出异常发生的环境说明

应建立信息充分的错误信息,并和异常一块儿传递出去。在消息中,包括失败的操做和失败类型。若是你的应用程序有日志系统,传递足够的信息给catch块,并记录下来。

依调用者须要定义异常类

// bad
ACMEPort port = new ACMEPort(12);
try {
    port.open();
} catch(DeviceResponseException e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} catch(ATM1212UnlockedException e) {
    reportPortError(e);
    logger.log("Unlock exception",e);
} catch(GMXError e) {
    reportPortError(e);
    logger.log("Device response exception",e);
} finally {
    // .....
}

经过打包调用API,确保它返回经过用异常类型,从而简化代码

// good
LocalPort port = new LocalPort(12);
try {
    port.open();
} catch(PortDeviceFailure e) {
    reportError(e);
    logger.log(e.getMessage(),e);
} finally {
    // .....
}

public class LocalPort{
 private ACMEPort innerPort;
 
 public LocalPort(int portNumber){
     innerPort = new ACMEPort(portNumber);
 }
 
 public open() {
  try {
       innerPort.open();
   } catch(DeviceResponseException e) {
           // 自定义的异常类
       throw new PortDeviceFailure(e);
   } catch(ATM1212UnlockedException e) {
       throw new PortDeviceFailure(e);
   } catch(GMXError e) {
       throw new PortDeviceFailure(e);
   }
 }
}

将第三方API打包是个良好的实践手段。当你打包一个第三方API,你就下降了对它的依赖。

其余

  • try catch语句块的范围不要太大,这样不利于对异常的分析
  • 别返回null值,这样能够减小调用者的防护性检测。与其返回null,不如抛出异常,或是返回特例对象(特例对象详见 p101)
  • 别传递null值,传递null就要求被调用函数须要一系列防护性检测,也就意味着程序有更大可能出错
相关文章
相关标签/搜索