| 一直以來(lái)想寫(xiě)點(diǎn)關(guān)于代碼質(zhì)量的心得,礙于自身的懶惰。今天終究找到一個(gè)提前忙完工作的午后,可以先讓自己的思路開(kāi)動(dòng)起來(lái)了。 最終促使我開(kāi)始整理自己對(duì)于代碼質(zhì)量的看法,還多虧了前陣子認(rèn)識(shí)的Long小朋友,他及時(shí)地向我推薦了《The Art of Readable Code》這本書(shū)(下文簡(jiǎn)稱(chēng)ARC)。在看過(guò)了馬叔叔的《Clean Code》和《Clean Coder》之后,這本書(shū)徹底讓我沉迷于代碼質(zhì)量之中了。 我就將每天讀書(shū)所做的筆記和自己的想法綜合起來(lái),再加上原來(lái)歷次項(xiàng)目之中的研究心得,陸續(xù)寫(xiě)出來(lái),與大家分享。也歡迎更多的代碼潔癖者一起交流。 可讀性一直是代碼質(zhì)量管控所追求的目標(biāo)之一。沒(méi)有這個(gè),后面的可維護(hù)和可修改都不太容易達(dá)成。可讀性怎么強(qiáng)調(diào)都不為過(guò),ARC一書(shū)的作者對(duì)具備可讀性的代碼給出說(shuō)法是: 
 私以為這個(gè)定義是我暫時(shí)能找到的比較合理的解釋了。可讀性的研究應(yīng)該從橫向與縱向兩個(gè)層面展開(kāi)。 橫向地說(shuō),團(tuán)隊(duì)內(nèi)部,尤其是開(kāi)源項(xiàng)目,更是要維護(hù)個(gè)成員之間對(duì)代碼的理解度。封裝與理解并不矛盾,封裝是為了更好的讓客戶代碼理解其結(jié)構(gòu)與功能,更為恰當(dāng)?shù)厥褂眠@個(gè)模塊。僥幸將自己搞不清爽的代碼以封裝的名義塞到某個(gè)莫名其妙的類(lèi)中,終歸會(huì)導(dǎo)致那部分代碼的缺陷通過(guò)接口或頻繁使用暴露出來(lái)。這一點(diǎn)是個(gè)大問(wèn)題,以后專(zhuān)文再述。 在同一個(gè)項(xiàng)目或者同一個(gè)模塊工作的開(kāi)發(fā)者之間一定要彼此理解對(duì)方所寫(xiě)的代碼。這就是我為何一再?gòu)?qiáng)調(diào)“交叉代碼評(píng)審” 的原因。為了減少橫向溝通的時(shí)間,提高合作效率,大家最好是在一份理解的比較透徹的代碼庫(kù)上進(jìn)行協(xié)作。如果發(fā)現(xiàn)某段代碼出現(xiàn)難于理解的情況,立即自我檢 查、并于其他同事討論,大家一起拿出來(lái)個(gè)所有人都易于理解的辦法來(lái)。切勿打出“時(shí)間緊,以后再說(shuō)”(一旦說(shuō)出這種話,我還沒(méi)見(jiàn)過(guò)以后還有人會(huì)主動(dòng)回過(guò)頭來(lái) 整理代碼)的擋箭牌或者“勿要過(guò)分偏執(zhí)于代碼質(zhì)量”這樣的理由。 實(shí)際上,很多工作中的溝通不暢都是源于對(duì)產(chǎn)品代碼、設(shè)計(jì)、架構(gòu)的理解不到位。對(duì)于這個(gè)問(wèn)題,我提倡采用極限編程或與其等效的協(xié)同式結(jié)對(duì)或組團(tuán)工作法,同時(shí)縮短代碼評(píng)審周期。所有一線程序員一定要經(jīng)常舉行20-25分鐘左右(番茄法)的技術(shù)討論對(duì)話。 縱向地說(shuō),代碼的理解實(shí)際上也是對(duì)程序員本人業(yè)務(wù)能力的一種拓展訓(xùn)練。在沒(méi)有系統(tǒng)地學(xué)習(xí)敏捷開(kāi)發(fā)等代碼管控技術(shù)之前,我經(jīng)常對(duì)自己幾個(gè)月前、幾 周前甚至三天前所寫(xiě)的代碼一頭霧水,根本不清楚當(dāng)時(shí)是在何種情境下寫(xiě)出那些代碼的。協(xié)同工作時(shí)更為嚴(yán)重,我們不僅要理解自己很久以前寫(xiě)過(guò)的代碼,還要理解 其他同事甚至離職人員的遺留代碼。如果不及時(shí)進(jìn)行品質(zhì)管控,整個(gè)項(xiàng)目就無(wú)法繼續(xù)健康的運(yùn)作下去,因?yàn)閷?duì)當(dāng)前模塊的編寫(xiě)勢(shì)必要引入原來(lái)的既有模塊,而且為了 應(yīng)對(duì)復(fù)雜多變的需求,必須經(jīng)常把原來(lái)的代碼拿出來(lái)曬太陽(yáng),以便理順?biāo)悸?,盡速應(yīng)對(duì)需求。長(zhǎng)時(shí)間進(jìn)行有意識(shí)的質(zhì)量訓(xùn)練,就可以在工作中積累大量的代碼范式和可復(fù)用模塊,并且在遇到新工作的新需求時(shí)及時(shí)從腦中呼出原有的高品質(zhì)解決方案。 在談到對(duì)“理解”的判定標(biāo)準(zhǔn)時(shí),ARC的作者提出的標(biāo)準(zhǔn)也比較有參考性。他們認(rèn)為,代碼閱讀者能夠?qū)ζ渥鞒鲂薷?、能夠指出其中的Bug、能夠理解它與其余部分代碼是如何進(jìn)行溝通的。做到了上述這些,才算“完全理解了代碼”。小翔我雅以為是。在進(jìn)行上述我提到的橫向和縱向溝通時(shí),都必須以“徹底”理解為溝通目標(biāo),不要蒙混過(guò)去。 除了橫向和縱向的溝通問(wèn)題之外,還有一個(gè)問(wèn)題就是如何處理代碼質(zhì)量與其他工程要素之間的關(guān)系,例如代碼執(zhí)行效率、軟件設(shè)計(jì)與架構(gòu)、代碼是否易于測(cè)試等等。很多反對(duì)花時(shí)間提升代碼質(zhì)量的人都拿這些來(lái)做文章。不過(guò)依我在實(shí)際工作中的感覺(jué)是,如果因?yàn)榇a品質(zhì)得不到保證而導(dǎo)致溝通不暢,那么相應(yīng)的效率、架構(gòu)、易測(cè)試性都可能隨之出現(xiàn)問(wèn)題,因?yàn)樗鼈冏罱K都要落實(shí)到具體代碼與具體開(kāi)發(fā)者身上,一個(gè)尊崇易讀性的編碼環(huán)境才能催生執(zhí)行高效、架構(gòu)合理、易于測(cè)試的代碼。 原來(lái)我之所以沒(méi)有及時(shí)將代碼質(zhì)量的相關(guān)心得與想法總結(jié)起來(lái),很重要的一個(gè)原因是代碼質(zhì)量所涉及的知識(shí)點(diǎn)太多、太散,而且和其他話題聯(lián)系頗多。在 開(kāi)始讀ARC這本書(shū)之后,我決定依照可讀性為主線,把我這個(gè)有代碼潔癖者的所思所想整理成系列文章,這樣更方便按照主題去閱讀、研究。 說(shuō)到到可讀性的具體判定標(biāo)準(zhǔn),這則是要靠每個(gè)人在學(xué)習(xí)、工作中不斷總結(jié)出來(lái)的。很多教材整本書(shū)所講的就是如何依據(jù)一系列的經(jīng)驗(yàn)法則來(lái)指導(dǎo)編程,比如《Clean Code》。我以為不妨按照代碼層級(jí),將可讀性的研究分為“零散代碼改觀”、“簡(jiǎn)化邏輯與循環(huán)”、“宏觀結(jié)構(gòu)重整”三 個(gè)部分。零散代碼改觀涉及函數(shù)或方法內(nèi)部的命名與注釋等僅涵蓋數(shù)行代碼的初階問(wèn)題。而邏輯與循環(huán)則是函數(shù)或方法代碼中的核心部分,它們通常以代碼塊或數(shù)行 與流程相關(guān)的代碼組成。針對(duì)此部分的品質(zhì)提升,主要表現(xiàn)在梳理控制流、簡(jiǎn)化表達(dá)式、考究循環(huán)控制變量等問(wèn)題。結(jié)構(gòu)重整就是在更為宏觀的函數(shù)、類(lèi)、包等級(jí)別 上進(jìn)行質(zhì)量管控。 為了說(shuō)明代碼質(zhì)量不是隨心所欲能決定的,我就翻炒一下ARC中的幾個(gè)小例子(小栗子)。 跟著感覺(jué)走,有時(shí)不可靠。 Node node = list.head; if (node == null) return; while (node.next != null) { print(node.data); node = node.next; } if (node != null) print(node.data); 這段代碼當(dāng)然不如下面這段簡(jiǎn)潔,這大家憑感覺(jué)就能看出來(lái): for (Node node = list.head; node != null; node = node.next) print(node.data); 然而 return exponent >=0? mantissa *(1<< exponent): mantissa /(1<<-exponent); 與 if (exponent >= 0) { return mantissa * (1 << exponent); } else { return mantissa / (1 << -exponent); } 誰(shuí)好誰(shuí)壞就難說(shuō)了。第一個(gè)更簡(jiǎn)潔,第二個(gè)更具親和力。 短代碼未必不好 assert((!(bucket = findBucket(key)))||!bucket.isOccupied()); 上一段代碼的可讀性不如下一段: bucket = findBucket(key); if(bucket !=null)assert(!bucket.isOccupied()); 多寫(xiě)點(diǎn)注釋也好 // 更快地執(zhí)行" hash = (65599 * hash) + c"hash =(hash <<6)+(hash <<16)- hash + c; 上面這段代碼多虧了這個(gè)注釋?zhuān)駝t立刻滑入雜技代碼的深淵。 下幾篇系列文章將講述如何選取易讀的標(biāo)識(shí)符名稱(chēng)。 | 
|  | 
來(lái)自: rookie > 《技術(shù)帖》