在 IT 技術圈中,軟件具有使用傳統管理技術難以構建的特性,這意味著軟件開發需要一種不同的、更具探索性和反複性的方法,而這種具體的措施又該如何實施?
在本文中,畢業于 MIT 計算機系、曾在 Google 做過 PM、後來擔任新加坡公民服務學院科技團隊負責人的 LI HONGYI(音譯:李鴻毅)將從自身多年經驗出發,分享構建一套優秀軟件的完整體系。
而提及李鴻毅本身,其可謂是出身非 IT 圈的 IT 之家,其母親何晶擁有新加坡國立大學電子工程專業的一等學位和斯坦福大學的電子工程碩士學位;其弟弟也是 MIT 計算機系畢業,在 Scala 圈子裏頗有名氣;而他的父親也曾在 Facebook 主頁 Po 出過一段複雜的 C++ 程序代碼,可見他的 C++ 基礎並不一般,且其還聲稱退休後打算讀一下 Haskell 教科書,另外,他的父親也就是當今的新加坡總理李顯龍。
作者 | LI HONGYI
譯者 | 彎月,責編 | 屠敏
出品 | CSDN(ID:CSDNnews)
以下爲譯文:
這個世界上金錢無法解決的事情爲數不多,然而糟糕的軟件就是其中之一。身價數十億美元的航空公司做出來的航班搜索應用往往還不如學生的作品。盡管全世界成熟的出租車公司都面臨著乘車共享服務的威脅,但他們的叫車應用依然很糟糕。痛苦的企業IT系統通常需要投入大量預算,花多年的時間才能建立起來。引發劣質軟件的原因各種各樣,但肯定不是缺乏資金。
令人驚訝的是,引發劣質軟件的根本原因似乎與工程上的抉擇關系不大,更多的是與開發項目的管理方式有關。糟糕的軟件項目的過程往往非常相似:
項目所有者想要構建某個解決方案,但絕不會明確定義希望解決的問題。然後,他們會收集一長串大量利益相關者的要求。然後,將此列表交給大型的外部開發團隊,由這個團隊從頭構建這個高度定制的軟件。等到所有需求都得到滿足後,每個人都會慶祝系統啓動,並宣布項目完成。
然而,盡管這樣的系統符合技術上的規範,但真正將它交到實際用戶手中時就會出現嚴重的問題。運行速度緩慢,使用方法含混不清,而且還塞滿了各種bug,最後只落得屢屢滑坡。不幸的是,到這個地步的時候,外部的開發團隊已然解散,已沒有剩余資源來進行必要的修複。等到幾年後,一個新的項目又啓動了,所有了解引發這些問題原因的人都已經離職,于是悲劇又一次開始重複上演。
正確的編程語言、系統架構或界面設計因項目而異。但是,軟件特有的一些特征會導致傳統的管理實踐失效,同時小型創業公司能夠以低廉的預算取得成功:
-
重用好的軟件很容易,這是快速構建優秀軟件的捷徑之一;
-
軟件的限制不在于構建它的資源量,而在于它在崩潰之前的複雜程度;
-
軟件的主要價值不在于生成的代碼,而在于生成代碼的人員積累的知識。
雖然了解這些特征可能無法保證良好的結果,但確實有助于理解爲什麽這麽多項目會産生不良後果。此外,我們還可以總結出如下核心的運營原則,從而大大提高成功的機會:
-
項目剛開始的時候越簡單越好;
-
找到問題並進行叠代;
-
聘請最優秀的工程師。
雖然還有很多更微妙的因素需要考慮,但這些原則爲我們構建優秀的軟件奠定了基礎。
借助重用快速構建優秀的軟件
軟件的複制方便易行。從機器的角度來看,你可以將代碼逐行複制並粘貼到另一台計算機上。更一般的情況,互聯網上提供了很多教程,你可以在線學習如何利用現成的代碼模塊構建不同類型的系統。現代軟件幾乎沒有一個是從頭開始開發的。即使是最具創新性的應用程序也是通過融合和修改現有的軟件而構建。
可重用代碼模塊最大的來源是開源社區。開源軟件的代碼可供自由發布,以及任何人的查看和使用。開源社區最大的貢獻者都是巨型科技公司。如果你想跟Facebook一樣使用最先進的可擴展數據庫,那麽只需下載他們于2008年開源的Cassandra代碼。如果你想親自試試Google尖端的機器學習,那麽可以下載他們于2015年發布的TensorFlow系統。使用開源代碼不僅可以加快應用程序的開發速度,而且還可以讓你接觸比你自己開發的任何技術都複雜百倍的技術。此外,流行的開源代碼更加安全,因爲關注它們以及修複它們的漏洞的人也更多。這就是數字技術發展如此迅速的原因:即使是新手工程師也可以利用我們提供的最先進的、最專業的工具構建軟件。
雲服務的出現進一步提高了可重用性,你只需支付訂閱費即可使用系統,甚至擁有專門的系統。你需要一個簡單的網站?那麽只需使用Squarespace或Wix等網站的構建服務,輕輕點擊幾下即可配置一個。你想要一個數據庫?那麽只需訂閱AWS或微軟的Azure。開發人員可以通過雲服務享受專業化的服務,由服務提供商處理設置和維護的工作,並持續爲所有的訂閱用戶開發可靠、高質量的軟件。如此一來,軟件開發人員就無需在這些問題上浪費時間,可以專心提供實際的價值。
如果我們把所有時間花在重建現有的技術上,那麽就無法取得技術上的進步。軟件工程的工作是構建自動化的系統,而我們應該自動化的第一件事就是日常的軟件工程工作。現在問題的關鍵在于了解我們需要重用哪些系統,如何根據自身的獨特需求改造這些系統,同時在這個過程中解決新發現的問題。
軟件受到複雜性的限制
軟件的功能性往往會受到複雜性的限制,卻與投入的資源量沒有太大關系。
通常IT系統都有很多功能,但仍然無法博得用戶的喜愛,因爲這些系統非常混亂。相比之下,名列前茅的移動應用往往因其簡單性和直觀性而備受稱贊。學習使用軟件很難。除此之外,實際上用戶並不喜歡新功能,因爲日益積累的複雜性終有一日會決堤。例如,iTunes在擔任蘋果的媒體生態系統中心近20年後,于今年分裂成了三個不同的應用程序(分別服務于音樂、播客和電視節目),因爲對于一個應用而言iTunes的功能過于複雜。從實用性的角度來看,限制不是說可以實現多少個功能,而是說哪些才是簡單直觀的功能。
即便抛開實用性不提,一旦項目變得過于複雜,工程進度就會停滯不前。向應用程序添加每一行新代碼都需要考慮與其他代碼的交互。應用程序的代碼庫越大,構建新功能時引入的bug就越多。最終,bug的産生速率會超越新功能的開發速率。這就是赫赫有名的“技術負債”,也是專業軟件開發的主要挑戰。這就是爲什麽許多大型IT系統中依然存在多年未解決的問題的原因。向項目添加人手只會讓局面更混亂:由于代碼庫無法承其重而崩潰,所以這些開發人員只能在原地打轉。
在這種情況下,唯一方法是以退爲進,改善並簡化代碼庫的結構。重新設計系統架構,防止意外的交互。有些非關鍵的功能,即便是已然構建完畢,也可以將其刪除。另外,我們還可以部署自動化工具來檢查bug和編寫不良的代碼。比爾·蓋茨曾說過:“用代碼行數來衡量編程的進度,就如同用重量來衡量飛機的制造進度。”人類思維只能處理有限的複雜性,因此軟件系統的複雜程度取決于如何有效地利用這些複雜度。
構建良好的軟件涉及擴展與降低複雜性的循環交替。隨著新功能的開發,系統中自然會積累混亂。當這種混亂開始引發更大的問題時,你就需要暫停進度,花時間清理混亂。這兩個步驟都是必要的過程,因爲這個世界上沒有柏拉圖式的工程:軟件工程取決于你的需求和實際遇到的問題。即使是非常簡單的用戶界面(比如Google的搜索欄),表面下也有可能包含大量的複雜性,你無法在一次叠代中完善這些複雜性。難點在于如何管理這個循環,在開發新功能的時候允許適度的複雜度,同時又不能讓複雜度過度積累以至于變得勢不可擋。
軟件的核心是積累知識而不是編寫代碼
在軟件開發中,大多數想法都很糟糕,但這不是任何人的錯。只不過人們的各種想法實在太多了,所以某些想法肯定會行不通,即使非常謹慎和明智地抉擇,也再所難免遭遇失敗。所以爲了順利地推進項目,你需要從一堆糟糕的想法中擯棄最壞的想法,並發展最有希望的想法。蘋果就是一個富有遠見的設計典範,但在最終的産品問世之前,它也經曆了幾十個原型。最終的産品看似簡單,但其中蘊含著錯綜複雜的知識,爲什麽他們選擇了這個特定的解決方案?爲什麽其他替代方案不可行?
即使在産品構建完成之後,這種知識仍然很重要。假設一個新團隊接管了一個不熟悉的代碼庫,然後很快開始升級該軟件。操作系統會更新,業務需求會發生變化,而且還會發現需要修複的安全問題。處理這些複雜的錯誤往往比構建軟件本身的難度都大,因爲你需要要對系統的體系結構和設計原則有深入的了解。
在短期內,一個不熟悉的開發團隊可以通過權宜之計修複這些問題。但隨著時間的推移,由于新添加代碼的權宜性,新的bug會不斷積累起來。由于新代碼不符合設計範例,用戶界面會變得混亂,而且整體的系統複雜性也會激增。所以,我們不應該將軟件視作靜態産品,而是應該當成團隊集體理解能力的生動體現。
這就是爲什麽你很難依靠外部供應商爲你開發核心軟件的原因。你可以拿到一個運行良好的系統以及代碼,但有關該軟件的構建及設計抉擇的寶貴知識卻流失了。這也是爲什麽將系統交給新的供應商進行“維護”時往往會引發各種問題的原因。即便這個系統有良好的文檔記錄,但每次交給新團隊接管時都會遺失一部分知識。多年以後,該系統就會因爲不同代碼的拼湊而變得千瘡百孔。長此以往,系統的運行就會越來越難,因爲沒有人真正理解系統的運作。
因此,爲了保證軟件的長期良好運行,讓你的員工與外部的幫手一起學習系統的知識,將關鍵性的工程知識保留在組織內部,這極其重要。
開發優秀軟件的三個原則
1.項目剛開始的時候越簡單越好
對于特定的領域而言,“一站式商店”的項目往往注定會失敗。其中的原因很明顯:確保你的應用可以爲用戶解決實際問題,還是解決盡可能多的問題,孰重孰輕?畢竟,“一站式商店”就如同超市一般的實體店。不同之處在于,雖然在實體店建立後添加新商品相對很容易,但是構建擁有兩倍功能應用的難度遠遠不止兩倍,而且很難使用。
構建優秀的軟件時需要關注:以解決某個問題的最簡單方案爲起點。一個設計精心又簡潔的應用永遠不會遇到添加功能的問題。但是,一個大型的IT系統雖然能夠解決很多問題,卻往往無法簡化或修複。即使是成功的“包羅萬象”的應用,比如微信、Grab和Facebook,剛開始時也有非常具體的功能,而且只有在確保了他們的地位後才開始進行擴展。軟件項目的失敗很少可以歸因于規模太小,它們的失敗往往由于規模太大。
遺憾的是,在實踐中保持項目聚焦重點是一件非常困難的事情:單單是收集所有利益相關者的需求就會得到一份巨長的功能列表。
管理這種膨脹的一種方法是使用優先級列表。我們仍然需要收集所有的需求,但每個需求都有相應的標記:是絕對性的關鍵功能、高附加值還是非常有幫助性。這種方式有助于建立一個壓力較小的計劃流程,因爲你不需要再明確駁回某些功能。而且利益相關者也可以更加理智地討論哪些功能才是最重要的,同時也不必擔心項目遺漏了某些問題。此外,這種方法也明確了添加更多功能的權衡。即便利益相關者想要提高功能優先級,他們也必須考慮願意優先考慮哪些功能。而開發團隊則可以從最關鍵的目標開始,在時間和資源允許的情況下沿著優先級列表逐個實現。
我們所有成功的應用開發都采用了類似的流程。Form.gov.sg剛開始的時候只是一個手動的Outlook宏,我們只花了6個小時爲我們的第一個用戶設置了這個宏,如今這個系統已經處理了大約一百多萬個公共提交的請求。Data.gov.sg剛開始的時候只是複制了一個開源項目,發展到如今每月的訪問量已經超過了30萬次。Parking.sg曾經有200多個等待構建的龐大功能列表,但我們從來都沒有構建過這些功能,可如今仍有超過110萬的用戶。這些系統雖然簡單,卻也正是因爲簡單才受到好評。
2.找到問題並進行叠代
事實上,現代軟件的結構如此複雜,變化如此之快,以至于我們無法通過良好的計劃消滅所有缺陷。如同撰寫一篇好文章一樣,即便早期的草稿非常尴尬,但我們只能通過這些草稿了解最終的論文。要想構建優秀的軟件,首先需要構建糟糕的軟件,然後再積極尋找問題並改進解決方案。
剛開始的時候,你只需要跟需要幫助的人交談。了解你需要解決的根本問題,並避免僅憑先入爲主的偏見早早提出解決方案。當年,我們剛啓動Parking.sg項目的時候,我們曾假設執法人員很難堅持心算紙質的優惠券。然而,在與某位經驗豐富的官員交談了一個下午之後,我們發現,對于專業人士來說,這些計算實際上非常容易。此次談話爲我們節省了數月的潛在工作,並讓我們將項目重點放在了幫助司機上。
謹防僞裝成問題陳述的官僚目標。比如:
“駕駛員在處理停車券時感到很沮喪”:是一個問題。
“作爲政府部門家庭數字化計劃的一部分,我們需要爲司機構建應用”:不是問題。
“用戶對很難在政府網站上查找到信息而感到惱火”:是一個問題。
“作爲數字政府藍圖的一部分,我們需要重建我們的網站以符合新的設計服務標准”:不是問題。
如果我們的最終目標是讓公民的生活更美好,那麽我們就需要清楚地了解他們生活中的困難。
明確的問題陳述可以讓你通過實驗來測試不同解決方案的可行性,因爲你很難僅通過理論確定解決方案。與聊天機器人交談的難度可能超過了浏覽網站,用戶可能不想在他們的手機上多裝一個應用,無論該軟件對于國家的安全有多麽重要。在開發軟件的時候,顯而易見的解決方案通常都帶有致命的缺陷,但只有投入實際使用你才能發現。所以,你的目標不是構建最終産品,而是首先通過快速且廉價的渠道找到這些問題。你可以利用非功能性的模型來測試界面設計,利用半工作的模型嘗試不同的功能,快速編寫原型代碼更快地獲得反饋。這個階段創建的所有東西都是一次性的。這個過程所需的輸出不是編寫的代碼,而是更清楚地了解哪些才是應該構建的功能。
在對正確的解決方案有了充分的理解之後,你可以開始構建實際産品。這時,你應該停止探索新想法並設法縮小範圍,以確定有待實現的特定問題。你可以從少數測試人員開始,他們很快就會發現需要修複的明顯錯誤。隨著一個個問題得到解決,你可以逐步擴大範圍,讓他們發現更多深奧的問題。
大多數人只會提供一次反饋。如果你上來就將軟件公開給大量用戶,那麽每個人都會爲你提供最顯而易見的雷同反饋,以後你就沒辦法再收集反饋了。即便再優秀的工程師構建再好的産品創意也會出現重大問題。所以我們的目標是反複改進,打磨邊緣案例,直到創建出最好的産品。
即使在完成所有叠代並發布軟件之後,産品的問題也很重要。有些問題的發生幾率只有0.1%,測試期間沒有被注意到。但是,一旦你有一百萬的用戶,那麽每天都會面臨一千個憤怒的用戶需要安撫。你需要搶在給用戶造成重大損害之前,修複新移動設備、網絡中斷或安全攻擊造成的問題。我們在Parking.sg中構建了一系列輔助系統,可以不斷檢查主系統是否存在付款差異、重複停車會話和應用程序崩潰等問題。隨著時間的推移,建立一個“免疫系統”可以讓你避免不堪重負,因爲新的問題必然會不斷湧現。
總的來說,我們的基本思路是利用不同的反饋回路來有效地識別問題。小的反饋回路可以快速找到問題並輕松修複,但會錯過更廣泛的問題。大型反饋循環可以捕捉到更廣泛的問題,但其速度慢、開銷大。你可以結合兩者,利用緊湊的循環解決盡可能多的問題,同時利用大型循環捕捉意外的錯誤。構建軟件不是爲了避免失誤,而是應該有策略地快速失敗,然後再獲取構建優秀軟件所需的信息。
3.聘請最優秀的工程師
建立優秀的工程團隊的關鍵在于擁有優秀的工程師。谷歌、Facebook、亞馬遜、Netflix和微軟擁有全球最大的令人眼花缭亂的技術系統,然而,雖然他們面臨招聘優秀人才的劇烈競爭,但他們仍然擁有最苛刻的招聘流程。應屆畢業生的工資隨著這些公司的發展壯大而節節升高,這是有一定原因的,當然不是因爲這些公司不在乎錢。
史蒂夫·喬布斯和馬克·紮克伯格都表示,最優秀的工程師的工作效率至少是普通工程師的10倍。這不是因爲優秀的工程師編寫代碼的速度是普通人的10倍,而是因爲他們能夠做出更好的決定,可以節省10倍的工作量。
優秀的工程師更好地掌握了他們可以重複使用的現有軟件,從而最大限度地減少他們必須從頭開始構建系統的工作量。他們更好地掌握了工程工具,能夠自動化大部分的日常工作。自動化還可以將人類解放出來,讓他們去處理意外的錯誤,而優秀的工程師在這個方面有著優異的表現。優秀的工程師設計的系統更健壯,更方便他人理解。這具有乘數效應,因爲他們可以幫助同事更快更可靠地完成各自的工作。總的來說,優秀的工程師的效率非常高,不是因爲他們可以編寫更多的代碼,而是因爲他們做出的決定可以將你從不必要的工作中解脫出來。
這也意味著,優秀的工程師組成的小型團隊構建軟件的速度遠遠超過了普通工程師組成的大型團隊。他們可以充分利用現成的開源代碼和複雜的雲服務,並利用自動化測試及其他工具擺脫單調的日常工作,讓每個人都專心思考如何創造性地解決問題。他們可以確定關鍵功能的優先級,並減少不必要的工作,快速測試用戶的不同想法。這是經典著作《神話人月》的核心論點:總的來說,增加軟件工程師對加快項目速度並無裨益,只會讓項目的規模擴大。
與普通工程師組成的大型團隊相比,優秀的工程師組成的小型團隊制造的bug和安全問題也會更少。與撰寫論文類似,編寫代碼的人越多,編程風格就越多,假設和怪癖就越多,最終只能在拼湊而成的産品中協調,因此出問題的幾率就越大。相比之下,由優秀的工程師組成的小型團隊構建的系統將更加簡潔、連貫,而且更加便于理解。沒有簡單性就無法保證安全性,大規模的協作很難鑄就簡單性。
工程工作的協作性越高,我們就越需要優秀的工程師。工程師代碼中的問題不僅會影響到他個人的工作,還會影響到身邊同事的工作。在大型項目中,糟糕的工程師只會給彼此添亂,因爲錯誤和糟糕的設計就像滾雪球一樣,越滾越大,直到出現重大問題。大型項目需要建立在可靠的代碼模塊上,並采用有效的設計,同時還需要非常明確的假設。你的工程師越優秀,系統在無法承其中而崩潰之前發展得就越大。這就是爲什麽成功的科技公司盡管規模龐大但堅持招聘優秀的人才。系統複雜性的硬性限制不在工作量,在乎質量之間也。
總結
良好的軟件開發始于對需要解決的問題有清楚的認識。你可以測試許多解決方案,並找到最佳方案。通過重用正確的開源代碼和雲服務,以及使用現成的軟件系統和複雜的新技術,來加速開發。開發周期在探索和整合之間交替,在控制一定的混亂的同時快速地開發新功能,然後集中和簡化,以保證複雜度的可管理性。隨著項目的進展,接受更多用戶群的測試,以消除日益罕見的問題。只有當一個優秀的開發團隊真正步入正軌之後才能正式啓動項目:你應當構建多層自動化系統,快速處理問題,並防止對實際用戶造成傷害。最後,盡管軟件開發存在無限的複雜性,但理解這一過程爲解決如何構建優秀軟件的複雜性提供了基礎。
原文:https://www.csc.gov.sg/articles/how-to-build-good-software
作者:LI HONGYI,新加坡公民服務學院科技團隊負責人,前Google員工,從事分布式數據庫與圖像搜索。
【END】