--- nav: title: 前端 path: /fea group: title: 书写规范 order: 4 path: /norms --- # 书写规范 ### 使用语义化的命名 **Bad:** ```js const yyyymmdstr = moment().format('YYYY/MM/DD'); ``` **Good:** ```js const currentDate = moment().format('YYYY/MM/DD'); ``` ### 对相同类型的变量使用相同的词汇表 **Bad:** ```javascript getUserInfo(); getClientData(); getCustomerRecord(); ``` **Good:** ```javascript getUser(); ``` ### 静态变量用全大写定义好 **Bad:** ```javascript // 86400000 是啥意思? setTimeout(blastOff, 86400000); ``` **Good:** ```javascript // 说明这个时间的定义原因 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000; setTimeout(blastOff, MILLISECONDS_PER_DAY); ``` ## **函数** ### 函数名应该说明它们的作用 **Bad:** ```javascript function addToDate(date, month) { // ... } const date = new Date(); // It's hard to tell from the function name what is added addToDate(date, 1); ``` **Good:** ```javascript function addMonthToDate(month, date) { // ... } const date = new Date(); addMonthToDate(1, date); ``` ### 函数应该只是一个抽象层次 当你有一个以上的抽象层次时,你的函数通常做得太多了。拆分功能可以带来可重用性和更容易的测试。 **Bad:** ```javascript function parseBetterJSAlternative(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:** ```javascript function parseBetterJSAlternative(code) { const tokens = tokenize(code); const syntaxTree = parse(tokens); syntaxTree.forEach((node) => { // parse... }); } function tokenize(code) { const REGEXES = [ // ... ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((REGEX) => { statements.forEach((statement) => { tokens.push(/* ... */); }); }); return tokens; } function parse(tokens) { const syntaxTree = []; tokens.forEach((token) => { syntaxTree.push(/* ... */); }); return syntaxTree; } ``` ### 删除重复的代码 尽最大努力避免重复代码。 重复代码是不好的,因为这意味着如果您需要更改某些逻辑,有不止一个地方可以更改某些内容。 想象一下,如果你经营一家餐厅并跟踪您的库存:所有的西红柿、洋葱、大蒜、香料等。如果有多个清单,那么当提供带有西红柿的菜肴时,所有清单都必须更新一遍。 但如果只有一个列表,只需要更新一个地方。 **Bad:** ```javascript function showDeveloperList(developers) { developers.forEach((developer) => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink, }; render(data); }); } function showManagerList(managers) { managers.forEach((manager) => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio, }; render(data); }); } ``` **Good:** ```javascript function showEmployeeList(employees) { employees.forEach((employee) => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); const data = { expectedSalary, experience, }; switch (employee.type) { case 'manager': data.portfolio = employee.getMBAProjects(); break; case 'developer': data.githubLink = employee.getGithubLink(); break; } render(data); }); } ``` ### 使用 Object.assign 设置默认对象 **Bad:** ```javascript const 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); ``` **Good:** ```javascript const menuConfig = { title: 'Order', // User did not include 'body' key buttonText: 'Send', cancellable: true, }; function createMenu(config) { let finalConfig = Object.assign( { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true, }, config, ); return finalConfig; // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true} // ... } createMenu(menuConfig); ``` ### 不要使用标志作为函数参数 标志告诉用户这个函数做不止一件事。函数应该只做一件事。如果函数基于布尔值执行不同的代码逻辑,则应该将它们分割开来。 **Bad:** ```javascript function createFile(name, temp) { if (temp) { fs.create(`./temp/${name}`); } else { fs.create(name); } } ``` **Good:** ```javascript function createFile(name) { fs.create(name); } function createTempFile(name) { createFile(`./temp/${name}`); } ``` ### 避免副作用(1) **Bad:** ```javascript let name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott']; ``` **Good:** ```javascript function splitIntoFirstAndLastName(name) { return name.split(' '); } const name = 'Ryan McDermott'; const newName = splitIntoFirstAndLastName(name); console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott']; ``` ### 避免副作用(2) **Bad:** ```javascript const addItemToCart = (cart, item) => { cart.push({ item, date: Date.now() }); }; ``` **Good:** ```javascript const addItemToCart = (cart, item) => { return [...cart, { item, date: Date.now() }]; }; ``` ### 不要更改全局函数 **Bad:** ```javascript Array.prototype.diff = function diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter((elem) => !hash.has(elem)); }; ``` **Good:** ```javascript class SuperArray extends Array { diff(comparisonArray) { const hash = new Set(comparisonArray); return this.filter((elem) => !hash.has(elem)); } } ``` ### 多用函数式编程 JavaScript 不像 Haskell 那样是一种函数式语言,但它具有函数式风格。函数式语言更简洁,更容易测试。如果可以的话,尽量使用这种编程风格。 **Bad:** ```javascript 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:** ```javascript const programmerOutput = [ { name: 'Uncle Bobby', linesOfCode: 500, }, { name: 'Suzie Q', linesOfCode: 1500, }, { name: 'Jimmy Gosling', linesOfCode: 150, }, { name: 'Gracie Hopper', linesOfCode: 1000, }, ]; const totalOutput = programmerOutput.reduce( (totalLines, output) => totalLines + output.linesOfCode, 0, ); ``` ### 封装条件 **Bad:** ```javascript if (fsm.state === 'fetching' && isEmpty(listNode)) { // ... } ``` **Good:** ```javascript function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode); } if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ... } ``` ### 避免条件 这似乎是一个不可能完成的任务。大多数人一听到这句话就会说:“如果没有 if 语句,我该怎么做呢?”答案是,您可以在许多情况下使用多态性来实现相同的任务。第二个问题通常是,"这很好,但我为什么要这么做?"答案是我们以前学到的一个干净的代码概念:一个函数应该只做一件事。当你有带有 if 语句的类和函数时,你是在告诉用户你的函数做不止一件事。记住,只做一件事。 **Bad:** ```javascript class Airplane { // ... 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:** ```javascript class Airplane { // ... } class Boeing777 extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getPassengerCount(); } } class AirForceOne extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude(); } } class Cessna extends Airplane { // ... getCruisingAltitude() { return this.getMaxAltitude() - this.getFuelExpenditure(); } } ``` ### 避免类型检查 (part 1) JavaScript 是无类型的,这意味着函数可以接受任何类型的参数。有时,您会被这种自由所困扰,并忍不住在函数中进行类型检查。有很多方法可以避免这样做。首先要考虑的是一致的 api。 **Bad:** ```javascript function travelToTexas(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:** ```javascript function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas')); } ``` ### 避免类型检查 (part 2) 如果你使用的是基本的原始值,比如字符串和整数,你不能使用多态性,但你仍然觉得有必要进行类型检查,你应该考虑使用 TypeScript。它是普通 JavaScript 的一个很好的替代方案,因为它提供了标准 JavaScript 语法之上的静态类型。手动类型检查常规 JavaScript 的问题是,要想做得好,需要太多额外的废话,以至于你得到的伪“类型安全”并不能弥补失去的可读性。保持 JavaScript 整洁,编写优秀的测试,并进行良好的代码审查。 **Bad:** ```javascript 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:** ```javascript function combine(val1, val2) { return val1 + val2; } ``` ### 不要过度优化 **Bad:** ```javascript for (let i = 0, len = list.length; i < len; i++) { // ... } ``` **Good:** ```javascript for (let i = 0; i < list.length; i++) { // ... } ``` ### 删除无用代码 死代码和重复代码一样糟糕。没有理由将它保存在代码库中。如果它没有被调用,就把它处理掉!如果您仍然需要它,它在您的版本历史中仍然是安全的。 **Bad:** ```javascript function oldRequestModule(url) { // ... } function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` **Good:** ```javascript function newRequestModule(url) { // ... } const req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` ## **对象和数据结构** ### 使用 getters/setters 语句 使用 getter 和 setter 访问对象上的数据可能比简单地查找对象上的属性更好。你可能会问:“为什么?“,下面列举了一些原因: - 当您想要做的不仅仅是获取一个对象属性时,您不必在代码库中查找和更改每个访问器。 - 使得`set`执行集合时添加验证变得简单。 - 易于添加日志和错误处理时,获取和设置。 - 你可以延迟加载对象的属性,比如说从服务器获取。 **Bad:** ```javascript function makeBankAccount() { // ... return { balance: 0, // ... }; } const account = makeBankAccount(); account.balance = 100; ``` **Good:** ```javascript function makeBankAccount() { // this one is private let balance = 0; // a "getter", made public via the returned object below function getBalance() { return balance; } // a "setter", made public via the returned object below function setBalance(amount) { // ... validate before updating the balance balance = amount; } return { // ... getBalance, setBalance, }; } const account = makeBankAccount(); account.setBalance(100); ``` ### 使对象具有私有成员 这可以通过闭包(针对 ES5 及以下版本)来实现。 **Bad:** ```javascript const Employee = function (name) { this.name = name; }; Employee.prototype.getName = function getName() { 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:** ```javascript function makeEmployee(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 ``` ## **Classes** ### 多用 ES6 **Bad:** ```javascript const Animal = function (age) { if (!(this instanceof Animal)) { throw new Error('Instantiate Animal with `new`'); } this.age = age; }; Animal.prototype.move = function move() {}; 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 = function speak() {}; ``` **Good:** ```javascript 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() { /* ... */ } } ``` ### 使用方法链 这种模式在 JavaScript 中非常有用,您可以在许多库中看到它,例如 jQuery 和 Lodash。 它使您的代码具有表现力,并且不那么冗长。 出于这个原因,我说,使用方法链并看看您的代码将有多干净。 在您的类函数中,只需在每个函数的末尾返回 this,您就可以将更多的类方法链接到它上面。 **Bad:** ```javascript 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:** ```javascript class Car { constructor(make, model, color) { this.make = make; this.model = model; this.color = color; } setMake(make) { this.make = make; // 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); // NOTE: Returning this for chaining return this; } } const car = new Car('Ford', 'F-150', 'red').setColor('pink').save(); ``` ### 偏好组合而非继承 正如在 Gang of Four 的设计模式中所说的那样,您应该尽可能地选择组合而不是继承。有很多很好的理由使用继承,也有很多很好的理由使用组合。这条格言的主要观点是,如果你的头脑本能地倾向于继承,试着思考一下构图是否能更好地模拟你的问题。在某些情况下是可以的。 您可能会想,“我应该在什么时候使用继承?”这取决于您手头的问题,但这是继承比组合更有意义的一些不错的点: 1. 你的继承代表的是“是-a”关系而不是“是-a”关系(Human->Animal vs. User->UserDetails)。 relationship (Human->Animal vs. User->UserDetails). 2. 您可以重用基类中的代码(人类可以像所有动物一样移动)。 3. 您希望通过更改基类来对派生类进行全局更改。(改变所有动物运动时的热量消耗)。 **Bad:** ```javascript 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; } // ... } ``` **Good:** ```javascript class EmployeeTaxData { constructor(ssn, salary) { 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); } // ... } ``` ## **固态** ### 单一责任原则 正如 Clean Code 中所述,“一个类的更改原因不应该超过一个”。如果你只能带一个行李箱上飞机,那就很容易把一门课程塞进很多功能中。这样做的问题是,你的类在概念上没有连贯性,这会给它带来很多改变的理由。将更改类的时间最小化是很重要的。这很重要,因为如果一个类中有太多的功能,而您修改了其中的一部分,就很难理解这将如何影响代码库中的其他依赖模块。 **Bad:** ```javascript class UserSettings { constructor(user) { this.user = user; } changeSettings(settings) { if (this.verifyCredentials()) { // ... } } verifyCredentials() { // ... } } ``` **Good:** ```javascript 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()) { // ... } } } ``` ### 开放封闭原则 正如 Bertrand Meyer 所说,“软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。”这意味着什么呢?这个原则基本上是说,您应该允许用户添加新功能,而不改变现有的代码。 **Bad:** ```javascript class AjaxAdapter extends Adapter { constructor() { super(); this.name = 'ajaxAdapter'; } } class NodeAdapter extends Adapter { constructor() { super(); this.name = 'nodeAdapter'; } } class HttpRequester { constructor(adapter) { this.adapter = adapter; } fetch(url) { if (this.adapter.name === 'ajaxAdapter') { return makeAjaxCall(url).then((response) => { // transform response and return }); } else if (this.adapter.name === 'nodeAdapter') { return makeHttpCall(url).then((response) => { // transform response and return }); } } } function makeAjaxCall(url) { // request and return promise } function makeHttpCall(url) { // request and return promise } ``` **Good:** ```javascript class AjaxAdapter extends Adapter { constructor() { super(); this.name = 'ajaxAdapter'; } request(url) { // request and return promise } } class NodeAdapter extends Adapter { constructor() { super(); this.name = 'nodeAdapter'; } request(url) { // request and return promise } } class HttpRequester { constructor(adapter) { this.adapter = adapter; } fetch(url) { return this.adapter.request(url).then((response) => { // transform response and return }); } } ``` ### 里氏替换原则 对于一个非常简单的概念来说,这是一个可怕的术语。它被正式定义为“如果 S 是 T 的子类型,那么 T 类型的对象可以被 S 类型的对象替换(也就是说,S 类型的对象可以替换 T 类型的对象),而不改变该程序的任何理想属性(正确性,执行的任务,等等)。”这是一个更可怕的定义。 对此最好的解释是,如果你有一个父类和一个子类,那么基类和子类可以互换使用,而不会得到错误的结果。这可能仍然令人困惑,所以让我们看看经典的正方形-矩形示例。从数学上讲,正方形是一个矩形,但是如果您通过继承使用“is-a”关系来建模它,您很快就会遇到麻烦。 **Bad:** ```javascript 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 { 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); const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20. rectangle.render(area); }); } const rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles(rectangles); ``` **Good:** ```javascript class Shape { setColor(color) { // ... } render(area) { // ... } } class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } getArea() { return this.width * this.height; } } class Square extends Shape { constructor(length) { super(); this.length = length; } getArea() { return this.length * this.length; } } function renderLargeShapes(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 没有接口,所以这个原则不像其他原则那么严格。然而,即使在 JavaScript 缺乏类型系统的情况下,它也很重要和相关。 ISP 声明:“不应该强迫客户依赖他们不使用的接口。”由于 duck 类型,接口在 JavaScript 中是隐式契约 在 JavaScript 中演示这一原则的一个很好的例子是需要大型设置对象的类。不需要客户设置大量的选项是有益的,因为大多数时候他们不需要所有的设置。使它们可选有助于防止出现“臃肿”。 **Bad:** ```javascript class DOMTraverser { constructor(settings) { this.settings = settings; this.setup(); } setup() { this.rootNode = this.settings.rootNode; this.settings.animationModule.setup(); } traverse() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), animationModule() {}, // Most of the time, we won't need to animate when traversing. // ... }); ``` **Good:** ```javascript 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() { // ... } } const $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), options: { animationModule() {}, }, }); ``` ### 依赖倒置原理 这个原则陈述了两个重要的事情: 1. 高级模块不应该依赖于低级模块。 两者都应该依赖于抽象。 2. 抽象不应该依赖于细节。 细节应该取决于抽象。 一开始这可能很难理解,但是如果您使用过 AngularJS,您就会看到该原则以依赖注入 (DI) 的形式实现。 虽然它们不是相同的概念,但 DIP 使高级模块无法了解其低级模块的详细信息并进行设置。 它可以通过 DI 实现这一点。 这样做的一个巨大好处是它减少了模块之间的耦合。 耦合是一种非常糟糕的开发模式,因为它使您的代码难以重构。 如前所述,JavaScript 没有接口,因此依赖的抽象是隐式契约。 也就是说,一个对象/类向另一个对象/类公开的方法和属性。 在下面的示例中,隐式约定是 `anInventoryTracker` 的任何请求模块都将具有 `requestItems` 方法。 **Bad:** ```javascript class InventoryRequester { constructor() { this.REQ_METHODS = ['HTTP']; } requestItem(item) { // ... } } 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); }); } } const inventoryTracker = new InventoryTracker(['apples', 'bananas']); inventoryTracker.requestItems(); ``` **Good:** ```javascript 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. const inventoryTracker = new InventoryTracker( ['apples', 'bananas'], new InventoryRequesterV2(), ); inventoryTracker.requestItems(); ``` ## **测试** ### 每次测试单一概念 **Bad:** ```javascript import assert from 'assert'; describe('MomentJS', () => { it('handles date boundaries', () => { let date; date = new MomentJS('1/1/2015'); date.addDays(30); assert.equal('1/31/2015', date); date = new MomentJS('2/1/2016'); date.addDays(28); assert.equal('02/29/2016', date); date = new MomentJS('2/1/2015'); date.addDays(28); assert.equal('03/01/2015', date); }); }); ``` **Good:** ```javascript import assert from 'assert'; describe('MomentJS', () => { it('handles 30-day months', () => { const date = new MomentJS('1/1/2015'); date.addDays(30); assert.equal('1/31/2015', date); }); it('handles leap year', () => { const date = new MomentJS('2/1/2016'); date.addDays(28); assert.equal('02/29/2016', date); }); it('handles non-leap year', () => { const date = new MomentJS('2/1/2015'); date.addDays(28); assert.equal('03/01/2015', date); }); }); ``` ## **并发性** ### 尽量使用 promise Callbacks aren't clean, and they cause excessive amounts of nesting. With ES2015/ES6, Promises are a built-in global type. Use them! **Bad:** ```javascript import { get } from 'request'; import { writeFile } from 'fs'; get( 'https://en.wikipedia.org/wiki/Robert_Cecil_Martin', (requestErr, response, body) => { if (requestErr) { console.error(requestErr); } else { writeFile('article.html', body, (writeErr) => { if (writeErr) { console.error(writeErr); } else { console.log('File written'); } }); } }, ); ``` **Good:** ```javascript import { get } from 'request-promise'; import { writeFile } from 'fs-extra'; get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') .then((body) => { return writeFile('article.html', body); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); }); ``` ### Async/Await 更优于 Promise Promise 是回调的一个非常干净的替代方案,但 `ES2017/ES8` 带来了 `async` 和 `await` ,它们提供了一个更干净的解决方案。 您所需要的只是一个以 async 关键字为前缀的函数,然后您就可以在没有 then 函数链的情况下命令式地编写逻辑。 如果您今天可以利用 `ES2017/ES8` 功能,请使用它! **Bad:** ```javascript import { get } from 'request-promise'; import { writeFile } from 'fs-extra'; get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin') .then((body) => { return writeFile('article.html', body); }) .then(() => { console.log('File written'); }) .catch((err) => { console.error(err); }); ``` **Good:** ```javascript import { get } from 'request-promise'; import { writeFile } from 'fs-extra'; async function getCleanCodeArticle() { try { const body = await get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); await writeFile('article.html', body); console.log('File written'); } catch (err) { console.error(err); } } getCleanCodeArticle(); ``` ## **错误警告捕捉** ### 不要忘记捕捉报错 对捕获的错误不采取任何措施并不能让您永远修复或应对所述错误。 将错误记录到控制台 (`console.log`) 并没有好到哪里去,因为它经常会迷失在打印到控制台的大量内容中。 如果你在 `try/catch` 中包装了任何代码,这意味着你认为那里可能会发生错误,因此你应该有一个计划,或者创建一个代码路径,以备不时之需。 **Bad:** ```javascript try { functionThatMightThrow(); } catch (error) { console.log(error); } ``` **Good:** ```javascript 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! } ``` ## **模式** ### 一致化命名 JavaScript 是无类型的,所以大写可以告诉你很多关于变量、函数等的信息。这些规则是主观的,所以你的团队可以选择他们想要的任何规则。关键是,无论你们选择什么,保持一致就好。 **Bad:** ```javascript 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']; function eraseDatabase() {} function restore_database() {} class animal {} class Alpaca {} ``` **Good:** ```javascript 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']; function eraseDatabase() {} function restoreDatabase() {} class Animal {} class Alpaca {} ``` ### 函数调用者和被调用者应该是接近的 如果一个函数调用另一个函数,在源文件中保持这些函数垂直接近。理想情况下,让打电话的人在被打电话的人的正上方。我们倾向于从上到下阅读代码,就像阅读报纸一样。因此,让您的代码以这种方式阅读。 **Bad:** ```javascript class PerformanceReview { 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:** ```javascript class PerformanceReview { 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:** ```javascript function hashIt(data) { // The hash let hash = 0; // Length of string const length = data.length; // Loop through every character in data for (let i = 0; i < length; i++) { // Get character code. const char = data.charCodeAt(i); // Make the hash hash = (hash << 5) - hash + char; // Convert to 32-bit integer hash &= hash; } } ``` **Good:** ```javascript function hashIt(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; // Convert to 32-bit integer hash &= hash; } } ``` ### 不要在你的代码库中留下注释掉的代码 **Bad:** ```javascript doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff(); ``` **Good:** ```javascript doStuff(); ``` ### 不要写日志评论 记住,使用版本控制!不需要死代码、注释代码,尤其是日志注释。使用 `git log` 来获取历史记录 **Bad:** ```javascript /** * 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; } ``` **Good:** ```javascript function combine(a, b) { return a + b; } ```