關(guān)于如何編寫Clean Code的6個簡單技巧
編寫簡潔代碼(Clean Code)不是一件非常容易的事情,為了保持代碼整潔,你需要嘗試不同的技巧和做法。問題是,在這個問題上,有太多的做法和技巧,是需要大量的重構(gòu)的。因此,開發(fā)者很難選擇出適合自己項目的做法和技巧。
讓我們來簡化這個任務(wù),在本文中,我們將討論編寫整潔代碼的一些好處,然后再來看看我們總結(jié)的 6 個技巧和實踐,以便讓開發(fā)人員理解,編寫 Clean Code 最常使用的一些技巧。
一、編寫簡潔代碼的好處
讓我們先看看編寫簡潔干凈的代碼有什么好處。其中一個主要的好處是,簡潔干凈的代碼可以幫助我們最大限度的減少閱讀和試圖理解代碼的時間。凌亂的代碼風(fēng)格,具有不可思議的能力,可以加大開發(fā)人員的工作效率,讓它們的工作變的更加困難。代碼越是混亂,開發(fā)人員維護起來需要的時間越多。而且,如果代碼太亂,維護的開發(fā)人員可能會決定停下來,然后重構(gòu)它。
1.1 更容易開始,也更容易繼續(xù)
讓我舉一個簡單的例子來說明這一點。
假設(shè)我們在很長一段時間之后,重新維護我們之前開發(fā)的某一個項目。也許,是我們以前的某個客戶,聯(lián)系了我們并重新需要我們對其進行繼續(xù)的擴展與維護。
現(xiàn)在,我們可以想像到,如果在項目開發(fā)階段,我們沒有使用簡潔代碼的技巧編寫簡潔的代碼,而是寫下了相反的代碼。你將第一次知道,你的代碼是多么的糟糕和混亂。而且這個時候你也會發(fā)現(xiàn),重新維護一個之前的項目,是多么的艱難。
因此,我們現(xiàn)在必須在這個項目上,花費更多的時間,因為我們需要重新了解我們之前編寫的代碼。而這并不是必要的,我們可以從一開始就堅持編寫簡潔代碼,來完全避免它。
現(xiàn)在我們必須增加成本,而且,我們的舊代碼如此的混亂不堪,如果我們決定從頭開始。我想這不是你的客戶們樂意聽到的,因為這代表了成本的增加和工期的延長。
Clean Code 就不會有這樣的問題,想像一下前面的例子,情況就會剛好相反?,F(xiàn)在,我們之前的代碼是干凈且優(yōu)雅的。那我們需要多長時間才能理解它?也許只需要幾分鐘,簡單閱讀一下代碼,了解之前是如何工作的。最后我們在這個基礎(chǔ)上,花費一段時間進行維護或者擴展新的功能。這次改動投資的成本和時間,將會小得多,我們的客戶可能都不會注意到你做了什么。
這就是我們討論堅持使用 Clean Code 編碼技巧的第一個好處。而且,這不僅僅是為了我們自己維護的項目,也是為了其他開發(fā)者。Clean Code 能夠讓我們更快的上手,我們或者其他開發(fā)者,都不需要花費數(shù)個小時的時間來研究它。我們可以更快的進入到工作之中。
1.2 更容易讓人加入團隊
Clean Code 的另一個好處是與第一個原因密切相關(guān)的,它將加快新人接手項目的速度。
我的意思是,假設(shè)我們需要聘請另外一個開發(fā)人員,需要多長時間才能理解代碼并學(xué)習(xí)如何使用它?這取決于,如果我們的代碼本身很混亂,寫的不好,他將需要更多的時間來完成首次接手的工作。而如果我們的代碼是保持簡潔的、易讀的,這將非常的易于理解,那么他就能更快的接手并且開始編寫新的需求。
有些人可能覺得這不是一個問題,因為我們這些作者還在這里,我們可以幫助他更快的理解,雖然這真的可以加快他的上手速度。但是,我們的幫助只應(yīng)該花費我們很短暫的時間,一兩天或者三天,而不應(yīng)該是一周或者很頻繁的。
當(dāng)我們決定招募另一位開發(fā)人員,是為了加速我們的項目研發(fā),而不是放緩它。我們的目標(biāo)不是幫助他學(xué)習(xí)如何在我們之前編寫的代碼之上繼續(xù)編碼,這將會消磨我們更多的時間。
1.3 更容易保持規(guī)范
有一件事情我們需要注意,了解和學(xué)習(xí)如何編寫代碼是一回事。但是,這只是一個開始,我們還需要確保開發(fā)者能夠并且愿意遵循我們的編碼規(guī)范,并將它持續(xù)的實踐在項目中。這將能保持項目是持續(xù)的干凈整潔,而不會因此而凌亂下去。這一點很重要,因為我們不僅要編寫簡潔的代碼,還要保持這樣的代碼,不管將來有多少人會來維護它,我們需要長期堅持和思考這個問題。
最后,如果我們團隊的開發(fā)者之一,決定不遵循當(dāng)前的編碼規(guī)范呢?
通常這個問題會被自行解決。假設(shè)我們有一群人在相同的項目代碼上進行工作,并且開始偏離既定的代碼規(guī)范。這可能會導(dǎo)致三個方向的發(fā)展。
首先,團隊的其他成員會推動這名開發(fā)人員遵循標(biāo)準(zhǔn),如果他不想離開團隊,他會接收的。
第二個方向是,開發(fā)人員能夠說服團隊的其他成員采用并遵循他的編碼規(guī)范。如果開發(fā)人員提出的編碼規(guī)范是更簡潔的,能帶來更好的結(jié)果,這應(yīng)該是一件好事情。編寫并保持我們的代碼簡潔,并不意味著我們應(yīng)該忽略任何改進它的機會。恰恰相反,我們相信我們總是應(yīng)該質(zhì)疑我們目前的做法,并尋求改善的機會。
所以,如果一個開發(fā)者偏離了我們的想法,而且他的做法更好,這樣的情況下,我們做出改變,可能是最好的結(jié)果,而不是讓他來改變。我認(rèn)為,我們審視和嘗試之前,我們不應(yīng)該忽視別人的方法。事情總有改進的余地,我們應(yīng)該持續(xù)的尋找改進的方法,而不是固守成規(guī)。
最后一種也是我們最不愿意看到的,開發(fā)者決定不采用我們的規(guī)范,也不嘗試說服我們采用他的規(guī)范。這可能導(dǎo)致他離開團隊。
二、編寫 Clean Code 的一些建議
之前,我們在討論編寫簡潔代碼的一些好處,現(xiàn)在是時候?qū)W習(xí)一些技巧來幫助我們做到這一點。
正如我們將要看到的,擁抱簡潔的代碼并遵循這些建議進行實踐下去。這些我們總結(jié)的做法,會使我們的代碼更加清晰、可讀,更簡單并且易于理解。
當(dāng)然,你并不需要實踐這些所有的建議,實踐并且遵循一兩個,就足以代碼有效的成果。
2.1 讓代碼可讀
是的,我們編寫的代碼,最終會被機器解釋。但是,這并不意味著我們應(yīng)該忽略它的可讀性和可理解性??倳辛硗獾拈_發(fā)者有機會閱讀我們的代碼,否者將無法維護它。即使作為獨立開發(fā)者堅持自己維護代碼,我們也可能會在將來繼續(xù)維護我們的項目。出于這些原因,我們應(yīng)該保持我們代碼的可讀性。
最簡單的方法是使用空格進行格式化,我們在發(fā)布代碼之前,將代碼壓縮(混淆)是可以的。但是我們并不需要編寫看起來像是被壓縮的代碼。相反,我們可以使用縮進、換行和空行,來使代碼的結(jié)構(gòu)更具有可讀性。當(dāng)我們決定采取這個做法的時候,我們代碼的可讀性和可理解性可以顯著提高。
接下來,看一下我們的示例代碼,就應(yīng)該能夠理解它了。
Code1:
- // Bad
- const userData=[{userId: 1, userName:
- 'Anthony Johnson', memberSince: '08-01-2017',
- fluentIn: [ 'English', 'Greek', 'Russian']},
- {userId: 2, userName: 'Alice Stevens',
- memberSince: '02-11-2016',
- fluentIn: [ 'English', 'French', 'German']},
- {userId: 3, userName: 'Bradley Stark',
- memberSince: '29-08-2013',
- fluentIn: [ 'Czech', 'English', 'Polish']},
- {userId: 4, userName: 'Hirohiro Matumoto',
- memberSince: '08-05-2015', fluentIn: [ 'Chinese',
- 'English', 'German', 'Japanese']}];
- // Better
- const userData = [
- {
- userId: 1,
- userName: 'Anthony Johnson',
- memberSince: '08-01-2017',
- fluentIn: [
- 'English',
- 'Greek',
- 'Russian'
- ]
- }, {
- userId: 2,
- userName: 'Alice Stevens',
- memberSince: '02-11-2016',
- fluentIn: [
- 'English',
- 'French',
- 'German'
- ]
- }, {
- userId: 3,
- userName: 'Bradley Stark',
- memberSince: '29-08-2013',
- fluentIn: [
- 'Czech',
- 'English',
- 'Polish'
- ]
- }, {
- userId: 4,
- userName: 'Hirohiro Matumoto',
- memberSince: '08-05-2015',
- fluentIn: [
- 'Chinese',
- 'English',
- 'German',
- 'Japanese'
- ]
- }
- ];
Code2:
- // Bad
- class CarouselLeftArrow extends
- Component{render(){return
- ( <a href="#" className="carousel__arrow carousel__arrow--left"
- onClick={this.props.onClick}> <span className="fa fa-2x
- fa-angle-left"/> </a> );}};
- // Better
- class CarouselLeftArrow extends Component {
- render() {
- return (
- <a
- href="#"
- className="carousel__arrow carousel__arrow--left"
- onClick={this.props.onClick}
- >
- <span className="fa fa-2x fa-angle-left" />
- </a>
- );
- }
- };
2.2 為變量、方法取有意義的名稱
再讓我們來看看第二個技巧,這將幫助我們編寫易于理解的代碼。
這個技巧是關(guān)于變量、函數(shù)和方法使用有意義的名稱。有意義是什么意思?有意義的名稱是具有描述性的名稱,不僅僅是我們,其他人也能夠看名稱就能理解,這些變量、函數(shù)和方法的目的。
換句話說,名稱本身應(yīng)該就可以提示變量、函數(shù)和方法的用途,或者它包含的內(nèi)容。
接下來看個例子。
- // Bad
- const fnm = ‘Tom’;
- const lnm = ‘Hanks’
- const x = 31;
- const l = lstnm.length;
- const boo = false;
- const curr = true;
- const sfn = ‘Remember the name’;
- const dbl = [‘1984’, ‘1987’, ‘1989’, ‘1991’].map((i) => {
- return i * 2;
- });
- // Better
- const firstName = ‘Tom’;
- const lastName = ‘Hanks’
- const age = 31;
- const lastNameLength = lastName.length;
- const isComplete = false;
- const isCurrentlyActive = true;
- const songFileName = ‘Remember the name’;
- const yearsDoubled = [‘1984’, ‘1987’, ‘1989’, ‘1991’].map((year) => {
- return year * 2;
- });
但是,我們應(yīng)該知道,使用描述性名稱并不意味著我們可以自由使用盡可能多的單詞來描述它。一個好的經(jīng)驗法則是將名稱限制在三個或者四個單詞。如果我們需要四個以上的單詞才能描述它,這也許是因為我們一次嘗試讓它做太多的事情,這個時候我們應(yīng)該簡化我們的代碼,而不是堅持使用它。
2.3 讓一個函數(shù)或者方法,只執(zhí)行一個任務(wù)
當(dāng)我開始寫代碼的時候,我曾經(jīng)寫過類似瑞士軍刀的功能和方法。它們可以處理和做任何事情。但是到了后期,其中的一個后果就是很難再找到一個好名字來擴展它。其次,除了我之外,幾乎沒有人知道這個和那個功能是什么以及如何使用它,有時候我自己也會面臨這個問題,所以我不得不寫下注釋。第三,這些功能是不可預(yù)測的,而我將因此寫了一段混亂的代碼。
然后,有人給了一個偉大的建議,讓每個功能或方法只執(zhí)行一項任務(wù)。這個簡單的建議改變了一切,并幫助我編寫簡潔的代碼,至少比之前更加干凈簡潔。從那一刻開始,其他人終于能夠理解我的代碼了?;蛘哒f,他們不需要像以前一樣,花費那么多的時間來理解我的代碼。我的功能和方法也變得可預(yù)測。在相同的輸入下,它們總是產(chǎn)生相同的輸出。而且,命名也變得更容易。
如果你很難為你的函數(shù)和方法找到描述性名稱,或者你需要編寫冗長的注釋以便其他人可以使用它們,請考慮實時單一職責(zé)。讓每個函數(shù)或方法只執(zhí)行一項任務(wù)。如果你的函數(shù)和方法看起來像瑞士軍刀一樣大而全,也可以實施這種做法。相信我,在編寫代碼上,任何多功能性都不是一個優(yōu)勢。任何時候都可能會導(dǎo)致適得其反的效果,這是一個相當(dāng)不利的因素。
注意:讓每個函數(shù)或方法只執(zhí)行一項任務(wù)的做法稱為單一責(zé)任原則。羅伯特·C·馬丁(Robert C. Martin)將這種編碼實踐作為五種面向?qū)ο笤O(shè)計原理之一(也稱為SOLID)進行了介紹。
- // Example no.1: 簡單的減法
- function subtract(x, y) {
- return x - y;
- }
- // Example no.1: 簡單的乘法
- function multiply(x, y) {
- return x * y;
- }
- // Example no.1: Double numbers in an array
- function doubleArray(array) {
- return array.map(number => number * 2)
- }
2.4 寫好注釋
不管我們?yōu)樽约旱淖兞?,函?shù)和方法,絞盡腦汁的想出有意義的名稱。我們的代碼本身可能還是有一些不清晰或者不易于理解的地方??赡苁怯幸恍┻壿嫹种枰獑为毥忉專@些可能不是它們很難理解和使用的原因。相反可能只是一些歷史遺留問題。
有時我們可能不得不采取非常規(guī)的方式來解決問題,因為沒有別的辦法可行,或者我們沒有足夠的時間來提出更好的解決方案。
這可能很難用代碼解釋。通過在我們的代碼中,增加注釋可以幫助我們解決這個問題。注釋可以幫助我們向其他人解釋為什么我們寫了這段不合規(guī)的代碼,以及我面臨的問題和現(xiàn)狀。因此,其他人在閱讀的時候就很清晰,不必猜測我的當(dāng)時的想法。
更重要的是,當(dāng)我們解釋我們的原因時,其他人可能會找到一個更好的方法來解決問題并改進代碼。這是可能的,因為他們可能知道問題是什么,期望的結(jié)果是什么。如果不知道這些信息,其他人就難以創(chuàng)造更好的解決方案。否者,其他人將不會嘗試優(yōu)化解決它,因為他們不認(rèn)為有這個需要,他們可能認(rèn)為這就是我們真實的想法。
所以,當(dāng)我們發(fā)現(xiàn)自己處于一種我們決定使用一些非常規(guī)的方式,快速修復(fù)或繞過問題方法的來解決問題情況下,我們就應(yīng)該用注釋來解釋為什么我們面臨的是什么問題,為什么要這么做。用一兩行注釋來解釋它比強迫其他人猜測更好。
也就是說,我們應(yīng)該只在有必要時才使用注釋,而不是解釋錯誤的代碼。不停的增加注釋,并不會幫助我們將寫得差的代碼轉(zhuǎn)換成干凈簡潔的代碼。如果代碼不好,我們應(yīng)該通過改進代碼來解決問題,而不是通過添加關(guān)于如何使用代碼的注釋。簡潔的代碼優(yōu)先于使用注釋解釋。
2.5 保持一致
當(dāng)我們找到我們喜歡的具體的編碼實踐或風(fēng)格時,我們應(yīng)該堅持并在任何地方使用它。在不同的項目中使用不同的編碼規(guī)范或樣式不是一個好主意。它幾乎與不使用任何編碼規(guī)范或風(fēng)格一樣無用。這將導(dǎo)致,回到我們的舊代碼將不會如此平穩(wěn)和自然。我們?nèi)匀恍枰恍r間來理解我們在該項目上使用的編碼規(guī)范,然后才能使用它。
最好的辦法是挑選一套編碼規(guī)范,然后在我們所有的項目中堅持這些規(guī)范。那么,回到我們以前的代碼會更容易,并繼續(xù)我們停止或改進的地方。嘗試新的編碼規(guī)范是一件好事。它可以幫助我們找到更好的方式來完成我們的工作。
但是,在挑選編碼規(guī)范的時候,我們應(yīng)該多做一些嘗試,并且實際使用在多個地方。我們應(yīng)該花精力去驗證我們的規(guī)范,來保證它整的適合我們,只有在我們對此感到滿意的時候,我們才應(yīng)該實施它。并且在全部項目中,使用這套規(guī)范。
2.6 定義檢查代碼,適時重構(gòu)
這是我編寫簡潔的代碼的最后一個技巧。
簡單地寫簡潔的代碼并不是一切。我們的工作并不因為我們使用了簡潔的風(fēng)格編寫代碼而結(jié)束。下一步是保持我們的代碼的簡潔。簡潔的代碼需要維護,當(dāng)我們寫代碼的時候,我們應(yīng)該定期回視檢查,清理垃圾代碼并進行改進。否則,如果我們不檢查和更新舊代碼,它很快就會過時。就像我們的硬件設(shè)備一樣。如果我們想要保持最佳狀態(tài),我們需要定期更新它們。
對于我們每天使用的代碼來說,情況尤其如此。代碼有變得越來越復(fù)雜和混亂的趨勢,而不是簡單和整潔。我們要防止這種情況發(fā)生,并保持我們的代碼的簡潔。要做到這一點的唯一方法是定期檢查我們的代碼。換句話說,我們需要維護它。對于我們不再關(guān)心或沒有前途的項目來說,這可能不是必要的。對于其重要的項目來說,維護是我們工作的一部分。
三、關(guān)于編寫 Clean Code 的總結(jié)
這篇文章到此就要結(jié)束了。
我們今天討論的這六種做法可能不是那些影響最大或效果最顯著的做法。但是,他們是經(jīng)驗豐富的開發(fā)人員最常提到的。這就是我選擇他們的原因。我希望這些實踐或技巧足以幫助你開始編寫干凈的代碼。
現(xiàn)在,像所有事情一樣,最重要的是開始。所以,選擇至少一個練習(xí),并嘗試一下。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】