軟體工程師忽略的隱形成本

發表於2015-01-04

有時候我們說,“實現這個功能,我只花了幾個小時”。但是完成之後,我們發現每隔幾周,我們要麼在修復該功能的bug、向另一個工程師解釋,要麼做客服回答問題、以解釋其工作原理。維護該功能總的投入時間要遠遠超過最初開發的幾個小時。

軟體開發中內化的最艱難教訓之一就是額外複雜度所帶來的隱形成本。有時候,複雜度在問題領域只是固有的。為了匹配乘客和司機,通過調整價格來平衡供求是一個複雜和痛苦的問題。因此,在擴大一個社群和維護社群質量的時候,把問題和答案疏通到喜歡回答和看問題的人們那裡,也是如此。或者像是開發一個相容所有裝置的富文件編輯器以支援實時協作。這是固有的複雜度,我們需要根據產品做出調整以取得成功。

但是其它時候,和我們較勁的複雜度恰恰是我們自己產生的複雜度。我們用新程式語言寫程式碼,很少人瞭解它,現在我們不得不維護它。或者我們增加了額外的基礎架構,因為我們嘗試從Hacker News看到的、熱門新技術,但是它失敗了,這是我們當初沒有想到的。或者我們引入了一個很少人使用的功能,但是修復和bug報告就花掉了極不對稱的大把時間。

額外的複雜度暴露了很多隱形成本。在開發軟體時,我們所做的決定不只是決定了我們當前的開發速度。它們還要反映出我們花在維護上的時間和努力程度。

複雜度的隱形成本

太多複雜度增加了認知負荷,併產生了做完事情的額外阻力。它以很多不同的方式滲入到團隊裡——大部分是直接滲入到程式碼、系統和產品複雜度裡,但是間接地滲入到了組織的複雜度裡。我們逐個看看這幾種不同型別複雜度的隱形成本。

程式碼複雜度

程式碼複雜度不只是隨著程式碼行數的線性函式而增長——它組合式地增長。在複雜的程式碼庫裡,每行程式碼可能與其它很多行程式碼互動和影響。我們對於組合式增長難以有足夠的認識,這就是為什麼我們傾向於嚴重低估完成大型軟體專案所需要的時間。這也是重寫專案有時候會大幅延期的主要原因。

當程式碼過於複雜的時候,它將變得難以擴充套件、難以理清其中緣由、難以修復bug,很難理清追蹤錯誤來源的依賴和資料流向。工程師或許會積極地避免程式碼庫最複雜的部分,即使它是可以做某種修改的、最有邏輯的地方,也要選擇繞彎來解決。他們或許避免把那些地方都組合起來,即使這項工作有著很大的影響。

系統複雜度

工程師喜歡擺弄新玩具,要麼因為好奇,要麼因為他們認為新技術可能為解決他們的緊迫問題提供了銀彈。當Pinterest在2011年剛開始擴容網站以應對快速增長時,他們只有3個工程師的後端小組卻使用了6種不同的儲存技術(MySQL、Cassandra、Membase、Memcache、Redis和MongoDB)。他們實驗每項新技術的諾言都是解決現有系統的某些限制。但是,每種新解決方案都以其自身特定方式失敗了,為了管理和維護而投入了更多時間和努力。最終,團隊明白了,增加更多機器而不是更多技術,更能簡化擴容,因此他們消除了Cassandra和MongoDB之類的系統,強化了架構的已有元件。

把基礎架構切分為太多系統,會帶來很多隱形成本。注意力被分散到了多個系統。對於每個系統來說,更難以整合資源以開發可複用的資源庫,更難以為日常工作招聘新人,更難以理解具體的失敗模式和每個系統的效能特點。每個系統的抽象最終變得更弱,因為沒有可投入的太多時間。當工具和抽象太複雜、或太多的時候,讓團隊去理解和探索將變得困難。

產品複雜度

產品複雜度可以導致一個不明確的版本、或引發缺乏產品聚焦的無節制野心。它希望在很多地方是優秀的,而不只是在一個核心領域,這種慾望使其不能向新使用者明確地解釋產品的意圖。產品複雜度引發了更多的程式碼和系統複雜度——團隊增加更多程式碼、更多基礎架構以支援新功能。當產品外圍寬泛時,增加一個新功能或修改現有功能,將需要放大很多的努力來理解和適應舊的功能。

過於複雜的產品意味著有更多的程式碼分支,更多要考慮的問題、更多的需要團隊解決的bug反饋。工程師和資料科學家需要分析更多的變數、做更多的一次性的報表,而不是集中於核心使用者行為的理解上。工程師需要投入更多時間來提供功能空間和提高效率。每個人最終在更多的專案中進行切換。投入在維護所有這些功能上的時間,並不是重新投入程式碼、償還技術債務、加固抽象的時間。

組織的複雜度

程式碼、系統和產品複雜度,依次產生了組織的複雜度。團隊需要僱傭更多人來處理和維護已開發的所有東西。越大的團隊意味著越多的溝通成本、越多的協調和和越低的總體效率。招聘過程本身,涉及的所有面試和彙報,消耗了團隊很大比例的時間。當然,所有新員工不得不被培訓才能上崗。

僱傭更多人的替代方法,就是將工程師組成劃分成更小的團隊——或許甚至建立了一人小組——來承擔較多程式碼、系統和產品外圍的工作。這降低了溝通成本,但是一人小組有他們自己的成本。一旦遇到難題,就完全拖延了專案中的唯一人手,因為有更少的人來分享這些低谷期,這種體驗對於士氣是有害的。與其他人合作的機會少了,會傷害到工作場所的快樂和員工的留任。除非每個人比較自覺,而且主動詢問反饋,否則個人收到的工作反饋將更少,因為分享相同專案上下文的人更少了。減少的反饋能夠降低程式碼質量、或因疏忽導致的複雜度引入到程式碼庫或基礎架構裡。

如何應付複雜度

Tony Hoare在1980年圖靈獎的演講中建議,“構造軟體設計有兩種方法:一種是簡單,明顯地沒有缺陷;另一種方法是使其複雜,卻沒有明顯的缺陷。”提到了由於複雜度而導致的非明顯的缺陷是如何傷害我們的,以及我們該如何應對這些成本?

下面是你能夠用到的一些策略:

  • 為簡單而優化。抵制增加更多複雜的主張。深思維護成本。要自問,為了解決20%的問題而引入的複雜是否值得,或者80%的解決方案已經足夠了。
  • 為你的團隊或產品定義一種任務說明以統一思想。在Team Geek,Brian W. Fitzpatric和Ben Collins-Sussman解釋了他們是如何輔導Google Web Toolkit(GWT)團隊、並鼓勵他們寫下任務說明的。接下來發生的、對於任務說明的內容和形式的爭論,表明了首席工程師並不真正認同產品方向!他們被迫面對、協調不同、並最終達成了,“GWT的任務是為使用者徹底提升web體驗,讓開發者使用現有的Java工具在任意現代瀏覽器裡構建高效能的AJAX。”如果他們不能儘早找出這些區別,隨之而來的努力上的分散又有多少呢?
  • 用較小的構建塊組成大型系統。Google就是個例子,致力於構建健壯的核心抽象,然後被非常寬泛的應用程式廣為使用。他們有基礎的構建塊,像Protocol Buffers、Google File System和遠端程式呼叫的Stubby伺服器。基於這些構建塊之上,他們還建立了其它抽象,比如MapReduce和BigTable。在此之上,包括大型web索引、Google Analytics站點追蹤、Google News聚合、Google Earth資料處理、Google Zeitgeist資料分析在內的數以千計的應用程式,還有很多程式都是這樣構建的。
  • 清晰地定義模組和服務之間的介面。模組和服務的退耦,將減少能夠產生一堆程式碼的組合複雜度。在Amazon,Jeff Bezos於2002年宣稱,公司將轉向面向服務的架構,所有團隊只能通過服務層級的介面彼此交流。雖然這個轉變造成了本身巨大的開發成本,但是它強制分離了程式碼和服務背後的邏輯,為現在極度成功的Amazon Web Services的建立提供了便利。
  • 優先償還技術債務。我們總是在資訊不完全的條件下開發軟體。做為條件變化的響應,程式碼庫在增大,熵也在增大。增加的複雜度成為了未來開發的代價。在開發日常上預算時間可以減少這項成本。很多工程師和團隊在專案之間預算這項時間,不過召開一次性的會議會有幫助。我過去在Quora組織過一次Code Purge Day(程式碼消除日)活動,工程師在這一天全部關注刪除無用程式碼的工作。我們在積分牌上追蹤程式碼消除的進度,這使得刪除你自己的程式碼更有趣味。
  • 使用資料修剪沒用的功能。在Yammer,當工程師或產品經理髮現在程式碼重構時,強化或保留一個功能將花費不菲的時間時,他們將檢視使用資料,以確定這項功能是否真正被使用了。如果沒有,他們將和團隊一起決定,他們是否應該只是砍掉這個功能以降低總體工作量。與簡化的程式碼是怎樣減少技術債務一樣,這個策略也減少了技術債務。
  • 基於主題對進行中的專案分組。使同事彼此分享同樣的環境,更容易地參與到設計討論、程式碼評審或構建可複用的資源庫。所有這些活動有助於提供檢查和平衡掉單個人或其他人所引發的問題。

當我們為學校課程開發軟體時,我們有著世界的過於簡單的視角——維護任意複雜度的成本隨著下課而消失了。但是在我們的職業生涯中,糟糕的軟體決定將產生數年負擔的影響。

不要使事情複雜化。

相關文章