關(guān)于如何編寫Clean Code的6個(gè)簡(jiǎn)單技巧
編寫簡(jiǎn)潔代碼(Clean Code)不是一件非常容易的事情,為了保持代碼整潔,你需要嘗試不同的技巧和做法。問(wèn)題是,在這個(gè)問(wèn)題上,有太多的做法和技巧,是需要大量的重構(gòu)的。因此,開(kāi)發(fā)者很難選擇出適合自己項(xiàng)目的做法和技巧。
讓我們來(lái)簡(jiǎn)化這個(gè)任務(wù),在本文中,我們將討論編寫整潔代碼的一些好處,然后再來(lái)看看我們總結(jié)的 6 個(gè)技巧和實(shí)踐,以便讓開(kāi)發(fā)人員理解,編寫 Clean Code 最常使用的一些技巧。
一、編寫簡(jiǎn)潔代碼的好處
讓我們先看看編寫簡(jiǎn)潔干凈的代碼有什么好處。其中一個(gè)主要的好處是,簡(jiǎn)潔干凈的代碼可以幫助我們最大限度的減少閱讀和試圖理解代碼的時(shí)間。凌亂的代碼風(fēng)格,具有不可思議的能力,可以加大開(kāi)發(fā)人員的工作效率,讓它們的工作變的更加困難。代碼越是混亂,開(kāi)發(fā)人員維護(hù)起來(lái)需要的時(shí)間越多。而且,如果代碼太亂,維護(hù)的開(kāi)發(fā)人員可能會(huì)決定停下來(lái),然后重構(gòu)它。
1.1 更容易開(kāi)始,也更容易繼續(xù)
讓我舉一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明這一點(diǎn)。
假設(shè)我們?cè)诤荛L(zhǎng)一段時(shí)間之后,重新維護(hù)我們之前開(kāi)發(fā)的某一個(gè)項(xiàng)目。也許,是我們以前的某個(gè)客戶,聯(lián)系了我們并重新需要我們對(duì)其進(jìn)行繼續(xù)的擴(kuò)展與維護(hù)。
現(xiàn)在,我們可以想像到,如果在項(xiàng)目開(kāi)發(fā)階段,我們沒(méi)有使用簡(jiǎn)潔代碼的技巧編寫簡(jiǎn)潔的代碼,而是寫下了相反的代碼。你將第一次知道,你的代碼是多么的糟糕和混亂。而且這個(gè)時(shí)候你也會(huì)發(fā)現(xiàn),重新維護(hù)一個(gè)之前的項(xiàng)目,是多么的艱難。
因此,我們現(xiàn)在必須在這個(gè)項(xiàng)目上,花費(fèi)更多的時(shí)間,因?yàn)槲覀冃枰匦铝私馕覀冎熬帉懙拇a。而這并不是必要的,我們可以從一開(kāi)始就堅(jiān)持編寫簡(jiǎn)潔代碼,來(lái)完全避免它。
現(xiàn)在我們必須增加成本,而且,我們的舊代碼如此的混亂不堪,如果我們決定從頭開(kāi)始。我想這不是你的客戶們樂(lè)意聽(tīng)到的,因?yàn)檫@代表了成本的增加和工期的延長(zhǎng)。
Clean Code 就不會(huì)有這樣的問(wèn)題,想像一下前面的例子,情況就會(huì)剛好相反。現(xiàn)在,我們之前的代碼是干凈且優(yōu)雅的。那我們需要多長(zhǎng)時(shí)間才能理解它?也許只需要幾分鐘,簡(jiǎn)單閱讀一下代碼,了解之前是如何工作的。最后我們?cè)谶@個(gè)基礎(chǔ)上,花費(fèi)一段時(shí)間進(jìn)行維護(hù)或者擴(kuò)展新的功能。這次改動(dòng)投資的成本和時(shí)間,將會(huì)小得多,我們的客戶可能都不會(huì)注意到你做了什么。
這就是我們討論堅(jiān)持使用 Clean Code 編碼技巧的第一個(gè)好處。而且,這不僅僅是為了我們自己維護(hù)的項(xiàng)目,也是為了其他開(kāi)發(fā)者。Clean Code 能夠讓我們更快的上手,我們或者其他開(kāi)發(fā)者,都不需要花費(fèi)數(shù)個(gè)小時(shí)的時(shí)間來(lái)研究它。我們可以更快的進(jìn)入到工作之中。
1.2 更容易讓人加入團(tuán)隊(duì)
Clean Code 的另一個(gè)好處是與第一個(gè)原因密切相關(guān)的,它將加快新人接手項(xiàng)目的速度。
我的意思是,假設(shè)我們需要聘請(qǐng)另外一個(gè)開(kāi)發(fā)人員,需要多長(zhǎng)時(shí)間才能理解代碼并學(xué)習(xí)如何使用它?這取決于,如果我們的代碼本身很混亂,寫的不好,他將需要更多的時(shí)間來(lái)完成首次接手的工作。而如果我們的代碼是保持簡(jiǎn)潔的、易讀的,這將非常的易于理解,那么他就能更快的接手并且開(kāi)始編寫新的需求。
有些人可能覺(jué)得這不是一個(gè)問(wèn)題,因?yàn)槲覀冞@些作者還在這里,我們可以幫助他更快的理解,雖然這真的可以加快他的上手速度。但是,我們的幫助只應(yīng)該花費(fèi)我們很短暫的時(shí)間,一兩天或者三天,而不應(yīng)該是一周或者很頻繁的。
當(dāng)我們決定招募另一位開(kāi)發(fā)人員,是為了加速我們的項(xiàng)目研發(fā),而不是放緩它。我們的目標(biāo)不是幫助他學(xué)習(xí)如何在我們之前編寫的代碼之上繼續(xù)編碼,這將會(huì)消磨我們更多的時(shí)間。
1.3 更容易保持規(guī)范
有一件事情我們需要注意,了解和學(xué)習(xí)如何編寫代碼是一回事。但是,這只是一個(gè)開(kāi)始,我們還需要確保開(kāi)發(fā)者能夠并且愿意遵循我們的編碼規(guī)范,并將它持續(xù)的實(shí)踐在項(xiàng)目中。這將能保持項(xiàng)目是持續(xù)的干凈整潔,而不會(huì)因此而凌亂下去。這一點(diǎn)很重要,因?yàn)槲覀儾粌H要編寫簡(jiǎn)潔的代碼,還要保持這樣的代碼,不管將來(lái)有多少人會(huì)來(lái)維護(hù)它,我們需要長(zhǎng)期堅(jiān)持和思考這個(gè)問(wèn)題。
最后,如果我們團(tuán)隊(duì)的開(kāi)發(fā)者之一,決定不遵循當(dāng)前的編碼規(guī)范呢?
通常這個(gè)問(wèn)題會(huì)被自行解決。假設(shè)我們有一群人在相同的項(xiàng)目代碼上進(jìn)行工作,并且開(kāi)始偏離既定的代碼規(guī)范。這可能會(huì)導(dǎo)致三個(gè)方向的發(fā)展。
首先,團(tuán)隊(duì)的其他成員會(huì)推動(dòng)這名開(kāi)發(fā)人員遵循標(biāo)準(zhǔn),如果他不想離開(kāi)團(tuán)隊(duì),他會(huì)接收的。
第二個(gè)方向是,開(kāi)發(fā)人員能夠說(shuō)服團(tuán)隊(duì)的其他成員采用并遵循他的編碼規(guī)范。如果開(kāi)發(fā)人員提出的編碼規(guī)范是更簡(jiǎn)潔的,能帶來(lái)更好的結(jié)果,這應(yīng)該是一件好事情。編寫并保持我們的代碼簡(jiǎn)潔,并不意味著我們應(yīng)該忽略任何改進(jìn)它的機(jī)會(huì)。恰恰相反,我們相信我們總是應(yīng)該質(zhì)疑我們目前的做法,并尋求改善的機(jī)會(huì)。
所以,如果一個(gè)開(kāi)發(fā)者偏離了我們的想法,而且他的做法更好,這樣的情況下,我們做出改變,可能是最好的結(jié)果,而不是讓他來(lái)改變。我認(rèn)為,我們審視和嘗試之前,我們不應(yīng)該忽視別人的方法。事情總有改進(jìn)的余地,我們應(yīng)該持續(xù)的尋找改進(jìn)的方法,而不是固守成規(guī)。
最后一種也是我們最不愿意看到的,開(kāi)發(fā)者決定不采用我們的規(guī)范,也不嘗試說(shuō)服我們采用他的規(guī)范。這可能導(dǎo)致他離開(kāi)團(tuán)隊(duì)。
二、編寫 Clean Code 的一些建議
之前,我們?cè)谟懻摼帉懞?jiǎn)潔代碼的一些好處,現(xiàn)在是時(shí)候?qū)W習(xí)一些技巧來(lái)幫助我們做到這一點(diǎn)。
正如我們將要看到的,擁抱簡(jiǎn)潔的代碼并遵循這些建議進(jìn)行實(shí)踐下去。這些我們總結(jié)的做法,會(huì)使我們的代碼更加清晰、可讀,更簡(jiǎn)單并且易于理解。
當(dāng)然,你并不需要實(shí)踐這些所有的建議,實(shí)踐并且遵循一兩個(gè),就足以代碼有效的成果。
2.1 讓代碼可讀
是的,我們編寫的代碼,最終會(huì)被機(jī)器解釋。但是,這并不意味著我們應(yīng)該忽略它的可讀性和可理解性。總會(huì)有另外的開(kāi)發(fā)者有機(jī)會(huì)閱讀我們的代碼,否者將無(wú)法維護(hù)它。即使作為獨(dú)立開(kāi)發(fā)者堅(jiān)持自己維護(hù)代碼,我們也可能會(huì)在將來(lái)繼續(xù)維護(hù)我們的項(xiàng)目。出于這些原因,我們應(yīng)該保持我們代碼的可讀性。
最簡(jiǎn)單的方法是使用空格進(jìn)行格式化,我們?cè)诎l(fā)布代碼之前,將代碼壓縮(混淆)是可以的。但是我們并不需要編寫看起來(lái)像是被壓縮的代碼。相反,我們可以使用縮進(jìn)、換行和空行,來(lái)使代碼的結(jié)構(gòu)更具有可讀性。當(dāng)我們決定采取這個(gè)做法的時(shí)候,我們代碼的可讀性和可理解性可以顯著提高。
接下來(lái),看一下我們的示例代碼,就應(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 為變量、方法取有意義的名稱
再讓我們來(lái)看看第二個(gè)技巧,這將幫助我們編寫易于理解的代碼。
這個(gè)技巧是關(guān)于變量、函數(shù)和方法使用有意義的名稱。有意義是什么意思?有意義的名稱是具有描述性的名稱,不僅僅是我們,其他人也能夠看名稱就能理解,這些變量、函數(shù)和方法的目的。
換句話說(shuō),名稱本身應(yīng)該就可以提示變量、函數(shù)和方法的用途,或者它包含的內(nèi)容。
接下來(lái)看個(gè)例子。
- // 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)該知道,使用描述性名稱并不意味著我們可以自由使用盡可能多的單詞來(lái)描述它。一個(gè)好的經(jīng)驗(yàn)法則是將名稱限制在三個(gè)或者四個(gè)單詞。如果我們需要四個(gè)以上的單詞才能描述它,這也許是因?yàn)槲覀円淮螄L試讓它做太多的事情,這個(gè)時(shí)候我們應(yīng)該簡(jiǎn)化我們的代碼,而不是堅(jiān)持使用它。
2.3 讓一個(gè)函數(shù)或者方法,只執(zhí)行一個(gè)任務(wù)
當(dāng)我開(kāi)始寫代碼的時(shí)候,我曾經(jīng)寫過(guò)類似瑞士軍刀的功能和方法。它們可以處理和做任何事情。但是到了后期,其中的一個(gè)后果就是很難再找到一個(gè)好名字來(lái)擴(kuò)展它。其次,除了我之外,幾乎沒(méi)有人知道這個(gè)和那個(gè)功能是什么以及如何使用它,有時(shí)候我自己也會(huì)面臨這個(gè)問(wèn)題,所以我不得不寫下注釋。第三,這些功能是不可預(yù)測(cè)的,而我將因此寫了一段混亂的代碼。
然后,有人給了一個(gè)偉大的建議,讓每個(gè)功能或方法只執(zhí)行一項(xiàng)任務(wù)。這個(gè)簡(jiǎn)單的建議改變了一切,并幫助我編寫簡(jiǎn)潔的代碼,至少比之前更加干凈簡(jiǎn)潔。從那一刻開(kāi)始,其他人終于能夠理解我的代碼了。或者說(shuō),他們不需要像以前一樣,花費(fèi)那么多的時(shí)間來(lái)理解我的代碼。我的功能和方法也變得可預(yù)測(cè)。在相同的輸入下,它們總是產(chǎn)生相同的輸出。而且,命名也變得更容易。
如果你很難為你的函數(shù)和方法找到描述性名稱,或者你需要編寫冗長(zhǎng)的注釋以便其他人可以使用它們,請(qǐng)考慮實(shí)時(shí)單一職責(zé)。讓每個(gè)函數(shù)或方法只執(zhí)行一項(xiàng)任務(wù)。如果你的函數(shù)和方法看起來(lái)像瑞士軍刀一樣大而全,也可以實(shí)施這種做法。相信我,在編寫代碼上,任何多功能性都不是一個(gè)優(yōu)勢(shì)。任何時(shí)候都可能會(huì)導(dǎo)致適得其反的效果,這是一個(gè)相當(dāng)不利的因素。
注意:讓每個(gè)函數(shù)或方法只執(zhí)行一項(xiàng)任務(wù)的做法稱為單一責(zé)任原則。羅伯特·C·馬丁(Robert C. Martin)將這種編碼實(shí)踐作為五種面向?qū)ο笤O(shè)計(jì)原理之一(也稱為SOLID)進(jìn)行了介紹。
- // Example no.1: 簡(jiǎn)單的減法
- function subtract(x, y) {
- return x - y;
- }
- // Example no.1: 簡(jiǎn)單的乘法
- 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ù)和方法,絞盡腦汁的想出有意義的名稱。我們的代碼本身可能還是有一些不清晰或者不易于理解的地方。可能是有一些邏輯分支需要單獨(dú)解釋,這些可能不是它們很難理解和使用的原因。相反可能只是一些歷史遺留問(wèn)題。
有時(shí)我們可能不得不采取非常規(guī)的方式來(lái)解決問(wèn)題,因?yàn)闆](méi)有別的辦法可行,或者我們沒(méi)有足夠的時(shí)間來(lái)提出更好的解決方案。
這可能很難用代碼解釋。通過(guò)在我們的代碼中,增加注釋可以幫助我們解決這個(gè)問(wèn)題。注釋可以幫助我們向其他人解釋為什么我們寫了這段不合規(guī)的代碼,以及我面臨的問(wèn)題和現(xiàn)狀。因此,其他人在閱讀的時(shí)候就很清晰,不必猜測(cè)我的當(dāng)時(shí)的想法。
更重要的是,當(dāng)我們解釋我們的原因時(shí),其他人可能會(huì)找到一個(gè)更好的方法來(lái)解決問(wèn)題并改進(jìn)代碼。這是可能的,因?yàn)樗麄兛赡苤绬?wèn)題是什么,期望的結(jié)果是什么。如果不知道這些信息,其他人就難以創(chuàng)造更好的解決方案。否者,其他人將不會(huì)嘗試優(yōu)化解決它,因?yàn)樗麄儾徽J(rèn)為有這個(gè)需要,他們可能認(rèn)為這就是我們真實(shí)的想法。
所以,當(dāng)我們發(fā)現(xiàn)自己處于一種我們決定使用一些非常規(guī)的方式,快速修復(fù)或繞過(guò)問(wèn)題方法的來(lái)解決問(wèn)題情況下,我們就應(yīng)該用注釋來(lái)解釋為什么我們面臨的是什么問(wèn)題,為什么要這么做。用一兩行注釋來(lái)解釋它比強(qiáng)迫其他人猜測(cè)更好。
也就是說(shuō),我們應(yīng)該只在有必要時(shí)才使用注釋,而不是解釋錯(cuò)誤的代碼。不停的增加注釋,并不會(huì)幫助我們將寫得差的代碼轉(zhuǎn)換成干凈簡(jiǎn)潔的代碼。如果代碼不好,我們應(yīng)該通過(guò)改進(jìn)代碼來(lái)解決問(wèn)題,而不是通過(guò)添加關(guān)于如何使用代碼的注釋。簡(jiǎn)潔的代碼優(yōu)先于使用注釋解釋。
2.5 保持一致
當(dāng)我們找到我們喜歡的具體的編碼實(shí)踐或風(fēng)格時(shí),我們應(yīng)該堅(jiān)持并在任何地方使用它。在不同的項(xiàng)目中使用不同的編碼規(guī)范或樣式不是一個(gè)好主意。它幾乎與不使用任何編碼規(guī)范或風(fēng)格一樣無(wú)用。這將導(dǎo)致,回到我們的舊代碼將不會(huì)如此平穩(wěn)和自然。我們?nèi)匀恍枰恍r(shí)間來(lái)理解我們?cè)谠擁?xiàng)目上使用的編碼規(guī)范,然后才能使用它。
最好的辦法是挑選一套編碼規(guī)范,然后在我們所有的項(xiàng)目中堅(jiān)持這些規(guī)范。那么,回到我們以前的代碼會(huì)更容易,并繼續(xù)我們停止或改進(jìn)的地方。嘗試新的編碼規(guī)范是一件好事。它可以幫助我們找到更好的方式來(lái)完成我們的工作。
但是,在挑選編碼規(guī)范的時(shí)候,我們應(yīng)該多做一些嘗試,并且實(shí)際使用在多個(gè)地方。我們應(yīng)該花精力去驗(yàn)證我們的規(guī)范,來(lái)保證它整的適合我們,只有在我們對(duì)此感到滿意的時(shí)候,我們才應(yīng)該實(shí)施它。并且在全部項(xiàng)目中,使用這套規(guī)范。
2.6 定義檢查代碼,適時(shí)重構(gòu)
這是我編寫簡(jiǎn)潔的代碼的最后一個(gè)技巧。
簡(jiǎn)單地寫簡(jiǎn)潔的代碼并不是一切。我們的工作并不因?yàn)槲覀兪褂昧撕?jiǎn)潔的風(fēng)格編寫代碼而結(jié)束。下一步是保持我們的代碼的簡(jiǎn)潔。簡(jiǎn)潔的代碼需要維護(hù),當(dāng)我們寫代碼的時(shí)候,我們應(yīng)該定期回視檢查,清理垃圾代碼并進(jìn)行改進(jìn)。否則,如果我們不檢查和更新舊代碼,它很快就會(huì)過(guò)時(shí)。就像我們的硬件設(shè)備一樣。如果我們想要保持最佳狀態(tài),我們需要定期更新它們。
對(duì)于我們每天使用的代碼來(lái)說(shuō),情況尤其如此。代碼有變得越來(lái)越復(fù)雜和混亂的趨勢(shì),而不是簡(jiǎn)單和整潔。我們要防止這種情況發(fā)生,并保持我們的代碼的簡(jiǎn)潔。要做到這一點(diǎn)的唯一方法是定期檢查我們的代碼。換句話說(shuō),我們需要維護(hù)它。對(duì)于我們不再關(guān)心或沒(méi)有前途的項(xiàng)目來(lái)說(shuō),這可能不是必要的。對(duì)于其重要的項(xiàng)目來(lái)說(shuō),維護(hù)是我們工作的一部分。
三、關(guān)于編寫 Clean Code 的總結(jié)
這篇文章到此就要結(jié)束了。
我們今天討論的這六種做法可能不是那些影響最大或效果最顯著的做法。但是,他們是經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員最常提到的。這就是我選擇他們的原因。我希望這些實(shí)踐或技巧足以幫助你開(kāi)始編寫干凈的代碼。
現(xiàn)在,像所有事情一樣,最重要的是開(kāi)始。所以,選擇至少一個(gè)練習(xí),并嘗試一下。
【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)微信公眾號(hào)聯(lián)系作者獲取授權(quán)】