程式碼質量隨想錄(一)可讀是王道

愛飛翔發表於2012-06-05

  一直以來想寫點關於程式碼質量的心得,礙於自身的懶惰。今天終究找到一個提前忙完工作的午後,可以先讓自己的思路開動起來了。

  最終促使我開始整理自己對於程式碼質量的看法,還多虧了前陣子認識的Long小朋友,他及時地向我推薦了《The Art of Readable Code》這本書(下文簡稱ARC)。在看過了馬叔叔的《Clean Code》和《Clean Coder》之後,這本書徹底讓我沉迷於程式碼質量之中了。

  我就將每天讀書所做的筆記和自己的想法綜合起來,再加上原來歷次專案之中的研究心得,陸續寫出來,與大家分享。也歡迎更多的程式碼潔癖者一起交流。

  可讀性一直是程式碼質量管控所追求的目標之一。沒有這個,後面的可維護和可修改都不太容易達成。可讀性怎麼強調都不為過,ARC一書的作者對具備可讀性的程式碼給出說法是:

  花最少的時間就能理解的程式碼。

  私以為這個定義是我暫時能找到的比較合理的解釋了。可讀性的研究應該從橫向與縱向兩個層面展開。

  橫向地說,團隊內部,尤其是開源專案,更是要維護個成員之間對程式碼的理解度。封裝與理解並不矛盾,封裝是為了更好的讓客戶程式碼理解其結構與功能,更為恰當地使用這個模組。僥倖將自己搞不清爽的程式碼以封裝的名義塞到某個莫名其妙的類中,終歸會導致那部分程式碼的缺陷通過介面或頻繁使用暴露出來。這一點是個大問題,以後專文再述。在同一個專案或者同一個模組工作的開發者之間一定要彼此理解對方所寫的程式碼。這就是我為何一再強調“交叉程式碼評審”的原因。為了減少橫向溝通的時間,提高合作效率,大家最好是在一份理解地比較透徹的程式碼庫上進行協作。如果發現某段程式碼出現難於理解的情況,立即自我檢查、並於其他同事討論,大家一起拿出來個所有人都易於理解的辦法來。切勿打出“時間緊,以後再說”(一旦說出這種話,我還沒見過以後還有人會主動回過頭來整理程式碼)的擋箭牌或者“勿要過分偏執於程式碼質量”這樣的理由。實際上,很多工作中的溝通不暢都是源於對產品程式碼、設計、架構的理解不到位。對於這個問題,我提倡採用極限程式設計或與其等效的協同式結對或組團工作法,同時縮短程式碼評審週期。所有一執行緒序員一定要經常舉行20-25分鐘左右(番茄法)的技術討論對話。

  縱向地說,程式碼的理解實際上也是對程式設計師本人業務能力的一種擴充訓練。在沒有系統地學習敏捷開發等程式碼管控技術之前,我經常對自己幾個月前、幾周前甚至三天前所寫的程式碼一頭霧水,根本不清楚當時是在何種情境下寫出那些程式碼的。協同工作時更為嚴重,我們不僅要理解自己很久以前寫過的程式碼,還要理解其他同事甚至離職人員的遺留程式碼。如果不及時進行品質管控,整個專案就無法繼續健康的運作下去,因為對當前模組的編寫勢必要引入原來的既有模組,而且為了應對複雜多變的需求,必須經常把原來的程式碼拿出來曬太陽,以便理順思路,儘速應對需求。長時間進行有意識的質量訓練,就可以在工作中積累大量的程式碼正規化和可複用模組,並且在遇到新工作的新需求時及時從腦中撥出原有的高品質解決方案。

  在談到對“理解”的判定標準時,ARC的作者提出的標準也比較有參考性。他們認為,程式碼閱讀者能夠對其作出修改、能夠指出其中的Bug、能夠理解它與其餘部分程式碼是如何進行溝通的。做到了上述這些,才算“完全理解了程式碼”。小翔我雅以為是。在進行上述我提到的橫向和縱向溝通時,都必須以“徹底”理解為溝通目標,不要矇混過去。

  除了橫向和縱向的溝通問題之外,還有一個問題就是如何處理程式碼質量與其他工程要素之間的關係,例如程式碼執行效率、軟體設計與架構、程式碼是否易於測試等等。很多反對花時間提升程式碼質量的人都拿這些來做文章。不過依我在實際工作中的感覺是,如果因為程式碼品質得不到保證而導致溝通不暢,那麼相應的效率、架構、易測試性都可能隨之出現問題,因為它們最終都要落實到具體程式碼與具體開發者身上,一個尊崇易讀性的編碼環境才能催生執行高效、架構合理、易於測試的程式碼

  原來我之所以沒有及時將程式碼質量的相關心得與想法總結起來,很重要的一個原因是程式碼質量所涉及的知識點太多、太散,而且和其他話題聯絡頗多。在開始讀ARC這本書之後,我決定依照可讀性為主線,把我這個有程式碼潔癖者的所思所想整理成系列文章,這樣更方便按照主題去閱讀、研究。

  說到到可讀性的具體判定標準,這則是要靠每個人在學習、工作中不斷總結出來的。很多教材整本書所講的就是如何依據一系列的經驗法則來指導程式設計,比如《Clean Code》。我以為不妨按照程式碼層級,將可讀性的研究分為“零散程式碼改觀”、“簡化邏輯與迴圈”、“巨集觀結構重整”三個部分。零散程式碼改觀涉及函式或方法內部的命名與註釋等僅涵蓋數行程式碼的初階問題。而邏輯與迴圈則是函式或方法程式碼中的核心部分,它們通常以程式碼塊或數行與流程相關的程式碼組成。針對此部分的品質提升,主要表現在梳理控制流、簡化表示式、考究迴圈控制變數等問題。結構重整就是在更為巨集觀的函式、類、包等級別上進行質量管控。

  為了說明程式碼質量不是隨心所欲能決定的,我就翻炒一下ARC中的幾個小例子(小栗子)。

  跟著感覺走,有時不可靠。


Node node = list.head;

if (node == null)

  return;

while (node.next != null) {

  print(node.data);

  node = node.next;

}

if (node != null)

  print(node.data);

  這段程式碼當然不如下面這段簡潔,這大家憑感覺就能看出來:


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);
}

  誰好誰壞就難說了。第一個更簡潔,第二個更具親和力。

  短程式碼未必不好


assert((!(bucket = findBucket(key))) || !bucket.isOccupied());

  上一段程式碼的可讀性不如下一段:


bucket = findBucket(key);

if (bucket != null) assert(!bucket.isOccupied());

  多寫點註釋也好


// 更快地執行"hash = (65599 * hash) + c"
hash = (hash << 6) + (hash << 16) - hash + c;

  上面這段程式碼多虧了這個註釋,否則立刻滑入雜技程式碼的深淵。

  下幾篇系列文章將講述如何選取易讀的識別符號名稱。

愛飛翔

2012年5月31日

本文使用Creative Commons BY-NC-ND 3.0協議(創作共用 自由轉載-保持署名-非商業使用-禁止衍生)釋出。

原文網址:http://agilemobidev.net/eastarlee/code-quality/think_in_code_quality_1_readablity_zh_cn/

相關文章