C 正交設計筆記3 變化驅動 正交設計

2021-08-16 12:19:30 字數 4418 閱讀 9020

總結

原文:

當談起軟體設計的目的時,能夠獲得所有人認同的答案只有乙個:功能實現。 因為這是乙個軟體存在的根本原因。

而在計算機軟體發展的初期,這一點也正是所有人做軟體設計的唯一動機。因而,很自然的,整個軟體都被放在單一過程中,然後用到處存在的goto語句控制流程。

儘管理論上講,任意複雜的系統都可以被放入同乙個函式裡。但隨著軟體越來複雜,即便是智商最為發達的程式設計師也發現,單一過程的複雜度已經超出他的掌控極限。這逼迫人們必須對大問題進行分解,分而治之。

時至今日,儘管超大函式,上帝類依然並不罕見,但當大到一定程度,上帝類的創造者最終也會發現自己終究沒有上帝般的掌控力。因而,哪怕是軟體設計素養為負值的開發者,或多或少也會對乙個複雜系統進行一定程度的拆分。

這就是模組化設計的最初動機。

一旦人們開始進行進行模組化拆分,就必須解決如下兩個問題:

更簡單的說:怎麼分?然後再怎麼合(設計api)?

而這兩個問題的答案,正是現代軟體設計的核心關注點。

為了找到這兩個問題的答案,我們需要重新回到最初的問題:為何要做軟體設計?

kent beck給出的答案是:軟體設計是為了讓軟體在長期範圍內容易應對變化

對此,kent beck提出了乙個更為精煉的原則:區域性化影響。意思是說:我們希望,任何乙個變化,對於我們當前的軟體設計影響範圍都可以控制在乙個盡量小的區域性。

這當然是所有嚴肅的軟體從業者都夢寐以求的。可問題在於,如何才能做到?

內聚與耦合

每個讀過基礎軟體工程教程的人都知道:乙個易於應對變化的軟體設計應該遵從高內聚,低耦合原則。

這樣的解釋,對於很多人而言,依然會感到過於抽象。但如果我們進一步思考,就會意識到:看似神秘的內聚與耦合,正好對應最初的兩個問題:

如果用圖來展現,就是下面的過程與關係:

這幅圖揭示了模組化設計的全部:

相對於區域性化影響,高內聚,低耦合原則已經清晰和具體許多。但依然更像是在描述目標或結果,而沒有指明該如何達成的方法。雖然《**大全》列舉了那麼多的內聚性和耦合性的分類,但對於想應用它們的軟體設計人員,依然感覺如隔靴撓癢,不得要領。

因而,我們需要從它推導出更為明確,更具指導性和操作性的設計原則。

為了做到這一點,我們必須首先搞清楚:內聚耦合,和變化之間的關係是怎樣的,以至於高內聚、低耦合的模組化方式能夠更容易應對變化?

在一些時候,我們可以直接判定乙個模組是否包含多重職責。因為它們確實包含著明顯沒有什麼關聯的兩組**元素。

但在另外一些場景下,我們則無法清晰的判定:乙個模組是否真的包含多重變化原因,或多重職責。比如如下**:

struct student 

;void sort_students_by_height(student students, size_t num_of_students) }}

}

這是乙個對學生按照身高從低到高進行排序的演算法。對於這段**,如果我們進行猜測,會發現很多點都有變化的可能,如果對這些變化都進行分離和管理,確實會提高系統的內聚度。但如果我們現在就將整個系統每個可能的變化點都分離出來,無疑會讓整個系統陷入無邊無際的不必要的複雜度。

破解這類難題的方法是:既然我們知道高內聚,低耦合的設計是為了軟體更容易應對變化的,那麼我們為何不反過來,讓實際發生的需求變化來驅動我們識別變化,管理變化,從而讓我們的系統達到恰如其分的內聚度和耦合度?

首先進入我們射程的就是重複**。編寫重複**不僅僅會讓有追求的程式設計師感到乏味。真正致命的是:「重複」極度違背高內聚、低耦合原則,從而會大幅提公升軟體的長期維護成本。

由此,我們得到了第乙個策略:消除重複。這個策略,非常明確,極具可操作性:當你看到重複時,盡力消除它。這個策略:

除了重複**外,另外乙個驅動系統朝向高內聚方向演進的訊號是:我們經常需要因為同一類原因,修改某個模組。而這個模組的其它部分卻保持不變。

比如,在之前我們對學生按照身高從低到高排序的例子中,如果現在我們需要增加對老師按照身高從低到高排序的需求,我們就知道,排序物件是乙個新的變化方向。於是,我們將**重構為:

template

void bulb_sort(t objects, size_t num_of_objects)

} }}

如果隨後又出現乙個新的需求:按照學生身高從高到低排序(原來為從低到高)。此時我們知道排序規則也是乙個變化的方向。因此,我們將這個變化方向也從現有**中分離出去。然後得到:

template

void bulb_sort(t objects, size_t num_of_objects)

} }}

分離不同變化方向,目標在於提高內聚度。因為多個變化方向,意味著乙個模組存在多重職責。將不同的變化方向進行分離,也意味著各個變化方向職責的單一化。

從這個例子可以看出,此策略的應用時機也非常明確:當你發現需求導致乙個變化方向出現時,將其從原有的設計中分離出去。

圖:分離新的變化方向

對於變化方向的分離,也得到了另外乙個我們追求的目標:可擴充套件性

如果我們足夠細心,會發現策略消除重複分離不同變化方向是兩個高度相似和關聯的策略:

儘管如此,我們依然需要兩個不同的策略。這是因為:變化方向並不總是以重複**的形式出現的(其典型症狀是散彈式修改,或者if-else、switch-case、模式匹配);儘管其背後往往存在乙個以重複**形式表現的等價形式(這也是為何copy-paste-modify如此流行的原因)。

前面兩個策略解決了軟體單元該如何劃分的問題。現在我們需要關注模組之間的粘合點——即api——的定義問題。

需要強調的是:兩個模組之間並不存在耦合,它們的都共同耦合在api上。因而* api如何定義才能降低耦合度*,才是我們應該關注的重點。

從這幅圖可以看出,對於api定義所帶來的耦合度影響,需要遵循如下原則:

而具體到策略縮小依賴範圍,它強調:

但是,無論我們如何縮小依賴範圍,如果兩個模組需要協作,它們之間必然存在耦合點(即api)。降低耦合度的努力似乎已經走到了盡頭。

我們知道,耦合的最大問題在於:耦合點的變化,會導致依賴方跟著變化。但這也意味著,如果耦合點從來不會變化,那麼依賴方也就不會因此而變化。換句話說,耦合點越穩定,依賴方受耦合變化影響的概率就越低

由此,我們得到最後乙個策略:向著穩定的方向依賴。

而這正是封裝或資訊隱藏的關鍵。

小結這四個策略,前兩者聚焦於如何劃分模組,後兩個聚焦於如何定義模組間的api。換句話說,前兩者關注於「如何分」,後兩條聚焦於「怎麼合」。

這四個策略的背後動力非常明確:變化。

由於這四個策略致力於讓系統朝著更具正交性的方向演進,因而它們也被稱做正交策略,或者正交四原則

由此得到了兩個問題:模組劃分必然要解決如何劃分,以及模組間如何協作(api 定義)的問題。

基於軟體易於應對變化的角度出發。高內聚、低耦合原則是最為核心和關鍵的高層原則。基於此我們得到了在模組化過程中,我們真正需要關注的三方關係

為了讓高內聚、低耦合更具指導性和操作性,我們提出了四個策略。它們以變化驅動,讓系統逐步向更好的正交性演進的策略,因此也被稱做正交策略正交原則。李光磊對此精煉的總結道:

我們已經在多個系統的設計和開發中,以這四個原則來驅動我們的軟體設計,不僅讓我們的系統在保持簡單的同時,具備所有必要的靈活性。也讓設計和開發活動變得高度有章可循,讓團隊生產率得以大幅提公升。

最後,推薦劉光聰的文章《實戰正交設計》。這篇文章通過乙個例子,來展示了正交策略是如何驅動出更加正交的設計的。

而正交設計與solid的關係,可參閱《正交設計,oo與solid》。

AMR linux S3c2440之ADC驅動實現

硬體描述 s3c2440有乙個10 bit的cmos adc 模數轉換器,支援8個模擬通道輸入,10位的解析度,最高速度可達500ksps 500 千次 每秒 從圖中可知 模擬adc,包含了2部分功能,一部分是觸屏功能,另一部分就是普通adc功能,分別可以產生int tc和int adc 兩個中斷。...

C 設計例子 3

存款利息如下 0.63 期限 1年 0.66 期限 2年 0.69 期限 3年 0.75 期限 5年 0.84 期限 8年 問2000元存20年,最佳的訪問方式是什麼?實際上採用的是演算法中例舉的窮舉法,程式如下 include include using namespace std int mai...

《Head First 設計模式》筆記3

動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。設計原則四 類應該對擴充套件開放,對修改關閉。如果使用過 python,應該聽過裝飾器,雖然概念有點不同,但都是通過動態新增的方式給物件擴充套件功能。星巴克的訂單系統系統中有個飲料抽象類 beverage,店內的飲料都...