本節書摘來自異步社群《面向對象設計實踐指南:ruby語言描述》一書中的第1章,第1.3節設計行為,作者【美】sandi metz,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
1.3 設計行為
面向對象設計實踐指南:ruby語言描述
随着常見設計原則和模式的出現與傳播,所有的ood問題可能都已被解決。既然基礎的規則都已知道,那麼設計面向對象的軟體還會有多難呢?
事實證明,它非常難。如果将軟體了解為可定制的家具,那麼原則和模式便像是木工的工具。了解軟體在完成後會是什麼樣子,并不能讓它自我建構成那個樣子。應用程式之是以存在,是因為有程式員使用了這些工具。最終的結果可能是,它要麼成為一個漂亮的櫥櫃,要麼成為一張搖搖晃晃的椅子。具體是哪一種結果,則取決于程式員使用設計工具的經驗。
1.3.1 設計失敗
第一種設計失敗的情形是缺乏設計知識。程式員最初對設計一無所知。不過,這還不構成威脅,因為在不知道設計首先該幹什麼的時候,也可以生産出能工作的應用程式。
除了部分語言以外,所有的oo語言比其他類型語言都更容易受到影響,這一情形千真萬确。尤其是像ruby這種“平易近人”的語言很容易“受傷”。ruby非常友好,它幾乎可以讓任何人,通過建立腳本完成一些自動的重複性任務。而且,它還有一個像ruby on rails那樣強悍的架構,将web應用程式放置到每一位程式員能觸及的地方。ruby語言的文法非常文雅,任何具備将想法串成邏輯順序能力的人,都可以編寫出能工作的應用程式。對面向對象設計一無所知的程式員,使用ruby也能輕松地獲得成功。
不過,雖然成功但卻未進行設計的應用程式,總是存在自我毀滅的風險。它們在編寫時很輕松,但更改起來則會變得越來越難。程式員過去的經驗并不能預測到未來。無痛開發的早期承諾都會逐漸地變得無法兌現。如果程式員在面對每個變更請求時開始說:“是的,我可以添加這個功能,但這會把所有事情都給破壞掉”,那麼這時原本樂觀的情形也會變得絕望。
稍有經驗的程式員也可能遭遇過另一種設計失敗的情形。這些程式員對oo設計技術都有所了解,但對于如何應用它們仍然不太清楚。盡管是出于好心使用這些設計技術,但他們很快便會掉進過度設計的陷阱。隻有“半桶水”是很危險的。伴随着知識的增加和希望的回歸,他們開始無情地進行設計。在熱情膨脹之後,他們會不合時宜地亂用這些原則。在不該有的地方,你也能看到他們在使用模式。他們一開始想要建造出一座複雜而又漂亮的代碼城堡,最後卻痛苦地發現自己早已被一面面石牆團團圍住。你能把這種程式員立即辨識出來,因為他們在面對變更請求時總是會報怨:“不行,我不能添加這項功能。它不是設計來幹這事兒的。”
最後,當設計行為與程式設計行為分開時,所開發的那個面向對象軟體也注定會失敗。設計是一個逐漸發現的過程,它依賴于往複不斷的回報。這種往複回報應該是适時的和遞增的。靈活軟體運動(agile software movement)的這種疊代技術,也是以非常适合用于建立優秀設計的面向對象應用程式。靈活開發(agile development)所提倡的這種疊代特性,讓設計可以有規律地調整和很自然地演變。當設計是由很遙遠的事情所決定時,就沒必要進行調整,早期的了解錯誤能讓代碼更鞏固。當程式員們被強迫編寫由孤陋寡聞的專家所設計的應用程式時,他們便可以說:“沒錯,我當然可以寫這個軟體,但是這并不是你真正想要的結果,你最終是會後悔的。”
1.3.2 設計時機
靈活開發方法相信:在客戶看到具體的軟體之前,他們對所想要的軟體是沒什麼概念的,是以向他們展示軟體的時機是宜早不宜遲。如果這個假設前提成立的話,那麼在邏輯上你就應該以微量遞增的方式來建構軟體,逐漸将你的方法疊代成滿足客戶真正需要的應用程式。靈活開發方法認為:想要生産出客戶真正需要的應用程式,最劃算的方法是與他們一起合作,逐漸地建構軟體。這樣,每次傳遞都有了對下一步想法進行更改的機會。靈活開發的經驗表明:這種合作産生出來的軟體與最初想象的結果總是存在差異。是以,最終的軟體是無法通過其他方式進行預測的。
如果靈活開發方法正确,那麼另外兩件事情也會是真的:第一件事情,即大規模預先設計(big up front design,bufd)完全沒有意義(因為它不可能正确);第二件事情,即沒人能預測應用程式什麼時候會完成(因為你事先無法知道它最終會幹什麼)。
有人不太喜歡靈活開發方法,這也是意料之中的事。“我們不知道在做什麼”以及“我們不知道什麼時候能完成”這兩個問題很難解決。對bufd的要求始終存在,因為在某種程度上,它提供了一種控制的感覺;否則,便會讓人感覺無法控制。盡管這種感覺可能讓人很安心,但認為這種編寫應用程式的行為會無法繼續下去,那隻是一時的錯覺。
bufd不可避免會導緻客戶和程式員之間出現敵對關系。因為在軟體真正形成之前的任何大規模設計都不可能是正确的,按照特定保證編寫出的應用程式根本無法滿足客戶的需求。在客戶嘗試着使用這個應用程式時,便會發現這一點。接着,他們要求進行更改。程式員們都會抗拒這些更改,因為他們是在按計劃行事,而實際情況是他們很可能已經落後于計劃。項目的參與者開始從努力想要讓它變得成功,轉變為努力想要避免因其失敗而被指責。随着這一情況的出現,這個項目便會逐漸走向消亡。
對于這種“潛規則”大家都心知肚明。當項目錯過了它的交貨期限時,哪怕出現這種情況的原因是因為修改了規定,但還是錯在程式員。不過,如果項目是按時傳遞的,盡管不滿足實際的需要,那麼就可以肯定是規定出了問題,是以這時便可以怪罪到客戶頭上。bufd的設計文檔在開始時常被用作應用程式開發的路線圖,但它們會逐漸成為争論的焦點。這些文檔不會産生出高品質的軟體,相反它們提供的都是一些被嚴重消化過的話語。在最後,這些話語會被大家競相引用,沒人想成為那個手持燙手山芋挨批的人。
一遍又一遍地做同樣的事情,并且期望能得到不同的結果。如果說這很瘋狂,那麼靈活宣言則讓我們大家都開始有所覺悟。靈活開發方法之是以有效,是因為它承認:在應用程式最終形成之前,确定性是遙不可及的。靈活開發方法認可這一事實,是以它提供了許多政策,并将它們用來克服軟體開發的各種障礙,同時也無須對具體的目标和時間表了解太多。
不過,靈活開發方法所說的“不進行大規模的預先設計”,并不是認為完全不做任何設計。在bufd裡使用的“設計”一詞與ood裡使用的“設計”有着不同的含義。bufd幾乎全部都是在指定和記錄所涉及那個應用程式被期望将來應具備的全部功能及其内部工作原理。如果有某位軟體架構師參與進來,則可能預先将決定擴充至如何編排所有的代碼上來。ood所關心的則是更為狹窄的領域,它主要是關于編排已有代碼以便讓它們更容易更改。
靈活開發過程為更改提供了保證,而所能做出更改的能力則取決于應用程式的設計。如果無法編寫出設計良好的代碼,那麼在每次疊代的時候,你就必須要重寫這個應用程式。
是以,靈活開發方法并不排斥設計,相反它還需要設計。不僅需要設計,它還需要非常優秀的設計。它需要你付出努力。它的成功離不開簡單、靈活和可塑性強的代碼。
1.3.3 設計評判
在以前有一段時間,大家常根據程式員所産生的代碼行數(也被叫做源代碼行數或sloc)來評判他們。很明顯,這種度量會變成什麼樣子:有的老闆認為程式設計就是一條流水線,在那裡同樣訓練有素的勞工都能建構出相同的部件。這樣的老闆很容易形成這樣一種觀念,即單個生産力可以通過簡單的權重輸出進行評判。對于那些迫切需要一種可靠方法來對程式員進行比較和對軟體進行評估的管理者來說,盡管sloc存在有很多明顯的問題,但總比什麼都沒有好。它至少是一種可再生的測量方式。
這種度量方法顯然不是程式員想出來的。盡管sloc可能提供了一種可用來衡量個人努力和應用程式複雜性的衡量标準,但它對整體品質卻“隻字未提”。它處罰的是有效率的程式員,獎勵的卻是那些編寫冗長代碼的人。它經常被專家用來與底層應用程式的危害相對照。當你了解到坐在你身旁的新手程式員常被認為更具效率,因為他或她為實作某項功能編寫了很多的代碼,而你卻能用更少行數的代碼來實作它,這個時候你會對此做何反應呢?這種度量方法以損害品質的方式改變了獎勵制度。
在當代世界裡,sloc是一個曆史産物,它在很大程度上已被新的度量方法所替代。有許多ruby程式包(在google裡搜尋一下“ruby metrics”,最靠前的那些就是它),它們可以幫你評估代碼遵循ood原則的情況。這些度量軟體通過掃描源代碼,并對預測的品質進行統計。針對你自己的代碼運作某個度量套件,可能會出現啟發、羞辱以及擔憂這三種狀況。看似精心設計的應用程式會出現大量違背ood的情況。
糟糕的ood路徑成本無疑也标志着設計很糟糕:得分很低的代碼将難以更改。不幸的是,得分很高的代碼也不能就此證明它易于更改。即是說,這些度量無法保證你下一次的更改一定是輕松和廉價的。其中的問題在于,有可能建立出對未來過度預測的漂亮設計。雖然這些設計可能會得到非常高的ood路徑成本,但如果它們對未來預測錯誤,那麼當真正的未來最終到來時,想要進行修正則會付出昂貴的代價。ood度量無法辨識出那些“在方法上正确而在做法卻是錯誤”的設計。
是以,對sloc所存在問題的警示也要擴充到ood度量上。對它們要半信半疑。度量非常有用,因為它們沒有偏執,而是會産生出一些資料。根據這些資料,你便可以推斷出與軟體有關的某些東西。不過,它們不是衡量品質的直接名額,而是更深層測量的“代理人”。終極的軟體度量應該是:在起關鍵作用的那段時間間隔裡,每一項功能所花費的成本。但它很不好計算。成本、功能和時間,都難以單獨定義、跟蹤和測量。
即使你有可能将某項單獨的功能隔離起來,并跟蹤與它相關的所有成本,但至關重要的時間間隔也會對代碼應該如何評判産生影響。有時,現在擁有該項功能的代價非常大,以緻它會超越未來所有成本的增長。如果今天缺失了某項功能會迫使你破産,那麼明天處理這些代碼會花費再多的成本也沒有關系,你必須要盡最大努力保證按期完成。做這樣的設計妥協就像是向未來借用時間,也就是衆所周知的要承擔技術債務。這是一筆最終必須歸還的貸款,極有可能還會帶上利息。
即使你并非故意想要承擔技術債務,設計也要占用時間和花費成本。因為你的目标是要編寫每一項功能成本都保證在最低水準的軟體,是以具體要做多少設計才合适,取決于這樣兩件事情:你的能力和時間表。如果這個月的設計占用了你一半的時間,并且在這一年之内都無法展現出它的好處,那麼這樣的設計就不值得去做。當設計行為阻礙軟體按時傳遞時,你便會輸掉。一個優秀設計的應用程式,如果隻能傳遞一半,那麼與完全不能傳遞所導緻的後果是一樣的。不過,如果設計在今天早上占用了你一半的時間,而在今天下午它就能讓你得到回報,并且在這個應用程式的整個生命周期裡都不斷采用這種做法,那麼在時間方面你便都能獲得一種日積月累的好處。這種設計努力會一直帶來好處。
設計的盈虧臨界點依賴于程式員。那些沒有經驗的程式員會做很多預先設計,但可能永遠無法獲得這樣的結果:早期的設計努力會得到回報。對于那些熟練的設計師,它們在今天早上還在編寫精心設計的代碼,而在今天下午便能節省成本。你的經曆可能介于這兩種極端情況之間,本書接下來的章節将教給大家一些技巧。它們可用來權衡設計,并為你帶來好處。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。