軟件設計的哲學:第十六章 修改現有代碼

第1章描述了軟件開發是如何迭代和增量的。大型軟件系統的開發經歷了一系列的演化階段,每一個階段都添加了新的功能並修改了現有的模塊。這意味着系統的設計是不斷髮展的。不可能在一開始就構思出一個系統的正確設計;一個成熟系統的設計更多地取決於系統發展過程當中的變化,而不是最初的概念。前幾章描述瞭如何在初始設計和實現過程當中擠出複雜性;本章討論瞭如何防止複雜性隨着系統的發展而增長。編程

16.1 保持戰略

第3章介紹了戰術編程和戰略編程的區別:在戰術編程中,主要目標是讓某些東西快速工做,即便這會致使額外的複雜性;在戰略規劃中,最重要的目標是產生一個偉大的系統設計。戰術方法很快就會致使混亂的系統設計。若是你想要一個易於維護和加強的系統,那麼「工做」並非一個足夠高的標準;你必須優先考慮設計並有策略地思考。這種思想也適用於修改現有代碼時。ide

不幸的是,當開發人員深刻到現有的代碼中進行諸如bug修復或新特性之類的更改時,他們一般不會進行戰略性思考。一個典型的心態是「我能作的最小的改變是什麼來知足個人須要?」「有時候開發人員這麼作是由於他們不喜歡修改代碼;他們擔憂更大的變化會帶來更大的引入新bug的風險。然而,這致使了戰術規劃。這些最小的更改中的每個都引入了一些特殊的狀況、依賴項或其餘形式的複雜性。結果,系統設計變得更糟了,問題在系統演進的每一步中不斷累積。工具

若是您想要維護一個乾淨的系統設計,您必須在修改現有代碼時採起策略方法。 理想狀況下,當您完成了每一個更改時,系統將具備若是您從一開始就在頭腦中進行設計時所具備的結構。爲了實現這個目標,您必須抵制快速修復的誘惑。相反,考慮當前的系統設計是否仍然是最好的,根據須要的更改。若是沒有,重構系統,以獲得最好的設計。經過這種方法,系統設計能夠隨着每一次修改而改進。學習

這也是第15頁介紹的投資思惟模式的一個例子:若是您投入一點額外的時間來重構和改進系統設計,您將獲得一個更簡潔的系統。這將加快開發速度,而且您將收回您在重構中投入的精力。即便您的特定更改不須要重構,您也應該在代碼中尋找能夠修復的設計缺陷。不管什麼時候修改任何代碼,都要設法改進系統設計,至少在這個過程當中改進一點。若是您沒有使設計變得更好,那麼您可能使它變得更糟。開發工具

正如在第3章中所討論的,投資心態有時與商業軟件開發的現實相沖突。若是以「正確的方式」重構系統須要3個月的時間,而快速而糟糕的修復只須要2個小時,那麼您可能不得不採起快速而糟糕的方法,特別是在您的工做時間緊迫的狀況下。或者,若是重構系統會形成不兼容,從而影響許多其餘人員和團隊,那麼重構多是不實際的。設計

儘管如此,您應該儘量地抵制這些妥協。問問你本身:「考慮到我目前的限制,這是我所能作的最好的建立一個乾淨的系統設計嗎?」也許有一種方法能夠像3個月的重構那樣簡潔,但能夠在幾天內完成?或者,若是你如今沒法承擔大規模重構的費用,讓你的老闆給你分配時間,讓你在當前的最後期限以後再作重構。每一個開發組織都應該計劃將其所有工做的一小部分用於清理和重構,這項工做從長遠來看是值得的。調試

16.2 維護註釋:將註釋放在代碼附近

當您更改現有代碼時,極可能會使某些現有註釋失效。修改代碼時很容易忘記更新註釋,這將致使註釋再也不準確。不許確的註釋會讓讀者感到沮喪,若是註釋太多,讀者就會開始懷疑全部的註釋。幸運的是,只要有一點規則和一些指導原則,就能夠在不付出巨大努力的狀況下更新註釋。本節和如下各節提出了一些具體的技術。日誌

確保註釋獲得更新的最佳方法是將它們放置在它們所描述的代碼附近,以便開發人員在更改代碼時可以看到它們。 註釋離相關代碼越遠,正確更新它的可能性就越小。例如,方法接口註釋的最佳位置是在代碼文件中,就在方法體旁邊。對方法的任何更改都將涉及這段代碼,所以開發人員可能會看到接口註釋並在須要時更新它們。code

對於像C和c++這樣具備獨立代碼和頭文件的語言,另外一種替代方法是將接口註釋放在.h文件中方法聲明的旁邊。然而,這是一個很長的路從代碼;開發人員在修改方法主體時不會看到這些註釋,並且須要額外的工做來打開不一樣的文件並找到接口註釋來更新它們。有些人可能認爲接口註釋應該放在頭文件中,這樣用戶就能夠學習如何使用抽象,而沒必要查看代碼文件。可是,用戶不須要讀取代碼或頭文件;他們應該從Doxygen或Javadoc等工具編譯的文檔中獲取信息。此外,許多ide將提取並向用戶顯示文檔,例如在鍵入方法名稱時顯示方法的文檔。對於這樣的工具,文檔應該放在對開發人員編寫代碼最方便的地方。

在編寫實現註釋時,不要將整個方法的全部註釋放在方法的頂部。將它們展開,將每一個註釋下推到最窄的範圍,包括註釋所引用的全部代碼。例如,若是一個方法有三個主要的階段,不要在方法的頂部寫一個註釋來詳細描述全部的階段。相反,爲每一個階段編寫一個單獨的註釋,並將該註釋置於該階段的第一行代碼之上。另外一方面,在一個方法的實現頂部有一個描述總體策略的註釋也是有幫助的,就像這樣:

//  We proceed in three phases:

//  Phase 1: Find feasible candidates

//  Phase 2: Assign each candidate a score

//  Phase 3: Choose the best, and remove it

16.3 註釋屬於代碼,而不是提交日誌

在修改代碼時,一個常見的錯誤是在源代碼存儲庫的提交消息中放入關於更改的詳細信息,而不是在代碼中記錄它。儘管未來能夠經過掃描存儲庫日誌來瀏覽提交消息,可是須要這些信息的開發人員不太可能想到要掃描存儲庫日誌。即便他們確實掃描日誌,查找正確的日誌消息也會很繁瑣。

在編寫提交消息時,問問本身未來開發人員是否須要使用這些信息。若是是,那麼在代碼中記錄這些信息。提交消息就是一個例子,它描述了一個引發代碼更改的微妙問題。若是代碼中沒有對此進行記錄,那麼開發人員可能稍後出現並撤消更改,而沒有意識到他們從新建立了一個錯誤。若是您也想在提交消息中包含此信息的副本,那很好,但最重要的是在代碼中得到它。這說明了將 文檔放在開發人員最可能看到的地方的原則;提交日誌不多在那個地方。

16.4 保留註釋:避免重複

使註釋保持最新的第二種技術是避免重複。若是文檔是重複的,開發人員就很難找到並更新全部相關的副本。相反,儘可能只記錄一次每一個設計決策。若是代碼中有多個地方受到某個特定決策的影響,那麼不要在每一個地方重複文檔。相反,找到最明顯的地方放置文檔。例如,假設有一個與變量相關的複雜行爲,它會影響變量使用的幾個不一樣位置。您能夠在變量聲明旁邊的註釋中記錄該行爲。若是開發人員在理解使用該變量的代碼時遇到困難,這是一個很天然的地方。

若是沒有一個「明顯的」地方來放置特定的文檔,開發人員能夠找到它,那麼建立一個designNotes文件,如第13.7節所述。或者,選擇最好的地方,把文檔放在那裏。另外,在引用中心位置的其餘地方添加簡短的註釋:「查看xyz中的註釋以瞭解下面代碼的解釋。「若是引用由於主註釋被移動或刪除而變得過期,這種不一致性將是不言而喻的,由於開發人員不會在指定的地方找到註釋;他們可使用修訂控制歷史記錄來查找註釋發生了什麼,而後更新引用。相反,若是文檔是重複的,而且一些副本沒有獲得更新,那麼開發人員就不會知道他們使用的是陳舊的信息。

若是信息已經在程序以外的某個地方記錄了,不要在程序內部重複記錄;只需參考外部文檔。例如,若是您編寫一個實現HTTP協議的類,那麼就不須要在代碼中描述HTTP協議。在網上已經有不少關於這個文檔的來源;只需在您的代碼中添加一個簡短的註釋,併爲其中一個源添加一個URL。另外一個例子是已經在用戶手冊中記錄的特性。假設您正在編寫一個實現命令集合的程序,其中有一個方法負責實現每一個命令。若是有描述這些命令的用戶手冊,就不須要在代碼中重複這些信息。相反,在每一個命令方法的接口註釋中包含以下簡短說明:

// Implements the Foo command; see the user manual for details.

重要的是讀者能夠很容易地找到全部須要的文檔來理解您的代碼,但這並不意味着您必須編寫全部的文檔。

不要在另外一個模塊中從新記錄一個模塊的設計決策。例如,不要在一個方法調用以前加上註釋來解釋在被調用的方法中發生了什麼。若是讀者想知道,他們應該查看方法的接口註釋。好的開發工具一般會自動提供這些信息,例如,若是您選擇了一個方法的名稱或者將鼠標懸停在它上面,就會顯示該方法的接口註釋。儘可能讓開發人員容易地找到適當的文檔,但不要重複文檔。

16.5 維護註釋:檢查差別

確保文檔保持最新的一個好方法是,在將更改提交到修訂控制系統以前花幾分鐘時間掃描提交的全部更改,確保每一個更改都正確地反映在文檔中。 這些預提交掃描還將檢測其餘幾個問題,如意外地在系統中留下調試代碼或未能修復TODO項。

16.6 更高級別的註釋更容易維護

關於維護文檔的最後一個想法:若是註釋比代碼更高級、更抽象,那麼它們更容易維護。這些註釋不反映代碼的細節,所以它們不會受到代碼的細微更改的影響;只有總體行爲的改變纔會影響這些註釋。固然,正如第13章所討論的,有些註釋確實須要詳細和精確。可是通常來講,最有用的註釋(它們不會簡單地重複代碼)也是最容易維護的。

相關文章
相關標籤/搜索