經驗之談:代碼該怎樣寫才能幹凈整潔
能把代碼寫出來是一回事,但是寫出整潔、可讀的代碼又是另一回事。然而,什麼是「乾淨的代碼」呢?怎麼才能寫出「乾淨的代碼」?為了解答這些問題,本文作者寫了一份針對初級開發者的乾淨代碼指南。
不妨想像一下,你正在閱讀一篇文章。文章開頭有一個段落簡要概述了文章的內容。文中還有一些標題,每個小標題會引出幾個段落。段落是通過將相關信息按照合理的順序組合起來而構成的,這樣文章就會變得「行雲流水」,可讀性很強。
現在,你可以反過來再想像一下這篇文章沒有任何標題的情況。文中有很多段落,但是它們十分冗長並雜亂無章。你無法快速瀏覽這篇文章,必須真正深入到內容中去,這樣才能對整篇文章有大概的了解。這可能會帶來很差的閱讀體驗!
你的代碼應該像一篇美文一樣,給讀者帶來很好的閱讀體驗。將你代碼的類/文件視為文章的標題,將你的方法(函數)視為文章的段落。你代碼中的語句就相當於文章中的句子。下面本文將列出一些乾淨代碼的特徵:
- 乾淨的代碼是專一的——每個函數、每個類、每個模塊都應該只做一件事,並且將其做好。
- 乾淨代碼應該是優雅的——乾淨的代碼應該易於閱讀。閱讀乾淨的代碼會讓你感到愉悅。它應該讓你認為「我確實知道這裡的代碼在做什麼」
- 乾淨代碼應該受到維護。會有人花時間讓它保持簡單有序。這些人會適當關注代碼的細節。
- 乾淨代碼應該通過測試——會崩潰的代碼肯定不是乾淨的!
那麼現在主要的問題就是——作為一個初級開發者,你如何才能編寫出乾淨的代碼?下面是我的一些建議。
使用一致的格式和縮進
如果行距不一致、字體大小不一,而且到處都是換行,那麼這樣的書肯定難以閱讀。代碼也是如此。
要使你的代碼清晰易讀,請確保縮進、換行、以及格式是一致的。下面本文將給出一個優秀範例和反面例子:
優秀範例
function getStudents(id) {
if (id !== null) {
go_and_get_the_student();
} else {
abort_mission();
}
}
- 你一眼就可以看出函數中有一個「if/else」語句
- 大括弧和一致的縮進讓代碼塊開始和結束的位置一目了然
- 大括弧是一致的——請注意函數和 if 代碼塊的左大括弧是和函數名和 if 關鍵字放在同一行上的
反面例子
function getStudents(id) {
if (id !== null) {
go_and_get_the_student();}
else
{
abort_mission();
}
}
這裡有太多不對勁的地方!
- 到處都是隨意的縮進——你無法看清函數在哪裡結束,也無法知道「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
使用清晰的變數名和方法名
在文章的開頭,我談到了讓你的代碼變得容易閱讀是多麼的重要。要做到這一點,一個重要的方面就是你選擇的命名方式(這是我在菜鳥階段犯過的錯誤之一)。下面讓我們看一個好的命名的例子:
function changeStudentLabelText(studentId){
const studentNameLabel = getStudentName(studentId);
}
function getStudentName(studentId){
const student = api.getStudentById(studentId);
return student.name;
}
這段代碼有下面 2 個優點:
- 函數的命名很清晰,參數也被命名地很好。當開發者看到這段代碼時,他們的思路會很清晰。「如果我使用 studentId 參數調用 getStudentName() 方法,我將得到一個學生的名字」——如果沒有必要的話,他們不必再轉而查看「getStudentName()」方法!
- 在「getStudentName()」方法內部,對變數和方法的調用也被很清晰地命名了——可以很清楚地看到該方法調用了一個 api,得到了一個 student 對象,並返回了一個 name 屬性。太容易了!
對於新手來說,在編寫乾淨的代碼時選取好的命名比你想像的要難。隨著你的應用程序不斷升級,請使用下面的規則確保你的代碼易於閱讀:
- 選擇一種命名風格並始終保持一致。要麼使用「camelCase」(駝峰式命名法),要麼使用「under_scores」(下劃線命名法),但是不要同時使用這兩種命名風格!
- 對於你的函數、方法、變數,根據他們所做的事來命名它們。例如,如果你的方法要獲取什麼東西,請將「get」放到該方法的名字中。如果你的變數要「存儲」一種汽車的顏色,請將它命名為「carColour」。
溫馨提示——如果你無法命名你的函數或方法,那就說明這個函數承載的任務太多了。請繼續將其分解為更小的函數!例如,如果你最終調用的是你的函數「updateCarAndSave()」,請分別創建兩個方法「updateCar()」和「saveCar()」。
在必要時使用注釋
人們常說:「代碼應該是自文檔化的」,這從根本上意味著,你的代碼應該足夠易讀,從而減少對注釋的需求。這個觀點貌似很有道理,我猜這種說法在完美的世界是說得通的。然而,碼農的世界卻遠遠不是一個完美的世界,所以使用一些注釋還是很有必要的。
文檔注釋是描述某個特定的函數或類做了什麼的注釋。如果你編寫了一個程序庫,這樣的注釋會對使用你的程序庫的開發者們很有幫助。下面是「useJSDoc」中的一個注釋的例子:
/** * Solves equations of the form a * x = b
* @example *
// returns 2 * globalNS.method1(5, 10);
* @example *
// returns 3 * globalNS.method(5, 15);
* @returns {Number} Returns the value of x for the equation. */ globalNS.method1 = function (a, b) { return b / a; };
說明注釋對於可能需要維護、重構或擴展你的代碼的任何人(包括未來的你自己)都適用。通常而言,可以避免使用說明注釋,轉而採用「自文檔化代碼」。下面是一個說明注釋的例子:
/* This function calls a third party API. Due to some issue with the API vender, the response returns "BAD REQUEST" at times. If it does, we need to retry */
function getImageLinks(){
const imageLinks = makeApiCall();
if(imageLinks === null){
retryApiCall();
} else {
doSomeOtherStuff();
}
}
下面給出了一些你應該盡量避免使用的注釋。他們不會提供太多的有效信息,可能會誤導用戶,並使代碼變得混亂。
不增添有效信息的冗餘注釋:
// this sets the students age
function setStudentAge();
誤導性的注釋:
//this sets the fullname of the student
function setLastName();
搞笑或輕蔑的注釋:
// this method is 5000 lines long but it"s impossible to refactor so don"t try
function reallyLongFunction();
牢記「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
※賈揚清:我對人工智慧方向的一點淺見
※SysML 2019論文解讀:推理優化
TAG:機器之心 |