CODE COMPLETE 2 軟體開發實務指南 第5章 軟體建構中的設計

        第五章的主題是軟體設計,內容可幫助讀者「開竅」。子曰:「吾道一以貫之。」軟體設計有同樣的情況,軟體設計的本源是化繁為簡降低複雜度,衍生出抽象、封裝、繼承與多型。讀者若是能夠看懂第五章,表示做到融會貫通了。


5.1 設計中的挑戰


軟體設計代表去構思、創造或發明一套方案,把電腦軟體的規格書,轉變為實際可行的軟體。


設計是把需求分析和編碼除錯連結在一起的活動。


好的高層設計能容納多的較低層次設計的結構。


設計是一個險惡的問題


學校寫作業老師給的題目不會改變,出社會工作面對實際情況,客戶的需求總是會一直改變。


設計是個毫無章法的過程(即使它能得出清爽的成果)


設計的過程是持續犯錯的過程,設計的好壞沒有一定的標準。


設計就是確定取捨和調整順序的過程


設計要根據現況進行調整。


設計受到諸多限制


設計一部分在創造,一部分又是在限制可能發生的事情。


設計是不確定的


人各有體,三個人有三種不同的設計。


設計程序本質上是個啟發式的程序


設計是一種啟示,根據經驗法則或嘗試說不定可行的辦法。沒有工具是一直都可以使用,不需要改變。


設計是自然而然形成的


設計是不斷地設計評估、非正式討論、寫試驗程式、修改試驗程式碼。


5.2  關鍵的設計概念


軟體的首要技術使命:管理複雜度


附屬的和本質上的難題(Accidental and Essential Difficulties)


語言、效能和開發環境屬於附屬問題。


細節、完全正確、實體間的互動屬於本質問題。


管理複雜度的重要性


軟體的首要技術使命就是管理複雜度。


可以透過把整個系統分解為多個子系統來降低問題的複雜度。軟體設計的目標都是將複雜的問題分解為簡單的問題。


子系統之間相互依賴越少,越容易專注問題的一小部分。


如何處理複雜度問題


高代價、低效率的設計來自於下面三種來源


用複雜的方法解決簡單的問題


用簡單但錯誤的方法解決複雜的問題


用不恰當的複雜方法解決複雜的問題


 


要用下面兩種方法管理複雜度


 


本質複雜度的量減到最少


不要讓附屬的複雜度無謂地快速增長


 


理想的設計特徵


 


最小的複雜度


易於維護


鬆散耦合


可擴充性


可重用性


高扇入  大量的類別使用某個特定的類別


低扇出  一個類別少量或適中地使用其他的類別


可移植性


精簡性


層次性  


假設存在舊的程式碼,層次化有兩個好處。將低劣程式碼拘禁起來、如果最後移除舊程式碼,不用修改其他層次的程式碼。


標準技術  儘量使用標準化、常用的方法。


設計的層次


第1層:軟體系統


作者認為從子系統或套件思考會比較有益。


第2層:分解為子系統或套件


識別出所有主要的子系統,定義個子系統如何使用其他的子系統。子系統之間要限制通訊,每個子系統才有存在的意義。


重要的問題


開發人員需要理解系統中多少不同的部分,才能在圖形子系統更動某些東西?


想在另一個系統中試圖使用業務規則,會發生什麼事?


加入新的使用者介面會發生什麼?


把資料儲存到一台遠端機器上,又會發生什麼?


子系統之間要避免環形依賴。


常用的子系統


業務規則


電腦系統中編入的法律、規則、政策及程序。


使用者介面


使用者介面元件與其他部分隔離開來,讓使用者介面的演化不會破壞其餘程式。


資料庫存取


對資料庫存取的實作應該隱藏起來,讓其他程式像在業務層次處理資料。


對系統的依賴性


對作業系統的依賴歸到一個子系統,有利於可移植性。


第3層:分解為類別


識別出系統所有的類別。


類別與物件的比較


類別就像是設計圖,物件是程式中實際存在的實體。


第4層:分解成子程式


把類別再細分為子程式。


第5層:子程式內部的設計


寫每個子程式內部詳細的功能。


5.3  設計構造塊:啟發式方法


找出現實世界中的物件


首選物件導向設計方法,要點是辨識現實世界中的物件與人造的物件。


使用物件進行設計的步驟是:


辨識物件及其屬性,屬性代表物件的方法和資料


確定可以對各個物件進行的操作


確定個各個物件能對其他物件進行操作


確定物件哪些部分是公用,哪些部分是私用。


定義每個物件的公開介面


這些步驟經常會反覆執行,Iteration迭代是非常重要的。


有兩種方法迭代


高層次的系統組織結構上進行迭代,以便獲得更好的類別組織結構


在以定義好的類別上進行迭代,設計每個類別進入更細節的層次


形成一致的抽象


抽象是一種簡化,稱呼房屋或是城鎮就是在使用抽象。


基底類別是一種抽象,集中精力關心衍生類別共同具有的特性,並在基底類別的層次上忽略具體衍生類別的細節。


抽象的好處在於能夠忽略無關的細節。


封裝實作細節


封裝彌補抽象留下的空白。看圖5-7與圖5-8就能夠明白。


圖5-7 抽象可以讓你用一種簡化的觀點來考慮複雜的概念。


圖5-8 封裝不讓你看到複雜概念的任何細節,你只能看到你能看到的部分。


當繼承能簡化設計時就繼承


出現一些大同小異的物件可以考慮用繼承化簡。定義物件之間的相同點和不同點叫做繼承。


有繼承才有多型,在執行期間才能確定物件的實際類型。


隱藏秘密(資訊隱藏)


結構化設計中的黑盒子概念來自資訊隱藏,物件導向設計中資訊隱藏又引出封裝和模組化的概念。


資訊隱藏是重要的啟發性方法,強調隱藏複雜度。


秘密和隱私權


在設計一個類別時,關鍵性決策確定類別的哪些部分對外可見,哪些特性應該被隱藏起來。


隱藏的秘密可能是某個易變的區域、某種檔案格式、某種資料類型的實作方式,某個需要被隔離的區域。


資訊隱藏的一個例子


????????????????????????


兩種秘密


隱藏複雜度


隱藏變化源


資訊隱藏的障礙


資訊過度分散


100數字出現在程式各處,可以將100寫入MAX_EMPLOYEES常數,只要更動一個常數就可以同時更動多處。


系統內部都有與GUI介面互動的內容,GUI介面改變會造成全部都要改,最好的方式是將人機互動邏輯集中到一個單獨類別、套件、子系統。


一個全域範圍1000個元素的員工資料陣列有可能到處分散,透過存取子程式來使用陣列,就只有存取子程式才知道實作的細節。


循環依賴


類別A使用類別B的子程式,類別B又使用類別A的子程式。要避免循環依賴,會讓系統不容易測試。


把類別的資料誤當成是全域資料


全域資料會出現多個子程式存取同一個全域資料的情況,類別內部只有少數子程式才能存取類別資料。


可以察覺的效能損耗


部分程式設計師擔心會拖慢速度,而放棄資訊隱藏。


資訊隱藏的價值


程式修改容易、資訊隱藏觀念補助物件導向程式設計,有助於設計類別公開介面。


作者建議要思考「我該隱藏些什麼?」


找出容易改變的區域


優秀的程式設計師都有對變化的預期能力,要把不穩定的區域隔離,將變化帶來的影響限制在一個子程式。


1.找出看起來容易變化的項目


2.把看起來容易變化的項目給分離出來


3.把看起來容易變化的項目隔離開來


找出->分離->隔離


容易發生變化的區域


 


業務規則


對硬體的依賴性


輸入和輸出


非標準的語言特性  


例如使用了特殊環境才能運行的子程式庫。


困難的設計區域和建構區域


困難的程式可能會寫得不夠好,將來有很大的機率還會更改。


狀態變數


不要用布林變數做為狀態變數,要用列舉類型。


使用存取子程式取代對狀態變數直接檢查,寫測試程式會比較容易,在一個子程式內就可以做到,不用到處都寫測試程式。


 


資料量的限制


程式參數經常都會改變。


預料不同程度的變化


如果一種變化很可能發生,就要確保系統能夠接受接納這個變化。


保持鬆散耦合


耦合度表示類別與類別、子程式與子程式之間的緊密程度。


耦合度設計的目標是要建立小的、直接的、清晰的類別或子程式。


儘量使模組不依賴或很少依賴其他模組。


耦合標準


規模


模組之間的連接數。只有一個參數的子程式,比有六個參數的子程式耦合更加鬆散。


可見性


兩個模組之間的連接顯著程度。透過參數列表傳遞參數是一種明顯連接,透過全域變數傳遞資料是偷偷摸摸的做法,將全域變數文件化又是稍微好一些的做法。


靈活性


模組之間的連接是否容易更動。舉例來說正常人會喜歡USB連接器而不是直接焊死。


一個模組越容易被其他模組呼叫,它們之間的耦合關係就會越鬆散。


耦合的種類


簡單資料參數耦合


兩個模組透過簡單參數傳遞資料。


簡單物件耦合


一個模組實例化一個物件。


物件參數耦合


兩個模組透過物件傳遞資料。例如Object1 要求Object2傳遞Object3給它,這種耦合關係要更緊密一些。


語義上的耦合


最緊密的耦合,一個模組不僅使用另一個語法元素,而且還使用模組內部工作細節語義知識。


底下是一些例子:


Module1向Module2傳遞一個控制標誌,命令Module2應該做什麼事。


Module2在Module1修改了某全域資料之後,使用該全域資料。


Module2實例化Module1之後,只呼叫Module1.Routine(),沒有呼叫Module1.Initialize。


Module1將Object傳給Module2,只有將Object部分初始化。


Module1將BaseObject傳給Module2,Module2呼叫DerivedObject的特有方法。


語義上的耦合非常危險,有可能編譯器無法察覺錯誤。


類別和子程式是用於降低複雜度的首選。


查閱常用的設計模式


設計模式透過提供現成的抽象來減少複雜度


不需要一行一行講解,別的程式設計師就能夠瞭解程式碼中的設計思維。


設計模式透過把常見解決方案的細節制度化來減少出錯


設計模式是前人經驗與智慧的累積。


設計模式透過提供多種設計方案帶來啟發性的價值


設計模式讓程式設計師容易找到解決問題的方法。


設計模式把設計對話提升到一個更高的層次來簡化交流


設計模式簡化程式設計師之間的交流。


表5-1列出常見的設計模式


要注意不可以誤用設計模式,也不可以為了模式而模式。


其他的啟發式方法


高內聚力


內聚力是類別內部所有子程式,或子程式內程式碼支援一個中心目標時的緊閉程度。


子程式越是使用同一個類別的變數或方法表示內聚力越高。


建構分層結構


抽象的概念表示位於層次關係的最上層,實作是最下層。


恪守類別契約


類別的客戶向該類別所作出的承諾稱為前置條件,物件向其客戶所做的承諾稱為後製條件。


分配職責


每個物件該對什麼負責,這個物件應該隱藏些什麼資訊


為測試而設計


測試驅動開發


避免失誤


要研究成功案例,也要關心失敗案例。


有意識地選擇綁定時間


綁定時間(Binding time)是把特定值綁定(寫入)到某一變數的時間。早綁定會比較簡單,但也缺乏靈活性。


建立中央控制點


控制可以被集中在類別、子程式、前置處理器及#include檔案裡,常數也有可能是中央控制點。


需要搜尋的地方越少,修改起來會越容易。


考慮使用蠻力突破


循序搜尋法解決問題。


畫一張圖


一圖能夠抵千言。


保持設計的模組化


模組化的目標是使得每個子程式或類別看起來像是個黑盒子。


關於設計啟發的總結


尋找現實事件的物件


形成一致的抽象


封裝實作細節


在可能的情況下使用繼承


藏住秘密資訊隱藏


找出容易變動的區域


保持鬆散耦合


探尋通用的設計模式


 


使用啟發式方法的原則


 


5.4 設計實踐


迭代(Iterate)


設計是來來回回重複的過程。


分而治之(Divide and Conquer)


把程式分解為不同的關注區域,然後分別處理每一個區域。


增量式改進:理解問題、形成計畫、執行計畫,回顧作法。


自上而下和自下而上的設計方法


自上而下設計從高層抽象開始,自下而上設計始於細節。


自上而下的論據


從一般類別出發,一步步分解為更具體的類別,大腦就不會處理過多的細節。


自下而上的論據


找出低層職責,會感覺到從頂層觀察系統一次舒服多了。


部分情況主要屬性由底層決定,可能需要與硬體打交道,介面需求決定設計一大部分。


自下而上合成時,需要考慮以下因素:


系統需要做的事項


找出具體的物件和職責


找出通用物件,按照適當方式組織起來


往上一層工作,或是回到最上層嘗試向下設計


其實沒有爭議


分解策略和合成策略的差別。


自上而下的優點簡單、延後建構細節。


自下而上的優點能夠更早找出所需功能,缺點在於較難執行,大多數人擅長大概念分成小概念,或是發現無法組合出想要的系統。


兩種設計方法不是互斥關係,可以同時進行。


建立試驗性原型


建立原型是寫出用於回答特定問題的、數量最少且隨時可拋棄的程式碼。


感覺就是在寫測試程式。


合作設計


合作可以用下面任意一種方式進行


到同事辦公桌前討論


會議室畫白板討論


結對程式設計


報告自己的設計想法


正式檢查


經過一個星期,再來回顧。


網路討論區提問


 


要做多少設計才夠


記錄你的設計成果


把設計文件插入到程式碼裡


用Wiki來記錄設計討論和決策


寫總結郵件


使用數位相機


保留設計掛圖


使用CRC卡片


在適當的細節層建立UML圖


 


5.5對流行的設計方法的評論


 


設計沒有一定的標準,無法定義需要做多少設計才足夠。


 


「設計一切」與「不做設計」都是極端不正確的作法。


 


把設計看成探索過程,不要停留在第一套解決方案、尋求合作、追求簡潔性,需要時做出原型,並進一步迭代。


 


核對表:軟體建構中的設計


設計實踐


 


多次迭代?


多種方案分解系統?


同時自上而下與自下而上設計?


對系統風險或不熟悉部分建立原型?


設計方案被人檢查過?


細節實作?


有保留設計成果?


 


設計目標


 


系統架構層定義


設計劃分層次


程式分解方式


類別分解方式


類別之間互動關係最小化


類別與子程式能否重用


易於維護


精簡


標準技術


最小化複雜度


 


要點


 


軟體首要使命是「管理複雜度」,以簡單作為努力目標。


簡單可用兩種方法,減少同一時間關注的複雜度數量與避免不必要的附加複雜度


設計是啟發過程,避免只使用一種方法


好的設計都是迭代嘗試的結果


資訊隱藏可解決很多困難的設計問題


 


 


留言

這個網誌中的熱門文章

異世界NTR web版第三章 觀後感 喧賓奪主 ,反派實力過強

泛而不精的我被逐出了勇者隊伍 web第三章 觀後感 菲莉真能打; 露娜超爽der

持有縮小技能的D級冒險者,與聖女結婚並加入勇者團隊 漫畫 01-04 觀後感 大我與小我