天天看點

代碼規範指南:怎樣寫才能幹淨整潔

作者:Chris Blakely

機器之心編譯

能把代碼寫出來是一回事,但是寫出整潔、可讀的代碼又是另一回事。然而,什麼是「幹淨的代碼」呢?怎麼才能寫出「幹淨的代碼」?為了解答這些問題,本文作者寫了一份針對開發者的幹淨代碼指南。

不妨想象一下,你正在閱讀一篇文章,文章開頭段簡要概述了文章的内容。文中還有一些小标題,它們會引出各部分的段落。段落是通過将相關資訊按照合理的順序組合起來而構成的,這樣文章就會變得「行雲流水」,可讀性很強。

現在,你可以反過來再想象一下如果這篇文章沒有任何小标題。文中隻有很多小段落,它們十分冗長并雜亂無章。那麼你就無法快速浏覽這篇文章,必須真正深入到内容中去,這樣才能對整篇文章有大概的了解。這确實會帶來很差的閱讀體驗!

你的代碼應該像一篇美文一樣,需要給讀者帶來很好的閱讀體驗。将你代碼的類/檔案視為文章的小标題,将你的方法(函數)視為文章的段落。你代碼中的語句就相當于文章中的句子。下面本文将列出一些幹淨代碼的特征:

  1. 幹淨的代碼是專一的:每個函數、類和子產品都應該隻做一件事,并且将其做好。
  2. 幹淨代碼應該是優雅的:幹淨的代碼應該易于閱讀,閱讀幹淨的代碼會讓你感到愉悅,它應該讓你認為「我确實知道這裡的代碼在做什麼」。
  3. 幹淨代碼應該經常維護:我們需要花時間讓它保持簡單有序,并适當關注代碼的細節。
  4. 幹淨代碼應該通過各種測試:會崩潰的代碼肯定不是幹淨的!

那麼現在主要的問題就是,作為一個開發者,你如何才能編寫出幹淨的代碼?下面是一些實用的小建議。

使用一緻的格式和縮進

如果行距不一緻、字型大小不一、或到處都是換行,那麼這樣的書肯定難以閱讀。代碼也是如此。

要使你的代碼清晰易讀,請確定縮進、換行、以及格式是一緻的。下面本文将給出一個優秀範例和反面例子:

優秀範例

代碼規範指南:怎樣寫才能幹淨整潔
  • 你一眼就可以看出函數中有一個「if/else」語句
  • 大括号和一緻的縮進讓代碼塊開始和結束的位置一目了然
  • 大括号是一緻的,請注意函數和 if 代碼塊的左大括号是和函數名和 if 關鍵字放在同一行上的

反面例子

代碼規範指南:怎樣寫才能幹淨整潔

這裡有太多不對勁的地方!

  • 到處都是随意的縮進,你無法看清函數在哪裡結束,也無法快速判斷「if/else」代碼塊從哪裡開始(是的,這一段裡面确實有一個「if/else」代碼塊!)
  • 括号混淆不清,使用方法不一緻
  • 行距不一緻

這個例子稍微有些誇張,但是它顯示出了使用一緻的縮進和規範格式的好處。我不知道你怎麼看,但我認為「優秀範例」中給出的例子對我來說讀起來容易地多!

好消息是,你可以使用過許多 IDE 的插件自動規定代碼的格式。哈利路亞!

  • VS Code:https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
  • Atom:https://atom.io/packages/atom-beautify
  • Sublime Text:https://packagecontrol.io/packages/HTML-CSS-JS%20Prettify

使用清晰的變量名和方法名

在文章的開頭,我談到了讓你的代碼變得容易閱讀是多麼的重要。要做到這一點,一個重要的方面就是你選擇的命名方式(這是我在菜鳥階段犯過的錯誤之一)。下面讓我們看一個好的命名的例子(JS使用小駝峰命名規則):

代碼規範指南:怎樣寫才能幹淨整潔

這段代碼有下面 2 個優點:

  • 函數的命名很清晰,參數也被命名地很好。當開發者看到這段代碼時,他們的思路會很清晰。「如果我給出 studentId 參數,并調用 getStudentName() 方法,我将得到一個學生的名字」——如果沒有必要的話,我們不必再轉而檢視「getStudentName()」方法!
  • 在「getStudentName()」方法内部,對變量和方法的調用也被很清晰地命名了——可以很清楚地看到該方法調用了一個 api,得到了一個 student 對象,并傳回了一個 name 屬性。太容易了!

對于新手來說,在編寫幹淨的代碼時選取好的命名比你想象的要難。随着你的應用程式不斷更新,請使用下面的規則確定你的代碼易于閱讀:

  • 選擇一種命名風格并始終保持一緻。要麼使用「camelCase」(駝峰式命名法),要麼使用「under_scores」(下劃線命名法),但是不要同時使用這兩種命名風格!
  • 對于你的函數、方法、變量,根據他們所完成的任務來進行命名。例如,如果你的方法要擷取什麼東西,請将「get」放到該方法的名字中。如果你的變量要「存儲」一種汽車的顔色,請将它命名為「carColour」。

溫馨提示,如果你無法命名你的函數或方法,那就說明這個函數承載的任務太多了。請繼續将其分解為更小的函數!例如,如果你最終調用的是你的函數「updateCarAndSave()」,請分别建立兩個方法「updateCar()」和「saveCar()」。

在必要時使用注釋

人們常說:「代碼應該是自文檔化的」,這從根本上意味着,你的代碼應該足夠易讀,進而減少對注釋的需求。這個觀點貌似很有道理,我猜這種說法在理想世界是說得通的。然而,碼農的世界卻遠遠不是一個完美的世界,是以使用一些注釋還是很有必要的。

文檔注釋是描述某個特定的函數或類做了什麼的注釋。如果你編寫了一個程式庫,這樣的注釋會對其它開發者們很有幫助。下面是「useJSDoc」中的一個注釋的例子:

代碼規範指南:怎樣寫才能幹淨整潔

說明注釋對于可能需要維護、重構或擴充你的代碼的任何人(包括未來的你自己)都适用。通常而言,可以避免使用說明注釋,轉而采用「自文檔化代碼」。下面是一個說明注釋的例子:

代碼規範指南:怎樣寫才能幹淨整潔

下面給出了一些你應該盡量避免使用的注釋。他們不會提供太多的有效資訊,可能會誤導使用者,并使代碼變得混亂。

不增添有效資訊的備援注釋:

代碼規範指南:怎樣寫才能幹淨整潔

誤導性的注釋:

代碼規範指南:怎樣寫才能幹淨整潔

搞笑或輕蔑的注釋:

代碼規範指南:怎樣寫才能幹淨整潔

牢記「DRY」原則(Don't Repeat Yourself,不要做重複的事)

「DRY」原則可以被表述為:

每一小段知識在一個系統中必須擁有一個單一、清晰、權威的呈現。

最簡單地說,這從根本上意味着你應該緻力于減少存在的重複代碼的數量。(注意,我這裡說的是「減少」而不是「消除」——有些情況下,重複的代碼也并不是世界末日!)

對于代碼維護來說,重複的代碼可能是一場噩夢。讓我們來看看一個例子:

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }

    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }

    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}      

假設你正在為客戶建立一個人力資源 web 應用程式。該應用程式允許管理者将扮演某種角色的使用者通過應用程式接口(API)添加到資料庫中。角色共有三種:雇員、經理和管理者。讓我們看看可能存在的一些函數:

這看起來似乎很酷!上面代碼的運作一切正常。但是,過了一會,我們的客戶跑過來說:

嘿!我們希望顯示出來的錯誤資訊包含「此處有一個錯誤」這句話。另外,更麻煩的是,我們希望把 API 的端點從「/user」改為「/users」。謝謝!

在開始程式設計之前,讓我們先回顧一下。在這篇文章開頭,我曾經說過「幹淨的代碼應該專一」(即做一件事,并把它做好)。這就是我們目前的代碼所具有的一個小問題。執行 API 調用和處理錯誤的代碼重複出現了——這意味着我們必須在三個地方同時更新代碼,以滿足新的需求。這太煩人了!

那麼,如果我們對代碼進行重構,讓它變得更專一呢?請繼續閱讀下面的内容:

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }

    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }

    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}

function saveUserToDatabase(user){
    axios.post('/users', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log("there was an error " + error);
  });
}      

我們已經将建立 API 調用的邏輯移到了它自己的方法「saveUserToDatabase(user)」中(這是個好名字嗎?看你怎麼想喽)。其它的方法将調用該方法來儲存使用者資訊。現在,如果我們需要再次變更 API 的邏輯,我們隻需要更新一個方法。同樣的,如果我們必須添加一個建立使用者的方法,那麼通過 API 将使用者資訊儲存到資料庫的方法就已經存在了。這真是太棒了!

使用我們目前所學的知識進行重構的一個例子

讓我們閉上眼睛,假設我們正在做一個電腦應用程式。該程式用到了一些可以分别讓我們做加法、減法、乘法、除法的函數,将運作結果輸出到控制台。

下面是我們目前已有的代碼,在繼續閱讀本文接下來的内容之前,看看你能否自己發現代碼中存在的問題:

function addNumbers(number1, number2){
    const result = number1 + number2;
        const output = 'The result is ' + result;
        console.log(output);
}

// this function substracts 2 numbers
function substractNumbers(number1, number2){

    //store the result in a variable called result
    const result = number1 - number2;
    const output = 'The result is ' + result;
    console.log(output);
}

function doStuffWithNumbers(number1, number2){
    const result = number1 * number2;
    const output = 'The result is ' + result;
    console.log(output);
}

function divideNumbers(x, y){
    const result = number1 / number2;
    const output = 'The result is ' + result;
    console.log(output);
}      

代碼中存在哪些問題呢?

  • 縮進是不一緻的——使用什麼樣的縮進格式并不重要,隻要格式保持一緻
  • 第二個函數有一些備援的注釋——我們可以通過閱讀函數名和函數内的代碼來判斷發生了什麼,是以我們真的需要這裡的注釋嗎?
  • 第三和第四個函數沒有使用良好的命名——「doStuffWithNumbers()」并不是用最恰當的函數名,因為它并沒有說明函數做了什麼。(x,y)不是描述性的的變量,x 和 y 有作用嗎?它們是什麼?是數字嗎?還是香蕉?
  • 這些方法做了不止一件事——它們要執行計算,但是也要顯示輸出。我們可以按照「DRY」原則将現實邏輯拆分為一個獨立的方法。

現在,我們将使用在這個為初學者編寫的幹淨代碼指南中學到的東西來重構代碼,由此得到的新代碼如下:

function addNumbers(number1, number2){
    const result = number1 + number2;
    displayOutput(result)
}

function substractNumbers(number1, number2){
    const result = number1 - number2;
    displayOutput(result)
}

function multiplyNumbers(number1, number2){
    const result = number1 * number2;
    displayOutput(result)
}

function divideNumbers(number1, number2){
    const result = number1 * number2;
    displayOutput(result)
}

function displayOutput(result){
    const output = 'The result is ' + result;
    console.log(output);
}      
  • 我們修正了縮進格式,使其保持一緻
  • 調整了函數和變量的命名
  • 删除了不必要的注釋
  • 将「displayOutput()」邏輯移到了它自己的方法中——如果需要變更輸出,我們隻需要在這一個地方進行變更。

恭喜你!現在你可以在面試中和撰寫你光彩照人的履歷時,談談你對編寫幹淨代碼的認識了!

不要「過度清理」你的代碼

我經常看到開發人員在清理代碼時矯枉過正。注意不要過度清理代碼,因為這會适得其反。實際上會讓你的代碼變得更難以閱讀和維護。如果開發者必須不斷地在許多檔案/方法之間進行跳轉才能進行簡單的變更,那這樣也會影響生産效率。

要有編寫幹淨代碼的意識,但是不要在項目的早期過多地考慮它。請確定你的代碼能正常工作,并很好地經過了測試。而在重構階段,你應該真正考慮如何使用像「DRY」這樣的原則來清理你的代碼。

在這篇為初學者編寫的幹淨代碼指南中,我們學會了如何:

  • 使用一緻的格式和縮進
  • 使用清晰的變量名和方法名
  • 在必要時使用注釋
  • 使用「DRY」原則(不要重複做一件事)
    代碼規範指南:怎樣寫才能幹淨整潔