什么是正交性
“正交性”是從幾何學中借來的術語。如果兩條直線相交成直角,它們就是正交的,比如圖中的坐標軸。用向量術語說,這兩條直線互不依賴。沿著某一條直線移動,你投影到另一條直線上的位置不變。
在計算技術中,該術語用于表示某種不相依賴性或是解耦性。如果兩個或更多事物中的一個發生變化,不會影響其他事物,這些事物就是正交的。在設計良好的系統中,數據庫代碼與用戶界面是正交的:你可以改動界面,而不影響數據庫;更換數據庫,而不用改動界面。
在我們考察正交系統的好處之前,讓我們先看一看非正交系統。
非正交系統
你正乘坐直升機游覽科羅拉多大峽谷,駕駛員——他顯然犯了一個錯誤,在吃魚,他的午餐——突然呻吟起來,暈了過去。幸運的是,他把你留在了離地面100英尺的地方。你推斷,升降桿控制總升力,所以輕輕將其壓低可以讓直升機平緩降向地面。然而,當你這樣做時,卻發現生活并非那么簡單。直升機的鼻子向下,開始向左盤旋下降。突然間你發現,你駕駛的這個系統,所有的控制輸入都有次級效應。壓低左手的操作桿,你需要補償性地向后移動右手柄,并踩右踏板。但這些改變中的每一項都會再次影響所有其他的控制。突然間,你在用一個讓人難以置信的復雜系統玩雜耍,其中每一項改變都會影響所有其他的輸入。你的工作負擔異常巨大:你的手腳在不停地移動,試圖平衡所有交互影響的力量。
直升機的各個控制器斷然不是正交的。
正交的好處
如直升機的例子所闡明的,非正交系統的改變與控制更復雜是其固有的性質。當任何系統的各組件互相高度依賴時,就不再有局部修正(local fix)這樣的事情。
提示13 |
|
Eliminate Effects Between Unrelated Things |
我們想要設計自足(self-contained)的組件:獨立,具有單一、良好定義的目的(Yourdon和Constantine稱之為內聚(cohesion)[YC86])。如果組件是相互隔離的,你就知道你能夠改變其中之一,而不用擔心其余組件。只要你不改變組件的外部接口,你就可以放心:你不會造成波及整個系統的問題。
如果你編寫正交的系統,你得到兩個主要好處:提高生產率與降低風險。
提高生產率
l 改動得以局部化,所以開發時間和測試時間得以降低。與編寫單個的大塊代碼相比,編寫多個相對較小的、自足的組件更為容易。你可以設計、編寫簡單的組件,對其進行單元測試,然后把它們忘掉——當你增加新代碼時,無須不斷改動已有的代碼。
l 正交的途徑還能夠促進復用。如果組件具有明確而具體的、良好定義的責任,就可以用其最初的實現者未曾想象過的方式,把它們與新組件組合在一起。
l 如果你對正交的組件進行組合,生產率會有相當微妙的提高。假定某個組件做M件事情,而另一個組件做N件事情。如果它們是正交的,而你把它們組合在一起,結果就能做M x N件事情。但是,如果這兩個組件是非正交的,它們就會重疊,結果能做的事情就更少。通過組合正交的組件,你的每一份努力都能得到更多的功能。
降低風險
正交的途徑能降低任何開發中固有的風險。
l 有問題的代碼區域被隔離開來。如果某個模塊有毛病,它不大可能把病癥擴散到系統的其余部分。要把它切掉,換成健康的新模塊也更容易。
l 所得系統更健壯。對特定區域做出小的改動與修正,你所導致的任何問題都將局限在該區域中。
l 正交系統很可能能得到更好的測試,因為設計測試、并針對其組件運行測試更容易。
l 你不會與特定的供應商、產品、或是平臺緊綁在一起,因為與這些第三方組件的接口將被隔離在全部開發的較小部分中。
讓我們看一看在工作中應用正交原則的幾種方式。
項目團隊
你是否注意到,有些項目團隊很有效率,每個人都知道要做什么,并全力做出貢獻,而另一些團隊的成員卻老是在爭吵,而且好像無法避免互相妨礙?
這常常是一個正交性問題。如果團隊的組織有許多重疊,各個成員就會對責任感到困惑。每一次改動都需要整個團隊開一次會,因為他們中的任何一個人都可能受到影響。
怎樣把團隊劃分為責任得到了良好定義的小組,并使重疊降至最低呢?沒有簡單的答案。這部分地取決于項目本身,以及你對可能變動的區域的分析。這還取決于你可以得到的人員。我們的偏好是從使基礎設施與應用分離開始。每個主要的基礎設施組件(數據庫、通信接口、中間件層,等等)有自己的子團隊。如果應用功能的劃分顯而易見,那就照此劃分。然后我們考察我們現有的(或計劃有的)人員,并對分組進行相應的調整。
你可以對項目團隊的正交性進行非正式的衡量。只要看一看,在討論每個所需改動時需要涉及多少人。人數越多,團隊的正交性就越差。顯然,正交的團隊效率也更高(盡管如此,我們也鼓勵子團隊不斷地相互交流)。
設計
大多數開發者都熟知需要設計正交的系統,盡管他們可能會使用像模塊化、基于組件、或是分層這樣的術語描述該過程。系統應該由一組相互協作的模塊組成,每個模塊都實現不依賴于其他模塊的功能。有時,這些組件被組織為多個層次,每層提供一級抽象。這種分層的途徑是設計正交系統的強大方式。因為每層都只使用在其下面的層次提供的抽象,在改動底層實現、而又不影響其他代碼方面,你擁有極大的靈活性。分層也降低了模塊間依賴關系失控的風險。你將常常看到像下一頁的圖2.1這樣的圖表示的層次關系。
對于正交設計,有一種簡單的測試方法。一旦設計好組件,問問你自己:如果我顯著地改變某個特定功能背后的需求,有多少模塊會受影響?在正交系統中,答案應
圖2.1 典型的層次圖 |
該是“一個”。移動GUI面板上的按鈕,不應該要求改動數據庫schema。增加語境敏感的幫助,也不應該改動記賬子系統。
讓我們考慮一個用于監視和控制供暖設備的復雜系統。原來的需求要求提供圖形用戶界面,但后來需求被改為要增加語音應答系統,用按鍵電話控制設備。在正交地設計的系統中,你只需要改變那些與用戶界面有關聯的模塊,讓它們對此加以處理:控制設備的底層邏輯保持不變。事實上,如果你仔細設計你的系統結構,你應該能夠用同一個底層代碼庫支持這兩種界面。157頁的“它只是視圖”將討論怎樣使用模型-視圖-控制器(MVC)范型編寫解耦的代碼,該范型在這里的情況下也能很好地工作。
還要問問你自己,你的設計在多大程度上解除了與現實世界中的的變化的耦合?你在把電話號碼當作顧客標識符嗎?如果電話公司重新分配了區號,會怎么樣?不要依賴你無法控制的事物屬性。
工具箱與庫
在你引入第三方工具箱和庫時,要注意保持系統的正交性。要明智地選擇技術。
我們曾經參加過一個項目,在其中需要一段Java代碼,既運行在本地的服務器機器上,又運行在遠地的客戶機器上。要把類按這樣的方式分布,可以選用RMI或CORBA。如果用RMI實現類的遠地訪問,對類中的遠地方法的每一次調用都可能會拋出異常;這意味著,一個幼稚的實現可能會要求我們,無論何時使用遠地類,都要對異常進行處理。在這里,使用RMI顯然不是正交的:調用遠地類的代碼應該不用知道這些類的位置。另一種方法——使用CORBA——就沒有施加這樣的限制:我們可以編寫不知道我們類的位置的代碼。
在引入某個工具箱時(甚或是來自你們團隊其他成員的庫),問問你自己,它是否會迫使你對代碼進行不必要的改動。如果對象持久模型(object persistence scheme)是透明的,那么它就是正交的。如果它要求你以一種特殊的方式創建或訪問對象,那么它就不是正交的。讓這樣的細節與代碼隔離具有額外的好處:它使得你在以后更容易更換供應商。
Enterprise Java Beans(EJB)系統是正交性的一個有趣例子。在大多數面向事務的系統中,應用代碼必須描述每個事務的開始與結束。在EJB中,該信息是作為元數據,在任何代碼之外,以聲明的方式表示的。同一應用代碼不用修改,就可以運行在不同的EJB事務環境中。這很可能是將來許多環境的模型。
正交性的另一個有趣的變體是面向方面編程(Aspect-Oriented Programming,AOP),這是Xerox Parc的一個研究項目([KLM+97]與[URL 49])。AOP讓你在一個地方表達本來會分散在源碼各處的某種行為。例如,日志消息通常是在源碼各處、通過顯式地調用某個日志函數生成的。通過AOP,你把日志功能正交地實現到要進行日志記錄的代碼中。使用AOP的Java版本,你可以通過編寫aspect、在進入類Fred的任何方法時寫日志消息:
aspect Trace {
advise * Fred.*(..) {
static before {
Log.write("-> Entering " + thisJoinPoint.methodName);
}
}
}
如果你把這個方面編織(weave)進你的代碼,就會生成追蹤消息。否則,你就不會看到任何消息。不管怎樣,你原來的源碼都沒有變化。
編碼
每次你編寫代碼,都有降低應用正交性的風險。除非你不僅時刻監視你正在做的事情,也時刻監視應用的更大語境,否則,你就有可能無意中重復其他模塊的功能,或是兩次表示已有的知識。
你可以將若干技術用于維持正交性:
l 讓你的代碼保持解耦。編寫“羞怯”的代碼——也就是不會沒有必要地向其他模塊暴露任何事情、也不依賴其他模塊的實現的模塊。試一試我們將在183頁的“解耦與得墨忒耳法則”中討論的得墨忒耳法則(Law of Demeter)[LH89]。如果你需要改變對象的狀態,讓這個對象替你去做。這樣,你的代碼就會保持與其他代碼的實現的隔離,并增加你保持正交的機會。
l 避免使用全局數據。每當你的代碼引用全局數據時,它都把自己與共享該數據的其他組件綁在了一起。即使你只想對全局數據進行讀取,也可能會帶來麻煩(例如,如果你突然需要把代碼改為多線程的)。一般而言,如果你把所需的任何語境(context)顯式地傳入模塊,你的代碼就會更易于理解和維護。在面向對象應用中,語境常常作為參數傳給對象的構造器。換句話說,你可以創建含有語境的結構,并傳遞指向這些結構的引用。
《設計模式》[GHJV95]一書中的Singleton(單體)模式是確保特定類的對象只有一個實例的一種途徑。許多人把這些singleton對象用作某種全局變量(特別是在除此而外不支持全局概念的語言中,比如Java)。使用singleton要小心——它們可能造成不必要的關聯。
l 避免編寫相似的函數。你常常會遇到看起來全都很像的一組函數——它們也許在開始和結束處共享公共的代碼,中間的算法卻各有不同。重復的代碼是結構問題的一種癥狀。要了解更好的實現,參見《設計模式》一書中的Strategy(策略)模式。
養成不斷地批判對待自己的代碼的習慣。尋找任何重新進行組織、以改善其結構和正交性的機會。這個過程叫做重構(refactoring),它非常重要,所以我們專門寫了一節加以討論(見“重構”,184頁)
測試
正交地設計和實現的系統也更易于測試,因為系統的各組件間的交互是形式化的和有限的,更多的系統測試可以在單個的模塊級進行。這是好消息,因為與集成測試(integration testing)相比,模塊級(或單元)測試要更容易規定和進行得多。事實上,我們建議讓每個模塊都擁有自己的、內建在代碼中的單元測試,并讓這些測試作為常規構建過程的一部分自動運行(參見“易于測試的代碼”,189頁)。
構建單元測試本身是對正交性的一項有趣測試。要構建和鏈接某個單元測試,都需要什么?只是為了編譯或鏈接某個測試,你是否就必須把系統其余的很大一部分拽進來?如果是這樣,你已經發現了一個沒有很好地解除與系統其余部分耦合的模塊。
修正bug也是評估整個系統的正交性的好時候。當你遇到問題時,評估修正的局部化程度。
你是否只改動了一個模塊,或者改動分散在整個系統的各個地方?當你做出改動時,它修正了所有問題,還是又神秘地出現了其他問題?這是開始運用自動化的好機會。如果你使用了源碼控制系統(在閱讀了86頁的“源碼控制”之后,你會使用的),當你在測試之后、把代碼簽回(check the code back)時,標記所做的bug修正。隨后你可以運行月報,分析每個bug修正所影響的源文件數目的變化趨勢。
文檔
也許會讓人驚訝,正交性也適用于文檔。其坐標軸是內容和表現形式。對于真正正交的文檔,你應該能顯著地改變外觀,而不用改變內容。現代的字處理器提供了樣式表和宏,能夠對你有幫助(參見“全都是寫”,248頁)。
認同正交性
正交性與27頁介紹的DRY原則緊密相關。運用DRY原則,你是在尋求使系統中的重復降至最小;運用正交性原則,你可降低系統的各組件間的相互依賴。這樣說也許有點笨拙,但如果你緊密結合DRY原則、運用正交性原則,你將會發現你開發的系統會變得更為靈活、更易于理解、并且更易于調試、測試和維護。
如果你參加了一個項目,大家都在不顧一切地做出改動,而每一處改動似乎都會造成別的東西出錯,回想一下直升機的噩夢。項目很可能沒有進行正交的設計和編碼。是重構的時候了。
另外,如果你是直升機駕駛員,不要吃魚……
原文地址:http://book.csdn.net/bookfiles/102/1001022939.shtml
本博客為學習交流用,凡未注明引用的均為本人作品,轉載請注明出處,如有版權問題請及時通知。由于博客時間倉促,錯誤之處敬請諒解,有任何意見可給我留言,愿共同學習進步。