第 3 章 能工作的程式碼是不夠的
(戰略式程式設計與戰術式程式設計)
好的軟體設計中最重要的要素之一是你在執行程式設計任務時所採用的思維方式。許多組織都鼓勵採取戰術式(Tactical)的思維方式,著眼於儘快讓功能正常執行。但是,如果你想要一個好的設計,就必須採取更具戰略性(Strategic)的方法,投入時間做出整潔的設計並解決問題。本章討論為什麼從長遠來看,戰略式方法既能產生更好的設計,實際成本又比戰術式方法更低。
本章概覽能工作的程式碼,為什麼還不夠?
3.1 戰術式程式設計
大多數程式設計師以我稱為戰術式程式設計的思維方式來進行軟體開發。在戰術式方法中,你主要關注的是讓程式碼正常工作,例如實作新功能或修復程式碼缺陷。乍一看,這似乎是完全合理的:還有什麼比編寫能工作的程式碼更重要的呢?但是,戰術式程式設計幾乎不可能產生良好的系統設計。
戰術式程式設計的問題在於它是短視的。如果你是戰術式程式設計人員,那麼你會設法儘快完成任務。也許你有必須遵守的截止期限,因此不會優先考慮為未來做計劃。你不會花費太多時間來尋找最佳設計。你只想儘快讓程式碼工作起來。你告訴自己,增加一些複雜性或引入一兩處蹩腳的小實作,如果可以使當前任務更快地完成,那就沒什麼大不了的。
系統就是這樣變得複雜的。如上一章所述,複雜性是逐步積累的。系統變得複雜,不是某個特定因素造成的,而是幾十或數百個小問題積累的結果。如果你在編碼時總是使用戰術式思維方式,那麼每個程式設計任務都會帶來一些此類複雜性。為了快速完成當前任務,每增加一點複雜性似乎都是合理的折衷。但是,複雜性會迅速累積,尤其是每個人都使用戰術式程式設計的時候。
不久之後,某些複雜性將開始引起問題,你也會開始後悔當初走了捷徑。但是,你會告訴自己,使下一個功能正常工作比回頭重構現有程式碼更為重要。從長遠來看,重構可能會有所幫助,但是肯定會拖慢當前任務的進度。因此,你會透過快速修補程式來解決遇到的問題。這只會增加更多的複雜性,繼而需要更多的補丁。很快,程式碼就變得一團糟,到了這個地步,清理它需要花費數月的時間。你的日程安排無法承受這種延誤,只解決一兩個問題似乎也於事無補,因此你就只能繼續保持這種戰術式的程式設計方式。
如果你長期參與大型軟體專案,我想你很可能已經在工作中見過戰術式程式設計,也遇到過它所導致的問題。一旦走上戰術式程式設計的道路,就很難改變。
幾乎每個軟體開發組織都有至少一個將戰術式程式設計發揮到極致的開發人員:戰術龍捲風。戰術龍捲風是一位多產的程式設計師,他產出程式碼的速度比其他人快得多,但完全以戰術方式工作。需要快速實作某項功能時,沒有人能比戰術龍捲風更快地完成任務。在某些組織中,管理層將戰術龍捲風視為英雄。但是,戰術龍捲風所到之處一片狼藉。將來必須使用其程式碼的工程師很少會把他們視為英雄。通常,其他工程師必須清理戰術龍捲風留下的混亂局面,這使得那些工程師(他們才是真正的英雄)的進度似乎比戰術龍捲風慢。
譯者圖解一次捷徑如何變成持續負擔?
3.2 戰略式程式設計
成為一名優秀的軟體設計師的第一步是要意識到 能工作的程式碼是不夠的。引入不必要的複雜性以更快地完成當前任務是不可接受的。最重要的是系統的長期結構。任何系統中的大多數程式碼都是在擴充現有程式碼庫時編寫的,因此,作為開發人員,最重要的工作就是讓未來的擴充更容易。儘管你的程式碼當然必須能工作,但你不應將“能工作的程式碼”視為主要目標。你的主要目標必須是做出出色的設計,同時讓程式碼正常工作。這就是 戰略式程式設計。
戰略式程式設計需要一種投資的思維方式。你必須花費時間來改進系統的設計,而不是採取最快的方式來完成當前的專案。這些投資會在短期內讓你放慢腳步,但從長遠來看會加快你的速度,如圖 3.1 所示。
有些投資是主動進行的。例如,值得花一些時間為每個新類找到一個簡單的設計,而不是直接去實作第一個想到的辦法。請嘗試幾種替代設計並選擇最整潔的設計。設想一下將來可能需要對系統做出哪些更改,並確保你的設計會讓這些更改更為容易。編寫好的文件是主動投資的另一個例子。
其他投資則是對已發現問題的應對。無論你預先投入多少,設計決策中都不可避免地會出現錯誤。隨著時間的推移,這些錯誤將變得顯而易見。發現設計問題時,不要忽略它或只是對其進行簡單的修補。花一些額外的時間來真正修復它。如果你進行戰略式程式設計,就會不斷對系統設計進行小幅改進。戰術式程式設計則恰恰相反:你不斷增加複雜性,而這些複雜性將來會引起問題。
3.3 該投資多少?
那麼,投入多少才合適?大量的前期投資(例如嘗試一次性設計整個系統)並不有效。這是瀑布式方法,我們知道它不起作用。隨著你對系統的理解不斷深入,理想的設計會逐漸湧現出來。因此,最好的方法是持續進行小額投資。我建議你將總開發時間的大約 10% 到 20% 用於投資。這個比例足夠小,不會對你的日程安排產生重大影響,但又足夠大,可以隨著時間的推移產生重大收益。因此,你的初始專案將比純戰術式方法多花費 10% 到 20% 的時間。額外投入的時間將帶來更好的軟體設計,並且你將在幾個月內開始體會到這些好處。不久之後,你的開發速度將比戰術式程式設計快至少 10 到 20%。到這個時候,你的投資就不再需要額外成本了:過去的投資所節省的時間將足以支援未來的投資。你將迅速收回初始投資的成本。圖 3.1 說明了這種現象。
圖 3.1:一開始,戰術式程式設計方法將比戰略式方法更快地取得進展。但是,在戰術式方法下,複雜性積累得更快,從而降低了生產率。隨著時間的推移,戰略式方法會取得更多進展。注意:此圖僅用於定性說明,我尚未見到關於這些曲線精確形狀的實證資料。
相反,如果你進行戰術式程式設計,則可以將初始專案的完成速度提高 10% 到 20%,但是隨著時間的推移,複雜性的累積會降低開發速度。不久之後,你的程式設計速度至少會降低 10% 到 20%。你將很快耗盡起初節省的所有時間,並且在系統此後的生命週期中,與採用戰略式方法相比,你的開發速度將更加緩慢。如果你從未碰到過品質非常糟糕的程式碼庫,請與有經驗的人談一談。他們會告訴你糟糕的程式碼品質會使開發速度至少降低 20%。
術語 技術債 經常被用來描述戰術式程式設計所導致的問題。透過戰術式程式設計,你將從未來借用時間:現在的開發會變得更快,但以後會更慢。就像金融債務一樣,你償還的金額將超過你借用的金額。與金融債務不同,大多數技術債永遠不會完全償還:你將不斷償還,直到永遠。
圖 3.1 提出了一個重要的問題:戰略式曲線和戰術式曲線的交叉點在哪裡?換句話說,戰略式方法要多久才能收回成本?不幸的是,我不知道是否有任何關於這個問題的資料,而且很難透過有說服力的受控實驗來回答這個問題。我個人認為,收回成本需要 6 到 18 個月。這很大程度上與開發人員的記憶力有關:一段程式碼寫完幾個月之後,開發人員已經忘記了編寫時的大部分思路,所以如果程式碼很複雜,開發速度會顯著下降。這些額外的成本很快就會抵消戰略式程式設計最初帶來的收益。同樣,這只是我的個人意見,我沒有任何資料來支援它。
譯者注(原文疑點):本段原文寫的是“戰略式程式設計最初帶來的收益”。結合前文“戰術式程式設計先省時、後付出額外成本”的論述,此處疑應為“戰術式程式設計”。正文保留原文表述。
3.4 初創公司與投資
在某些環境中,現實壓力會阻礙戰略式方法的實施。例如,初創公司在早期面臨巨大的壓力,需要儘快釋出產品的早期版本。在這些公司中,10% 至 20% 的投資似乎也負擔不起。結果,許多初創公司採取了戰術式方法,在設計上花費的精力很少,在問題出現後花在清理上的精力更少。他們為此辯解說,只要公司成功了,就有足夠的錢聘請更多工程師來清理問題。
如果你正在一家朝著這個方向發展的公司工作,就應該意識到,一旦程式碼庫像義大利麵條一樣糾纏不清,就幾乎不可能修復。你可能會在產品的整個生命週期內付出高昂的開發成本。此外,好設計的收益或壞設計的代價很快就會顯現,因此戰術式方法甚至很可能無法讓你的首個產品更早釋出。
另一件要考慮的事情是,公司成功的最重要因素之一就是工程師的素質。降低開發成本的最佳方法是聘請優秀的工程師:聘用他們的成本不會比普通工程師高很多,但生產率卻高得多。但是,最好的工程師非常重視良好的設計。如果你的程式碼庫很糟糕,這個訊息總是會傳出去,讓招聘變得更加困難。最終你可能只能聘請普通工程師。這將增加未來的成本,並可能導致系統結構進一步退化。
Facebook 在初創時期曾鼓勵戰術式程式設計。多年來,公司的座右銘是“快速行動,不怕犯錯”。公司鼓勵剛大學畢業的新工程師立即著手修改公司的程式碼庫;工程師在工作的第一週就將程式碼部署到生產環境也很常見。從積極的一面來看,Facebook 以給予員工充分自主權而享有聲譽。工程師擁有極大的自由度,幾乎沒有什麼規則和限制束縛他們。
Facebook 作為一家公司已經取得了令人矚目的成功,但是公司的戰術式方法損害了程式碼庫的品質。許多程式碼不穩定且難以理解,幾乎沒有註解或測試,使用起來很痛苦。隨著時間的推移,該公司意識到這種文化不可持續。最終,Facebook 將座右銘改為“在堅實的基礎架構上快速行動”,以鼓勵工程師在良好的設計上進行更多投資。但 Facebook 是否能夠成功清理多年來戰術式程式設計積累的問題,還有待觀察。
為了公平起見,我應該指出,Facebook 的程式碼可能並不比初創公司的平均水平差很多。戰術式程式設計在初創公司中司空見慣,Facebook 只是一個特別明顯的例子。
幸運的是,使用戰略式方法也有可能在矽谷取得成功。Google 和 VMware 與 Facebook 差不多同時成長,但是這兩家公司都採用了更具戰略性的方法。兩家公司都非常重視高品質的程式碼和良好的設計,也都開發了精良的產品,透過可靠的軟體系統解決複雜的問題。這兩家公司濃厚的技術文化在矽谷廣為人知。在招聘頂級技術人才方面,很少有其他公司能與它們競爭。
這些例子表明,公司採用任何一種方法都有可能成功。但是,在一家關心軟體設計並擁有整潔程式碼庫的公司中工作會有趣得多。
3.5 結論
好的設計需要付出成本。你必須持續為它投入精力,小問題才不會累積成大問題。幸運的是,好的設計最終會收回成本,而且比你想像的要早。
始終如一地運用戰略式方法,把投資看作今天而不是明天要做的事情,這一點至關重要。在緊要關頭,人們很容易想把清理工作推遲到忙過這一陣之後。但是,這樣很容易越拖越久(slippery slope):當前的忙碌過去之後,幾乎肯定還會有下一次,接著又是下一次。一旦開始推遲設計改進,就很容易一直拖下去,讓團隊文化滑向戰術式方法。設計問題拖得越久,就會變得越大;解決起來也越令人畏難,於是更容易繼續拖延。最有效的方法是,每位工程師都持續為良好的設計做小額投資。
