<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                Original Repository: [ryanmcdermott/clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript) [TOC] # JavaScript 風格指南 ## 目錄 1. [介紹](#介紹) 2. [變量](#變量) 3. [函數](#函數) 4. [對象和數據結構](#objects-and-data-structures) 5. [類](#類) 6. [測試](#測試) 7. [并發](#并發) 8. [錯誤處理](#錯誤處理) 9. [格式化](#格式化) 10. [注釋](#注釋) ## 介紹 [作者](https://github.com/ryanmcdermott)根據 Robert C. Martin [*《代碼整潔之道》*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)總結了適用于 JavaScript 的軟件工程原則[《Clean Code JavaScript》](https://github.com/ryanmcdermott/clean-code-javascript)。 本文是對其的翻譯。 不必嚴格遵守本文的所有原則,有時少遵守一些效果可能會更好,具體應根據實際情況決定。這是根據《代碼整潔之道》作者多年經驗整理的代碼優化建議,但也僅僅只是一份建議。 軟件工程已經發展了 50 多年,至今仍在不斷前進。現在,把這些原則當作試金石,嘗試將他們作為團隊代碼質量考核的標準之一吧。 最后你需要知道的是,這些東西不會讓你立刻變成一個優秀的工程師,長期奉行他們也并不意味著你能夠高枕無憂不再犯錯。千里之行,始于足下。我們需要時常和同行們進行代碼評審,不斷優化自己的代碼。不要懼怕改善代碼質量所需付出的努力,加油。 ## **變量** ### 使用有意義,可讀性好的變量名 **反例**: ```javascript var yyyymmdstr = moment().format('YYYY/MM/DD'); ``` **正例**: ```javascript var yearMonthDay = moment().format('YYYY/MM/DD'); ``` **[回到目錄](#目錄)** ### 使用 ES6 的 const 定義常量 反例中使用"var"定義的"常量"是可變的。 在聲明一個常量時,該常量在整個程序中都應該是不可變的。 **反例**: ```javascript var FIRST_US_PRESIDENT = "George Washington"; ``` **正例**: ```javascript const FIRST_US_PRESIDENT = "George Washington"; ``` **[回到目錄](#目錄)** ### 對功能類似的變量名采用統一的命名風格 **反例**: ```javascript getUserInfo(); getClientData(); getCustomerRecord(); ``` **正例**: ```javascript getUser(); ``` **[回到目錄](#目錄)** ### 使用易于檢索名稱 我們需要閱讀的代碼遠比自己寫的要多,使代碼擁有良好的可讀性且易于檢索非常重要。閱讀變量名晦澀難懂的代碼對讀者來說是一種相當糟糕的體驗。 讓你的變量名易于檢索。 **反例**: ```javascript // 525600 是什么? for (var i = 0; i < 525600; i++) { runCronJob(); } ``` **正例**: ```javascript // Declare them as capitalized `var` globals. var MINUTES_IN_A_YEAR = 525600; for (var i = 0; i < MINUTES_IN_A_YEAR; i++) { runCronJob(); } ``` **[回到目錄](#目錄)** ### 使用說明變量(即有意義的變量名) **反例**: ```javascript const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; saveCityState(cityStateRegex.match(cityStateRegex)[1], cityStateRegex.match(cityStateRegex)[2]); ``` **正例**: ```javascript const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/; const match = cityStateRegex.match(cityStateRegex) const city = match[1]; const state = match[2]; saveCityState(city, state); ``` **[回到目錄](#目錄)** ### 不要繞太多的彎子 顯式優于隱式。 **反例**: ```javascript var locations = ['Austin', 'New York', 'San Francisco']; locations.forEach((l) => { doStuff(); doSomeOtherStuff(); ... ... ... // l是什么? dispatch(l); }); ``` **正例**: ```javascript var locations = ['Austin', 'New York', 'San Francisco']; locations.forEach((location) => { doStuff(); doSomeOtherStuff(); ... ... ... dispatch(location); }); ``` **[回到目錄](#目錄)** ### 避免重復的描述 當類/對象名已經有意義時,對其變量進行命名不需要再次重復。 **反例**: ```javascript var Car = { carMake: 'Honda', carModel: 'Accord', carColor: 'Blue' }; function paintCar(car) { car.carColor = 'Red'; } ``` **正例**: ```javascript var Car = { make: 'Honda', model: 'Accord', color: 'Blue' }; function paintCar(car) { car.color = 'Red'; } ``` **[回到目錄](#目錄)** ### 避免無意義的條件判斷 **反例**: ```javascript function createMicrobrewery(name) { var breweryName; if (name) { breweryName = name; } else { breweryName = 'Hipster Brew Co.'; } } ``` **正例**: ```javascript function createMicrobrewery(name) { var breweryName = name || 'Hipster Brew Co.' } ``` **[回到目錄](#目錄)** ## **函數** ### 函數參數 (理想情況下應不超過 2 個) 限制函數參數數量很有必要,這么做使得在測試函數時更加輕松。過多的參數將導致難以采用有效的測試用例對函數的各個參數進行測試。 應避免三個以上參數的函數。通常情況下,參數超過兩個意味著函數功能過于復雜,這時需要重新優化你的函數。當確實需要多個參數時,大多情況下可以考慮這些參數封裝成一個對象。 JS 定義對象非常方便,當需要多個參數時,可以使用一個對象進行替代。 **反例**: ```javascript function createMenu(title, body, buttonText, cancellable) { ... } ``` **正例**: ```javascript var menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true } function createMenu(menuConfig) { ... } ``` **[回到目錄](#目錄)** ### 函數功能的單一性 這是軟件功能中最重要的原則之一。 功能不單一的函數將導致難以重構、測試和理解。功能單一的函數易于重構,并使代碼更加干凈。 **反例**: ```javascript function emailClients(clients) { clients.forEach(client => { let clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); } ``` **正例**: ```javascript 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(); } ``` **[回到目錄](#目錄)** ### 函數名應明確表明其功能 **反例**: ```javascript function dateAdd(date, month) { // ... } let date = new Date(); // 很難理解dateAdd(date, 1)是什么意思 ``` **正例**: ```javascript function dateAddMonth(date, month) { // ... } let date = new Date(); dateAddMonth(date, 1); ``` **[回到目錄](#目錄)** ### 函數應該只做一層抽象 當函數的需要的抽象多于一層時通常意味著函數功能過于復雜,需將其進行分解以提高其可重用性和可測試性。 **反例**: ```javascript 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... }) } ``` **正例**: ```javascript 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... }) } ``` **[回到目錄](#目錄)** ### 移除重復的代碼 永遠、永遠、永遠不要在任何循環下有重復的代碼。 這種做法毫無意義且潛在危險極大。重復的代碼意味著邏輯變化時需要對不止一處進行修改。JS 弱類型的特點使得函數擁有更強的普適性。好好利用這一優點吧。 **反例**: ```javascript 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); }); } ``` **正例**: ```javascript function showList(employees) { employees.forEach(employee => { var expectedSalary = employee.calculateExpectedSalary(); var experience = employee.getExperience(); var portfolio; if (employee.type === 'manager') { portfolio = employee.getMBAProjects(); } else { portfolio = employee.getGithubLink(); } var data = { expectedSalary: expectedSalary, experience: experience, portfolio: portfolio }; render(data); }); } ``` **[回到目錄](#目錄)** ### 采用默認參數精簡代碼 **反例**: ```javascript function writeForumComment(subject, body) { subject = subject || 'No Subject'; body = body || 'No text'; } ``` **正例**: ```javascript function writeForumComment(subject = 'No subject', body = 'No text') { ... } ``` **[回到目錄](#目錄)** ### 使用 Object.assign 設置默認對象 **反例**: ```javascript 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); ``` **正例**: ```javascript 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)作為函數參數 這通常意味著函數的功能的單一性已經被破壞。此時應考慮對函數進行再次劃分。 **反例**: ```javascript function createFile(name, temp) { if (temp) { fs.create('./temp/' + name); } else { fs.create(name); } } ``` **正例**: ```javascript function createTempFile(name) { fs.create('./temp/' + name); } ---------- function createFile(name) { fs.create(name); } ``` **[回到目錄](#目錄)** ### 避免副作用 當函數產生了除了“接受一個值并返回一個結果”之外的行為時,稱該函數產生了副作用。比如寫文件、修改全局變量或將你的錢全轉給了一個陌生人等。 程序在某些情況下確實需要副作用這一行為,如先前例子中的寫文件。這時應該將這些功能集中在一起,不要用多個函數/類修改某個文件。用且只用一個 service 完成這一需求。 **反例**: ```javascript // Global variable referenced by following function. // If we had another function that used this name, now it'd be an array and it could break it. var name = 'Ryan McDermott'; function splitIntoFirstAndLastName() { name = name.split(' '); } splitIntoFirstAndLastName(); console.log(name); // ['Ryan', 'McDermott']; ``` **正例**: ```javascript function splitIntoFirstAndLastName(name) { return name.split(' '); } var name = 'Ryan McDermott' var newName = splitIntoFirstAndLastName(name); console.log(name); // 'Ryan McDermott'; console.log(newName); // ['Ryan', 'McDermott']; ``` **[回到目錄](#目錄)** ### 不要寫全局函數 在 JS 中污染全局是一個非常不好的實踐,這么做可能和其他庫起沖突,且調用你的 API 的用戶在實際環境中得到一個 exception 前對這一情況是一無所知的。 想象以下例子:如果你想擴展 JS 中的 Array,為其添加一個 `diff` 函數顯示兩個數組間的差異,此時應如何去做?你可以將 diff 寫入 `Array.prototype`,但這么做會和其他有類似需求的庫造成沖突。如果另一個庫對 diff 的需求為比較一個數組中首尾元素間的差異呢? 使用 ES6 中的 class 對全局的 Array 做簡單的擴展顯然是一個更棒的選擇。 **反例**: ```javascript 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; } ``` **正例**: ```javascript 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; } } ``` **[回到目錄](#目錄)** ### 采用函數式編程 函數式的編程具有更干凈且便于測試的特點。盡可能的使用這種風格吧。 **反例**: ```javascript 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; } ``` **正例**: ```javascript 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); ``` **[回到目錄](#目錄)** ### 封裝判斷條件 **反例**: ```javascript if (fsm.state === 'fetching' && isEmpty(listNode)) { /// ... } ``` **正例**: ```javascript function shouldShowSpinner(fsm, listNode) { return fsm.state === 'fetching' && isEmpty(listNode); } if (shouldShowSpinner(fsmInstance, listNodeInstance)) { // ... } ``` **[回到目錄](#目錄)** ### 避免“否定情況”的判斷 **反例**: ```javascript function isDOMNodeNotPresent(node) { // ... } if (!isDOMNodeNotPresent(node)) { // ... } ``` **正例**: ```javascript function isDOMNodePresent(node) { // ... } if (isDOMNodePresent(node)) { // ... } ``` **[回到目錄](#目錄)** ### 避免條件判斷 這看起來似乎不太可能。 大多人聽到這的第一反應是:“怎么可能不用 if 完成其他功能呢?”許多情況下通過使用多態(polymorphism)可以達到同樣的目的。 第二個問題在于采用這種方式的原因是什么。答案是我們之前提到過的:保持函數功能的單一性。 **反例**: ```javascript class Airplane { //... getCruisingAltitude() { switch (this.type) { case '777': return getMaxAltitude() - getPassengerCount(); case 'Air Force One': return getMaxAltitude(); case 'Cessna': return getMaxAltitude() - getFuelExpenditure(); } } } ``` **正例**: ```javascript 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(); } } ``` **[回到目錄](#目錄)** ### 避免類型判斷(part 1) JS 是弱類型語言,這意味著函數可接受任意類型的參數。 有時這會對你帶來麻煩,你會對參數做一些類型判斷。有許多方法可以避免這些情況。 **反例**: ```javascript 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')); } } ``` **正例**: ```javascript function travelToTexas(vehicle) { vehicle.move(this.currentLocation, new Location('texas')); } ``` **[回到目錄](#目錄)** ### 避免類型判斷(part 2) 如果需處理的數據為字符串,整型,數組等類型,無法使用多態并仍有必要對其進行類型檢測時,可以考慮使用 TypeScript。 **反例**: ```javascript 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'); } } ``` **正例**: ```javascript function combine(val1, val2) { return val1 + val2; } ``` **[回到目錄](#目錄)** ### 避免過度優化 現代的瀏覽器在運行時會對代碼自動進行優化。有時人為對代碼進行優化可能是在浪費時間。 [這里可以找到許多真正需要優化的地方](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers) **反例**: ```javascript // 這里使用變量len是因為在老式瀏覽器中, // 直接使用正例中的方式會導致每次循環均重復計算list.length的值, // 而在現代瀏覽器中會自動完成優化,這一行為是沒有必要的 for (var i = 0, len = list.length; i < len; i++) { // ... } ``` **正例**: ```javascript for (var i = 0; i < list.length; i++) { // ... } ``` **[回到目錄](#目錄)** ### 刪除無效的代碼 不再被調用的代碼應及時刪除。 **反例**: ```javascript function oldRequestModule(url) { // ... } function newRequestModule(url) { // ... } var req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` **正例**: ```javascript function newRequestModule(url) { // ... } var req = newRequestModule; inventoryTracker('apples', req, 'www.inventory-awesome.io'); ``` **[回到目錄](#目錄)** ## **對象和數據結構** ### 使用 getters 和 setters JS 沒有接口或類型,因此實現這一模式是很困難的,因為我們并沒有類似 `public` 和 `private` 的關鍵詞。 然而,使用 getters 和 setters 獲取對象的數據遠比直接使用點操作符具有優勢。為什么呢? 1. 當需要對獲取的對象屬性執行額外操作時。 2. 執行 `set` 時可以增加規則對要變量的合法性進行判斷。 3. 封裝了內部邏輯。 4. 在存取時可以方便的增加日志和錯誤處理。 5. 繼承該類時可以重載默認行為。 6. 從服務器獲取數據時可以進行懶加載。 **反例**: ```javascript class BankAccount { constructor() { this.balance = 1000; } } let bankAccount = new BankAccount(); // Buy shoes... bankAccount.balance = bankAccount.balance - 100; ``` **正例**: ```javascript 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); ``` **[回到目錄](#目錄)** ### 讓對象擁有私有成員 可以通過閉包完成 **反例**: ```javascript 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 ``` **正例**: ```javascript 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 ``` **[回到目錄](#目錄)** ## **類** ### 單一職責原則 (SRP) 如《代碼整潔之道》一書中所述,“修改一個類的理由不應該超過一個”。 將多個功能塞進一個類的想法很誘人,但這將導致你的類無法達到概念上的內聚,并經常不得不進行修改。 最小化對一個類需要修改的次數是非常有必要的。如果一個類具有太多太雜的功能,當你對其中一小部分進行修改時,將很難想象到這一修夠對代碼庫中依賴該類的其他模塊會帶來什么樣的影響。 **反例**: ```javascript class UserSettings { constructor(user) { this.user = user; } changeSettings(settings) { if (this.verifyCredentials(user)) { // ... } } verifyCredentials(user) { // ... } } ``` **正例**: ```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()) { // ... } } } ``` **[回到目錄](#目錄)** ### 開/閉原則 (OCP) “代碼實體(類,模塊,函數等)應該易于擴展,難于修改。” 這一原則指的是我們應允許用戶方便的擴展我們代碼模塊的功能,而不需要打開 js 文件源碼手動對其進行修改。 **反例**: ```javascript 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) { // ... } } ``` **正例**: ```javascript class AjaxRequester { constructor() { this.HTTP_METHODS = ['POST', 'PUT', 'GET']; } get(url) { // ... } addHTTPMethod(method) { this.HTTP_METHODS.push(method); } } ``` **[回到目錄](#目錄)** ### 利斯科夫替代原則 (LSP) “子類對象應該能夠替換其超類對象被使用”。 也就是說,如果有一個父類和一個子類,當采用子類替換父類時不應該產生錯誤的結果。 **反例**: ```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 { 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); ``` **正例**: ```javascript 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); ``` **[回到目錄](#目錄)** ### 接口隔離原則 (ISP) “客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。” 在 JS 中,當一個類需要許多參數設置才能生成一個對象時,或許大多時候不需要設置這么多的參數。此時減少對配置參數數量的需求是有益的。 **反例**: ```javascript 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. // ... }); ``` **正例**: ```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() { // ... } } let $ = new DOMTraverser({ rootNode: document.getElementsByTagName('body'), options: { animationModule: function() {} } }); ``` **[回到目錄](#目錄)** ### 依賴反轉原則 (DIP) 該原則有兩個核心點: 1. 高層模塊不應該依賴于低層模塊。他們都應該依賴于抽象接口。 2. 抽象接口應該脫離具體實現,具體實現應該依賴于抽象接口。 **反例**: ```javascript 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(); ``` **正例**: ```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. let inventoryTracker = new InventoryTracker(['apples', 'bananas'], new InventoryRequesterV2()); inventoryTracker.requestItems(); ``` **[回到目錄](#目錄)** ### 使用 ES6 的 classes 而不是 ES5 的 Function 典型的 ES5 的類(function)在繼承、構造和方法定義方面可讀性較差。 當需要繼承時,優先選用 classes。 但是,當在需要更大更復雜的對象時,最好優先選擇更小的 function 而非 classes。 **反例**: ```javascript 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() {}; ``` **正例**: ```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() {} } ``` **[回到目錄](#目錄)** ### 使用方法鏈 這里我們的理解與《代碼整潔之道》的建議有些不同。 有爭論說方法鏈不夠干凈且違反了[德米特法則](https://en.wikipedia.org/wiki/Law_of_Demeter),也許這是對的,但這種方法在 JS 及許多庫(如 JQuery)中顯得非常實用。 因此,我認為在 JS 中使用方法鏈是非常合適的。在 class 的函數中返回 this,能夠方便的將類需要執行的多個方法鏈接起來。 **反例**: ```javascript 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(); ``` **正例**: ```javascript 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(); ``` **[回到目錄](#目錄)** ### 優先使用組合模式而非繼承 在著名的[設計模式](https://en.wikipedia.org/wiki/Design_Patterns)一書中提到,應多使用組合模式而非繼承。 這么做有許多優點,在想要使用繼承前,多想想能否通過組合模式滿足需求吧。 那么,在什么時候繼承具有更大的優勢呢?這取決于你的具體需求,但大多情況下,可以遵守以下三點: 1. 繼承關系表現為"是一個"而非"有一個"(如動物->人 和 用戶->用戶細節) 2. 可以復用基類的代碼("Human"可以看成是"All animal"的一種) 3. 希望當基類改變時所有派生類都受到影響(如修改"all animals"移動時的卡路里消耗量) **反例**: ```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; } // ... } ``` **正例**: ```javascript 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; } // ... } ``` **[回到目錄](#目錄)** ## **測試** [一些好的覆蓋工具](http://gotwarlost.github.io/istanbul/)。 [一些好的 JS 測試框架](http://jstherightway.org/#testing-tools)。 ### 單一的測試每個概念 **反例**: ```javascript 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); }); }); ``` **正例**: ```javascript 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 替代回調 回調不夠整潔并會造成大量的嵌套。ES6 內嵌了 Promises,使用它吧。 **反例**: ```javascript 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'); } }) } }) ``` **正例**: ```javascript 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 是較 Promises 更好的選擇 Promises 是較回調而言更好的一種選擇,但 ES7 中的 async 和 await 更勝過 Promises。 在能使用 ES7 特性的情況下可以盡量使用他們替代 Promises。 **反例**: ```javascript 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); }) ``` **正例**: ```javascript 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` 的意味著你認為這里可能出現一些錯誤,你應該對這些可能的錯誤存在相應的處理方案。 **反例**: ```javascript try { functionThatMightThrow(); } catch (error) { console.log(error); } ``` **正例**: ```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! } ``` ### 不要忽略被拒絕的 promises 理由同 `try/catch`。 **反例**: ```javascript getdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { console.log(error); }); ``` **正例**: ```javascript 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! }); ``` **[回到目錄](#目錄)** ## **格式化** 格式化是一件主觀的事。如同這里的許多規則一樣,這里并沒有一定/立刻需要遵守的規則。可以在[這里](http://standardjs.com/rules.html)完成格式的自動化。 ### 大小寫一致 JS 是弱類型語言,合理的采用大小寫可以告訴你關于變量/函數等的許多消息。 這些規則是主觀定義的,團隊可以根據喜歡進行選擇。重點在于無論選擇何種風格,都需要注意保持一致性。 **反例**: ```javascript 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 {} ``` **正例**: ```javascript 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 {} ``` **[回到目錄](#目錄)** ### 調用函數的函數和被調函數應放在較近的位置 當函數間存在相互調用的情況時,應將兩者置于較近的位置。 理想情況下,應將調用其他函數的函數寫在被調用函數的上方。 **反例**: ```javascript 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(); ``` **正例**: ```javascript 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(); ``` **[回到目錄](#目錄)** ## **注釋** ### 只對存在一定業務邏輯復雜性的代碼進行注釋 注釋并不是必須的,好的代碼是能夠讓人一目了然,不用過多無謂的注釋。 **反例**: ```javascript 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; } } ``` **正例**: ```javascript 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; } } ``` **[回到目錄](#目錄)** ### 不要在代碼庫中遺留被注釋掉的代碼 版本控制的存在是有原因的。讓舊代碼存在于你的 history 里吧。 **反例**: ```javascript doStuff(); // doOtherStuff(); // doSomeMoreStuff(); // doSoMuchStuff(); ``` **正例**: ```javascript doStuff(); ``` **[回到目錄](#目錄)** ### 不需要版本更新類型注釋 記住,我們可以使用版本控制。廢代碼、被注釋的代碼及用注釋記錄代碼中的版本更新說明都是沒有必要的。 需要時可以使用 `git log` 獲取歷史版本。 **反例**: ```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; } ``` **正例**: ```javascript function combine(a, b) { return a + b; } ``` **[回到目錄](#目錄)** ### 避免位置標記 這些東西通常只能代碼麻煩,采用適當的縮進就可以了。 **反例**: ```javascript //////////////////////////////////////////////////////////////////////////////// // Scope Model Instantiation //////////////////////////////////////////////////////////////////////////////// let $scope.model = { menu: 'foo', nav: 'bar' }; //////////////////////////////////////////////////////////////////////////////// // Action setup //////////////////////////////////////////////////////////////////////////////// let actions = function() { // ... } ``` **正例**: ```javascript let $scope.model = { menu: 'foo', nav: 'bar' }; let actions = function() { // ... } ``` **[回到目錄](#目錄)** ### 避免在源文件中寫入法律評論 將你的 `LICENSE` 文件置于源碼目錄樹的根目錄。 **反例**: ```javascript /* 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() { // ... } ``` **正例**: ```javascript function calculateBill() { // ... } ``` **[回到目錄](#目錄)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看