代码整洁之道

什么是整洁的代码

C++之父 Bjarne Stroustrup 认为:

  • 代码逻辑应该直截了当,叫缺陷难以隐藏;

  • 尽量减少依赖关系,使之便于维护;

  • 依据某种分层战略完善错误处理代码;

  • 性能调至最优,省的引诱别人做没规矩的优化,搞出一堆混乱来。

关键在于:整洁的代码只做好一件事

《面向对象分析与设计》作者 Grady Booch 认为:

  • 整洁的代码简单直接。

  • 整洁的代码如同优美的散文。

  • 整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。

关键在于:干净利落的抽象

编写整洁的代码

有意义的命名

  1. 名副其实

    变量、函数或类名应该答复了所有的大问题,如果名称需要注释来补充,那就不算名副其实。

    如下flag变量名,非常不符合名副其实这条原则

    const flag = true // 是否是原始值
    
  2. 避免误导

    避免使用与本意相悖的词。

    如下productList变量,名称看起来是一个集合,但值却是字符串,与本意相悖

    const productList = '255,26,223'
    
  3. 做有意义的区分

    如下三个方法名,很难从名称上区分应该使用哪个

    function getProduct() {}
    function getProducts() {}
    function getProductList() {}
    
  4. 使用读得出来的名称

    人类善于记忆和使用单词,不要自己造词。

  5. 使用可搜索的名称

    便于搜索,如MAX_PRICE容易搜索,a不容易找到

  6. 避免使用编码

  7. 类名和对象名应该是名词或名词短语,不应当是动词

  8. 方法名应该是动词或动词短语

  9. 给每个抽象概念选一个词,并一以贯之

    比如获取动作都用queryqueryProductInfoqueryCourseInfo,风格统一,便于理解和阅读

  10. 别用双关语

    避免将同一单词用于不同目的

    如下两个add方法,用于不同目的,难以区分

    function add(value) {
      return value + 1
    }
    function add(value) {
      collection.push(value)
    }
    
  11. 添加有意义的语境

    如下变量名,status放在成组的变量名中可以理解是学生考试相关的状态,而单拿出来很难知道具体含义,可以通过添加前缀的方式,给变量添加有意义的语境,如examStatus

    const studentName, status, level, score, pass
    

函数

  1. 短小

  2. 只做一件事

  3. 函数参数

    1. 最理想的参数数量是零,其次是一,二,有足够特殊的理由才能使用三个以上的参数
    2. 标识参数大声宣布本函数不止做一件事
  4. 无副作用

  5. 分隔指令与询问

    function set(attribute, value) {
      // 设置某个指定属性,成功返回true,不存在返回false
    }
    
    if (set('username', 'xiaoming')) {}
    // 难以读懂,if语句在问username属性值之前已经设置成xiaoming,还是问username属性成功设置成xiaoming
    
    // 解决方案:把指令与询问分隔开
    if (attributeExists('username')) {
      setAttribute('username', 'xiaoming')
    }
    
  6. 别重复自己

注释

用代码阐述意图 — 与其花时间为糟糕的代码编写注释,不如花时间清理掉糟糕的代码

  1. 好的注释
    1. 法律信息,如版权和著作权声明
    2. 提供信息的注释,如解释方法的返回值
    3. 对意图的解释
    4. 阐释
    5. 警示
    6. TODO注释
  2. 坏的注释
    1. 喃喃自语
    2. 多余的注释
    3. 位置标记
    4. 注释掉的代码
    5. 信息过多

格式

垂直格式

短文件通常比长文件易于理解。有可能用大多数为200行,最长500行的单个文件构造出出色的系统。

向报纸学习,报纸一般从上到下阅读,在顶部有头条,告诉故事主题,第一段故事大纲,粗线条描述,接着读下去了解所有细节。报纸由多篇文章组成,多数短小精悍,有些稍微长点,很少有占满一整页的。

从报纸可以学习到

  1. 文件名称应当简单且一目了然,名称本身应该足够告诉我们是否在正确的模块中;
  2. 源文件顶部应该给出高层次概念和算法,细节应该往下渐次展开,直至找到最底层函数和细节。

具体原则:

  1. 垂直方向的区隔

    几乎所有代码都是从上往下读,从左往右读。每行展现一个表达式或一个句子,每组代码行展示一条完整的思路。这些思路用空白行区隔开来

    如下两端代码,同样的代码,垂直方向有区隔的第一段明显比第二段更易阅读

    import Vue from 'vue';
    import Main from './main.vue';
    import merge from 'element-ui/src/utils/merge';
    import { PopupManager } from 'element-ui/src/utils/popup';
    import { isVNode } from 'element-ui/src/utils/vdom';
    const NotificationConstructor = Vue.extend(Main);
    
    let instance;
    let instances = [];
    let seed = 1;
    
    const Notification = function(options) {
      if (Vue.prototype.$isServer) return;
      options = merge({}, options);
      const userOnClose = options.onClose;
      const id = 'notification_' + seed++;
      const position = options.position || 'top-right';
    
      options.onClose = function() {
        Notification.close(id, userOnClose);
      };
    
      instance = new NotificationConstructor({
        data: options
      });
    };
    
    import Vue from 'vue';
    import Main from './main.vue';
    import merge from 'element-ui/src/utils/merge';
    import { PopupManager } from 'element-ui/src/utils/popup';
    import { isVNode } from 'element-ui/src/utils/vdom';
    const NotificationConstructor = Vue.extend(Main);
    let instance;
    let instances = [];
    let seed = 1;
    const Notification = function(options) {
      if (Vue.prototype.$isServer) return;
      options = merge({}, options);
      const userOnClose = options.onClose;
      const id = 'notification_' + seed++;
      const position = options.position || 'top-right';
      options.onClose = function() {
        Notification.close(id, userOnClose);
      };
      instance = new NotificationConstructor({
        data: options
      });
    }
    
  1. 垂直距离

    1. 关系密切的概念应该互相靠近。除非有很好的理由,否则不要把关系密切的概念放到不同文件中。
    2. 变量声明应尽可能靠近其使用位置。短函数中本地变量应该在函数顶部出现,较长函数中变量也可在某个代码块顶部,或在循环之前声明。
    3. 相关函数应该放在一起。如某个函数调用了另一个,且调用者应该尽可能放在被调用者上面。
    4. 概念相关代码应该放到一起。相关性越强,彼此之间距离就该越短。
    function open(options) {}
    function doOpen(props) {}
    function doAfterOpen() {}
    
  1. 垂直顺序

    一般而言自上向下展开函数调用依赖顺序,这样就建立了一种自顶向下贯穿源代码模块的良好信息流。

横向格式

行宽、间隔、缩进、水平对齐等,每个团队都有各自规范,并通过eslint等格式校验工具约束,不做过多阐述

跃进

Kent Beck 简单设计四条规则

  1. 运行所有测试—保证系统如预期一般工作
  2. 不可重复
  3. 表达了程序员的意图
  4. 尽可能减少类和方法的数量

只要系统可测试,就会导向保持类短小且目的单一的设计方案。测试编写越多就越能持续走向编写较容易测试的代码,所以,确保系统完全可测试能帮助我们创建更好的设计。

紧耦合的代码难以编写测试。

遵循有关编写测试并持续运行测试的简单、明确的规则,系统就会更贴近低耦合度、高内聚度的目标。编写测试引至更好的设计。

第2-4条都是重构相关工作,重构技巧可以参阅《重构》等书籍。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容