• JavaScript 代码整洁之道


    来自: GitHub
    链接:https://github.com/alivebao/clean-code-js

    介绍

    作者根据 Robert C. Martin 《代码整洁之道》总结了适用于 JavaScript 的软件工程原则《Clean Code JavaScript》,本文是对其的翻译
    不必严格遵守本文的所有原则,有时少遵守一些效果可能会更好,具体应根据实际情况决定。这是根据《代码整洁之道》作者多年经验整理的代码优化建议,但也仅仅只是一份建议

    变量

    • 使用有意义,可读性好的变量名
    //反例
    var yyyymmdstr = moment().format('YYYY/MM/DD');
    //正例
    var yearMonthDay = moment().format('YYYY/MM/DD');
    
    • 使用ES6的const定义常量
    //反例
    var FIRST_US_PRESIDENT = "George Washington";
    //正例
    const FIRST_US_PRESIDENT = "George Washington";
    
    
    • 对功能类似的变量名采用统一的命名风格
    //反例
    getUserInfo();
    getClientData();
    getCustomerRecord();
    //正例
    getUser();
    
    • 使用易于检索名称
    //反例
    // 525600 是什么?
    for (var i = 0; i < 525600; i++) {
      runCronJob();
    }
    //正例
    var MINUTES_IN_A_YEAR = 525600;
    for (var i = 0; i < MINUTES_IN_A_YEAR; i++) {
      runCronJob();
    }
    
    • 使用说明变量(即有意义的变量名)
    //反例
    const cityStateRegex = /^(.+)[,\s]+(.+?)s*(d{5})?$/;
    saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]);
    //正例
    const ADDRESS = 'One Infinite Loop, Cupertino 95014';
    var cityStateRegex = /^(.+)[,\s]+(.+?)s*(d{5})?$/;
    var match = ADDRESS.match(cityStateRegex)
    var city = match[1];
    var state = match[2];
    saveCityState(city, state);
    
    • 显式优于隐式
    //反例
    var locations = ['Austin', 'New York', 'San Francisco'];
    locations.forEach((l) => {
      doStuff();
      doSomeOtherStuff();
      ...
      ...
      ...
      // l是什么?
      dispatch(l);
    });
    //正例
    var locations = ['Austin', 'New York', 'San Francisco'];
    locations.forEach((location) => {
      doStuff();
      doSomeOtherStuff();
      ...
      ...
      ...
      dispatch(location);
    });
    
    
    • 避免重复的描述 当类/对象名已经有意义时 对其变量进行命名不需要再次重复
    //反例
    var Car = {
      carMake: 'Honda',
      carModel: 'Accord',
      carColor: 'Blue'
    };
    
    function paintCar(car) {
      car.carColor = 'Red';
    }
    //正例
    var Car = {
      make: 'Honda',
      model: 'Accord',
      color: 'Blue'
    };
    
    function paintCar(car) {
      car.color = 'Red';
    }
    
    
    • 避免无意义的条件判断
    //反例
    function createMicrobrewery(name) {
      var breweryName;
      if (name) {
        breweryName = name;
      } else {
        breweryName = 'Hipster Brew Co.';
      }
    }
    //正例
    function createMicrobrewery(name) {
      var breweryName = name || 'Hipster Brew Co.'
    }
    

    函数

    • 函数参数
      理想情况下不超过2个
    //反例
    function createMenu(title, body, buttonText, cancellable) {
      ...
    }
    //正例
    var menuConfig = {
      title: 'Foo',
      body: 'Bar',
      buttonText: 'Baz',
      cancellable: true
    }
    
    function createMenu(menuConfig) {
      ...
    }
    
    • 函数功能的单一性
      功能不单一的函数将导致难以重构,测试和理解.功能单一的函数易于重构,并使代码更加干净
    //反例
    function emailClients(clients) {
      clients.forEach(client => {
        let clientRecord = database.lookup(client);
        if (clientRecord.isActive()) {
          email(client);
        }
      });
    }
    //正例
    function emailClients(clients) {
      clients.forEach(client => {
        emailClientIfNeeded(client);
      });
    }
    
    function emailClientIfNeeded(client) {
      if (isClientActive(client)) {
        email(client);
      }
    }
    
    function isClientActive(client) {
      let clientRecord = database.lookup(client);
      return clientRecord.isActive();
    }
    
    • 函数名应明确表明其功能
    //反例
    function dateAdd(date, month) {
      // ...
    }
    
    let date = new Date();
    
    // 很难理解dateAdd(date, 1)是什么意思
    dateAdd(date, 1);
    
    //正例
    function dateAddMonth(date, month) {
      // ...
    }
    
    let date = new Date();
    dateAddMonth(date, 1);
    
    
    • 函数应该只做一层抽象
      当函数需要的抽象多于一层时通常意味着函数功能过于复杂,需将其进行分解以提高其可重用性和可测试性
    //反例
    function parseBetterJSAlternative(code) {
      let REGEXES = [
        // ...
      ];
    
      let statements = code.split(' ');
      let tokens;
      REGEXES.forEach((REGEX) => {
        statements.forEach((statement) => {
          // ...
        })
      });
    
      let ast;
      tokens.forEach((token) => {
        // lex...
      });
    
      ast.forEach((node) => {
        // parse...
      })
    }
    //正例
    function tokenize(code) {
      let REGEXES = [
        // ...
      ];
    
      let statements = code.split(' ');
      let tokens;
      REGEXES.forEach((REGEX) => {
        statements.forEach((statement) => {
          // ...
        })
      });
    
      return tokens;
    }
    
    function lexer(tokens) {
      let ast;
      tokens.forEach((token) => {
        // lex...
      });
    
      return ast;
    }
    
    function parseBetterJSAlternative(code) {
      let tokens = tokenize(code);
      let ast = lexer(tokens);
      ast.forEach((node) => {
        // parse...
      })
    }
    
    
    • 移除重复的代码
      不要在任何循环下有重复的代码
    //反例
    function showDeveloperList(developers) {
      developers.forEach(developer => {
        var expectedSalary = developer.calculateExpectedSalary();
        var experience = developer.getExperience();
        var githubLink = developer.getGithubLink();
        var data = {
          expectedSalary: expectedSalary,
          experience: experience,
          githubLink: githubLink
        };
    
        render(data);
      });
    }
    
    function showManagerList(managers) {
      managers.forEach(manager => {
        var expectedSalary = manager.calculateExpectedSalary();
        var experience = manager.getExperience();
        var portfolio = manager.getMBAProjects();
        var data = {
          expectedSalary: expectedSalary,
          experience: experience,
          portfolio: portfolio
        };
    
        render(data);
      });
    }
    
    
    • 采用默认参数精简代码
    //反例
    function writeForumComment(subject, body) {
      subject = subject || 'No Subject';
      body = body || 'No text';
    }
    //正例
    function writeForumComment(subject = 'No subject', body = 'No text') {
      ...
    }
    
    • 使用Object.assign设置默认对象
    //反例
    var menuConfig = {
      title: null,
      body: 'Bar',
      buttonText: null,
      cancellable: true
    }
    
    function createMenu(config) {
      config.title = config.title || 'Foo'
      config.body = config.body || 'Bar'
      config.buttonText = config.buttonText || 'Baz'
      config.cancellable = config.cancellable === undefined ? config.cancellable : true;
    
    }
    
    createMenu(menuConfig);
    //正例
    var menuConfig = {
      title: 'Order',
      // User did not include 'body' key
      buttonText: 'Send',
      cancellable: true
    }
    
    function createMenu(config) {
      config = Object.assign({
        title: 'Foo',
        body: 'Bar',
        buttonText: 'Baz',
        cancellable: true
      }, config);
    
      // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
      // ...
    }
    
    createMenu(menuConfig);
    
    • 不要使用flag作为函数参数
      这意味着函数的功能的单一性已经被破坏,此时应考虑对函数进行再次划分
    //反例
    function createFile(name, temp) {
      if (temp) {
        fs.create('./temp/' + name);
      } else {
        fs.create(name);
      }
    }
    //正例
    function createTempFile(name) {
      fs.create('./temp/' + name);
    }
    
    function createFile(name) {
      fs.create(name);
    }
    
    • 避免副作用
      当函数产生了除了"接受一个值并返回一个结果"之外的行为时 称该函数产生了副作用
    //反例
    var name = 'Ryan McDermott';
    
    function splitIntoFirstAndLastName() {
      name = name.split(' ');
    }
    
    splitIntoFirstAndLastName();
    
    console.log(name); // ['Ryan', 'McDermott'];
    //正例
    function splitIntoFirstAndLastName(name) {
      return name.split(' ');
    }
    
    var name = 'Ryan McDermott'
    var newName = splitIntoFirstAndLastName(name);
    
    console.log(name); // 'Ryan McDermott';
    console.log(newName); // ['Ryan', 'McDermott'];
    
    • 不要写全局函数
      使用 ES6 中的 class 对全局的 Array 做简单的扩展显然是一个更棒的选择
    //反例
    Array.prototype.diff = function(comparisonArray) {
      var values = [];
      var hash = {};
    
      for (var i of comparisonArray) {
        hash[i] = true;
      }
    
      for (var i of this) {
        if (!hash[i]) {
          values.push(i);
        }
      }
    
      return values;
    }
    //正例
    class SuperArray extends Array {
      constructor(...args) {
        super(...args);
      }
    
      diff(comparisonArray) {
        var values = [];
        var hash = {};
    
        for (var i of comparisonArray) {
          hash[i] = true;
        }
    
        for (var i of this) {
          if (!hash[i]) {
            values.push(i);
          }
        }
    
        return values;
      }
    }
    
    
    • 采用函数式编程
      函数式的编程具有更干净且便于测试的特
    //反例
    const programmerOutput = [
      {
        name: 'Uncle Bobby',
        linesOfCode: 500
      }, {
        name: 'Suzie Q',
        linesOfCode: 1500
      }, {
        name: 'Jimmy Gosling',
        linesOfCode: 150
      }, {
        name: 'Gracie Hopper',
        linesOfCode: 1000
      }
    ];
    
    var totalOutput = 0;
    
    for (var i = 0; i < programmerOutput.length; i++) {
      totalOutput += programmerOutput[i].linesOfCode;
    }
    //正例
    const programmerOutput = [
      {
        name: 'Uncle Bobby',
        linesOfCode: 500
      }, {
        name: 'Suzie Q',
        linesOfCode: 1500
      }, {
        name: 'Jimmy Gosling',
        linesOfCode: 150
      }, {
        name: 'Gracie Hopper',
        linesOfCode: 1000
      }
    ];
    
    var totalOutput = programmerOutput
      .map((programmer) => programmer.linesOfCode)
      .reduce((acc, linesOfCode) => acc + linesOfCode, 0);
    
    • 封装判断条件
    //反例
    if (fsm.state === 'fetching' && isEmpty(listNode)) {
      /// ...
    }
    //正例
    function shouldShowSpinner(fsm, listNode) {
      return fsm.state === 'fetching' && isEmpty(listNode);
    }
    
    if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
      // ...
    }
    
    • 避免否定情况的判断
    //反例
    function isDOMNodeNotPresent(node) {
      // ...
    }
    
    if (!isDOMNodeNotPresent(node)) {
      // ...
    }
    //正例
    function isDOMNodePresent(node) {
      // ...
    }
    
    if (isDOMNodePresent(node)) {
      // ...
    }
    
    • 避免条件判断
    //反例
    class Airplane {
      //...
      getCruisingAltitude() {
        switch (this.type) {
          case '777':
            return getMaxAltitude() - getPassengerCount();
          case 'Air Force One':
            return getMaxAltitude();
          case 'Cessna':
            return getMaxAltitude() - getFuelExpenditure();
        }
      }
    }
    //正例
    class Airplane {
      //...
    }
    
    class Boeing777 extends Airplane {
      //...
      getCruisingAltitude() {
        return getMaxAltitude() - getPassengerCount();
      }
    }
    
    class AirForceOne extends Airplane {
      //...
      getCruisingAltitude() {
        return getMaxAltitude();
      }
    }
    
    class Cessna extends Airplane {
      //...
      getCruisingAltitude() {
        return getMaxAltitude() - getFuelExpenditure();
      }
    }
    
    • 避免类型判断
    //反例
    function travelToTexas(vehicle) {
      if (vehicle instanceof Bicycle) {
        vehicle.peddle(this.currentLocation, new Location('texas'));
      } else if (vehicle instanceof Car) {
        vehicle.drive(this.currentLocation, new Location('texas'));
      }
    }
    function combine(val1, val2) {
      if (typeof val1 == "number" && typeof val2 == "number" ||
          typeof val1 == "string" && typeof val2 == "string") {
        return val1 + val2;
      } else {
        throw new Error('Must be of type String or Number');
      }
    }
    
    //正例
    function travelToTexas(vehicle) {
      vehicle.move(this.currentLocation, new Location('texas'));
    }
    function combine(val1, val2) {
      return val1 + val2;
    }
    
    • 避免过度优化
    //反例
    for (var i = 0, len = list.length; i < len; i++) {
      // ...
    }
    //正例
    for (var i = 0; i < list.length; i++) {
      // ...
    }
    
    • 删除无效的代码
      不再被调用的代码应及时删除
    //反例
    function oldRequestModule(url) {
      // ...
    }
    
    function newRequestModule(url) {
      // ...
    }
    
    var req = newRequestModule;
    inventoryTracker('apples', req, 'www.inventory-awesome.io');
    //正例
    function newRequestModule(url) {
      // ...
    }
    
    var req = newRequestModule;
    inventoryTracker('apples', req, 'www.inventory-awesome.io');
    

    对象和数据结构

    • 使用getters和setters
    //反例
    class BankAccount {
      constructor() {
        this.balance = 1000;
      }
    }
    
    let bankAccount = new BankAccount();
    
    // Buy shoes...
    bankAccount.balance = bankAccount.balance - 100;
    //正例
    class BankAccount {
      constructor() {
        this.balance = 1000;
      }
    
      // It doesn't have to be prefixed with `get` or `set` to be a getter/setter
      withdraw(amount) {
       if (verifyAmountCanBeDeducted(amount)) {
         this.balance -= amount;
       }
      }
    }
    
    let bankAccount = new BankAccount();
    
    // Buy shoes...
    bankAccount.withdraw(100);
    
    • 让对象拥有私有成员
      可以通过闭包完成
    //反例
    
    var Employee = function(name) {
      this.name = name;
    }
    
    Employee.prototype.getName = function() {
      return this.name;
    }
    
    var employee = new Employee('John Doe');
    console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
    delete employee.name;
    console.log('Employee name: ' + employee.getName()); // Employee name: undefined
    //正例
    var Employee = (function() {
      function Employee(name) {
        this.getName = function() {
          return name;
        };
      }
    
      return Employee;
    }());
    
    var employee = new Employee('John Doe');
    console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
    delete employee.name;
    console.log('Employee name: ' + employee.getName()); // Employee name: John Doe
    
    

    • 单一职责原则
    //反例
    class UserSettings {
      constructor(user) {
        this.user = user;
      }
    
      changeSettings(settings) {
        if (this.verifyCredentials(user)) {
          // ...
        }
      }
    
      verifyCredentials(user) {
        // ...
      }
    }
    //正例
    class UserAuth {
      constructor(user) {
        this.user = user;
      }
    
      verifyCredentials() {
        // ...
      }
    }
    
    
    class UserSettings {
      constructor(user) {
        this.user = user;
        this.auth = new UserAuth(user)
      }
    
      changeSettings(settings) {
        if (this.auth.verifyCredentials()) {
          // ...
        }
      }
    }
    
    • 开/闭原则
      类,模块,函数等应该易于扩展,难于修改
      这一原则指的是我们应允许用户方便的拓展我们代码模块的功能,而不需要打开js文件源码手动对其进行修改
    //反例
    class AjaxRequester {
      constructor() {
        // What if we wanted another HTTP Method, like DELETE? We would have to
        // open this file up and modify this and put it in manually.
        this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
      }
    
      get(url) {
        // ...
      }
    
    }
    //正例
    class AjaxRequester {
      constructor() {
        this.HTTP_METHODS = ['POST', 'PUT', 'GET'];
      }
    
      get(url) {
        // ...
      }
    
      addHTTPMethod(method) {
        this.HTTP_METHODS.push(method);
      }
    }
    
    • 子类对象应该能够替换其超类对象被使用
      也就是说,如果有一个父类和一个子类,当采用子类替换父类时不应该产生错误的结果
    //反例
    class Rectangle {
      constructor() {
        this.width = 0;
        this.height = 0;
      }
    
      setColor(color) {
        // ...
      }
    
      render(area) {
        // ...
      }
    
      setWidth(width) {
        this.width = width;
      }
    
      setHeight(height) {
        this.height = height;
      }
    
      getArea() {
        return this.width * this.height;
      }
    }
    
    class Square extends Rectangle {
      constructor() {
        super();
      }
    
      setWidth(width) {
        this.width = width;
        this.height = width;
      }
    
      setHeight(height) {
        this.width = height;
        this.height = height;
      }
    }
    
    function renderLargeRectangles(rectangles) {
      rectangles.forEach((rectangle) => {
        rectangle.setWidth(4);
        rectangle.setHeight(5);
        let area = rectangle.getArea(); // BAD: Will return 25 for Square. Should be 20.
        rectangle.render(area);
      })
    }
    
    let rectangles = [new Rectangle(), new Rectangle(), new Square()];
    renderLargeRectangles(rectangles);
    //正例
    class Shape {
      constructor() {}
    
      setColor(color) {
        // ...
      }
    
      render(area) {
        // ...
      }
    }
    
    class Rectangle extends Shape {
      constructor() {
        super();
        this.width = 0;
        this.height = 0;
      }
    
      setWidth(width) {
        this.width = width;
      }
    
      setHeight(height) {
        this.height = height;
      }
    
      getArea() {
        return this.width * this.height;
      }
    }
    
    class Square extends Shape {
      constructor() {
        super();
        this.length = 0;
      }
    
      setLength(length) {
        this.length = length;
      }
    
      getArea() {
        return this.length * this.length;
      }
    }
    
    function renderLargeShapes(shapes) {
      shapes.forEach((shape) => {
        switch (shape.constructor.name) {
          case 'Square':
            shape.setLength(5);
          case 'Rectangle':
            shape.setWidth(4);
            shape.setHeight(5);
        }
    
        let area = shape.getArea();
        shape.render(area);
      })
    }
    
    let shapes = [new Rectangle(), new Rectangle(), new Square()];
    renderLargeShapes(shapes);
    
    • 接口隔离原则
      在js中 当一个类需要许多参数设置才能生成一个对象时 或许大多时候不需要设置那么多的参数 此时减少对配置参数数量的需求是有益的
    //反例
    class DOMTraverser {
      constructor(settings) {
        this.settings = settings;
        this.setup();
      }
    
      setup() {
        this.rootNode = this.settings.rootNode;
        this.animationModule.setup();
      }
    
      traverse() {
        // ...
      }
    }
    
    let $ = new DOMTraverser({
      rootNode: document.getElementsByTagName('body'),
      animationModule: function() {} // Most of the time, we won't need to animate when traversing.
      // ...
    });
    //正例
    class DOMTraverser {
      constructor(settings) {
        this.settings = settings;
        this.options = settings.options;
        this.setup();
      }
    
      setup() {
        this.rootNode = this.settings.rootNode;
        this.setupOptions();
      }
    
      setupOptions() {
        if (this.options.animationModule) {
          // ...
        }
      }
    
      traverse() {
        // ...
      }
    }
    
    let $ = new DOMTraverser({
      rootNode: document.getElementsByTagName('body'),
      options: {
        animationModule: function() {}
      }
    })
    
    • 依赖反转原则
    1. 高层模块不应该依赖于低层模块,他们都应该依赖于抽象接口
    2. 抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口
    //反例
    class InventoryTracker {
      constructor(items) {
        this.items = items;
    
        // BAD: We have created a dependency on a specific request implementation.
        // We should just have requestItems depend on a request method: `request`
        this.requester = new InventoryRequester();
      }
    
      requestItems() {
        this.items.forEach((item) => {
          this.requester.requestItem(item);
        });
      }
    }
    
    class InventoryRequester {
      constructor() {
        this.REQ_METHODS = ['HTTP'];
      }
    
      requestItem(item) {
        // ...
      }
    }
    
    let inventoryTracker = new InventoryTracker(['apples', 'bananas']);
    inventoryTracker.requestItems();
    //正例
    class InventoryTracker {
      constructor(items, requester) {
        this.items = items;
        this.requester = requester;
      }
    
      requestItems() {
        this.items.forEach((item) => {
          this.requester.requestItem(item);
        });
      }
    }
    
    class InventoryRequesterV1 {
      constructor() {
        this.REQ_METHODS = ['HTTP'];
      }
    
      requestItem(item) {
        // ...
      }
    }
    
    class InventoryRequesterV2 {
      constructor() {
        this.REQ_METHODS = ['WS'];
      }
    
      requestItem(item) {
        // ...
      }
    }
    
    // By constructing our dependencies externally and injecting them, we can easily
    // substitute our request module for a fancy new one that uses WebSockets.
    let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2());
    inventoryTracker.requestItems();
    
    • 使用es6的class而不是es5的function
      function在继承,构造和方法定义方面可读性比较差
      当需要继承时,优先选用class
      当需要更大更复杂的对象时 优先选择更小的function而非class
    //反例
    var Animal = function(age) {
        if (!(this instanceof Animal)) {
            throw new Error("Instantiate Animal with `new`");
        }
    
        this.age = age;
    };
    
    Animal.prototype.move = function() {};
    
    var Mammal = function(age, furColor) {
        if (!(this instanceof Mammal)) {
            throw new Error("Instantiate Mammal with `new`");
        }
    
        Animal.call(this, age);
        this.furColor = furColor;
    };
    
    Mammal.prototype = Object.create(Animal.prototype);
    Mammal.prototype.constructor = Mammal;
    Mammal.prototype.liveBirth = function() {};
    
    var Human = function(age, furColor, languageSpoken) {
        if (!(this instanceof Human)) {
            throw new Error("Instantiate Human with `new`");
        }
    
        Mammal.call(this, age, furColor);
        this.languageSpoken = languageSpoken;
    };
    
    Human.prototype = Object.create(Mammal.prototype);
    Human.prototype.constructor = Human;
    Human.prototype.speak = function() {};
    //正例
    class Animal {
        constructor(age) {
            this.age = age;
        }
    
        move() {}
    }
    
    class Mammal extends Animal {
        constructor(age, furColor) {
            super(age);
            this.furColor = furColor;
        }
    
        liveBirth() {}
    }
    
    class Human extends Mammal {
        constructor(age, furColor, languageSpoken) {
            super(age, furColor);
            this.languageSpoken = languageSpoken;
        }
    
        speak() {}
    }
    
    • 使用方法链
      在 class 的函数中返回 this,能够方便的将类需要执行的多个方法链接起来 实现链式调用
    //反例
    class Car {
      constructor() {
        this.make = 'Honda';
        this.model = 'Accord';
        this.color = 'white';
      }
    
      setMake(make) {
        this.name = name;
      }
    
      setModel(model) {
        this.model = model;
      }
    
      setColor(color) {
        this.color = color;
      }
    
      save() {
        console.log(this.make, this.model, this.color);
      }
    }
    
    let car = new Car();
    car.setColor('pink');
    car.setMake('Ford');
    car.setModel('F-150')
    car.save();
    //正例
    class Car {
      constructor() {
        this.make = 'Honda';
        this.model = 'Accord';
        this.color = 'white';
      }
    
      setMake(make) {
        this.name = name;
        // NOTE: Returning this for chaining
        return this;
      }
    
      setModel(model) {
        this.model = model;
        // NOTE: Returning this for chaining
        return this;
      }
    
      setColor(color) {
        this.color = color;
        // NOTE: Returning this for chaining
        return this;
      }
    
      save() {
        console.log(this.make, this.model, this.color);
      }
    }
    
    let car = new Car()
      .setColor('pink')
      .setMake('Ford')
      .setModel('F-150')
      .save();
    
    • 优先使用组合模式而非继承
      取决于需求 如果遵守以下三点 可能继承具有更大优势
    1. 继承关系表现为'是一个'而非'有一个'(如动物->人和用户->用户细节)
    2. 可以复用基类的代码('Human'可以看成是'All animal'的一种)
    3. 当希望改变基类时所有派生类都收到影响(如修改'All animial'移动时的卡路里消耗量,Human会受到影响)
    //反例
    class Employee {
      constructor(name, email) {
        this.name = name;
        this.email = email;
      }
    
      // ...
    }
    
    // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
    class EmployeeTaxData extends Employee {
      constructor(ssn, salary) {
        super();
        this.ssn = ssn;
        this.salary = salary;
      }
    
      // ...
    }
    //正例
    class Employee {
      constructor(name, email) {
        this.name = name;
        this.email = email;
    
      }
    
      setTaxData(ssn, salary) {
        this.taxData = new EmployeeTaxData(ssn, salary);
      }
      // ...
    }
    
    class EmployeeTaxData {
      constructor(ssn, salary) {
        this.ssn = ssn;
        this.salary = salary;
      }
    
      // ...
    }
    

    测试

    • 单一测试
    //反例
    const assert = require('assert');
    
    describe('MakeMomentJSGreatAgain', function() {
      it('handles date boundaries', function() {
        let date;
    
        date = new MakeMomentJSGreatAgain('1/1/2015');
        date.addDays(30);
        date.shouldEqual('1/31/2015');
    
        date = new MakeMomentJSGreatAgain('2/1/2016');
        date.addDays(28);
        assert.equal('02/29/2016', date);
    
        date = new MakeMomentJSGreatAgain('2/1/2015');
        date.addDays(28);
        assert.equal('03/01/2015', date);
      });
    });
    //正例
    const assert = require('assert');
    
    describe('MakeMomentJSGreatAgain', function() {
      it('handles 30-day months', function() {
        let date = new MakeMomentJSGreatAgain('1/1/2015');
        date.addDays(30);
        date.shouldEqual('1/31/2015');
      });
    
      it('handles leap year', function() {
        let date = new MakeMomentJSGreatAgain('2/1/2016');
        date.addDays(28);
        assert.equal('02/29/2016', date);
      });
    
      it('handles non-leap year', function() {
        let date = new MakeMomentJSGreatAgain('2/1/2015');
        date.addDays(28);
        assert.equal('03/01/2015', date);
      });
    });
    

    并发

    • 用promises代替回调
      回调不够整洁并会造成大量的嵌套
    //反例
    require('request').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', function(err, response) {
      if (err) {
        console.error(err);
      }
      else {
        require('fs').writeFile('article.html', response.body, function(err) {
          if (err) {
            console.error(err);
          } else {
            console.log('File written');
          }
        })
      }
    })
    //正例
    require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
      .then(function(response) {
        return require('fs-promise').writeFile('article.html', response);
      })
      .then(function() {
        console.log('File written');
      })
      .catch(function(err) {
        console.error(err);
      })
    
    
    • async await
      async await 更胜过promise 尽量使用async await代替promise
    //反例
    require('request-promise').get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')
      .then(function(response) {
        return require('fs-promise').writeFile('article.html', response);
      })
      .then(function() {
        console.log('File written');
      })
      .catch(function(err) {
        console.error(err);
      })
    //正例
    async function getCleanCodeArticle() {
      try {
        var request = await require('request-promise')
        var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
        var fileHandle = await require('fs-promise');
    
        await fileHandle.writeFile('article.html', response);
        console.log('File written');
      } catch(err) {
        console.log(err);
      }
    }
    
    

    错误处理

    • 捕获错误
      对捕获的错误不做任何处理是没有意义的
      代码中try/catch意味着你认为这里可能出现一些错误 应该对这些可能的错误存在相应的处理方案
    //反例
    async function getCleanCodeArticle() {
      try {
        var request = await require('request-promise')
        var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');
        var fileHandle = await require('fs-promise');
    
        await fileHandle.writeFile('article.html', response);
        console.log('File written');
      } catch(err) {
        console.log(err);
      }
    }
    //正例
    try {
      functionThatMightThrow();
    } catch (error) {
      // One option (more noisy than console.log):
      console.error(error);
      // Another option:
      notifyUserOfError(error);
      // Another option:
      reportErrorToService(error);
      // OR do all three!
    }
    
    • 不要忽略被拒绝的promise
    //反例 
    getdata()
    .then(data => {
      functionThatMightThrow(data);
    })
    .catch(error => {
      console.log(error);
    });
    //正例
    getdata()
    .then(data => {
      functionThatMightThrow(data);
    })
    .catch(error => {
      // One option (more noisy than console.log):
      console.error(error);
      // Another option:
      notifyUserOfError(error);
      // Another option:
      reportErrorToService(error);
      // OR do all three!
    });
    

    格式化

    • 大小写一致
      JS 是弱类型语言,合理的采用大小写可以告诉你关于变量/函数等的许多消息
    //反例
    var DAYS_IN_WEEK = 7;
    var daysInMonth = 30;
    
    var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
    var Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
    
    function eraseDatabase() {}
    function restore_database() {}
    
    class animal {}
    class Alpaca {}
    //正例
    var DAYS_IN_WEEK = 7;
    var DAYS_IN_MONTH = 30;
    
    var songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];
    var artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];
    
    function eraseDatabase() {}
    function restoreDatabase() {}
    
    class Animal {}
    class Alpaca {}
    
    • 调用函数 的函数和被调函数应放在较近的位置
      当函数间存在互相调用的情况时 应将两者置于较近的位置
      理想情况下 应将调用其他函数的函数写在被调用函数的上方
    //反例
    class PerformanceReview {
      constructor(employee) {
        this.employee = employee;
      }
    
      lookupPeers() {
        return db.lookup(this.employee, 'peers');
      }
    
      lookupMananger() {
        return db.lookup(this.employee, 'manager');
      }
    
      getPeerReviews() {
        let peers = this.lookupPeers();
        // ...
      }
    
      perfReview() {
          getPeerReviews();
          getManagerReview();
          getSelfReview();
      }
    
      getManagerReview() {
        let manager = this.lookupManager();
      }
    
      getSelfReview() {
        // ...
      }
    }
    
    let review = new PerformanceReview(user);
    review.perfReview();
    
    //正例
    class PerformanceReview {
      constructor(employee) {
        this.employee = employee;
      }
    
      perfReview() {
          getPeerReviews();
          getManagerReview();
          getSelfReview();
      }
    
      getPeerReviews() {
        let peers = this.lookupPeers();
        // ...
      }
    
      lookupPeers() {
        return db.lookup(this.employee, 'peers');
      }
    
      getManagerReview() {
        let manager = this.lookupManager();
      }
    
      lookupMananger() {
        return db.lookup(this.employee, 'manager');
      }
    
      getSelfReview() {
        // ...
      }
    }
    
    let review = new PerformanceReview(employee);
    review.perfReview();
    

    注释

    • 只对存在一定业务逻辑复杂性的代码进行注释
    //反例
    function hashIt(data) {
      // The hash
      var hash = 0;
    
      // Length of string
      var length = data.length;
    
      // Loop through every character in data
      for (var i = 0; i < length; i++) {
        // Get character code.
        var char = data.charCodeAt(i);
        // Make the hash
        hash = ((hash << 5) - hash) + char;
        // Convert to 32-bit integer
        hash = hash & hash;
      }
    }
    //正例
    function hashIt(data) {
      var hash = 0;
      var length = data.length;
    
      for (var i = 0; i < length; i++) {
        var char = data.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
    
        // Convert to 32-bit integer
        hash = hash & hash;
      }
    }
    
    
    
    • 删掉被注释掉的代码
    //反例
    doStuff();
    // doOtherStuff();
    // doSomeMoreStuff();
    // doSoMuchStuff();
    
    //正例
    doStuff();
    
    
    • 不需要版本更新类型注释
      我们可以使用版本控制,废代码,被注释的代码及用注释记录代码中的版本更新说明都是没有必要的
      需要时可以使用git log获取历史版本
    //反例
    /**
     * 2016-12-20: Removed monads, didn't understand them (RM)
     * 2016-10-01: Improved using special monads (JP)
     * 2016-02-03: Removed type-checking (LI)
     * 2015-03-14: Added combine with type-checking (JR)
     */
    function combine(a, b) {
      return a + b;
    }
    //正例
    function combine(a, b) {
      return a + b;
    }
    
    • 避免位置标记
      这些东西通常只能代码麻烦,采用适当的缩进就可以了
    //反例
    ////////////////////////////////////////////////////////////////////////////////
    // Scope Model Instantiation
    ////////////////////////////////////////////////////////////////////////////////
    let $scope.model = {
      menu: 'foo',
      nav: 'bar'
    };
    
    ////////////////////////////////////////////////////////////////////////////////
    // Action setup
    ////////////////////////////////////////////////////////////////////////////////
    let actions = function() {
      // ...
    }
    //正例
    let $scope.model = {
      menu: 'foo',
      nav: 'bar'
    };
    
    let actions = function() {
      // ...
    }
    
    
    • 避免在源文件中写入法律评论
      将你的license文件置于源码目录树的根目录
    //反例
    /*
    The MIT License (MIT)
    
    Copyright (c) 2016 Ryan McDermott
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE
    */
    
    function calculateBill() {
      // ...
    }
    //正例
    function calculateBill() {
      // ...
    }
    
  • 相关阅读:
    CSS高级应用参考手册
    通过ClassLoader说明容器热部署实现机制
    自定义类加载器
    zz Ubuntu常用命令大全
    设计模式之Decorator(油漆工)
    zz JNI学习(一)、JNI简介和HelloWorld示例
    类加载器的基本概念
    使用 StAX 解析 XML,第 1 部分: Streaming API for XML (StAX) 简介
    ubuntu apache mod_expires模块
    清空ubuntu 日志
  • 原文地址:https://www.cnblogs.com/hanyan99/p/14627653.html
Copyright © 2020-2023  润新知