在另一篇文章中說明了“規(guī)劃項(xiàng)目結(jié)構(gòu)”的重要性抽减,在這篇文章中則要來談?wù)勅绾螌?shí)踐橄碾。
決定結(jié)構(gòu)的依據(jù)
在決定項(xiàng)目結(jié)構(gòu)的分類方式時(shí),不外乎是依 Feature 或是依 Layer 來設(shè)置所謂的 Package 或是 Namespace史汗,一般都會(huì)與實(shí)體的目錄名稱做搭配停撞,形成一個(gè)同步的樹狀結(jié)構(gòu)悼瓮。這二種分類的差別,其實(shí)說白了就是倒底是要依照 SA 還是 SD 的文件內(nèi)容來做分類的基準(zhǔn)埋市。
在進(jìn)行系統(tǒng)設(shè)計(jì)時(shí)命贴,理所當(dāng)然地會(huì)以 SA 文件為基礎(chǔ)來開展工作胸蛛,因?yàn)橄到y(tǒng)分析本來就是做為設(shè)計(jì)之前的信息分析與統(tǒng)整的工作。在這樣的前提之下省咨,所設(shè)計(jì)出來的 Class 就注定會(huì)帶有 SA 文件分類的屬性玷室。然而在 OOP 的原則之下,單一個(gè) Class 不太可能負(fù)擔(dān)所有的工作箩兽,所以設(shè)計(jì)出一組 Class 用來實(shí)現(xiàn) SA 文件所描述的功能是很常見的手法章喉。當(dāng)設(shè)計(jì)工作再演化下去,為了有系統(tǒng)組織設(shè)計(jì)的結(jié)果落包,就會(huì)在設(shè)計(jì)中導(dǎo)入 Design Pattern 或是 Framework咐蝇,來試圖形成多個(gè) Class 的群組巷查。這時(shí) Class 就具備了第二種的分類屬性岛请,因?yàn)樵诿恳唤M的設(shè)計(jì)中 Class 都會(huì)有特定的角色或定位來協(xié)助完成對(duì)應(yīng)的工作。
在經(jīng)過以上的說明后可以看到盅称,大部份 Class 都最少有二種的分類方式僚匆。然而,項(xiàng)目或者是目錄的結(jié)構(gòu)只能以一種視點(diǎn)來表達(dá)逞盆,抉擇就因此而產(chǎn)生云芦。依 SA 文件會(huì)以功能面或是處理的數(shù)據(jù)類型為主贸桶,所以分類上就會(huì)形成類似 Customer皇筛、Product、Order 這樣的結(jié)構(gòu)旗笔。依 SD 文件則會(huì)是以 Class 的角色定位為主,如果 SD 文件中規(guī)定要使用 MVC 的 Design Pattern拳魁,則分類就會(huì)出現(xiàn) Model撮弧、View贿衍、Controller 這樣的結(jié)構(gòu)。
決定結(jié)構(gòu)的首要考量
至于依 Feature 或是 Layer 何者熟優(yōu)熟劣岂却,就過去的經(jīng)驗(yàn)法則裙椭,我本身是比較傾向依 Feature 來分類揉燃。不過筋栋,在這之前其實(shí)要先考慮的是程序?qū)嶓w切割的問題弊攘。怎么說?在系統(tǒng)成長迈倍、擴(kuò)張到極致時(shí)捣域,勢(shì)必得導(dǎo)入分散式的架構(gòu)設(shè)計(jì)焕梅,也就是程序是散落在不同的運(yùn)行環(huán)境之中,并且大多都以網(wǎng)絡(luò)為交換信息的媒介斜棚。
在進(jìn)入到分散式架構(gòu)的設(shè)計(jì)之前沒有先預(yù)留好必要的彈性,進(jìn)入之后又沒有足夠的決心打掉重來蚤霞。接著下來在設(shè)計(jì)上的調(diào)整工作争便,對(duì)負(fù)責(zé)的人來說將會(huì)是一個(gè)相當(dāng)耗費(fèi)心力的過程断医。
在這個(gè)過程中數(shù)據(jù)傳遞的問題會(huì)是最大的障壁鉴嗤,很多理所當(dāng)然的 Class 之間交換數(shù)據(jù)之手法,在移至遠(yuǎn)端后就晉級(jí)到完全不同的次元兔簇。在不是分散式架構(gòu)時(shí)垄琐,所有的 Class 共用內(nèi)存经柴,所以數(shù)據(jù)在 Class 間可以直接共享坯认、存取。在跨設(shè)備交換時(shí)陋气,則需要增加額外的程序來達(dá)成引润,不論是對(duì)數(shù)據(jù)進(jìn)行包裝或轉(zhuǎn)換椰拒,不是單純地把 Class 分別放置在不同的實(shí)體中就可以順利的運(yùn)作。
再來褒脯,需要進(jìn)行的是:調(diào)整不適用分散式架構(gòu)的設(shè)計(jì)內(nèi)容番川。數(shù)據(jù)傳遞的過程變復(fù)雜了,原本的設(shè)計(jì)就有可能不敷使用践啄,增加接腳屿讽、改變調(diào)用方式都是必經(jīng)的過程吠裆∈愿恚可見范圍的改變也直接沖擊著原有的設(shè)計(jì)思維,不像是所有的 Class 都被裝在同一個(gè)容器中履澳,在跨設(shè)備進(jìn)行遠(yuǎn)端調(diào)用時(shí)不可能“看得見”遠(yuǎn)端所有的 Class距贷,只會(huì)有被設(shè)計(jì)要用來揭露的介面吻谋,所以碰觸到這些部份的設(shè)計(jì)都需要重新來過滨溉。有時(shí)候一些違反設(shè)計(jì)精神长赞、便宜行事的做法得哆,譬如讓不相干的二個(gè) Class 逕行互相調(diào)用,在這種環(huán)境下就會(huì)被嚴(yán)格地指正出來栋操。相關(guān)的問題一般都是潛藏在設(shè)計(jì)的各個(gè)角落矾芙,等到系統(tǒng)運(yùn)作出了問題才會(huì)發(fā)現(xiàn)這樣的計(jì)設(shè)方式行不通近上。當(dāng)系統(tǒng)出錯(cuò)的情況經(jīng)過幾次之后,對(duì)負(fù)責(zé)設(shè)計(jì)的人而言耗掉了心力不說感帅,工作的品質(zhì)也會(huì)面臨嚴(yán)重的挑戰(zhàn)地淀。
既然分割這么麻煩帮毁,那就不要分,所有的 Class 都往同一個(gè)項(xiàng)目丟硬梁,不就什么事都沒有了荧止?以結(jié)論來說阶剑,這不是一個(gè)謹(jǐn)慎的架構(gòu)師會(huì)采用的策略牧愁。這個(gè)方式的好處除了在更新版本時(shí),不用考慮各端點(diǎn)設(shè)備版本配對(duì)的問題兔朦、直接將所有設(shè)備用相同的文件覆蓋過一次之外磨确,我想不到還有其他的優(yōu)點(diǎn)沽甥。
首先,在開發(fā)階段會(huì)碰到的問題是剛才提到的可見范圍的議題乏奥,開發(fā)人員會(huì)因?yàn)樗?fù)責(zé)的 Class 看得見其他遠(yuǎn)端的 Class 而產(chǎn)生混淆摆舟,然后開始不停地質(zhì)疑為什么明明就在眼前卻不能直接調(diào)用。但其中的差別大概也只有負(fù)責(zé)設(shè)計(jì)的人才弄得清楚邓了,光是解釋就要花掉不少的唇舌恨诱。
再來就是部署時(shí),不見得每一個(gè)端點(diǎn)的設(shè)備都有足夠的硬件資源提供給程序運(yùn)作之用骗炉。當(dāng)所有的代碼都放在一起照宝,就會(huì)出現(xiàn)一個(gè)現(xiàn)象是不論在哪種設(shè)備上痕鳍,所提供的程序文件都是一樣的肥大硫豆,會(huì)出現(xiàn)最糟的情況是因資源不足而有運(yùn)行不穩(wěn)定的情況龙巨。如果是在移動(dòng)平臺(tái)上,就有可能會(huì)因?yàn)橐螺d的文件過大而使 App 的下載率降低熊响,導(dǎo)因卻是 App 里塞了很多用不到的代碼這種低級(jí)的問題旨别。
在設(shè)計(jì)時(shí)容易被忽略的重點(diǎn)
在 ISO 27001 的定義里,所謂的風(fēng)險(xiǎn)指的是威脅加上弱點(diǎn)的組合結(jié)果汗茄。沒有威脅就算全部都是弱點(diǎn)也無所謂秸弛,如同把一個(gè)人放到完全沒有病毐及細(xì)菌的環(huán)境中,即使免疫功能不正常也不會(huì)有致病的風(fēng)險(xiǎn)洪碳。反之递览,如果把一顆石頭放到充滿病毐及細(xì)菌的環(huán)境中,也不會(huì)有人擔(dān)心石頭有生病的疑慮瞳腌,所以沒有弱點(diǎn)就算有威脅也不用擔(dān)心绞铃。
在網(wǎng)絡(luò)世代中,設(shè)計(jì)系統(tǒng)如果不考慮安全議題嫂侍,是一個(gè)不及格的設(shè)計(jì)儿捧。然而很多時(shí)候有關(guān)安全的需求并不會(huì)被載入 SA 文件內(nèi),以致安全防護(hù)的設(shè)計(jì)在有心無心之下被排除在外挑宠。而把所有的 Class 都丟在同一個(gè)項(xiàng)目中菲盾,這種便宜行事的做法就是一個(gè)很不安全的策略、只有不在意信息安全才會(huì)選擇的方式各淀。
延續(xù)之前的內(nèi)容懒鉴,當(dāng)設(shè)備中所部署的程序中包含了許多不需要的部份,就會(huì)增加弱點(diǎn)出現(xiàn)的機(jī)會(huì)碎浇,即便這些程序片斷在正常的情況下并不運(yùn)作临谱。而網(wǎng)絡(luò)攻擊的威脅則不可能會(huì)消失、甚至手法不斷的翻新奴璃,二者結(jié)合就會(huì)大大地提高被入侵的風(fēng)險(xiǎn)值吴裤。
換個(gè)角度來說,設(shè)計(jì)與開發(fā)安全防護(hù)機(jī)制是需要成本的溺健,在沒有必要的情況下開發(fā)人員自然是多一事不如少一事,檢查寫得是愈少愈好钮蛛。畢竟在分工程度較高的團(tuán)隊(duì)中鞭缭,開發(fā)人員不一定會(huì)接觸到部署的相關(guān)規(guī)劃或執(zhí)行,自然不會(huì)對(duì)資安具備敏感度魏颓。再加上 SD 的文件中沒有特別指明防護(hù)要做到的層級(jí)岭辣,產(chǎn)出的結(jié)果一定是只有最低限度。一旦這些代碼被入侵者以不正當(dāng)?shù)氖址ㄟ\(yùn)行甸饱,就有可能形成很大的安全漏洞沦童。所以在部署的思維上應(yīng)該要做到精確的配置仑濒、只提供必要運(yùn)行的部份,以期減少安全上的風(fēng)險(xiǎn)偷遗。
在安全上墩瞳,被侵入是一個(gè)議題,信息的泄露則是另一個(gè)氏豌。假設(shè)在移動(dòng)平臺(tái)中所下載的程序中包含有 Server 端的邏輯喉酌,以目前移動(dòng)平臺(tái)對(duì)代碼的保護(hù)等級(jí)來說,無疑是送給駭客一份大禮泵喘。在這樣的情況之下泪电,就算移動(dòng)端的防護(hù)做得再好,也能夠依據(jù) App 中額外的 Server 端代碼來按圖索驥纪铺,找到破解的方法相速。
實(shí)體分割的原則
那該如何規(guī)劃以進(jìn)行實(shí)體分割?常見的三層式架構(gòu)是一個(gè)很好的切入點(diǎn)鲜锚,也就是把設(shè)計(jì)的內(nèi)容切分為 Presentation突诬、Business Logic、Data Access 三大區(qū)塊烹棉。主要是一般的部署策略中攒霹,硬件設(shè)備的配置也是以這樣的架構(gòu)做為雛型。所以當(dāng)數(shù)據(jù)傳遞的切割點(diǎn)以這些區(qū)塊的界線做為基準(zhǔn)時(shí)浆洗,在部署規(guī)劃有異動(dòng)時(shí)比較容易配合硬件架構(gòu)上的需求催束。
在最開始就把代碼以實(shí)體的方式分開,像是在 Visual Studio 里使用不同的 Project 并以 Solution 來封裝伏社,或是在 Android Studio 里在同一個(gè) Project 中分出多個(gè) Module抠刺。依據(jù)不同開發(fā)工具的特性,可以做到一部份的早期設(shè)計(jì)預(yù)警的效果摘昌。像是之前提到可見度的問題速妖,在分開后多少能降低開發(fā)人員在這方面的疑惑,減少其誤用的情況聪黎。同時(shí)也可以驗(yàn)證所設(shè)計(jì)的內(nèi)容罕容,在上線被分開部署后基本的調(diào)用過程是可以順利地運(yùn)作。如果是使用 Visual Studio 特定的版本稿饰,甚至提供了自動(dòng)化檢查的功能锦秒,負(fù)責(zé)設(shè)計(jì)的人員只要把相關(guān)的限制輸入好,就能在編譯時(shí)顯示警告喉镰,防止開發(fā)人員沒有按圖施工旅择,可以節(jié)省很多查驗(yàn)的工作。
預(yù)先做好切割還有另一項(xiàng)好處侣姆,可以提高 Class 的重用率生真、累積團(tuán)隊(duì)的技術(shù)資產(chǎn)沉噩、減少重工。因?yàn)樵诤罄m(xù)的開發(fā)工作中如果需要相同的設(shè)計(jì)柱蟀,直接引用已經(jīng)現(xiàn)有的獨(dú)立單元即可川蒙。如不是,則要在過往的程序項(xiàng)目中巡覽产弹、在一堆 Class 中做挑選派歌、復(fù)制的工作,而每一個(gè)新項(xiàng)目都要再重復(fù)一次這個(gè)循環(huán)痰哨。
當(dāng)然胶果,事情永遠(yuǎn)不可能單純到依照原則切割后,所有的 Class 就會(huì)自動(dòng)歸位斤斧,接下來要煩惱的是 Class 的分配問題早抠。譬如決定哪些是前一段提到可獨(dú)立于三層結(jié)構(gòu)之外的共用 Class、哪些負(fù)責(zé)顯示數(shù)據(jù)撬讽、哪些用于處理商業(yè)邏輯蕊连、哪些直接存取數(shù)據(jù)。定位明確的好處理游昼,曖昧不明甘苍、模棱兩可的則是會(huì)讓人燒腦。
以一個(gè)大家常用的 MVC Design Pattern 為例烘豌,View 毫無疑問地是要被放在 Presentation 層內(nèi)载庭,那 Controller 和 Model 應(yīng)該要放在 Presentation 還是 Business Logic?在這里賣個(gè)關(guān)子廊佩,留給各位看倌去思考囚聚,也歡迎留言一起討論。
分類原則的選擇
做完了實(shí)體的分割标锄,就下來就是怎么決定分類的原則顽铸。
就像一開始提到的,我傾向以依 Feature 為主料皇,不過這樣的說法并不精確谓松。還是那句話,這個(gè)世界還沒有單純到只用一種原則就可以搞定所有的事践剂。剛才也有提到共用的 Class 應(yīng)該要被獨(dú)立出來以便跨系統(tǒng)可以共用毒返,既然是可以跨系統(tǒng)代表是系統(tǒng)間共同的需求,或是因應(yīng)設(shè)計(jì)產(chǎn)生的慣例舷手。系統(tǒng)間共用的需求會(huì)出現(xiàn)在 SA 文件中,但是因應(yīng)設(shè)計(jì)產(chǎn)生的慣例在 SA 文件中不會(huì)有劲绪,如果要以 Feature 為主進(jìn)行分類男窟,這些 Class 被獨(dú)立出來之后怎么分類盆赤?
那看起來是依 Layer 比較保險(xiǎn)啰?不是歉眷!因?yàn)閷?shí)務(wù)上依 Feature 在擴(kuò)充結(jié)構(gòu)牺六、任務(wù)分配、版本控管汗捡、代碼巡覽上還是比較有優(yōu)勢(shì)淑际。例如:在需求變更時(shí),不同的變更項(xiàng)目可以被結(jié)構(gòu)給分離出來扇住,所以在發(fā)行版本時(shí)能夠更精確的選擇要異動(dòng)的項(xiàng)目清單春缕,并且把未完成的部份隔離在要發(fā)行的版本之外。
再舉一個(gè)例子艘蹋,想像一下锄贼,當(dāng)你的系統(tǒng)在分散式的架構(gòu)上,某個(gè)提供服務(wù)的設(shè)備超出負(fù)載女阀,在評(píng)估后決定要進(jìn)行分割程序打散到不同的硬件上以平衡負(fù)載宅荤。這時(shí)的切割方式是依照 Customer、Product浸策、Order 比較合理冯键?還是依照 Model、View庸汗、Controller 比較合理惫确?
所以最后的結(jié)論是:以 Feature 為分類主干、在細(xì)部中包含依 Layer 分類的混合結(jié)構(gòu)夫晌。當(dāng) Feature 的分類到了盡頭雕薪,則可以用 Layer 來接力分類∠恚或是在分類共用 Class 時(shí)所袁,于慣用的 Library、Utility凶掰、Support 名稱下燥爷,再以 Layer 做更細(xì)部的分類。