“
不求甚解亦或是固步自封,都是從事IT行業所不可取的。如果只會寫一手好代碼,卻不會思考,那只能稱作碼農,而不是Coder。——尼古拉斯·小Q
前言
我做過很多爛項目。爛項目有益個人成長,但早期它挺困擾我。因為它讓我懷疑自己就是那個敗事鬼:為啥我參與的項目都如此不堪?這種自責隨著職位的升高有所緩解。但原因絕不是可以訓斥我的人在減少。這有點象透過一個鏡頭觀察世界。
多年來這個鏡頭逐漸拉遠,視野擴大,我得以看到越來越多的東西。伴隨這個過程的,是思維的伸展:什么力量在驅動軟件開發進程?軟件開發困境(high failure rate)是如何形成的?是我們用的不成熟的框架?那位喜歡指責下屬的經理?還是今年糟糕的天氣?
(high failure rate):IT項目、尤其是軟件研發項目的成功率長期維持在慘淡的水平上。讀者可以參考這個來自IPLA的報告:()
本文是我對此問題的一點看法。雖沒帶來解決問題的秘密武器,但也許能在思想上給同行們一點啟示。
軟件開發最大挑戰是?
如果你問周圍同事,軟件開發的最大挑戰來自哪里?得到的答復可能五花八門:技術、管理、需求、甚至睡眠。如果你問我同樣的問題,我的今天回答將是“變化”。這里的“變化”尤指“未來的未知變化”,它基本與“不確定性”、“混沌”、“復雜”等單詞等價。因此本文會混用這些術語。
我們先看看變化是怎么發生的。
從歷史說起
計算機發明之初,它還是一種巨大、昂貴、只有少數科學家能接觸到的設備。這種設備死氣沉沉、一無用處,除非你給它輸入一組指令讓它“活”起來。這些指令就是我們現在說的“軟件”。雖然比現在的軟件簡單很多,但當時世上沒幾個人會寫它。要修改它,同樣要勞煩那些尊貴的科學家。事實上,除了專業的小圈子,沒人會對它提出修改要求。可以猜測那時的軟件開發沒啥挑戰,因為硬件的問題遠比軟件的多。
隨后幾十年的技術進步推動著計算機硬件快速演進:標準化、小型化、價格降低并進入家庭。最后軟件開發成為專門的行業從硬件中獨立出來。軟件所面對的用戶數量迅速擴大。一旦意識到軟件是發揮計算機硬件能力的唯一方式(用CPU煎雞蛋似乎只有少數人會用到),人們對它的欲求便開始迅速膨脹。而且他們發現,相比房子、洗衣機、甚至一張桌子,軟件這種商品的修改要容易的多,還不用啥材料!比如,“把保存按鈕給我換成紅色能花你多長時間?!”。軟件開發人員的苦難歷程就是這么開始的。
今天,手機應用、互聯網服務等軟件形式能把企業的價值以最快的速度傳遞到最大規模的用戶群。這迅速拉近了企業與用戶的距離。事實上,距離如此之近,以至企業可以騰挪的空間大大縮小了。同時競爭讓用戶的選擇變的更多。在這么短的距離上,用戶的選擇可以迅速決定企業的生死。
而軟件的創造者,即開發人員的日子似乎正變得愈發艱難。他們不得不努力迎合用戶多變的需求,如果他們知道需求的話。對那些沒有確切掌握需求而又急于占領市場的開發團隊,情況會更糟。他們唯一的機會可能來自“敏捷性”——適應變化的能力:盡早發布產品、密集收集用戶反饋、盡快分析并調整產品、再發布。通過快速迭代,他們在維持市場存在的同時,不斷提升自己產品的競爭力。
內部的復雜性
其實情況還不算太糟,因為我還沒談到軟件創造者自己內部的問題。
當今軟件開發的復雜性已經顯著增大。比如僅從代碼行數看,幾十萬行代碼構成的軟件產品已經很常見,上千萬行的產品也并不稀罕。在這么大的復雜度面前,壓根就沒有“銀彈”,“一招鮮”是絕對不夠的。專業分工、團隊組織、工具優化、甚至心理咨詢,我們要把所有能用到的武器投入戰場方能應付。
可努力之后我們會發現,除了外部世界帶給我們的苦,我們自身已經成了痛苦的另一個來源。因為分工、協作、工具等等諸多變量已經讓研發過程變成一個復雜的動力系統。很多時候這一系統本身的問題會成為我們的主要挑戰。在有些項目中,那些曾被我們痛恨的趾高氣揚的客戶,現在被折磨成與我們一起苦苦掙扎、惺惺相惜的同命人,就是這個原因。
兩線作戰
從上文很容易總結出軟件開發工作的進展過程。它通常從未明確的需求開始(這樣說并不嚴格,因為任何項目在開始之初總或多或少掌握了一些需求。但未知或可能變化的需求總是我們最頭痛的。本文想闡明的是這部分內容。)
這些未知需求構成了一個黑暗的混沌世界。開發團隊靠大無畏的業務經理找到突破口,然后向外摸索前進。慢慢地,他們澄清了一些不確定性,并據此建立起一些秩序(設計、代碼實現等等)。隨著團隊開疆辟土,混沌初開、曙光浮現。但內部的混沌也隨之升起。它是未明確的職責、被誤解的需求、未掌握的技巧、待研究的算法等等內容的混合物。
內部的復雜性隨著工作規模的擴大不斷增長。直到有一天團隊發現自己陷入兩線作戰的局面,一面是外部持續的壓力,一面是內部增長的混亂。他們竭盡所能、苦苦支撐,希望在任何一條戰線崩潰前把產品交付客戶了事。有些團隊深陷其中,甚至會忘記 “交付”這個最初的目標。
變化的影響
變化催生復雜結構
構成軟件的基本材料是程序代碼。變化對開發進程的影響最終會體現到代碼上。所以我想通過下面的 Python 程序來展示[^]變化的影響。這些代碼很簡單,希望不懂Python語言的讀者也能明白。
假定我們要寫一個軟件,姑且叫它 hello。用戶說只要這個軟件能打印出 “hello world” 就行了。那么我們能很快完成這個軟件 1.0 版:
print "hello world"
毫無疑問,你必須事先知道你要打印“hello world”字符串才能寫下上述代碼。可需求會變化。比如用戶還想打印出“Hi people”。好在我們不會把用戶的話奉為圭臬。我們能預見到這種變化。所以我們會象下面這樣實現它,姑且稱其為 hello-2.0 :
def (msg):
print msg
通過定義一個函數``,我們現在可以應付更多的可能性了。定義并實現這個函數的兩行代碼,就是我們(暫時)凍結的內容。我們靠遠見成功地在不確定性中找到了可以固定的東西。
但遠見覆蓋不住全部的可能性。比如,變化可能再次發生。這次用戶可能說,消息不僅要打印出來,還要能輸出到文件。當然,這個變化依然難不倒我們。我們會用新的方式實現 3.0 版的 hello,即象下面這樣把它分成兩個模塊`foo`和`bar`。
foo.py(v3.0):
def (send, msg):
send(msg)
bar.py(v3.0):
def (msg):
print msg
def (msg):
out = open('out.txt', 'w')
out.write(msg)
out.close()
很多機智的開發人員會在項目之初,即第一次聽到用戶說“只要這個軟件能打印出hello world就好啦”后就設計出 hello-3.0 的結構來。這樣雖然覆蓋不住全部的可能性,但至少,我們固定住了一部分東西,即模塊`foo`,而把變化交給`bar`模塊來應付。
這就是我們軟件開發者的生存方式。由于程序員不可能對未確定的要求編寫代碼,團隊的各種努力都是在幫助程序員在一定范圍內消滅不確定性,即固化某些東西。我們一點點澄清和凍結不確定因素,直到范圍擴大到可以交付的標準。在此過程中, _變化_ 迫使系統的復雜性增大并最終形成一種特定的結構。
雖然上例展示的需求變化是來自組織外部,但同樣的道理也適用于內部。這里我就不再舉例詳述了。另外,我是用編程來展示變化的影響,但我相信它所包含的方法論含義也可以應用在其他方面,包括體系架構、團隊組織等。
比如對常見的多層軟件架構,如果未來不會出現變化,我們是不需要分層的:沒有系統壓力的變化,我們就不需為伸縮性()而把承擔壓力的主要業務邏輯部分單獨出來做一層;沒有用戶交互的變化,我們沒必要把界面處理從業務邏輯中抽出來作為獨立的GUI層;沒有數據存儲系統的變化,我們把數據訪問層抽出來也是多余的... 讀者可以按這個角度聯想一下,就很容易意識到,是變化這一力量,促使軟件系統、甚至創作軟件的社會化組織——研發團隊,在微觀和宏觀層面產生出特定的結構。這與生物學中的適應性進化多么相似啊。
變化帶來破壞
已經固化的代碼(結構)并不是萬無一失的。它會受到變化的侵襲。這種入侵會沿著代碼的依賴路徑向整個代碼庫蔓延,在復雜性上疊加混亂。我們還是以上面的 hello-3.0 為例。在 3.0 版中,為了分離出可以固化的東西即`foo`模塊,我們把易變的代碼放到了另一個模塊`bar`中。這創建了一個依賴關系,即`foo`對`bar`的依賴,即“依賴路徑”。現在,假定一個未預料到的變化來臨,比如,如果用戶希望文件名可以用其他的怎么辦?好吧,你說,我們接受這個變化,因為我們本來也沒打算凍結`bar`模塊(把它分離出來就是為了應對這一情況的)。然后你把它改成:
bar.py(v4.0):
def (msg, dest):
print msg
def (msg, dest):
out = open(dest, 'w')
out.write(msg)
out.close()
現在,`bar`模塊中的各個輸出函數都增加了一個參數`dest`以支持可變的輸出文件名。但現在你發現,這一變化已經不可阻擋地蔓延到`foo`模塊了。因為你不得不修改它來適應這個新的參數:
foo.py(v4.0):
def (send, msg, dest):
send(msg, dest)
當然你可以用一些語言特色,比如變長參數,來保持`foo`模塊的穩定。比如你可以這樣來實現 3.0 版的`foo`:
def (send, *args):
send(*args)
有很多技巧(比如設計模式)幫我們處理類似情況。但這些技巧無法應對全部的可能性。變化對結構的破壞,或多或少,是無法避免的。
上例展示的變化蔓延在開發期就可以被發現。但還有很多情況要在運行期才可能暴露出來。那些不確定性將對我們固定的結構產生更難預料的后果(與構建期相比,嚴重的問題好像總是在交付給客戶后才出現,而它們的原因經常又是詭異難尋)。
我們可以改進系統設計和團隊組織方式來緩解困境。但是,不僅我們無法杜絕變化,我們也無法完全阻止變化的蔓延。這就象病毒混在血液里向身體其他部分擴散,我們不能掐斷自己的動脈來阻絕它。
與其對抗,不如擁抱
傳統工程領域,比如機械、建筑等,在資源組織、設計、施工等一系例工作中也會對未來的不確定性做準備。比如橋梁設計中要考慮負荷范圍,機械零件設計中要考慮公差等。但變化對這些領域的影響,似乎遠沒有對軟件工程這么深遠。它對我們的影響體現在方方面面,比如:
這個列表可以很長。從中你大概能感受到我們對變化的抗爭。有一些人采用鴕鳥策略,固執地對變化視而不見或認為它們不會發生。這是否是造成軟件困境的原因呢?
我覺得也許我們要改變一下看問題的視角了。我想說的是,響應變化不應再是我們臨時的、不得以的工作。**它就是我們的軟件開發工作本身**。這不是在否定那些缺乏未知因素的開發工作。那一類開發項目依然存在,只是它們代表不了未來,可能也引不起推動行業進步的那些精英們的興趣。
這一視角也許能幫我們找到走出困境的新方法。事實上,現代軟件工程中的許多概念,比如敏捷開發、DevOps,我認為是與本文的視角一致的。所以如果以這樣的視角來理解上述概念,不僅能幫助我們正確地應用它們,還可能發展出更符合自己實際情況的獨特做法。再不濟,它也能緩解我們疲于應付時的精神痛苦。
后記
如果不存在變化,軟件開發就簡單多了。但不論在外部還是內部,變化都永遠存在。進入信息時代后,變化正在以更大的規模、更快的速度發生著。完全應對這未知的洪流是不可能的。我們應坦然面對,并找到一個可以接受當面沖擊的小根據地,然后逐漸擴大它。這個思路可以應用在日常的工作決策中,比如選擇合適的測試時機。
老司機介紹
王錦全,男,本科學歷,1996年畢業于中南大學機電工程學院。長期從事軟的研發和管理工作,先后擔任過軟件工程師、系統架構師、CTO等職務。曾服務于多家公司,包括世界100強、國內大中型國有、私營軟件企業等,也有過自主創業的經歷。目前是杭州一家中型軟件企業的技術總監,主要負責公司的管理制度優化、敏捷研發推廣以及大數據技術研發等工作。他是資深的 Java 和 Python 程序員,也是 Clojure(Lisp) 愛好者。讀書和思考是他的愛好。 您在其 主頁( )可了解他的更詳細經歷。
▽
延展閱讀(點擊標題):
【深圳2016】15大精彩專題,50位大咖講師,、Hearsay Social、Uber、、Twitter等等,你將為哪家公司技術點贊?阿里巴巴、騰訊、百度、美團、餓了么、滴滴、新浪微博等等,核心業務技術較量誰又能觸動你的神經...最精彩的技術切磋從這開始,九折門票倒計時,詳情請戳閱讀原文
本文系InfoQ原創首發,未經授權謝絕轉載。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。