「軟件分析」與「軟件設(shè)計」這樣的詞眼經(jīng)常聽到,然而要真正理解「軟件分析」和「軟件設(shè)計」的本質(zhì)是比較難的,本文帶你了解軟件分析與設(shè)計的「邏輯性」到底是什么。
一、系統(tǒng)分析與設(shè)計的邏輯性框架
在日常的工作中,「軟件分析」與「軟件設(shè)計」這樣的詞眼經(jīng)常聽到,然而要真正理解「軟件分析」和「軟件設(shè)計」的本質(zhì)是比較難的,它依賴極強的工作經(jīng)驗,又加上軟件分析與設(shè)計沒有標(biāo)準(zhǔn)的程式化步驟,導(dǎo)致不同的人有自己不同的方法,也就造成了很多人認(rèn)為軟件分析與設(shè)計是非常「空洞」,還不如寫具體的代碼實在,而大部分的人寫的是業(yè)務(wù)型代碼,被嘲弄寫CRUD的代碼沒成就感。 軟件分析與設(shè)計如其它行業(yè)一樣,具有很強的邏輯性,沒有邏輯性支撐,很難做好事。比如寫作,有「謀篇布局」、「起承轉(zhuǎn)合」、「遣詞造句」等這些「原則」和「方法」,沒有洞悉到這些底層邏輯,一個高手寫的文章和一個普通人寫的文章,效果是天差地別。因此,首先要講清楚的是軟件分析與設(shè)計的「邏輯性」到底是什么,下圖是軟件分析與設(shè)計的邏輯全景圖。
?
1.1 方法
1.1.1 分析階段
軟件分析與設(shè)計并沒有那么神秘,本質(zhì)來講還是為了解決現(xiàn)實的問題,和「醫(yī)生看病」、「工人修車」、「廚師做菜」一樣的,都需要方法作為指導(dǎo),否則沒有任何頭緒,只能抓瞎。方法是具有普適性,只是不同的行業(yè)有各自的特性,具體落地上有差異。?
既然是要解決問題,那么總得知道問題是什么吧,就好比醫(yī)生看病,做各種檢查、化驗,都是為了全面地了解疾病,所以第一步是需要定義好問題,然而當(dāng)下很多人都忽略了這一步,直接上來想我要用哪種中間件、哪個框架,連問題都沒有定義好,直接想實現(xiàn)無疑是本末倒置。
問題是理想與實現(xiàn)差距的矛盾,現(xiàn)實是不滿足理想的訴求,因此,首先需要了解用戶的訴求是什么,想解決怎樣的問題,這也即為是需求。需求分析最大的挑戰(zhàn)是什么是真正想要的,就好比一個病人說了一大堆的癥狀,他所說的癥狀表現(xiàn)與書本上的描述有時是有出入的,定義出真正的需求至關(guān)重要,接下來就要思考通過怎樣的方法去梳理清楚用戶需求。?
用例是表達(dá)用戶使用系統(tǒng)實現(xiàn)某些目標(biāo)文本化的情節(jié)描述,它強調(diào)的是用戶目標(biāo)、觀點。我們應(yīng)該從用戶目標(biāo)的角度出發(fā)思考,也有的人稱之為利益相關(guān)者的關(guān)注點,道理很簡單,我們做出來的軟件是為了服務(wù)于用戶的,不是用戶想要的,就算用了多高科技的的技術(shù)也是失敗的。
用例只是一個概括的描述,因此還需要細(xì)化,一個用例包括一個或多個場景,場景是參與者與系統(tǒng)之間的活動和交互,比如用戶下單,有下單成功,有下單失敗兩個場景。因此到這里還只是分析階段,分析的目的是了解現(xiàn)狀和目標(biāo),以及系統(tǒng)要素組成,它不關(guān)心如何實現(xiàn)做、如何實現(xiàn)。分析不僅限于軟件行業(yè),其它的行業(yè)也是如此,只是在分析過程的程式化步驟、方法不一樣而已。
1.1.2 設(shè)計階段
當(dāng)我們清楚地知道要做什么之后,接下來要思考如何去實現(xiàn),實現(xiàn)的途徑有很多種,如同一百個廚師做同樣的菜,做出來的效果不一樣。設(shè)計階段有兩點需要考慮的:一是如何將功能細(xì)化實現(xiàn);另一點是如何更好地實現(xiàn)。第一點不管用什么方法都可以實現(xiàn),難的是第二點,更好地實現(xiàn)是需要遵循一些章法,也需要評判體系,要不然你怎么知道好與不好呢,常見的定量評判指標(biāo)有:成本、性能、可靠性、效率等,還有一類是定性的評判指標(biāo)有:開放性、體驗性等。?
度量的指標(biāo)相對容易,就像醫(yī)生看病,看療效、看成本,軟件設(shè)計也是一樣,歸根到底是「多快好省」。然而軟件設(shè)計的章法就復(fù)雜得多,它具有很強的藝術(shù)性,正所謂「文無第一,武無第二」,比武一定可以比出個高下,誰打贏了就是勝利一方,然而比文就難了,不能說你寫的就一定比我寫的好,只是不同人的喜好不一樣而已,當(dāng)然這里指的旗鼓相當(dāng)?shù)貙Ρ?,不同層次的對比一眼還是能看得出來的。
在設(shè)計階段最為關(guān)鍵的是定義出「關(guān)鍵的技術(shù)問題」,一般分類兩類:一是站在用戶視角的設(shè)計,它重點考量的是「便捷性」和「易理解」;另一個是站在系統(tǒng)層面,重點考量的是「復(fù)用性」、「擴展性」和「穩(wěn)定性」。貼近用戶的設(shè)計,讓用戶用最少的理解就能使用它,用戶無須感知底層復(fù)雜的設(shè)計,核心是回答用戶最樸素的原始訴求。系統(tǒng)層面的設(shè)計要靈活,多用組合正交設(shè)計提升系統(tǒng)的復(fù)用性和擴展性,在具體方案設(shè)計中要考慮到穩(wěn)定性因素,比如寫日志會帶來性能問題,這個在方案設(shè)計中就要考慮到。洞察到關(guān)鍵技術(shù)問題,并非一朝一夕就能練就成,需要在工作中大量地實踐,總結(jié)經(jīng)驗,保持技術(shù)的敏感度才行,在第二節(jié)中通過實際的案例方便大家加深理解。
1.2 工具
有了方法,接下來要有工具來幫我們更好地做事,與方法對應(yīng)的,我們軟件設(shè)計的工具是UML,接下來介紹UML中常用的圖。
1.2.1 活動圖
軟件從本質(zhì)上是在模擬現(xiàn)實業(yè)務(wù)運行的過程,是由一個個交互活動組成的,因此,在分析階段需要梳理出業(yè)務(wù)的活動是怎樣的,通過圖形的方式記錄下來。
活動圖要體現(xiàn)出「參與者」、「活動起點」、「活動關(guān)鍵路徑」、「活動終點」,比如用戶下單,有「瀏覽」、「加購」、「支付」等活動。通過活動圖可以看出業(yè)務(wù)的生命周期是怎樣的,能夠抓住業(yè)務(wù)的關(guān)鍵流程。
1.2.2 用例圖
用例圖是強調(diào)用戶的目標(biāo)和觀點,是文本化的情節(jié)描述,用例從本質(zhì)上講并不是圖,它是文本,用圖形是簡化了表達(dá)形式,它核心有三點:「參與者」、「要做什么」、「結(jié)果是怎樣的」。用例圖是對活動圖的細(xì)化,對其中的一個活動定義出要實現(xiàn)怎樣的目標(biāo)。場景又是對用例圖的細(xì)化,你會發(fā)現(xiàn),從目標(biāo)到實現(xiàn),一步一步地細(xì)化下來,細(xì)化是對擴大對認(rèn)識的理解,不在認(rèn)識范圍內(nèi),也就不會去做。
1.2.3 順序圖
將場景通過順序圖表達(dá)出來,它核心強調(diào)的是系統(tǒng)應(yīng)該提供怎樣的能力,注意順序圖與時序圖的區(qū)別,順序圖是人與系統(tǒng)的交互,它想表達(dá)的是系統(tǒng)應(yīng)該提供怎樣的能力滿足用戶的訴求,而時序圖系統(tǒng)內(nèi)部實現(xiàn),強調(diào)的是如何實現(xiàn)這種能力。?
其實到順序圖這一步,基本上系統(tǒng)要提供的能力就清楚了,當(dāng)然,這里是知道系統(tǒng)要做什么,至于要怎么實現(xiàn)它并不關(guān)心。以上我認(rèn)為它是分析階段,把用戶想實現(xiàn)的內(nèi)容清楚地定義出來,接下來就是思考怎么實現(xiàn)。
1.2.4 時序圖
時序圖有兩種作用:一是表達(dá)功能是如何實現(xiàn)的;另一個是看責(zé)任分配是否合理。第一點比較好理解,一個功能實現(xiàn)是由多個不同的對象組合來實現(xiàn),對象間有交互依賴。第二點是評判對象設(shè)計是否合理,如何兩個對象頻繁交互,是不是可以合并在一起,如何一對象中的操作過多,是不是可以拆解。
1.2.5 類圖
類圖的作用也有兩種:一是表達(dá)屬性和職責(zé);另一個是層次結(jié)構(gòu)。類中的屬性和職責(zé)是一個統(tǒng)一體,屬性體現(xiàn)的是認(rèn)知能力,職責(zé)體現(xiàn)的是行為能力,擁有怎樣的認(rèn)識,就會產(chǎn)生怎樣的行為。類不是一個孤零零的個體,它與其它的類之間有依賴、協(xié)作關(guān)系,因此,類圖中體現(xiàn)繼承、依賴、泛化、包含等關(guān)系。
1.3 原則篇
軟件設(shè)計原則汗牛充棟,簡化下來就三點:「復(fù)用」、「變化」、「認(rèn)知復(fù)雜度」,好的設(shè)計處處體現(xiàn)設(shè)計原則,把這些原則刻畫到骨子里,而不是刻意體現(xiàn),如同「沒有規(guī)矩不成方圓」一樣,重點是要理解為什么要這些原則,從本質(zhì)上講是為了軟件能夠「多快好省」地完成。
利潤 = 收入 - 成本,從這個公式中,很明顯我們想要實現(xiàn)利潤最大化,怎么辦呢,有兩個方法:一是收入變多,最好地方式是實現(xiàn)規(guī)?;?;二是成本降低,不需要或者很少投入成本。從這兩點中,引申出「復(fù)用」和「變化」兩個原則,復(fù)用是不投入或者少投入實現(xiàn)功能,相比從頭做是不是要節(jié)省成本呢,我們的產(chǎn)品不可能一成不變的,那么變化是在所難免的,如果能支撐靈活地擴展是不是也能節(jié)省成本呢。
1.3.1 復(fù)用
從上面的分析看,「復(fù)用性」的重要程度不言而喻,比起煙囪式開發(fā),復(fù)用的成本要低得多,所以產(chǎn)生出了xx平臺、xx中臺,它們的本質(zhì)目的還是為了復(fù)用,減少重復(fù)開發(fā)成本。實現(xiàn)復(fù)用的手段有很多,復(fù)用的程度也不一樣,這個就要靠平時的積累,就像醫(yī)生積累「藥」和「藥方」一樣,這些都是我們解決問題的「工具」。先要有復(fù)用的思維,否則只有工具也是無用的,不知道要怎么用、在哪里用。?
不同的場景,復(fù)用采用的設(shè)計方法是不一樣的,舉幾個例子方便大家理解。
完全復(fù)用
最簡單的復(fù)用是100%的復(fù)用,比如加法計算操作,它肯定是100%復(fù)用的,只用傳輸不同的數(shù)字進(jìn)去,就可以計算出結(jié)果。一般完全復(fù)用的是工具型的能力,它與具體的業(yè)務(wù)語義無關(guān)。?
配置化復(fù)用
這一類的復(fù)用程度接近100%,只用配置一些與業(yè)務(wù)相關(guān)的具體的參數(shù)即可,比如對賬場景,配置兩個不同的數(shù)據(jù)源表,再配置對賬規(guī)則即可完成對賬。這一類場景適用于業(yè)務(wù)比較固定,流程是通用的,并且差異變化是可枚舉的。
部分復(fù)用
然而,在實現(xiàn)世界中,沒有太多像完全復(fù)用的事情,如果變化還不能通過配置化來實現(xiàn),可以使用「模板方法」或「策略模式」,將變化延遲到子類中去實現(xiàn),這種方法在大家日常工作很常見,也有的使用SPI擴展點實現(xiàn)。這一類復(fù)用場景是有明確的「主流程」,只有少量的變化隨業(yè)務(wù)變化,變化也是可枚舉的,那么就可以抽象出擴展點。?
還有一類變化是很難枚舉的,不知道會有怎樣的變化,此時最好的方法是通過「事件」解耦,主流程完成之后,發(fā)一個事件消息出來,誰關(guān)注就去實現(xiàn)該功能,本質(zhì)上講Spring中Aware機制也屬于事件的處理方法。
完全不能復(fù)用
還有一類完全不能復(fù)用,但要抽象出標(biāo)準(zhǔn)的接口,比如常用的數(shù)據(jù)庫操作,Connection、Statement就是標(biāo)準(zhǔn)的接口,不同的數(shù)據(jù)庫廠商實現(xiàn)具體的數(shù)據(jù)庫操作。不要覺得這種沒有意義,它從更高的層面定義了規(guī)范,使用者是面向抽象使用,可以不關(guān)注具體的實現(xiàn)是怎樣的。
1.3.2 變化
復(fù)用和變化是一起出來的,軟件唯一不變的是變化,怎么支撐未來更好地擴展是我們要思考的,如果一個功能千年不變,怎么簡單就怎么實現(xiàn),而如果有變化的話,那就需要好好地設(shè)計,用最少的成本去支撐未來的變化。比較難的是要洞察出什么在變化,這個還真不是那么好想到的,需要有行業(yè)經(jīng)驗積累,看多了、實踐多了,會發(fā)現(xiàn)里面的一些門道。?
舉一個應(yīng)對變化的例子,稅務(wù)在計稅時,不同的業(yè)務(wù)計稅規(guī)則不一樣,有的金本位要計稅,有的不需要計稅,有的非金本位要計稅,有的不需要。如果放在一個大的擴展點中實現(xiàn),那么這就是典型的面向過程的設(shè)計思維,我們抽象出了「計稅表達(dá)式」這個實體來應(yīng)對變化,「計稅項」要不要計稅、計稅口徑是怎樣的,新業(yè)務(wù)接入通過「配置化」來解決。
1.3.3 認(rèn)識復(fù)雜度
認(rèn)識是分層次的,最高層越簡單,最低層越復(fù)雜,對于使用者來講,他希望看到的是簡單的內(nèi)容,如果太復(fù)雜,很難上手,比如命令行式的操作系統(tǒng)和桌面式的操作系統(tǒng),明顯桌面式的操作系統(tǒng)更受大眾的歡迎,這也是微軟在目前依然在操作系統(tǒng)市場占用份額上還是大頭的原因。軟件分層的目的不僅是讓關(guān)注點分離,還有另外一個目的是降低認(rèn)識復(fù)雜度,從簡單到復(fù)雜,這和我們軟件分析一樣的,從粗到細(xì)。?
類的設(shè)計也是一樣的,舉一個例子,稅務(wù)在開發(fā)票時,開票這個模型結(jié)構(gòu)需要調(diào)用者感知嗎,肯定不需要,因為發(fā)票中有很多的領(lǐng)域概念,如開票主體、發(fā)票行等,用戶的目的就是開一張發(fā)票,他只用告訴你他知道的信息,不關(guān)心你內(nèi)部要怎么實現(xiàn),基于這個思考,我們抽象出了「開票申請」這個實體出來,它本質(zhì)是貼近業(yè)務(wù)場景的實體,所包含的信息也是有限的,極大地降低了認(rèn)知復(fù)雜度。
二、系統(tǒng)分析與設(shè)計的2個案例
2.1 日志框架
2.1.1 日志框架分析
打印日志在我們?nèi)粘9ぷ鲙缀跏侨巳硕紩佑|到,日志的核心作用是記錄關(guān)鍵有效的信息,幫助我們快速地排查、定位問題,否則沒有日志信息兩眼一抓瞎。根據(jù)我們的經(jīng)驗,希望日志中包含時間、類、方法、代碼行、關(guān)鍵日志信息,用了這些信息就能方便我們排查問題。?
根據(jù)上面的分析,我們很快可以畫出日志的概念模型,如下圖所示。從本質(zhì)上講,我們是將日志信息存儲到指定的地方,如存儲文件中,輸出到控制臺上,另外還有日志存儲的格式可以有多種,比如普通的格式,還有XML、HTML的格式等。?
從概念模型上看,設(shè)計一個日志框架并不復(fù)雜,但在設(shè)計階段中,還需要挖掘更多的信息,我們輸入的信息更多,設(shè)計時考慮的因素也就越全,更能滿足用戶的訴求。
2.1.1 日志框架設(shè)計
2.1.1.1 貼近用戶的設(shè)計
站在用戶的視角,他關(guān)注的是信息以怎樣的格式存儲在哪里,因此,有兩個概念用戶是要關(guān)注的,一個是「存儲目的地」,另一個是「存儲樣式」,這兩個是可以根據(jù)用戶的喜好配置的,將這兩個概念抽象下,「存儲目的地」抽象成「Appender」,「存儲樣式」抽象成「LayoutPattern」。除了配置外,用戶在使用時需要一個接口類,將其抽象成「Logger」門面類,只用簡單的調(diào)用日志打印接口即可。
2.1.1.1 系統(tǒng)視角的設(shè)計
站在系統(tǒng)視角上,用戶在日志打印時,他關(guān)注的是日志信息,如時間、類名、方法名等信息他不會顯示去寫,因此還需要抽象一個概念出來表達(dá)日志信息的概念,抽象成「LogRecord」,這樣概念類圖如下圖所示。?
按照這個設(shè)計,很快可以設(shè)計出一個簡易的日志框架,代碼結(jié)構(gòu)如下所示。
Appender類如下所示,它定義的是一個模板方法,先調(diào)用LayoutPattern獲取格式化的日志數(shù)據(jù),然后再輸出到目標(biāo)存儲上,Appender和LayoutPattern是可以獨立變化的,同時將寫操作延遲到子類中實現(xiàn)。
再細(xì)細(xì)想一下,日志本身是為了方便排查問題,但額外日志的存儲是有性能開銷的,這個在設(shè)計時就要著重考慮了。如何減少寫日志帶來的性能開銷呢,從三個方面考慮:
大文件變小文件:打開一個1G的文件和打開1M的文件肯定是不一樣的,復(fù)雜問題的治理也是同樣的思路,拆分成小問題處理,因此在寫文件時可以按照「時間」、「容量大小」切分成小的文件。
內(nèi)存映射寫文件:傳統(tǒng)的IO寫數(shù)據(jù),操作系統(tǒng)需要從用戶態(tài)切換到內(nèi)核態(tài),性能開銷會很大,可以使用內(nèi)存映射的方式寫文件,提升IO性能。
同步變異步寫文件:同步寫文件是需要等文件寫好了后再往下執(zhí)行業(yè)務(wù)代碼,而異步就不一樣,它將日志記錄存儲在一個隊列中,開戶另一個線程慢慢寫到文件中,不阻塞業(yè)務(wù)邏輯執(zhí)行。?
通過上面的分析,一個簡單的日志框架隨著對它的理解加深,設(shè)計方案也在變化,核心是要能看到關(guān)鍵的技術(shù)問題有哪些,能提供哪些增量價值或差異化的價值。?
2.2 定時任務(wù)框架
2.2.1 定時任務(wù)框架分析
定時任務(wù)在我們平時的工作中也經(jīng)常使用,如每天定時發(fā)送郵件、定時做數(shù)據(jù)檢查等,完成定時任務(wù)的訴求也比較簡單,就是在指定的時間執(zhí)行指定的任務(wù),從這個角度看,業(yè)務(wù)模型并不復(fù)雜,如下圖所示。調(diào)度任務(wù)和調(diào)度時間是兩個獨立的變化維度。為了讓任務(wù)能夠調(diào)度起來,還有一個「調(diào)度器」的概念并沒有在業(yè)務(wù)模型上體現(xiàn)出來,將要執(zhí)行的任務(wù),以及調(diào)度時間告知給調(diào)度器,調(diào)度器就能根據(jù)要求調(diào)度任務(wù)。
?
2.2.2 定時任務(wù)框架設(shè)計
2.2.2.1 貼近用戶的設(shè)計
站在用戶視角,他關(guān)心的有三點:
任務(wù)要按照怎樣的規(guī)范去編寫,因此需要一個概念「Job」去表達(dá),它定義了一個execute()接口。
調(diào)度時間如何去表達(dá),抽象出「Trigger」這個概念,表示到時就觸發(fā)任務(wù)執(zhí)行。
任務(wù)如何提交,需要一個門面類「Scheduler」去表達(dá),接受用戶提交的任務(wù)。
2.2.2.2 系統(tǒng)視角的設(shè)計
站在系統(tǒng)的視角,很快能想到解決方法,用戶提交任務(wù)后要保存至一個隊列中「JobQueue」,「JobQueue」中存儲的是「JobDetail」,「JobQueue」包含了「Job」和「Trigger」兩部分信息,然后有一個調(diào)度線程「SchedulerThread」不斷掃描「JobQueue」,判斷當(dāng)前任務(wù)是否要被執(zhí)行,如果需要執(zhí)行就調(diào)用「Job」的execute()方法,類的概念模型如下圖所示。
按照這個設(shè)計,很快可以設(shè)計出一個簡易的任務(wù)調(diào)度框架,代碼結(jié)構(gòu)如下所示。?
?
定時任務(wù)最關(guān)鍵的技術(shù)挑戰(zhàn)是查找要調(diào)度執(zhí)行的任務(wù),很容易想到對「JobQueue」中的「JobDetail」排序,發(fā)現(xiàn)Job的executeTime快到了就執(zhí)行,然而排序是消耗CPU資源的,不同的排序算法時間復(fù)雜度也不一樣。怎么降低排序算法的時間復(fù)雜度呢,最簡單的用最小堆排序算法,每次從堆頂獲取任務(wù)執(zhí)行,而每次添加任務(wù),又涉及到堆的調(diào)整,這個過程也是消耗CPU資源的。有沒有更快的算法呢,有人提出了時間輪的解決方法,類似鐘一樣,如一分鐘是60格,類似將任務(wù)的時間計算好,放到對應(yīng)的格中,如果有多個相同的任務(wù),就有一個鏈表鏈起來,xxl-job就用了時間輪的方法,將未來周期性需要 調(diào)度執(zhí)行的任務(wù)放在時間輪中,時間輪的數(shù)據(jù)結(jié)構(gòu)是一個Map。
這還是一個簡單的任務(wù)調(diào)度框架,還有很多問題沒有考慮到,比如任務(wù)分片、分布式定時任務(wù)等,還是回到需求分析上,我們要做的功能邊界是什么,目標(biāo)是什么,再去設(shè)計對應(yīng)的解決方案。?
三、總結(jié)
在日常技術(shù)方案設(shè)計時,最為關(guān)鍵的是要能定義出關(guān)鍵的技術(shù)問題,有兩類問題是我們要著重考慮的:一是貼近用戶視角的便捷性設(shè)計,主要是對業(yè)務(wù)概念的抽象,用戶以最小的知識感知系統(tǒng);另一個是系統(tǒng)的視角設(shè)計,除了完成功能外,還要定義出關(guān)鍵的技術(shù)問題以及度量的方法,如打印日志帶來的系統(tǒng)開銷,怎么做到對系統(tǒng)產(chǎn)生最小的影響;定時任務(wù)調(diào)度的調(diào)度算法設(shè)計,選用不同的數(shù)據(jù)結(jié)構(gòu)的效果是不一樣的,普通的排序算法沒有堆排序算法好,但堆排序同樣涉及到性能開銷,這就讓我們不斷想更好地方案去解決。
編輯:黃飛
-
cpu
+關(guān)注
關(guān)注
68文章
10863瀏覽量
211763 -
軟件設(shè)計
+關(guān)注
關(guān)注
3文章
58瀏覽量
17773 -
線程
+關(guān)注
關(guān)注
0文章
504瀏覽量
19683 -
調(diào)度器
+關(guān)注
關(guān)注
0文章
98瀏覽量
5248
原文標(biāo)題:一文探究系統(tǒng)分析與設(shè)計的邏輯性
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論