能把程式碼寫出來是一回事,但是寫出整潔、可讀的程式碼又是另一回事。然而,什麼是「乾淨的程式碼」呢?怎麼才能寫出「乾淨的程式碼」?為了解答這些問題,本文作者寫了一份針對開發者的乾淨程式碼指南。
不妨想象一下,你正在閱讀一篇文章,文章開頭段簡要概述了文章的內容。文中還有一些小標題,它們會引出各部分的段落。段落是通過將相關資訊按照合理的順序組合起來而構成的,這樣文章就會變得「行雲流水」,可讀性很強。
現在,你可以反過來再想象一下如果這篇文章沒有任何小標題。文中只有很多小段落,它們十分冗長並雜亂無章。那麼你就無法快速瀏覽這篇文章,必須真正深入到內容中去,這樣才能對整篇文章有大概的瞭解。這確實會帶來很差的閱讀體驗!
你的程式碼應該像一篇美文一樣,需要給讀者帶來很好的閱讀體驗。將你程式碼的類/檔案視為文章的小標題,將你的方法(函式)視為文章的段落。你程式碼中的語句就相當於文章中的句子。下面本文將列出一些乾淨程式碼的特徵:
乾淨的程式碼是專一的:每個函式、類和模組都應該只做一件事,並且將其做好。
乾淨程式碼應該是優雅的:乾淨的程式碼應該易於閱讀,閱讀乾淨的程式碼會讓你感到愉悅,它應該讓你認為「我確實知道這裡的程式碼在做什麼」。
乾淨程式碼應該經常維護:我們需要花時間讓它保持簡單有序,並適當關注程式碼的細節。
乾淨程式碼應該通過各種測試:會崩潰的程式碼肯定不是乾淨的!
那麼現在主要的問題就是,作為一個開發者,你如何才能編寫出乾淨的程式碼?下面是一些實用的小建議。
使用一致的格式和縮排
如果行距不一致、字型大小不一、或到處都是換行,那麼這樣的書肯定難以閱讀。程式碼也是如此。
要使你的程式碼清晰易讀,請確保縮排、換行、以及格式是一致的。下面本文將給出一個優秀範例和反面例子:
優秀範例
你一眼就可以看出函式中有一個「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」原則(不要重複做一件事)
原文連結:https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Fmedium.freecodecamp.org%2Fthe-junior-developers-guide-to-writing-super-clean-and-readable-code-cd2568e08aae