--- 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; } ```