Skip to content

代码整洁之道 读书笔记

统计信息:字数 7762 阅读16分钟

作者马丁

第一张 前言

代码重构:类似于机器的日常维护,将组建维护到最新的状态,确保组件的正常运行。写出阅读性很好的代码(不仅仅是代码的功能和架构的实现)。代码重构类似于整理房间。日常功能的暂时增减,最后定期进行重构,整体代码更有逻辑。

懂得已有功能和未来需求,才能在已有代码上做出更好的改进。

破窗理论:如果一个窗户已经破损,很久没有人修复,那么其他的窗户也会破损,甚至有人主动破碎窗户。所以,以后做的事情,现在最好完全处理。later is never.

同理,代码上的小问题也需要处理,解决代码的小问题。

每个函数、每个类、每个组件都关注一个事情,不会被周边其他的细节影响。如果一个对象的功能很多,可以抽取成多个对象进行完成。

要求:能通过所有测试;没有重复代码;完成全部设计理念;尽量少的实体(类、方法、函数)。

规定:修改代码时,让代码比来之前更整洁;

第二章 命名

函数和变量名应该尽量具体,根据变量名可以看出变量的含义,变量的数据类型等。变量中不要出现 variable, list 等,可以出现 group, 因为变量已经是一个数组,加入这个就是没用的。

代码 01

public class GuessStaticsMessage {

  // 创建类变量
  private String number;
  private String verb;
  private String modifier;

  // 表示这个方法的返回值是字符串,如果是 void 表示返回值是空
  public String make(char candidate, int count) {
    createMessageParts(count);
    return String.firmat(
    );
  }

  // 根据不同的数量执行不同的函数
  private void createDependentMessageParts(int count) {
    if (count == 0) {
      thereAreNoLetters();
    } else if (count == 1) {
      thereIsOneLetter();
    } else {
      thereAreManyLetters(count);
    }
  }

  // 对应三种不同的处理逻辑,并没有放在上面一个函数内部
  private void thereAreManyLetters(int count) {
    number = Integer.toString(count);
    verb = 'are';
    modifier = 's';
  }

  private void thereIsOneLetter() {
    number = '1';
    verb = 'is';
    modifier = '';
  }

  private void thereAreNoLetters() {
    number = 'no';
    verb = 'are';
    modifier = 's';
  }
}

第三章 函数

函数需要短小;最好不超过20行(如果超过20行的函数,需要分析函数的功能是否单一,是否可以拆分成多个函数,是否可以去掉无关功能)

if else if 等避免不必要的换行。一个函数体内部不要三层的if嵌套。

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

函数的参数尽量少(最好是0-1,不要超过3个参数)——同理,组件的props也尽量简单,尽量少。这样使用函数或者复用组件更方便。函数参数较少有益于测试。所以说,向函数中传递布尔值的做法不规范(其他数据类型可以进行强制数据转换,如果使用单一的开关函数控制组件可能效果更好)。这样可以设置两个无参数函数。

如果函数的参数天然就是一对(坐标xy等),那么这样比传入一个对象包含两个属性更好。其他的二参数或者三参数,这样可能造成参数的顺序错误,使用不是很方便。如果是三个参数,那么三个参数哪个是可选参数,哪个参数是什么数据类型等问题就会很麻烦。如果是三个参数,最好封装成一个对象。

函数同时没有副作用。一个函数执行时,不能影响组件内部的其他属性或者函数的执行。

尽量避免使用输出函数。如果函数必须修改某种状态时,最好修改对象的属性,而不是直接输出一个变量(除去工具函数)。

抽离 try-catch 结构。这样函数的主要逻辑和错误捕获会更好分离。

代码 02

// 这只一个 web 测试框架
// htmlUtil.java
public static String renderPageWithSetupsAndTreardowns (
  PageData PageData, boolean isSuite
) throws Exception {
  boolean isTestPage = pageData.hasAttribute("Test");
  if (isTestPage) {
    WikiPage testPage = pageData.getWikiPage();
    StringBuffer newPageCountent = new StringBuffer();
    includeSetupPages(testPage, newPageCountent, isSuite);
    newPageCountent.append(pageData, getContent());
    includeTeardownPages(testPage, newPageCountent, isSuite);
    pageData.setContent(newPageCountent.toString());
  }
  return pageData.getHtml();
}

代码 03

public void delete(Page page) {
  try {
    deletePageAndAllReferences(page);
  }
  catch (Exception error) {
    logError(e);
  }
}

private void deletePageAndAllReferences(Page oage) throws Exception {
  deletePage(page);
  registry.deleteRefrence(page.name);
  configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
  logger.log(e.getMessgae());
}

函数开始设计时,可能函数功能和函数参数等和最后的功能不合适。所以代码需要打磨。

代码 04

package fitnesse.html;

import fitnesse.responder.run.SuiteResponder;
import fitnesse.wiki.*;

public class SetupTeardownIncluder {

  private PageData PageData;
  private boolean isSuite;
  private WikiPage testPage;
  private StringBuffer testPage;
  private PageCrawler PageCrawler;

  public static String render(PageData PageData) throw Exception {
    return render(PageData, false);
  }

  public static String render(PageData PageData, boolean isSuite) throws Exception {
    return new SetupTeardownIncluder(PageData).render(isSuite);
  }

  private SetupTeardownIncluder(PageData PageData) {
    this.PageData = PageData;
    testPage = PageData.getWikiPage();
    pageCrawler = testPage.getPageCrawler();
    newPageContent = new StringBUffer();
  }

  private String render(blloean isSuite) throws Exception {
    this.isSuite = isSuite;
    if (isTestPage())
      includeSetupAndTeardownPages();
    return pageData.getHtml();
  }

  private boolean isTestPage() throws Exception {
    return pageData.hasAttribute("Test");
  }

  private void includeSetupAndTeardownPages() throws Exception {
    includeSetupPages();
    includePageContent();
    includeTeardownPages();
    updatePageContent();
  }
}

个人感想:实际项目中的函数或者组件,可能有超过5个参数,或者超过20个 props,因为不同人开发不同功能时,会反复改动一个函数和组件,同时自己没有精力重构这个组件,所以这就是已有的不好的代码。可能是故意写成别人不便于阅读的函数,可能是历史改动造成的问题。尽量让自己写的函数或组件可读可理解。

第四章 注释

注释的作用是解释写的不好的代码(这部分代码并不能通过函数名或者变量名看出其中的作用)所以需要必要的注释。(个人学习的注释会说明下参数的类型和变量)对于JAVA等类型明确的语言可以少用注释,对于JS等数据转换很灵活的语言尽量少写注释,减少不必要的注释。

函数的功能会更改,但是注释不一定及时更改,那么注释不一定会准确描述函数的作用。

个人感想:精致的代码不需要注释(实际上代码写的可能有问题,最好还是加上说明,便于自己使用,便于整体团队阅读等)个人和团队的英文能力并不强,所以变量名和函数名不一定代表函数的意思。总之:简单的函数不需要加注释,复杂的函数,特殊情况的处理,需要加注释和引用链接(Stack Overflow)。

读到 86 页。


Last update: November 9, 2024