代码规范2

测试代码质量的唯一方式:别人看你代码时说 f * k 的次数。

代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。

本文并不是代码风格指南,而是关于代码的可读性、复用性、扩展性探讨。

我们将从几个方面展开讨论:

变量

函数

对象和数据结构

SOLID

测试

异步

错误处理

代码风格

注释

变量

用有意义且常用的单词命名变量

Bad:

constyyyymmdstr = moment().format('YYYY/MM/DD');

Good:

constcurrentDate = moment().format('YYYY/MM/DD');

保持统一

可能同一个项目对于获取用户信息,会有三个不一样的命名。应该保持统一,如果你不知道该如何取名,可以去codelf 搜索,看别人是怎么取名的。

Bad:

getUserInfo();

getClientData();

getCustomerRecord();

Good:

getUser()

每个常量都该命名

可以用 buddy.js或者ESLint检测代码中未命名的常量。

Bad:

// 三个月之后你还能知道 86400000 是什么吗?

setTimeout(blastOff,86400000);

Good:

constMILLISECOND_IN_A_DAY =86400000;

setTimeout(blastOff, MILLISECOND_IN_A_DAY);

可描述

通过一个变量生成了一个新变量,也需要为这个新变量命名,也就是说每个变量当你看到他第一眼你就知道他是干什么的。

Bad:

constADDRESS ='One Infinite Loop, Cupertino 95014';

constCITY_ZIP_CODE_REGEX =/^[^,\]+[,\s]+(.+?)s*(d{5})?$/;

saveCityZipCode(ADDRESS.match(CITY_ZIP_CODE_REGEX)[1],

ADDRESS.match(CITY_ZIP_CODE_REGEX)[2]);

Good:

constADDRESS ='One Infinite Loop, Cupertino 95014';

constCITY_ZIP_CODE_REGEX =/^[^,\]+[,\s]+(.+?)s*(d{5})?$/;

const[, city, zipCode] = ADDRESS.match(CITY_ZIP_CODE_REGEX) || [];

saveCityZipCode(city, zipCode);

直接了当

Bad:

const locations = ['Austin', 'New York', 'San Francisco'];

locations.forEach((l) => {

  doStuff();

  doSomeOtherStuff();

  // ...

  // ...

  // ...

  // 需要看其他代码才能确定 'l' 是干什么的。

  dispatch(l);

});

Good:

const locations = ['Austin', 'New York', 'San Francisco'];

locations.forEach((location) => {

  doStuff();

  doSomeOtherStuff();

  // ...

  // ...

  // ...

  dispatch(location);

});

避免无意义的前缀

如果创建了一个对象 car,就没有必要把它的颜色命名为 carColor。

Bad:

const car = {

    carMake: 'Honda',

    carModel: 'Accord',

    carColor: 'Blue'

  };

  functionpaintCar(car){

    car.carColor = 'Red';

  }

Good:

const car = {

  make: 'Honda',

  model: 'Accord',

  color: 'Blue'

};

functionpaintCar(car){

  car.color = 'Red';

}

使用默认值

Bad:

functioncreateMicrobrewery(name){

  const breweryName = name || 'Hipster Brew Co.';

  // ...

}


Good:

functioncreateMicrobrewery(name ='Hipster Brew Co.'){

  // ...

}

函数


参数越少越好

如果参数超过两个,使用 ES2015/ES6 的解构语法,不用考虑参数的顺序。

Bad:

functioncreateMenu(title, body, buttonText, cancellable){

  // ...

}

Good:

function  createMenu({ title, body, buttonText, cancellable }){

  // ...

}

createMenu({

  title: 'Foo',

  body: 'Bar',

  buttonText: 'Baz',

  cancellable: true

});

只做一件事情

这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。

Bad:

functionemailClients(clients){

      clients.forEach((client) =>{

                     constclientRecord = database.lookup(client);

                    if(clientRecord.isActive()) {

                          email(client);

                    }

    });

}

Good:

functionemailActiveClients(clients){

        clients

                 .filter(isActiveClient)

                .forEach(email);

}

functionisActiveClient(client){

             const  clientRecord = database.lookup(client);

             return  clientRecord.isActive();

}

顾名思义

看函数名就应该知道它是干啥的。

Bad:

functionaddToDate(date, month){

// ...

}

constdate =newDate();

// 很难知道是把什么加到日期中

addToDate(date,1);

Good:

functionaddMonthToDate(month, date){

// ...

}

constdate =newDate();

addMonthToDate(1, date);

只需要一层抽象层

如果函数嵌套过多会导致很难复用以及测试。

Bad:

functionparseBetterJSAlternative(code){

  const REGEXES = [

    // ...

  ];

  const statements = code.split(' ');

  const tokens = [];

  REGEXES.forEach((REGEX) => {

    statements.forEach((statement) => {

      // ...

    });

  });

  const ast = [];

  tokens.forEach((token) => {

    // lex...

  });

  ast.forEach((node) => {

    // parse...

  });

}

Good:

function  parseBetterJSAlternative(code){

  const tokens = tokenize(code);

  const ast = lexer(tokens);

  ast.forEach((node) => {

    // parse...

  });

}

function tokenize(code){

  const REGEXES = [

    // ...

  ];

  const statements = code.split(' ');

  const tokens = [];

  REGEXES.forEach((REGEX) => {

    statements.forEach((statement) => {

      tokens.push( /* ... */ );

    });

  });

  return tokens;

}

functionlexer(tokens){

  const ast = [];

  tokens.forEach((token) => {

    ast.push( /* ... */ );

  });

  return ast;

}

删除重复代码

很多时候虽然是同一个功能,但由于一两个不同点,让你不得不写两个几乎相同的函数。

要想优化重复代码需要有较强的抽象能力,错误的抽象还不如重复代码。所以在抽象过程中必须要遵循 SOLID 原则(SOLID 是什么?稍后会详细介绍)。

Bad:

functionshowDeveloperList(developers){

  developers.forEach((developer) => {

    const expectedSalary = developer.calculateExpectedSalary();

    const experience = developer.getExperience();

    const githubLink = developer.getGithubLink();

    const data = {

      expectedSalary,

      experience,

      githubLink

    };

    render(data);

  });

}

functionshowManagerList(managers){

  managers.forEach((manager) => {

    const expectedSalary = manager.calculateExpectedSalary();

    const experience = manager.getExperience();

    const portfolio = manager.getMBAProjects();

    const data = {

      expectedSalary,

      experience,

      portfolio

    };

    render(data);

  });

}


Good:

functionshowEmployeeList(employees){

  employees.forEach(employee=> {

    const expectedSalary = employee.calculateExpectedSalary();

    const experience = employee.getExperience();

    const data = {

      expectedSalary,

      experience,

    };

    switch(employee.type) {

      case 'develop':

        data.githubLink = employee.getGithubLink();

        break

      case 'manager':

        data.portfolio = employee.getMBAProjects();

        break

    }

    render(data);

  })

}

对象设置默认属性

Bad:

const menuConfig = {

  title: null,

  body: 'Bar',

  buttonText: null,

  cancellable: true

};

functioncreateMenu(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);

Good:

const menuConfig = {

  title: 'Order',

  // 'body' key 缺失

  buttonText: 'Send',

  cancellable: true

};

function createMenu(config) {

  config = Object.assign({

    title: 'Foo',

    body: 'Bar',

    buttonText: 'Baz',

    cancellable: true

  }, config);

  // config 就变成了: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}

  // ...

}

createMenu(menuConfig);

不要传 flag 参数

通过 flag 的 true 或 false,来判断执行逻辑,违反了一个函数干一件事的原则。

Bad:

functioncreateFile(name, temp){

  if (temp) {

    fs.create(`./temp/${name}`);

  } else {

    fs.create(name);

  }

}

Good:

functioncreateFile(name){

  fs.create(name);

}

functioncreateFileTemplate(name){

  createFile(`./temp/${name}`)

}

避免副作用(第一部分)

函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。

当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。

副作用的三大天坑:随意修改可变数据类型、随意分享没有数据结构的状态、没有在统一地方处理副作用。

Bad:

// 全局变量被一个函数引用

// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。

var name = 'Ryan McDermott';

functionsplitIntoFirstAndLastName(){

  name = name.split(' ');

}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Good:

var name = 'Ryan McDermott';

var newName = splitIntoFirstAndLastName(name)

functionsplitIntoFirstAndLastName(name){

  return name.split(' ');

}

console.log(name); // 'Ryan McDermott';

console.log(newName); // ['Ryan', 'McDermott'];

避免副作用(第二部分)

在 JavaScript 中,基本类型通过赋值传递,对象和数组通过引用传递。以引用传递为例:

假如我们写一个购物车,通过 addItemToCart() 方法添加商品到购物车,修改 购物车数组。此时调用 purchase() 方法购买,由于引用传递,获取的 购物车数组 正好是最新的数据。

看起来没问题对不对?

如果当用户点击购买时,网络出现故障, purchase() 方法一直在重复调用,与此同时用户又添加了新的商品,这时网络又恢复了。那么 purchase() 方法获取到 购物车数组 就是错误的。

为了避免这种问题,我们需要在每次新增商品时,克隆 购物车数组 并返回新的数组。

Bad:

constaddItemToCart =(cart, item) =>{

     cart.push({ item,date:Date.now() });

};

Good:

constaddItemToCart =(cart, item) =>{

     return[...cart, {item,date:Date.now()}]

};

不要写全局方法

在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype 上新增一个 diff 方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff 方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array 进行扩展。

Bad:

Array.prototype.diff = functiondiff(comparisonArray){

  const hash = new Set(comparisonArray);

  return this.filter(elem=> !hash.has(elem));

};

Good:

class SuperArray extends  Array{

  diff(comparisonArray) {

    const hash = new Set(comparisonArray);

    return this.filter(elem=> !hash.has(elem));        

  }

}

比起命令式我更喜欢函数式编程

函数式变编程可以让代码的逻辑更清晰更优雅,方便测试。

Bad:

const programmerOutput = [

  {

    name: 'Uncle Bobby',

    linesOfCode: 500

  }, {

    name: 'Suzie Q',

    linesOfCode: 1500

  }, {

    name: 'Jimmy Gosling',

    linesOfCode: 150

  }, {

    name: 'Gracie Hopper',

    linesOfCode: 1000

  }

];

let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {

  totalOutput += programmerOutput[i].linesOfCode;

}

Good:

const programmerOutput = [

  {

    name: 'Uncle Bobby',

    linesOfCode: 500

  }, {

    name: 'Suzie Q',

    linesOfCode: 1500

  }, {

    name: 'Jimmy Gosling',

    linesOfCode: 150

  }, {

    name: 'Gracie Hopper',

    linesOfCode: 1000

  }

];

let totalOutput = programmerOutput

  .map(output=> output.linesOfCode)

  .reduce((totalLines, lines) => totalLines + lines, 0)

封装条件语句

Bad:

if(fsm.state ==='fetching'&& isEmpty(listNode)) {

// ...

}

Good:

functionshouldShowSpinner(fsm, listNode){

        returnfsm.state ==='fetching'&& isEmpty(listNode);

}

if(shouldShowSpinner(fsmInstance, listNodeInstance)) {

// ...

}

尽量别用“非”条件句

Bad:

functionisDOMNodeNotPresent(node){

// ...

}

if(!isDOMNodeNotPresent(node)) {

// ...

}

Good:

functionisDOMNodePresent(node){

// ...

}

if(isDOMNodePresent(node)) {

// ...

}

避免使用条件语句

Q:不用条件语句写代码是不可能的。

A:绝大多数场景可以用多态替代。

Q:用多态可行,但为什么就不能用条件语句了呢?

A:为了让代码更简洁易读,如果你的函数中出现了条件判断,那么说明你的函数不止干了一件事情,违反了函数单一原则。

Bad:

classAirplane{

  // ...

  // 获取巡航高度

  getCruisingAltitude() {

    switch (this.type) {

      case '777':

        return this.getMaxAltitude() - this.getPassengerCount();

      case 'Air Force One':

        return this.getMaxAltitude();

      case 'Cessna':

        return this.getMaxAltitude() - this.getFuelExpenditure();

    }

  }

}

Good:

classAirplane{

// ...

// 获取巡航高度

getCruisingAltitude() {

        switch(this.type) {

                case'777':

                            returnthis.getMaxAltitude() -this.getPassengerCount();

                case'Air Force One':

                            returnthis.getMaxAltitude();

               case'Cessna':

                            returnthis.getMaxAltitude() -this.getFuelExpenditure();

           }

    }

}

Good:

classAirplane{

// ...

}

// 波音777

classBoeing777extendsAirplane{

// ...

getCruisingAltitude() {

returnthis.getMaxAltitude() -this.getPassengerCount();

}

}

// 空军一号

classAirForceOneextendsAirplane{

// ...

getCruisingAltitude() {

returnthis.getMaxAltitude();

}

}

// 赛纳斯飞机

classCessnaextendsAirplane{

// ...

getCruisingAltitude() {

returnthis.getMaxAltitude() -this.getFuelExpenditure();

}

}

避免类型检查(第一部分)

JavaScript 是无类型的,意味着你可以传任意类型参数,这种自由度很容易让人困扰,不自觉的就会去检查类型。仔细想想是你真的需要检查类型还是你的 API 设计有问题?

Bad:

functiontravelToTexas(vehicle){

  if (vehicle instanceof Bicycle) {

    vehicle.pedal(this.currentLocation, new Location('texas'));

  } else if (vehicle instanceof Car) {

    vehicle.drive(this.currentLocation, new Location('texas'));

  }

}

Good:

functiontravelToTexas(vehicle){

  vehicle.move(this.currentLocation, new Location('texas'));

}

避免类型检查(第二部分)

如果你需要做静态类型检查,比如字符串、整数等,推荐使用 TypeScript,不然你的代码会变得又臭又长。

Bad:

function  combine(val1, val2){

  if (typeof val1 === 'number' && typeof val2 === 'number' ||

      typeof val1 === 'string' && typeof val2 === 'string') {

    return val1 + val2;

  }

  throw new Error('Must be of type String or Number');

}

Good:

function   combine(val1, val2){

        return   val1 + val2;

}

不要过度优化

现代浏览器已经在底层做了很多优化,过去的很多优化方案都是无效的,会浪费你的时间,想知道现代浏览器优化了哪些内容,请点这里。

Bad:

// 在老的浏览器中,由于 `list.length` 没有做缓存,每次迭代都会去计算,造成不必要开销。

// 现代浏览器已对此做了优化。

for(leti =0, len = list.length; i < len; i++) {

// ...

}

Good:

for(leti =0; i < list.length; i++) {

// ...

}

删除弃用代码

很多时候有些代码已经没有用了,但担心以后会用,舍不得删。

如果你忘了这件事,这些代码就永远存在那里了。

放心删吧,你可以在代码库历史版本中找他它。

Bad:

functionoldRequestModule(url){

// ...

}

functionnewRequestModule(url){

// ...

}

constreq = newRequestModule;

inventoryTracker('apples', req,'www.inventory-awesome.io');

Good:

functionnewRequestModule(url){

// ...

}

constreq = newRequestModule;

inventoryTracker('apples', req,'www.inventory-awesome.io');

对象和数据结构


用 get、set 方法操作数据

这样做可以带来很多好处,比如在操作数据时打日志,方便跟踪错误;在 set 的时候很容易对数据进行校验…

Bad:

functionmakeBankAccount(){

  // ...

  return {

    balance: 0,

    // ...

  };

}

const account = makeBankAccount();

account.balance = 100;

Good:

functionmakeBankAccount(){

  // 私有变量

  let balance = 0;

  functiongetBalance(){

    return balance;

  }

  functionsetBalance(amount){

    // ... 在更新 balance 前,对 amount 进行校验

    balance = amount;

  }

  return {

    // ...

    getBalance,

    setBalance,

  };

}

const account = makeBankAccount();

account.setBalance(100);

使用私有变量

可以用闭包来创建私有变量

Bad:

const Employee = function(name){

  this.name = name;

};

Employee.prototype.getName = functiongetName(){

  return this.name;

};

const 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

Good:

functionmakeEmployee(name){

  return {

    getName() {

      return name;

    },

  };

}

const employee = makeEmployee('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

在 ES2015/ES6 之前,没有类的语法,只能用构造函数的方式模拟类,可读性非常差。

Bad:

// 动物

const Animal = function(age){

  if (!(this instanceof Animal)) {

    throw new Error('Instantiate Animal with `new`');

  }

  this.age = age;

};

Animal.prototype.move = functionmove(){};

// 哺乳动物

const 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 liveBirth(){};

// 人类

const 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 = functionspeak(){};

Good:

// 动物

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() {};

}

链式调用

这种模式相当有用,可以在很多库中发现它的身影,比如 jQuery、Lodash 等。它让你的代码简洁优雅。实现起来也非常简单,在类的方法最后返回 this 可以了。

Bad:

class Car{

  constructor(make, model, color) {

    this.make = make;

    this.model = model;

    this.color = color;

  }

  setMake(make) {

    this.make = make;

  }

  setModel(model) {

    this.model = model;

  }

  setColor(color) {

    this.color = color;

  }

  save() {

    console.log(this.make, this.model, this.color);

  }

}

const car = new Car('Ford','F-150','red');

car.setColor('pink');

car.save();

Good:

class Car{

  constructor(make, model, color) {

    this.make = make;

    this.model = model;

    this.color = color;

  }

  setMake(make) {

    this.make = make;

    return this;

  }

  setModel(model) {

    this.model = model;

    return this;

  }

  setColor(color) {

    this.color = color;

    return this;

  }

  save() {

    console.log(this.make, this.model, this.color);

    return this;

  }

}

const car = new Car('Ford','F-150','red')

  .setColor('pink');

  .save();

不要滥用继承

很多时候继承被滥用,导致可读性很差,要搞清楚两个类之间的关系,继承表达的一个属于关系,而不是包含关系,比如 Human->Animal vs. User->UserDetails

Bad:

classEmployee{

  constructor(name, email) {

    this.name = name;

    this.email = email;

  }

  // ...

}

// TaxData(税收信息)并不是属于 Employee(雇员),而是包含关系。

classEmployeeTaxDataextendsEmployee{

  constructor(ssn, salary) {

    super();

    this.ssn = ssn;

    this.salary = salary;

  }

  // ...

}

Good:

classEmployeeTaxData{

  constructor(ssn, salary) {

    this.ssn = ssn;

    this.salary = salary;

  }

  // ...

}

classEmployee{

  constructor(name, email) {

    this.name = name;

    this.email = email;

  }

  setTaxData(ssn, salary) {

    this.taxData = new EmployeeTaxData(ssn, salary);

  }

  // ...

}

SOLID


SOLID 是几个单词首字母组合而来,分别表示 单一功能原则、开闭原则、里氏替换原则、接口隔离原则以及依赖反转原则。

单一功能原则

如果一个类干的事情太多太杂,会导致后期很难维护。我们应该厘清职责,各司其职减少相互之间依赖。

Bad:

classUserSettings{

  constructor(user) {

    this.user = user;

  }

  changeSettings(settings) {

    if (this.verifyCredentials()) {

      // ...

    }

  }

  verifyCredentials() {

    // ...

  }

}

Good:

class  UserAuth{

  constructor(user) {

    this.user = user;

  }

  verifyCredentials() {

    // ...

  }

}

classUserSetting{

  constructor(user) {

    this.user = user;

    this.auth = new UserAuth(this.user);

  }

  changeSettings(settings) {

    if (this.auth.verifyCredentials()) {

      // ...

    }

  }

}

}

开闭原则

“开”指的就是类、模块、函数都应该具有可扩展性,“闭”指的是它们不应该被修改。也就是说你可以新增功能但不能去修改源码。

Bad:

class AjaxAdapterextendsAdapter{

  constructor() {

    super();

    this.name = 'ajaxAdapter';

  }

}

class NodeAdapterextendsAdapter{

  constructor() {

    super();

    this.name = 'nodeAdapter';

  }

}

class HttpRequester{

  constructor(adapter) {

    this.adapter = adapter;

  }

  fetch(url) {

    if (this.adapter.name === 'ajaxAdapter') {

      return makeAjaxCall(url).then((response) => {

        // 传递 response 并 return

      });

    } else if (this.adapter.name === 'httpNodeAdapter') {

      return makeHttpCall(url).then((response) => {

        // 传递 response 并 return

      });

    }

  }

}

functionmakeAjaxCall(url){

  // 处理 request 并 return promise

}

functionmakeHttpCall(url){

  // 处理 request 并 return promise

}

Good:

class AjaxAdapterextendsAdapter{

  constructor() {

    super();

    this.name = 'ajaxAdapter';

  }

  request(url) {

    // 处理 request 并 return promise

  }

}

class NodeAdapterextendsAdapter{

  constructor() {

    super();

    this.name = 'nodeAdapter';

  }

  request(url) {

    // 处理 request 并 return promise

  }

}

class HttpRequester{

  constructor(adapter) {

    this.adapter = adapter;

  }

  fetch(url) {

    return this.adapter.request(url).then((response) => {

      // 传递 response 并 return

    });

  }

}

里氏替换原则

名字很唬人,其实道理很简单,就是子类不要去重写父类的方法。

Bad:

// 长方形

classRectangle{

  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;

  }

}

// 正方形

classSquareextendsRectangle{

  setWidth(width) {

    this.width = width;

    this.height = width;

  }

  setHeight(height) {

    this.width = height;

    this.height = height;

  }

}

functionrenderLargeRectangles(rectangles){

  rectangles.forEach((rectangle) => {

    rectangle.setWidth(4);

    rectangle.setHeight(5);

    const area = rectangle.getArea(); 

    rectangle.render(area);

  });

}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];

renderLargeRectangles(rectangles);

Good:

classShape{

  setColor(color) {

    // ...

  }

  render(area) {

    // ...

  }

}

classRectangleextendsShape{

  constructor(width, height) {

    super();

    this.width = width;

    this.height = height;

  }

  getArea() {

    return this.width * this.height;

  }

}

classSquareextendsShape{

  constructor(length) {

    super();

    this.length = length;

  }

  getArea() {

    return this.length * this.length;

  }

}

functionrenderLargeShapes(shapes){

  shapes.forEach((shape) => {

    const area = shape.getArea();

    shape.render(area);

  });

}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];

renderLargeShapes(shapes);

接口隔离原则

JavaScript 几乎没有接口的概念,所以这条原则很少被使用。官方定义是“客户端不应该依赖它不需要的接口”,也就是接口最小化,把接口解耦。

Bad:

classDOMTraverser{

  constructor(settings) {

    this.settings = settings;

    this.setup();

  }

  setup() {

    this.rootNode = this.settings.rootNode;

    this.animationModule.setup();

  }

  traverse() {

    // ...

  }

}

const $ = new DOMTraverser({

  rootNode: document.getElementsByTagName('body'),

  animationModule() {} // Most of the time, we won't need to animate when traversing.

  // ...

});

Good:

classDOMTraverser{

  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() {

    // ...

  }

}

const $ = new DOMTraverser({

  rootNode: document.getElementsByTagName('body'),

  options: {

    animationModule() {}

  }

});

依赖反转原则

说就两点:

高层次模块不能依赖低层次模块,它们依赖于抽象接口。

抽象接口不能依赖具体实现,具体实现依赖抽象接口。

总结下来就两个字,解耦。

Bad:

// 库存查询

classInventoryRequester{

  constructor() {

    this.REQ_METHODS = ['HTTP'];

  }

  requestItem(item) {

    // ...

  }

}

// 库存跟踪

classInventoryTracker{

  constructor(items) {

    this.items = items;

    // 这里依赖一个特殊的请求类,其实我们只是需要一个请求方法。

    this.requester = new InventoryRequester();

  }

  requestItems() {

    this.items.forEach((item) => {

      this.requester.requestItem(item);

    });

  }

}

const inventoryTracker = new InventoryTracker(['apples', 'bananas']);

inventoryTracker.requestItems();

Good:

// 库存跟踪

classInventoryTracker{

  constructor(items, requester) {

    this.items = items;

    this.requester = requester;

  }

  requestItems() {

    this.items.forEach((item) => {

      this.requester.requestItem(item);

    });

  }

}

// HTTP 请求

classInventoryRequesterHTTP{

  constructor() {

    this.REQ_METHODS = ['HTTP'];

  }

  requestItem(item) {

    // ...

  }

}

// webSocket 请求

classInventoryRequesterWS{

  constructor() {

    this.REQ_METHODS = ['WS'];

  }

  requestItem(item) {

    // ...

  }

}

// 通过依赖注入的方式将请求模块解耦,这样我们就可以很轻易的替换成 webSocket 请求。

const inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterHTTP());

inventoryTracker.requestItems();

测试


随着项目变得越来越庞大,时间线拉长,有的老代码可能半年都没碰过,如果此时上线,你有信心这部分代码能正常工作吗?测试的覆盖率和你的信心是成正比的。

PS: 如果你发现你的代码很难被测试,那么你应该优化你的代码了。

单一化

Bad:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {

  it('handles date boundaries', () => {

    let date;

    date = new MakeMomentJSGreatAgain('1/1/2015');

    date.addDays(30);

    assert.equal('1/31/2015', date);

    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);

  });

});

Good:

import assert from 'assert';

describe('MakeMomentJSGreatAgain', () => {

  it('handles 30-day months', () => {

    const date = new MakeMomentJSGreatAgain('1/1/2015');

    date.addDays(30);

    assert.equal('1/31/2015', date);

  });

  it('handles leap year', () => {

    const date = new MakeMomentJSGreatAgain('2/1/2016');

    date.addDays(28);

    assert.equal('02/29/2016', date);

  });

  it('handles non-leap year', () => {

    const date = new MakeMomentJSGreatAgain('2/1/2015');

    date.addDays(28);

    assert.equal('03/01/2015', date);

  });

});

异步

不再使用回调

不会有人愿意去看嵌套回调的代码,用 Promises 替代回调吧。

Bad:

import { get } from 'request';

import { writeFile } from 'fs';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response) => {

  if (requestErr) {

    console.error(requestErr);

  } else {

    writeFile('article.html', response.body, (writeErr) => {

      if (writeErr) {

        console.error(writeErr);

      } else {

        console.log('File written');

      }

    });

  }

});

Good:

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')

  .then((response) => {

    return writeFile('article.html', response);

  })

  .then(()=> {

    console.log('File written');

  })

  .catch((err) => {

    console.error(err);

  });

Async/Await 比起 Promises 更简洁

Bad:

import { get } from 'request-promise';

import { writeFile } from 'fs-promise';

get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin')

  .then((response) => {

    return writeFile('article.html', response);

  })

  .then(()=> {

    console.log('File written');

  })

  .catch((err) => {

    console.error(err);

  });

Good:

import{ get }from'request-promise';

import{ writeFile }from'fs-promise';

asyncfunctiongetCleanCodeArticle(){

try{

constresponse =awaitget('https://en.wikipedia.org/wiki/Robert_Cecil_Martin');

awaitwriteFile('article.html', response);

console.log('File written');

}catch(err) {

console.error(err);

}

}

错误处理


不要忽略抛异常

Bad:

try {

  functionThatMightThrow();

} catch (error) {

  console.log(error);

}

Good:

try {

  functionThatMightThrow();

} catch (error) {

  // 这一种选择,比起 console.log 更直观

  console.error(error);

  // 也可以在界面上提醒用户

  notifyUserOfError(error);

  // 也可以把异常传回服务器

  reportErrorToService(error);

  // 其他的自定义方法

}

不要忘了在 Promises 抛异常

Bad:

getdata()

  .then((data) => {

    functionThatMightThrow(data);

  })

  .catch((error) => {

    console.log(error);

  });

Good:

getdata()

  .then((data) => {

    functionThatMightThrow(data);

  })

  .catch((error) => {

    // 这一种选择,比起 console.log 更直观

    console.error(error);

    // 也可以在界面上提醒用户

    notifyUserOfError(error);

    // 也可以把异常传回服务器

    reportErrorToService(error);

    // 其他的自定义方法

  });

代码风格


代码风格是主观的,争论哪种好哪种不好是在浪费生命。市面上有很多自动处理代码风格的工具,选一个喜欢就行了,我们来讨论几个非自动处理的部分。

常量大写

Bad:

const DAYS_IN_WEEK = 7;

const daysInMonth = 30;

const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];

const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles'];

functioneraseDatabase(){}

functionrestore_database(){}

classanimal{}

classAlpaca{}

Good:

const DAYS_IN_WEEK = 7;

const DAYS_IN_MONTH = 30;

const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude'];

const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles'];

functioneraseDatabase(){}

functionrestoreDatabase(){}

classAnimal{}

classAlpaca{}

先声明后调用

就像我们看报纸文章一样,从上到下看,所以为了方便阅读把函数声明写在函数调用前面。

Bad:

classPerformanceReview{

  constructor(employee) {

    this.employee = employee;

  }

  lookupPeers() {

    return db.lookup(this.employee, 'peers');

  }

  lookupManager() {

    return db.lookup(this.employee, 'manager');

  }

  getPeerReviews() {

    const peers = this.lookupPeers();

    // ...

  }

  perfReview() {

    this.getPeerReviews();

    this.getManagerReview();

    this.getSelfReview();

  }

  getManagerReview() {

    const manager = this.lookupManager();

  }

  getSelfReview() {

    // ...

  }

}

const review = new PerformanceReview(employee);

review.perfReview();

Good:

classPerformanceReview{

  constructor(employee) {

    this.employee = employee;

  }

  perfReview() {

    this.getPeerReviews();

    this.getManagerReview();

    this.getSelfReview();

  }

  getPeerReviews() {

    const peers = this.lookupPeers();

    // ...

  }

  lookupPeers() {

    return db.lookup(this.employee, 'peers');

  }

  getManagerReview() {

    const manager = this.lookupManager();

  }

  lookupManager() {

    return db.lookup(this.employee, 'manager');

  }

  getSelfReview() {

    // ...

  }

}

const review = new PerformanceReview(employee);

review.perfReview();

注释

只有业务逻辑需要注释

代码注释不是越多越好。

Bad:

functionhashIt(data){

  // 这是初始值

  let hash = 0;

  // 数组的长度

  const length = data.length;

  // 循环数组

  for (let i = 0; i < length; i++) {

    // 获取字符代码

    const char = data.charCodeAt(i);

    // 修改 hash

    hash = ((hash << 5) - hash) + char;

    // 转换为32位整数

    hash &= hash;

  }

}

Good:

functionhashIt(data){

  let hash = 0;

  const length = data.length;

  for (let i = 0; i < length; i++) {

    const char = data.charCodeAt(i);

    hash = ((hash << 5) - hash) + char;

    // 转换为32位整数

    hash &= hash;

  }

}

删掉注释的代码

git 存在的意义就是保存你的旧代码,所以注释的代码赶紧删掉吧。

Bad:

doStuff();

// doOtherStuff();

// doSomeMoreStuff();

// doSoMuchStuff();

Good:

doStuff();


javascript

不要记日记

记住你有 git!,git log 可以帮你干这事。

Bad:

/**

* 2016-12-20: 删除了 xxx

* 2016-10-01: 改进了 xxx

* 2016-02-03: 删除了第12行的类型检查

* 2015-03-14: 增加了一个合并的方法

*/

functioncombine(a, b){

returna + b;

}

Good:

functioncombine(a, b){

returna + b;

}

注释不需要高亮

注释高亮,并不能起到提示的作用,反而会干扰你阅读代码。

Bad:

////////////////////////////////////////////////////////////////////////////////

// Scope Model Instantiation

////////////////////////////////////////////////////////////////////////////////

$scope.model = {

menu:'foo',

nav:'bar'

};

////////////////////////////////////////////////////////////////////////////////

// Action setup

////////////////////////////////////////////////////////////////////////////////

constactions =function(){

// ...

};

Good:

$scope.model = {

menu:'foo',

nav:'bar'

};

constactions =function(){

// ...

};

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

推荐阅读更多精彩内容

  • 现在写代码比以前好多了,代码的格式都有eslint,prettier,babel(写新版语法)这些来保证,然而,技...
    零星小雨_c84a阅读 358评论 0 0
  • 1 Array.includes 与条件判断 一般我们判断或用 || // condition function ...
    零星小雨_c84a阅读 167评论 0 0
  • css相关骚操作: 1./* 隐藏scroll的滚动条 */ ::-webkit-scrollbar{ width...
    ok第一阅读 138评论 0 0
  • 当时我去你家玩了几天我们一起坐车去成都,我回家。你去学校补课,当时你说你想睡觉。我就在你睡着了偷偷的拍了你的丑...
    chafferer111阅读 272评论 0 0
  • 昨日邻居大爷对着刚放暑假久坐家中的我说:“年轻人不要天天呆在家里,多出去走走。” 今日我一气之下就跑去了网吧。 刚...
    我嘴巴真大阅读 365评论 5 2