AOP在大規(guī)模軟件開(kāi)發(fā)項(xiàng)目中應(yīng)用的嘗試與思考
發(fā)表時(shí)間:2024-01-19 來(lái)源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]本文的寫作源于一個(gè)真實(shí)的大型軟件開(kāi)發(fā)項(xiàng)目,我們努力嘗試在這個(gè)項(xiàng)目中推廣應(yīng)用AOP。在此我們將對(duì)曾經(jīng)面臨過(guò)的一些實(shí)際問(wèn)題與困難進(jìn)行分析,試圖引發(fā)關(guān)于面向方面軟件開(kāi)發(fā)(AOSD)的一些更深層次的思考。本文的作者將站在開(kāi)發(fā)者的角度做出客觀的判斷,既不是AOP的狂熱鼓吹者,同樣也不是AOP反對(duì)陣營(yíng)的一員。...
本文的寫作源于一個(gè)真實(shí)的大型軟件開(kāi)發(fā)項(xiàng)目,我們努力嘗試在這個(gè)項(xiàng)目中推廣應(yīng)用AOP。在此我們將對(duì)曾經(jīng)面臨過(guò)的一些實(shí)際問(wèn)題與困難進(jìn)行分析,試圖引發(fā)關(guān)于面向方面軟件開(kāi)發(fā)(AOSD)的一些更深層次的思考。本文的作者將站在開(kāi)發(fā)者的角度做出客觀的判斷,既不是AOP的狂熱鼓吹者,同樣也不是AOP反對(duì)陣營(yíng)的一員。因此可以視作來(lái)自Java開(kāi)發(fā)者對(duì)AOP技術(shù)應(yīng)用的客觀分析和建設(shè)性意見(jiàn)。
關(guān)于AOP 關(guān)于AOP的概念,筆者在這里不再贅述。誰(shuí)最先創(chuàng)造了AOP,業(yè)界一直有些爭(zhēng)議,但普遍接受的說(shuō)法大概是最先由Gregor J Kiczales在ECOOP'97提出來(lái)的,隨后Gregor又申請(qǐng)了AOP的專利[US06467086]。很多人可能不太服氣,因?yàn)樗麄兓蚨嗷蛏僭缫延辛祟愃频南敕ǎ徊贿^(guò)沒(méi)有想到給他起個(gè)新名字罷了。無(wú)論是OOP,MOP,還是AOP,其本質(zhì)的想法都是試圖在更貼近現(xiàn)實(shí)世界的層次上實(shí)現(xiàn)軟件開(kāi)發(fā)的模塊化。從這個(gè)角度看,AOP的想法不過(guò)是新瓶裝舊酒罷了。其實(shí)AOP作為新生事物的出現(xiàn),并不是一種技術(shù)上的飛躍,而是軟件模塊化發(fā)展到某一個(gè)階段的一個(gè)階段性產(chǎn)物。人的思維通常都有一些慣性,在我們飽嘗了OOP的艱辛后,有一種新的概念跳出來(lái)分析總結(jié)了OOP的某些缺點(diǎn),而且以看起來(lái)合理的方式做出改進(jìn),難免會(huì)給大家一種耳目一新的感覺(jué)。但不可否認(rèn)的是,到目前為止,AOP角色所扮演的應(yīng)用角色更多的只是對(duì)OOP的一種補(bǔ)充,因此作為一種重要的"OP"存在似乎有些名過(guò)其實(shí),看起來(lái)更像是一種高級(jí)的設(shè)計(jì)模式。然而,在很多人的眼中AOP的分量甚至不亞于OOP,甚至AOP被視作未來(lái)軟件開(kāi)發(fā)的一個(gè)趨勢(shì)。筆者一直思考一個(gè)問(wèn)題,AOP出現(xiàn)的七八年時(shí)間在IT界并不算很短了,有趣的現(xiàn)象是AOP始終保持了小火慢燉的熱度,一直沒(méi)有像大家所期望的那樣大紅大紫起來(lái)。
那么AOP究竟在多大程度上可以幫助我們解決實(shí)際的問(wèn)題呢?讓我們嘗試在一個(gè)真實(shí)的軟件開(kāi)發(fā)項(xiàng)目中應(yīng)用AOP。對(duì)AOP所推崇的各個(gè)典型應(yīng)用方向加以研究,例如,日志(Log),事務(wù)(Transaction), 安全性(Security), 線程池等等。必須說(shuō)明,我們這里提到的場(chǎng)景,是有一定規(guī)模的軟件產(chǎn)品開(kāi)發(fā),我們最終完成的是百兆數(shù)量級(jí)的軟件產(chǎn)品,因此我們研究的范圍既不是程序員的個(gè)人行為,也不是小范圍的示例。讓我們一起來(lái)看一看有什么有趣的發(fā)現(xiàn)。
AOP的實(shí)踐
我們?cè)囼?yàn)應(yīng)用AOP的方向很多,這里僅以最具代表性的Log為例。大多數(shù)人了解AOP,都是從經(jīng)典的Log 關(guān)注點(diǎn)的剝離開(kāi)始的。因此這是每一個(gè)AOP的愛(ài)好者都耳熟能詳?shù)陌咐0吹览恚瑧?yīng)該是無(wú)可爭(zhēng)辯的。很不幸,在我們的研究過(guò)程中還是碰到了很棘手的問(wèn)題。
讓我們來(lái)看一看一個(gè)經(jīng)典的AOP 做日志的例子。
我們假定,按照AOP的思想,主邏輯的開(kāi)發(fā)人員在寫代碼的時(shí)候不應(yīng)該考慮邊緣邏輯。因此,上述的日志代碼實(shí)際對(duì)主邏輯的開(kāi)發(fā)者不可見(jiàn)。假定我們以主流的Log4J為記日志的實(shí)現(xiàn)方式,以AspectJ作為Aspect的實(shí)現(xiàn)方式。需要重申,本文的寫作目的并不是針對(duì)某一種AOP的實(shí)現(xiàn)平臺(tái),選用AspectJ主要因?yàn)閺恼Z(yǔ)法的角度而言,AspectJ是目前所有AOP實(shí)現(xiàn)中覆蓋范圍最廣的一種實(shí)現(xiàn)。
這樣一個(gè)記日志的橫切關(guān)注點(diǎn)描述,是最經(jīng)典的AOP應(yīng)用,它本身是沒(méi)有任何問(wèn)題的。通常我們會(huì)怎樣用它呢?在繼承了這個(gè)抽象Aspect的子Aspect實(shí)現(xiàn)中指定切入點(diǎn)的位置①,并在這個(gè)位置上將實(shí)現(xiàn)的邏輯填入通知(Advice)②。
在一個(gè)小規(guī)模的應(yīng)用開(kāi)發(fā)環(huán)境中這樣做是不會(huì)有問(wèn)題的,首先,記日志的切入點(diǎn)不多,無(wú)論是采用一對(duì)一的位置直接描述,還是利用統(tǒng)一的編碼規(guī)范來(lái)約束都是可行的方案;其次,通知中的邏輯不會(huì)很復(fù)雜。整體的軟件開(kāi)發(fā)流程不會(huì)有什么變化的需要,通常的做法是由專門的Aspect開(kāi)發(fā)人員統(tǒng)一編寫Aspect,而由大家共享記Log的Aspect。但是不鼓勵(lì)每一個(gè)開(kāi)發(fā)人員都寫自己的Aspect,這樣就不是橫(cross-cut),變成過(guò)篩子了(cross-point),軟件開(kāi)發(fā)變成一盤散沙,失去控制,AOP帶來(lái)的好處喪失殆盡。
那么,在我們的項(xiàng)目中,情況怎樣呢?上述看似簡(jiǎn)單的兩個(gè)點(diǎn)都存在問(wèn)題:
(1) 具我們統(tǒng)計(jì),在我們開(kāi)發(fā)的軟件上一個(gè)版本的軟件代碼中,總共有7萬(wàn)句記Log的調(diào)用。如果我們不做任何相關(guān)的總結(jié)工作,直接一對(duì)一的對(duì)切入點(diǎn)進(jìn)行描述,那么在位置①上的切入點(diǎn)描述就有7萬(wàn)條之多;姑且不算工作量,即使這樣做了,將來(lái)帶來(lái)的代碼維護(hù)將是天文數(shù)字的成本,此路不通。
那么我們只能寄希望能夠提煉出這7萬(wàn)句日志調(diào)用的公共模式,我們?cè)谶@里用到的是一種優(yōu)化過(guò)的Log組件,接口與LOG4J類似,考慮到LOG4J的廣泛應(yīng)用,我們下面將以LOG4J為參照。Log Level類中預(yù)定義了五個(gè)級(jí)別,DEBUG, INFO, WARN, ERROR,F(xiàn)ATAL,根據(jù)統(tǒng)計(jì),F(xiàn)atal類型的調(diào)用最少,根據(jù)Fatal的級(jí)別定義,我們或許可以花一定時(shí)間整理代碼,提煉出捕捉Fatal點(diǎn)的規(guī)則。然后次之,WARN和ERROR大約占7%左右,這一部分就不好辦了,WARN/ERROR類型的LOG并沒(méi)有嚴(yán)格的界定,代碼的分布點(diǎn)也難尋規(guī)律,一定要找到規(guī)律,要付出相當(dāng)大的代價(jià)。最后,DEBUG, INFO占據(jù)了很大的比例30%-50%,顧名思義,這一部分的代碼出現(xiàn)的隨機(jī)性很大,無(wú)論怎樣努力都不可能找到有意義的公共規(guī)律。此路還是不通。
有一種說(shuō)法也許可以解釋這種想象:如果切入點(diǎn)難于描述的時(shí)候,很大原因是因?yàn)殛P(guān)注點(diǎn)的定義不準(zhǔn)確。此說(shuō)法有一定道理,以"日志"作為一個(gè)方面來(lái)切入粒度似乎太大了。那么,唯一的辦法是將"日志"關(guān)注點(diǎn)進(jìn)一步拆解。一直拆解到可以接受的程度。但是,我們的問(wèn)題似乎還沒(méi)有解決,如果我們的項(xiàng)目足夠小,那么這樣的拆解總是有一定的限度的,這種做法或許可行。但很不幸,我們的項(xiàng)目很大,要經(jīng)過(guò)相當(dāng)多的分解才能最終找到日志的規(guī)律性。我們還是可能需要成百上千條語(yǔ)句來(lái)指定切入點(diǎn)的位置,最終的結(jié)果將很難維護(hù),這樣的做法對(duì)于一個(gè)不斷演化中的項(xiàng)目而言是脆弱乃至于不可接受的。況且,像Debug這樣的Log級(jí)別,無(wú)論你怎樣拆解,都不可能找到完美的規(guī)律。通常,任何一個(gè)系統(tǒng)中的Log都會(huì)保持邏輯的一致性,如果經(jīng)過(guò)了這樣的層層分解,Log作為一個(gè)邏輯主體的完整性被完全破壞了。這是一種為了AOP而AOP的做法,非但工作量沒(méi)有減輕,還帶來(lái)了無(wú)窮的后患。
好了,只剩最后一招了,為了用AOP, 我們犧牲掉Log的某些特性,預(yù)先定義好編碼的規(guī)則和日志原則,強(qiáng)制推行。當(dāng)然,如果想要全面覆蓋所有的日志點(diǎn),這樣的日志原則只能定得非常粗。從AOP的角度來(lái)講,技術(shù)上是可行的,但粗放的日志規(guī)則會(huì)帶來(lái)Log的信息量瘋長(zhǎng),對(duì)于我們的軟件項(xiàng)目來(lái)說(shuō),還是不可接受,因?yàn)槿罩臼チ怂木_性,會(huì)對(duì)系統(tǒng)的維護(hù)產(chǎn)生較大影響,而且大量日志信息的增長(zhǎng)對(duì)系統(tǒng)整體運(yùn)行性能的沖擊是顯而易見(jiàn)的。
(2) 在圖1 的第二個(gè)要點(diǎn)上我們也同樣面臨問(wèn)題,也許從圖上的例子你可能還看不出來(lái),因?yàn)樵谒械慕榻BAOP的文檔材料中介紹Log的例子都很簡(jiǎn)單。但是現(xiàn)實(shí)生活中的Log很不留情面。很多時(shí)候程序?qū)og的調(diào)用都有其特殊的原因,它們的Advice需要分別編寫。例如在例子產(chǎn)品中我們經(jīng)常需要把一個(gè)變量傳給"日志"方面, 而且,經(jīng)常要傳入一個(gè)局部變量。至少現(xiàn)在,所有的AOP實(shí)現(xiàn)都還不支持對(duì)局部變量的捕捉,在可見(jiàn)的將來(lái)也不會(huì)有這種實(shí)現(xiàn)。好吧,只好重構(gòu)代碼,如果您想傳參數(shù),必須要遵守我們預(yù)先定義的編碼命名規(guī)則。但是,這樣給編程人員帶來(lái)的限制會(huì)很大,你很難說(shuō)服開(kāi)發(fā)人員手捧厚厚的編碼規(guī)范,小心翼翼的寫程序。
綜合上述兩個(gè)致命缺陷,我們?cè)噲D推行Log Aspect的工作已經(jīng)遇到了相當(dāng)?shù)淖枇Α?