面向對象軟件開發的敏捷過程(一)
面向對象軟件開發的敏捷過程
軟件開發的復雜性 :
計算機硬件界的摩爾定律(每隔 18 個月計算機硬件的運算速度提高一倍,價格下降一半)
適用于硬件的發展規律已經超過三十年了。人們想當然的認為計算機軟件的發展速度和硬件的發展速度相當,但是不幸的是:每次重大的硬件升級之后,隨著更大功能更豐富的軟件的出現,硬件的潛能再一次被無情的榨取殆盡。許多開發的軟件系統不斷的遭受進度延期,人員資金和時間等預算無休止的增加,軟件質量的不斷反復,開發出來的系統對客戶的新需求響應緩慢,更改困難的噩夢。
這樣的現實是由軟件的固有復雜性造成的,軟件不同于硬件的生產過程,是由人的智力勞動完成人的需求到機器程序的翻譯轉換過程。需求可能不清晰,對需求可能出現個人理解上的差異,選擇實現方法的差異,需求的不斷變化,具體實現語言平臺的差異,軟件生產中采用的過程,具體實現人員的變動等等都會對最終的產品產生影響。想象一下,如果一種變化的因素只有兩種可能,那么可以使用簡單的 0 , 1 表示,只有 10 個變化因素的組合就已經達到了 2--10=1024 種可能性,而實際開發中變化的因素輕易就超過 10 個以上,每個變化的可能是還不止兩個,因此軟件的復雜性很快就會超出人的理解程度。有一句經典的軟件開發名言:世界上唯一不變的是變化本身。不斷出現的變化,會使初始的設計和最終的需求之間的距離越來越遠。
軟件的臭味:
軟件開發,使用,維護中出現了以下的“臭味”:
僵化性: rigidity 很難對系統進行改動,因為每個改動都會迫使許多對系統其他部分的其他改動。即使是簡單的改動,也會迫使導致右依賴關系的模塊的連鎖改動。
脆弱性: fragility 對系統的改動會導致系統中和改動的地方在概念上無關的許多地方出現問題。出現新問題的地方和改動的地方沒有概念上的關聯,難以排錯,排錯的過程中又會引入更多的“臭蟲”。
牢固性 immobility 很難解開系統的糾結,使它成為其他系統中重用的組件。系統中包含了對其他系統中有用的功能,當其他人想復用這個功能到新的系統時,剝離出獨立的組件的難度遠遠大于重新實現的難度,在時間和進度的壓力下,大多數人只有選擇拷貝涂鴉的方式來實現新系統的功能。
粘滯性: viscosity 做正確的事情比錯誤的事情要困難。程序完成正常的功能總是傾向于得到不正確的結果。
不必要的復雜性: needless complexity 設計中包含有不具有任何直接好處的基礎結構。為了預防后期維護更改需求的對源碼的修改,在設計之初放置了那些處理潛在變化的代碼來保持軟件的靈活性,這樣的結果是軟件中包含了很多復雜的結構,理解起來更加困難。
不必要的重復: needless repetition 設計中包含有重復的結構,而該重復的結構可以使用單一的抽象進行統一。對鼠標右鍵(剪切,復制,粘貼)的濫用,使得完成同一或類似的代碼片斷出現在系統各處。如果原始的代碼段完成的功能需要變化,或者存在錯誤,排錯和增加新的功能變得非常困難。
晦澀性: opacity 很難閱讀,理解。沒有很好的表現出意圖。
以上討論了系統構架的臭味,下面討論微觀層次上代碼的臭味:
代碼的臭味
重復代碼:重復的代碼使得更改功能和排錯更加困難。同樣的模塊錯誤會在拷貝粘貼的程序各處多次出現。
過長的函數:程序越長越難于理解,這已經是軟件業開發的常識。越難理解的程序,使用維護的成本就越大。如果一個函數的行數超過一頁,很少有人能夠在看到下一頁的時候還清楚的記得函數開頭的變量定義,理解和查錯更加困難。
過大類:在一個類中完成幾乎所有需要的功能。十項全能的人是不存在的,軟件也一樣。
過長的參數列:如果一個函數(方法)的調用參數過長,使用這個函數的調用過程也一定是困難的。想象一下,調用一個十個以上參數存儲過程會有多么痛苦。這還只是開始,如果任一個參數的定義(名稱,類型)發生輕微的變化,函數的調用客戶端會有多么大的改動。
其他的臭味還有發散式變化,散彈槍修改,依戀情結,數據泥團,基本型別偏執,復雜的 switch 分支語句,平行的繼承體系,冗贅類,夸夸其談的未來性,令人迷惑的暫時值域,過度耦合的消息鏈,中間轉手人,狎昵關系,異曲同工的類,不完美的程序庫類,純數據類(數據啞元),子類不需要父類的某些特性,過多注釋。詳細的討論可以參見《重構》的介紹。
面向對象軟件設計的原則 :
總體原則 :
1.? 針對于接口(抽象)編程,而不要針對于實現(具體)編程。
舉例來說:操作系統是對邏輯計算機的抽象,通過操作系統的抽象我們不需要考慮具體使用的硬件配置,可以在較高的層次上進行更高生產力的應用。再如:匯編語言對機器的 0 , 1 代碼進行了抽象,大大加快了開發效率,后來使用的高級語言和第四代語言模型驅動抽象的級別更高,生產力也更高。再如: java 和 .net 實現于一個抽象的軟件虛擬機,進一步使開發出來的組件可以跨平臺和操作系統。通過抽象出數據訪問層(持久化層),可以使業務邏輯和具體的數據庫訪問代碼分離,更換數據庫提供商對已有的組件沒有影響。具體實現可以參照 hibernate 實現和 dao (數據訪問對象)模式。
優勢: 1 )降低程序各個部分之間的耦合性,使程序模塊互換成為可能。調用的客戶端無需知道具體使用的對象類型,只要對象有客戶希望的接口就可以使用,對象具體是如何實現這些接口的,客戶并不需要考慮。 2 )簡化了程序各個部分的單元測試,將需要測試的程序模塊中通過重構提煉出抽象的接口,然后編制和接口一致的 Mock 類,測試就會變得很容易。如果應用了測試優先的方法,從簡化客戶端調用的角度,還可以經過抽象改善軟件模塊的設計。 3 )模塊的部署升級由于模塊之間的耦合度降低變得更加容易。
相關的設計模式有創建型模式中的工廠模式,結構型模式中的代理模式和組合模式等。
2.? 對象組合優于類繼承。
面向對象為軟件開發引入了三大工具:繼承,多態和重載。繼承使得程序員可以快速的通過擴展子類來增加功能,但是由于繼承是在編譯時確定的,因此增加的功能較多時,繼承不夠靈活,還有可能出現“子類爆炸”的局面(為了完成新添功能,不得不在繼承體系中添入大量的之間只有細微差別的子類,掌握使用擴展都會變得非常困難)。而通過對象的組合,可以動態透明的添加功能。相關設計模式有裝飾模式和代理模式等。
3.? 分離變化。前面說過需求是在不斷變化的,不同的變化可能是倉庫安全庫存的計算方法,可能是報表和數據的展現形式,通過把這些不同的變化識別并分離出來不同的對象委托,簡化了客戶端的調用和升級。相關的設計模式有命令模式,觀察者模式,策略模式,狀態模式,模版方法模式等。
具體原則:
1.? 單一職責原則 srp ( single responsibility principle ):一個模塊的功能應該盡可能的內聚。如果一個類發生了變化,引起變化的原因應該有且只有一個。每一個類承擔的職責都是一個變化的軸線,需求變化時,會體現為類的職責的變化。如果一個類承擔的職責過多,就等于把這些職責耦合在了一起,一個職責的變化會影響這個類完成其他職責的能力,會出現前面所說的軟件的臭味之一脆弱性。相關的設計模式有
2.? 開放封閉原則 ocp ( open closed principle ):一個模塊應該對功能的擴展開放,支持新的行為,對自身的更改封閉。每次對模塊的修改都可能會引入新的錯誤和新的依賴。因此擴展新功能時,已經編好的模塊源碼和二進制代碼都是不應該修改的。相關的設計模式有適配器模式,橋接模式,訪問者模式等。
3.?
Liskov
替換原則
lsp
(
liskov subtitle principle
)子類型必須可以替換掉他的基類型。一個基類的多個子類型之間要完成動態的替換,各個子類型必須都可以被他們的基類型替換,這樣他們之間動態替換后,客戶端調用的代碼就不需要冗贅的
switch
類型判斷代碼。如果
子類型無法替換基類型,將會導致在派生類對象作為基類對象進行傳值時的錯誤。這樣多態機制處于癱瘓狀態了。相關設計模式為組合模式。
4.? 依賴倒置原則 dip ( dependent inverse principle )高層模塊不應該依賴于底層模塊,抽象不應該依賴于細節,細節應該依賴于抽象。假定所有的具體類都是回變化的,因此如果一個客戶端依賴于(調用或聲明)具體的類型,那么當這個具體的類型變化時,依賴的客戶端業必須同時進行修改。這些具體的更改可能出現在使用了某個特定的網絡協議,特殊的系統 api 調用,特定的數據庫儲存過程等,這些用法或多或少都會使客戶端調用和具體類型成為鐵板一塊,比較難于重用。 Java 社區中比較熱門的 j2ee 輕量級容器框架 spring 就很好的實現了本原則。
5 .接口隔離原則 isp ( interface segregation principle )不應該強迫客戶依賴于它們不使用的方法。因為每一個實現接口的對象必須實現所有接口中定義的方法。如果接口的粒度比較小,實現接口的對象可以使用一種即用即付的方式動態實現接口。每個接口的粒度很小,復用起來也非常容易。
這體現了一個趨勢:為了更好的實現重用,接口,函數,模塊和類等傾向于更容易使用的“小”體積。
敏捷軟件開發的宣言和實踐:
軟件開發項目的失敗使得人們開始思考軟件開發的過程,人們希望通過引入嚴格的過程控制產生軟件生命周期中各個階段的文檔和制品來保證軟件的質量。比較出名的業界實施方法論有 cmmi (能力成熟度模型)和 rup (瑞理統一過程),這些方法論都是重型的。舉例來說,沒有經過剪裁的 Rup 實現起來,至少需要在全周期完成四十個以上的制品文檔,文檔的編寫維護和源代碼的同步等需要非常多的資源,十人以下的開發團隊一般沒有精力、時間、人員配置完成這些制品。失控的過程的膨脹迫使人們尋找一種快速工作,相應變化的“敏捷的”方法。敏捷團隊提倡通過團隊成員之間充分有效的溝通統一大家的目標,結伴的方式完成開發技術的團隊內傳承,使用“夠用就好”的輕量甚至免費的工具管理過程。可以正常工作的代碼擺在首要地位,只有必要的時候才生產必要的文檔。強調和客戶面對面地交流合作,積極地響應客戶需求的變化而不是遵循機械的計劃。使用較短的迭代周期,近早和持續提交有價值的軟件給客戶來驗證并修正和用戶需求的吻合程度。提倡可以持續的穩定的開發節奏,長期“小步快走”的方式代替突然的“百米沖刺”。保持設計最優,最簡單的設計并且持續改進,不斷調整。