前車(chē)之覆,后車(chē)之鑒
——開(kāi)源項(xiàng)目經(jīng)驗(yàn)談
(本文發(fā)表于《程序員》2005年第2期)
隨著開(kāi)源文化的日益普及,“參與開(kāi)源”似乎也變成了一種時(shí)尚。一時(shí)間,似乎大家都樂(lè)于把自己的代碼拿出來(lái)分享了。就在新年前夕,我的一位老朋友、一位向來(lái)對(duì)開(kāi)源嗤之以鼻的
J2EE
架構(gòu)師竟然也發(fā)布了一個(gè)開(kāi)源的
J2EE
應(yīng)用框架(姑且稱(chēng)之為“
X
框架”),不得不令我驚嘆開(kāi)源文化的影響力之強(qiáng)大。
可惜開(kāi)源并非免費(fèi)的午餐,把源碼公開(kāi)就意味著要承受眾目睽睽的審視。僅僅幾天之后,國(guó)內(nèi)幾位資深的 J2EE 架構(gòu)師就得出一個(gè)結(jié)論:細(xì)看之下, X 框架不管從哪個(gè)角度都只能算一個(gè)失敗的開(kāi)源項(xiàng)目。究竟是什么原因讓一個(gè)良好的愿望最終只能得到一個(gè)失敗的結(jié)果?本文便以 X 框架為例,點(diǎn)評(píng)初涉開(kāi)源的項(xiàng)目領(lǐng)導(dǎo)者常犯的一些錯(cuò)誤,指出投身開(kāi)源應(yīng)當(dāng)遵循的一些原則,為后來(lái)的開(kāi)源愛(ài)好者掃清些許障礙。
成熟度
打開(kāi) X 框架在 SourceForge 的項(xiàng)目站點(diǎn),我們立刻可以看到:在“ Development Status ”一欄赫然寫(xiě)著“ 5 – Production/Stable ”。也就是說(shuō),作者認(rèn)為 X 框架已經(jīng)成熟穩(wěn)定,可以交付用戶(hù)使用。那么,現(xiàn)在對(duì)其進(jìn)行評(píng)估便不應(yīng)該有為時(shí)過(guò)早之嫌。可是, X 框架真的已經(jīng)做好準(zhǔn)備了嗎?
打開(kāi)從 SourceForge 下載的 X 框架的源碼包,筆者不禁大吃一驚:壓縮包里真的 只有源碼 ——編譯、運(yùn)行整個(gè)項(xiàng)目所需的庫(kù)文件全都不在其中。從作者自己的論壇得知,該項(xiàng)目需要依賴(lài) JBoss 、 JDOM 、 Castor 、 Hibernate 等諸多開(kāi)源項(xiàng)目,筆者只好自己動(dòng)手下載了這些項(xiàng)目,好一番折騰總算是在 Eclipse 中成功編譯了整個(gè)項(xiàng)目。
不需要對(duì)開(kāi)源文化有多么深刻的了解,只要曾經(jīng)用過(guò)一些主流的開(kāi)源產(chǎn)品,你就應(yīng)該知道:一個(gè)開(kāi)源軟件至少應(yīng)該同時(shí)提供源碼發(fā)布包和二進(jìn)制發(fā)布包,源碼包中至少應(yīng)該有所有必需的依賴(lài)庫(kù)文件(或者把依賴(lài)庫(kù)單獨(dú)打包發(fā)布)、完整的單元測(cè)試用例(對(duì)于 Java 項(xiàng)目通常是 Junit 測(cè)試套件)、以及執(zhí)行編譯構(gòu)建的腳本(對(duì)于 Java 項(xiàng)目通常是 Ant 腳本或者 Maven 腳本),但這些內(nèi)容在 X 框架的發(fā)布包中全都不見(jiàn)蹤影。用戶(hù)如果想要使用這個(gè)框架,就必須像筆者一樣手工下載所有的依賴(lài)庫(kù),然后手工完成編譯和構(gòu)建,而且構(gòu)建完成之后也無(wú)從知曉其中是否有錯(cuò)誤存在(因?yàn)闆](méi)有單元測(cè)試)。這樣的發(fā)布形式,算得上是“ Production/Stable ”嗎?
開(kāi)源必讀:便捷構(gòu)建
開(kāi)源軟件應(yīng)該提供最便捷的構(gòu)建方式,讓用戶(hù)可以只輸入一條命令就完成整個(gè)項(xiàng)目的編譯、構(gòu)建和測(cè)試,并得到可運(yùn)行的二進(jìn)制程序。對(duì)于 Java 項(xiàng)目,這通常意味著提供完整的 JUnit 測(cè)試套件和 Ant 腳本。你的潛在用戶(hù)可能會(huì)在一天之內(nèi)試用所有類(lèi)似的開(kāi)源軟件,如果一個(gè)軟件需要他用半天時(shí)間才能完成構(gòu)建、而且還無(wú)從驗(yàn)證正確性、無(wú)從著手編寫(xiě)他自己的測(cè)試用例,這個(gè)軟件很可能在第一時(shí)間被扔到墻角。 |
從 SourceForge 的項(xiàng)目頁(yè)面可以看到, X 框架的授權(quán)協(xié)議是 Apache License V2.0 ( APL )。然而在它的發(fā)布包中,筆者沒(méi)有看到任何形式的正式授權(quán)協(xié)議文本。眾所周知, SourceForge 的項(xiàng)目描述是可以隨時(shí)修改的( X 框架本身的授權(quán)協(xié)議就曾經(jīng)是 GPL ),如果發(fā)布包中沒(méi)有一份正式的授權(quán)協(xié)議文本,一旦作者修改了 SourceForge 的項(xiàng)目描述,用戶(hù)又該到哪里去尋找證據(jù)支持自己的合法使用呢?
在 X 框架的源碼中,大部分源文件在開(kāi)始處加上了 APL 的授權(quán)聲明,但有一部分源碼很是令人擔(dān)心。例如 UtilCache 這個(gè)類(lèi),開(kāi)始處沒(méi)有任何授權(quán)聲明,而 JavaDoc 中則這樣聲明作者信息:
@author ???? <a href="mailto:jonesde@ ofbiz.org ">David E. Jones</a>
也就是說(shuō),這個(gè)類(lèi)的源碼來(lái)自另一個(gè)開(kāi)源項(xiàng)目
Ofbiz
。值得一提的是,
Ofbiz
一直是“商業(yè)開(kāi)源”的倡導(dǎo)者,它的授權(quán)協(xié)議相當(dāng)嚴(yán)格。凡是使用
Ofbiz
源碼,必須將它的授權(quán)協(xié)議一并全文復(fù)制。像
X
框架這樣復(fù)制
Ofbiz
源碼、卻刪掉了授權(quán)協(xié)議的行為,實(shí)際上已經(jīng)構(gòu)成了對(duì)
Ofbiz
的侵權(quán)。
另外,作者打包用的壓縮格式是 RAR ,而這個(gè)壓縮格式對(duì)于商業(yè)用戶(hù)是收費(fèi)的。對(duì)于一個(gè)希望在商業(yè)項(xiàng)目中應(yīng)用的框架項(xiàng)目來(lái)說(shuō),選擇這樣一個(gè)壓縮格式實(shí)在算不得明智。而且筆者在源碼包中還看到了好幾個(gè) .jbx 文件,這是 JBuilder 的項(xiàng)目描述文件。把這些 JBuilder 專(zhuān)用的文件放在源碼包中,又怎能讓那些買(mǎi)不起或是不想買(mǎi) JBuilder 的用戶(hù)放心呢?更何況,出于朋友的關(guān)心,筆者還不得不擔(dān)心 X 框架的作者是否會(huì)收到 Borland 公司的律師信呢。
開(kāi)源必讀:授權(quán)先行
在啟動(dòng)一個(gè)開(kāi)源項(xiàng)目時(shí),第一件大事就是要確定自己的授權(quán)協(xié)議,并在最醒目的地方用最正式的方式向所有人聲明——當(dāng)然,在此之前你必須首先了解各種開(kāi)源授權(quán)協(xié)議。譬如說(shuō),
GPL
(
Linux
采用的授權(quán)協(xié)議)要求在軟件之上的擴(kuò)展和衍生也必須繼承
GPL
,因此這種協(xié)議對(duì)軟件的商業(yè)化應(yīng)用很不友好;相反,
APL
則允許用戶(hù)將軟件的擴(kuò)展產(chǎn)物私有化,便于商業(yè)應(yīng)用,卻不利于開(kāi)發(fā)者社群的發(fā)展。作為一個(gè)開(kāi)源項(xiàng)目的領(lǐng)導(dǎo)者,對(duì)于各種授權(quán)協(xié)議的利弊是不可不知的。
除了源碼本身的授權(quán)協(xié)議之外,軟件需要使用的類(lèi)庫(kù)、 IDE 、解壓工具等等都需要考慮授權(quán)問(wèn)題。開(kāi)源絕對(duì)不僅僅意味著“免費(fèi)使用”,開(kāi)源社群的人們有著更加強(qiáng)烈的版權(quán)意識(shí)和法律意識(shí)。如果你的開(kāi)源軟件會(huì)給用戶(hù)帶來(lái)潛在的法律麻煩,它離著被拋棄的命運(yùn)也就不遠(yuǎn)了。 |
可以看到,不管從法律的角度還是從發(fā)布形式的角度, X 框架都遠(yuǎn)夠不上“ Production/Stable ”的水準(zhǔn)——說(shuō)實(shí)在的,以它的成熟度,頂多只能算是一個(gè)尚未計(jì)劃周全的開(kāi)源項(xiàng)目。雖然作者在自己的網(wǎng)站上大肆宣傳,但作為一個(gè)潛在的用戶(hù),我不得不冷靜地說(shuō):即便 X 框架的技術(shù)真的能夠吸引我,但它遠(yuǎn)未成熟的項(xiàng)目形態(tài)決定了它根本無(wú)法在任何有實(shí)際意義的項(xiàng)目中運(yùn)用。要讓商業(yè)用戶(hù)對(duì)它產(chǎn)生興趣,作者需要做的工作還很多。
我剛才說(shuō)“即便 X 框架的技術(shù)真的能夠吸引我”,這算得上是一個(gè)合理的假設(shè)嗎?下面,就讓我們進(jìn)入這個(gè)被作者寄予厚望的框架內(nèi)部,看看它的技術(shù)水平吧。
整體架構(gòu)
在 X 框架的宣傳頁(yè)面上,我們看到了這樣的宣傳詞:
X
框架解決了以往
J2EE
開(kāi)發(fā)存在的諸多問(wèn)題:
EJB
難用、
J2EE
層次復(fù)雜、
DTO
太亂、
Struts
繞人、緩存難做性能低等。
X
框架是
Aop/Ico
[
注:應(yīng)為“
IoC
”,此處疑似筆誤
]
的實(shí)現(xiàn),優(yōu)異的緩存性能是其優(yōu)點(diǎn)。
下面是 X 框架的整體架構(gòu)圖:
可以看到,在作者推薦的架構(gòu)中, EJB 被作為業(yè)務(wù)邏輯實(shí)現(xiàn)的場(chǎng)所,而 POJO 被用于實(shí)現(xiàn) Fa?ade 。這是一個(gè)好的技術(shù)架構(gòu)嗎?筆者曾在一篇 Blog 中這樣評(píng)價(jià)它 [1] :
讓我們先回想一下,使用
EJB
的理由是什么?常見(jiàn)的答案有:可分布的業(yè)務(wù)對(duì)象;聲明性的基礎(chǔ)設(shè)施服務(wù)(例如事務(wù)管理)。那么,如果在
EJB
的上面再加上一
層
POJO
的
Fa?ade
,顯然你不能再使用
EJB
的基礎(chǔ)設(shè)施了,因?yàn)橥暾臉I(yè)務(wù)操作(也就是事務(wù)邊界)將位于
POJO Fa?ade
的方法這里,所以你必須重新
——
以聲明性的方式
——
實(shí)現(xiàn)事務(wù)管理、安全性管理、
remoting
、緩存等基礎(chǔ)設(shè)施服務(wù)。換句話(huà)說(shuō),你失去了
session bean
的一半好處。另一方面,“可分布的業(yè)務(wù)對(duì)象”也不復(fù)存在,因?yàn)?
POJO
本身是不能
——
像
EJB
那樣
——
分布的,這樣你又失去了
session bean
的另一半好處。
繼續(xù)回想,使用基于
POJO
的輕量級(jí)架構(gòu)的理由是什么?常見(jiàn)的答案有:易于測(cè)試;便于移植;“開(kāi)發(fā)
-
發(fā)布”周期短。而如果僅僅把
POJO
作為一層
Fa?ade
,把業(yè)務(wù)邏輯放在下面的
EJB
,那么你仍然無(wú)法輕易地測(cè)試業(yè)務(wù)邏輯,移植自然也無(wú)從談起了,并且每次修改
EJB
之后必須忍受漫長(zhǎng)的發(fā)布周期。
即便是僅僅把
EJB
作為
O/R mapping
,而不是業(yè)務(wù)邏輯的居所,你最多只能通過(guò)
DAO
封裝獲得比較好的業(yè)務(wù)可測(cè)性,但“修改
-
發(fā)布”的周期仍然很長(zhǎng),因?yàn)槿匀挥?
entity bean
存在。也就是說(shuō),即使是往最好的方面來(lái)說(shuō),這個(gè)架構(gòu)至少損失了輕量級(jí)架構(gòu)的一半優(yōu)點(diǎn)。
作為一個(gè)總結(jié),
X
框架即便是在使用得最恰當(dāng)?shù)那闆r下,它仍然不具備輕量級(jí)架構(gòu)的全部?jī)?yōu)點(diǎn),至少會(huì)對(duì)小步前進(jìn)的敏捷開(kāi)發(fā)造成損害(因?yàn)?
EJB
的存在),并且沒(méi)有
Spring
框架已經(jīng)實(shí)現(xiàn)的基礎(chǔ)設(shè)施(例如事務(wù)管理、
remoting
等),必須重新發(fā)明這些輪子;另一方面,它也不具備
EJB
的任何優(yōu)點(diǎn),
EJB
的聲明性基礎(chǔ)設(shè)施、可分布業(yè)務(wù)對(duì)象等能力它全都不能利用。因此,可以簡(jiǎn)單地總結(jié)說(shuō),
X
框架是一個(gè)這樣的架構(gòu):
它結(jié)合了
EJB
和輕量級(jí)架構(gòu)兩者各自的短處,卻拋棄了兩者各自的長(zhǎng)處
。
在不得不使用 EJB 的時(shí)候,一種常見(jiàn)的架構(gòu)模式是:用 session bean 作為 Fa?ade ,用 POJO 實(shí)現(xiàn)可移植、可測(cè)試的業(yè)務(wù)邏輯。這種模式可以結(jié)合 EJB 和 POJO 兩者的長(zhǎng)處。而 X 框架推薦的架構(gòu)模式,雖然乍看起來(lái)也是依葫蘆畫(huà)瓢,效果卻恰恰相反,正可謂是“取其糟粕、去其精華”。
開(kāi)源必讀:架構(gòu)必須正確
在開(kāi)源軟件的初始階段,功能可以不完善,代碼可以不漂亮,但架構(gòu)思路必須是正確的。即使你沒(méi)有完美的實(shí)現(xiàn),參與開(kāi)源的其他人可以幫助你;但如果架構(gòu)思路有嚴(yán)重失誤,誰(shuí)都幫不了你。從近兩年容器項(xiàng)目的更迭就可以看出端倪:
PicoContainer
本身只有
20
個(gè)類(lèi)、數(shù)百行代碼,但它有清晰而優(yōu)雅的架構(gòu),因此有很多人為它貢獻(xiàn)外圍的功能;
Avalon
容器盡管提供了完備的功能,但架構(gòu)的落伍迫使
Apache
基金會(huì)只能將其全盤(pán)廢棄。
所以如果你有志于啟動(dòng)一個(gè)開(kāi)源項(xiàng)目(尤其是框架性的項(xiàng)目),務(wù)必先把架構(gòu)思路拿出來(lái)給整個(gè)社群討論。只要大家都認(rèn)可你的架構(gòu),你就有機(jī)會(huì)得到很多的幫助;反之,恐怕你就只能得到無(wú)盡的嘲諷了。 |
技術(shù)細(xì)節(jié)
既然整體架構(gòu)已經(jīng)無(wú)甚可取之處,那么
X
框架的實(shí)現(xiàn)是否又像它所宣稱(chēng)的那樣,能夠解決諸多問(wèn)題呢?既然
X
框架號(hào)稱(chēng)是“
AOP/IoC
的實(shí)現(xiàn)”,我們就選中這兩項(xiàng)技術(shù),看看它們?cè)?
X
框架中的實(shí)現(xiàn)和應(yīng)用情況。
IoC
X 框架宣稱(chēng)自己是一個(gè)“基于 IoC 的應(yīng)用框架”。按照定義,框架本身就具有“業(yè)務(wù)代碼不調(diào)用框架,框架調(diào)用業(yè)務(wù)代碼”的特性,因此從廣義上來(lái)說(shuō),所有的框架必然是基于 IoC 模式的。所以,在框架這里,“基于 IoC ”通常是特指“對(duì)象依賴(lài)關(guān)系的管理和組裝基于 IoC ”,也就是 Martin Fowler 所說(shuō)的 Dependency Injection 模式 [2] :由容器統(tǒng)一管理組件的創(chuàng)建和組裝,組件本身不包含依賴(lài)查找的邏輯。那么, X 框架實(shí)現(xiàn) IoC 的情況又如何呢?
我們很快找到了 ContainerWrapper 這個(gè)接口,其中指定了一個(gè) POJO 容器核心應(yīng)該具備的主要功能:
public interface ContainerWrapper {
? public void registerChild(String name);
? public void register(String name, Class className);
? public void register(String name, Class className, Parameter[] parameters);
? public void register(String name, Object instance);
? public void start();
? public void stop();
? public Collection getAllInstances();
? public Object lookup(String name);
}
在這個(gè)接口的默認(rèn)實(shí)現(xiàn) DefaultContainerWrapper 中,這些功能被轉(zhuǎn)發(fā)給 PicoContainer 的對(duì)應(yīng)方法。也就是說(shuō), X 框架本身并沒(méi)有實(shí)現(xiàn)組件容器的功能,這部分功能將被轉(zhuǎn)發(fā)給其他的 IoC 組件容器(例如 PicoContainer 、 Spring 或 HiveMind 等)來(lái)實(shí)現(xiàn)。在 ContainerWrapper 接口的注釋中,我們看到了一句頗可玩味的話(huà):
/**
? * 封裝了 Container ,解耦具體應(yīng)用系統(tǒng)和 PicoContainer 關(guān)系。
了解 IoC 容器的讀者應(yīng)該知道,在使用 PicoContainer 或 Spring 等容器時(shí),絕大多數(shù) POJO 組件并不需要對(duì)容器有任何依賴(lài):它們只需要是最普通的 JavaBean ,只需要實(shí)現(xiàn)自己的業(yè)務(wù)接口。既然對(duì)容器沒(méi)有依賴(lài),自然也不需要“解耦”。至于極少數(shù)需要獲得生命周期回調(diào)、因此不得不依賴(lài)容器的組件,讓它們依賴(lài) PicoContainer 和依賴(lài) X 框架難道有什么區(qū)別嗎?更何況, PicoContainer 是一個(gè)比 X 框架更成熟、更流行的框架,為什么用戶(hù)應(yīng)該選擇 X 框架這么一個(gè)不那么成熟、不那么流行的框架夾在中間來(lái)“解耦”呢?
不管怎么說(shuō),至少我們可以看到: X 框架提供了組件容器的核心功能。那么, IoC (或者說(shuō), Dependency Injection )在 X 框架中的應(yīng)用又怎么樣呢?眾所周知,引入 IoC 容器的目標(biāo)就是要消除應(yīng)用程序中泛濫的工廠(包括 Service Locator ),由容器統(tǒng)一管理組件的創(chuàng)建和組裝。遺憾的是,不論在框架內(nèi)部還是在示例應(yīng)用中,我們?nèi)匀豢吹搅舜罅康墓S和 Service Locator 。例如作者引以為傲的緩存部分,具體的緩存策略(即 Cache 接口的實(shí)現(xiàn)對(duì)象)就是由 CacheFactory 負(fù)責(zé)創(chuàng)建的,并且使用的實(shí)現(xiàn)類(lèi)還是硬編碼在工廠內(nèi)部:
? public ? CacheFactory() {
cache = new LRUCache();
也就是說(shuō),如果用戶(hù)需要改變緩存策略,就必須修改 CacheFactory 的源代碼——請(qǐng)注意,這是一個(gè) X 框架內(nèi)部的類(lèi),用戶(hù)不應(yīng)該、也沒(méi)有能力去修改它。換句話(huà)說(shuō),用戶(hù)實(shí)際上根本無(wú)法改變緩存策略。既然如此,那這個(gè) CacheFactory 又有什么用呢?
開(kāi)源必讀:開(kāi)放
-封閉原則
開(kāi)源軟件應(yīng)該遵守開(kāi)放
-
封閉原則(
Open-Close Principle
,
OCP
):對(duì)
擴(kuò)展
開(kāi)放,對(duì)
修改
封閉。如果你希望為用戶(hù)提供任何靈活性,必須讓用戶(hù)以擴(kuò)展(例如派生子類(lèi)或配置文件)的方式使用,不能要求(甚至不能允許)用戶(hù)修改源代碼。如果一項(xiàng)靈活性必須通過(guò)修改源碼才能獲得,那么它對(duì)于用戶(hù)就毫無(wú)意義。
|
在示例應(yīng)用中,我們同樣沒(méi)有看到 IoC 的身影。例如 JdbcDAO 需要使用數(shù)據(jù)源(即 DataSource 對(duì)象),它就在構(gòu)造子中通過(guò) Service Locator 主動(dòng)獲取這個(gè)對(duì)象:
? public JdbcDAO() {
????? ServiceLocator sl = new ServiceLocator();
?????
dataSource = (DataSource) sl.getDataSource(JNDINames.DATASOURCE);
同樣的情況也出現(xiàn)在 JdbcDAO 的使用者那里。也就是說(shuō),雖然 X 框架提供了組件容器的功能,卻沒(méi)有(至少是目前沒(méi)有)利用它的依賴(lài)注入能力,僅僅把它作為一個(gè)“大工廠”來(lái)使用。這是對(duì) IoC 容器的一種典型的誤用:用這種方式使用容器,不僅沒(méi)有獲得“自動(dòng)管理依賴(lài)關(guān)系”的能力,而且也失去了普通 Service Locator “強(qiáng)類(lèi)型檢查”的優(yōu)點(diǎn),又是一個(gè)“取其糟粕、去其精華”的設(shè)計(jì)。
開(kāi)源必讀:了解你自己
當(dāng)你決定要在開(kāi)源軟件中使用某項(xiàng)技術(shù)時(shí),請(qǐng)確定你了解它的利弊和用法。如果僅僅為了給自己的軟件貼上“基于 xx 技術(shù)”的標(biāo)簽而使用一種自己不熟悉的技術(shù),往往只會(huì)給你的項(xiàng)目帶來(lái)負(fù)面的影響。 |
AOP
在 X 框架的源碼包中,我們找到了符合 AOP-Alliance API 的一些攔截器,例如用于實(shí)現(xiàn)緩存的 CacheInterceptor 。盡管——毫不意外地——沒(méi)有找到如何將這些攔截器織入( weave in )的邏輯或配置文件,但我們畢竟可以相信:這里的確有 AOP 的身影??墒?,甫一深入這個(gè)“基于 AOP 的緩存機(jī)制”內(nèi)部,筆者卻又發(fā)現(xiàn)了更多的問(wèn)題。
單從 CacheInterceptor 的實(shí)現(xiàn)來(lái)看,這是一個(gè)最簡(jiǎn)單、也最常見(jiàn)的緩存攔截器。它攔截所有業(yè)務(wù)方法的調(diào)用,并針對(duì)每次方法調(diào)用執(zhí)行下列邏輯:
??? IF 需要緩存
?????? key = ( 根據(jù)方法簽名生成 key);
?????? IF (cache.get(key) == null)
??? ?????? value = ( 實(shí)際調(diào)用被攔截方法 );
??? ??? ??? cache.put(key, value);
?????? RETURN (cache.get(key));
??? ELSE
?????? RETURN ( 實(shí)際調(diào)用被攔截方法 );
看上去很好,基于 AOP 的緩存實(shí)現(xiàn)就應(yīng)該這么做……可是,清除緩存的邏輯在哪里?如果我們把業(yè)務(wù)方法分為“讀方法”和“寫(xiě)方法”兩種,那么這個(gè)攔截器實(shí)際上只照顧了“讀方法”的情況。而“寫(xiě)方法”被調(diào)用時(shí)會(huì)改變業(yè)務(wù)對(duì)象的狀態(tài),因此必須將其操作的業(yè)務(wù)對(duì)象從緩存中清除出去,但這部分邏輯在 CacheInterceptor 中壓根不見(jiàn)蹤影。如果緩存內(nèi)容不能及時(shí)清理的話(huà),用戶(hù)從緩存中取出的信息豈不是完全錯(cuò)誤的嗎?
被驚出一身冷汗之后,筆者好歹還是從幾個(gè)
Struts action
(也就是調(diào)用
POJO Fa?ade
的
client
代碼)中找到了清除緩存的邏輯。原來(lái)
X
框架所謂“基于
AOP
的緩存機(jī)制”只實(shí)現(xiàn)了一條腿:“把數(shù)據(jù)放入緩存”和“從緩存中取數(shù)據(jù)”的邏輯確實(shí)用攔截器實(shí)現(xiàn)了,但“如何清除失效數(shù)據(jù)”的邏輯還得散布在所有的客戶(hù)代碼中。
AOP
原本就是為了把緩存這類(lèi)橫切性(
crosscutting
)的基礎(chǔ)設(shè)施邏輯集中到一個(gè)模塊管理,像
X
框架的這個(gè)緩存實(shí)現(xiàn),不僅橫切性的代碼仍然四下散布,連緩存邏輯的相關(guān)性和概念完整性都被打破了,豈不是弄巧成拙么?
開(kāi)源必讀:言而有信
如果你在宣傳詞中承諾了一項(xiàng)特性,請(qǐng)務(wù)必在你的軟件中完整地實(shí)現(xiàn)它。不要僅僅提供一個(gè)半吊子的實(shí)現(xiàn),更不要讓你的任何承諾放空。如果你沒(méi)有把握做好一件事,就不要承諾它。不僅對(duì)于開(kāi)源軟件,對(duì)于任何軟件開(kāi)發(fā),這都是應(yīng)該記住的原則。 |
更有趣的是, X 框架的作者要求領(lǐng)域模型對(duì)象繼承 Model 基類(lèi),并聲稱(chēng)這是為了緩存的需要——事實(shí)也的確如此: CacheInterceptor 只能處理 Model 的子對(duì)象。但只要對(duì)緩存部分的實(shí)現(xiàn)稍加分析就會(huì)發(fā)現(xiàn),這一要求完全是作者憑空加上的:用于緩存對(duì)象的 Cache 接口允許放入任何 Object ;而 Model 盡管提供了 setModified() 、 setCacheable() 等用于管理緩存邏輯的方法,卻沒(méi)有任何代碼調(diào)用它們。換句話(huà)說(shuō),即便我們修改 CacheInterceptor ,使其可以緩存任何 Object ,對(duì) X 框架目前的功能也不會(huì)有任何影響。既然如此,又為什么要給用戶(hù)憑空加上這一層限制呢?
退一萬(wàn)步說(shuō),即使我們認(rèn)為 X 框架今后會(huì)用 Model 的方法來(lái)管理緩存邏輯,這個(gè)限制仍然是理由不足的。畢竟,目前 X 框架還僅僅提供了緩存這一項(xiàng)基礎(chǔ)設(shè)施( infrastructure )而已。如果所有基礎(chǔ)設(shè)施都用“繼承一個(gè)基類(lèi)”的套路來(lái)實(shí)現(xiàn),當(dāng)它真正提供企業(yè)級(jí)應(yīng)用所需的所有基礎(chǔ)設(shè)施時(shí), Model 類(lèi)豈不是要變得碩大無(wú)朋?用戶(hù)的領(lǐng)域?qū)ο筘M不是再也無(wú)法移植到這個(gè)框架之外?況且,“由領(lǐng)域?qū)ο笈袛嘧约菏欠裥枰彺妗钡乃悸繁旧硪彩清e(cuò)誤的:如果不僅要緩存領(lǐng)域?qū)ο螅€要緩存 String 、 Integer 等簡(jiǎn)單對(duì)象,該怎么辦?如果同一個(gè)領(lǐng)域?qū)ο笤诓煌姆椒ㄖ行枰煌木彺娌呗裕衷撛趺崔k? X 框架的設(shè)計(jì)讓領(lǐng)域?qū)ο蟊池?fù)了太多的責(zé)任,而這些責(zé)任原本應(yīng)該是通過(guò) AOP 轉(zhuǎn)移到 aspect 中的。在 X 框架這里, AOP 根本沒(méi)有發(fā)揮它應(yīng)有的效用。
開(kāi)源必讀:避免綁定
開(kāi)源軟件(尤其是框架類(lèi)軟件)應(yīng)該盡量避免對(duì)你的用戶(hù)造成綁定。能夠在 POJO 上實(shí)現(xiàn)的功能,就不要強(qiáng)迫用戶(hù)實(shí)現(xiàn)你的接口;能夠通過(guò)接口實(shí)現(xiàn)的功能,就不要強(qiáng)迫用戶(hù)繼承你的基類(lèi)。尤其是 Java 語(yǔ)言只允許單根繼承,一旦要求用戶(hù)的類(lèi)繼承框架基類(lèi),那么前者就無(wú)法再繼承其他任何基類(lèi),這是一種非常嚴(yán)重的綁定,不論用戶(hù)和框架設(shè)計(jì)者都應(yīng)當(dāng)極力避免。 |
寫(xiě)在最后
看完這篇多少有些尖刻的批評(píng),恐怕讀者難免要怪責(zé)我“不厚道”——畢竟,糟糕的開(kāi)源軟件堪比恒河沙數(shù),為什么偏要選中 X 框架大加撻伐呢?在此,我要給各位讀者、各位有志于開(kāi)源的程序員一個(gè)最后、卻是最重要的建議:
開(kāi)源必讀:切忌好大喜功
開(kāi)源是一件長(zhǎng)期而艱巨的工作,對(duì)于只能用業(yè)余時(shí)間參與的我們更是如此。做開(kāi)源務(wù)必腳踏實(shí)地,做出產(chǎn)品首先在小圈子里內(nèi)部討論,然后逐漸擴(kuò)大宣傳的圈子。切勿吹大牛、放衛(wèi)星,把“未來(lái)的愿景”當(dāng)作“今天的承諾”來(lái)說(shuō)——因?yàn)橐坏┕ぷ髅ζ饋?lái),誰(shuí)都不敢保證這個(gè)愿景到哪天才能實(shí)現(xiàn)。
國(guó)人還有個(gè)愛(ài)好:凡事喜歡趕個(gè)年節(jié)“獻(xiàn)禮”,或是給自己綁上個(gè)“民族軟件”的旗號(hào),這更是開(kāi)源的大忌。凡是做過(guò)政府項(xiàng)目的程序員,想必都對(duì)“國(guó)慶獻(xiàn)禮”、“新年獻(xiàn)禮”之類(lèi)事情煩不勝煩,輪到自己做開(kāi)源項(xiàng)目時(shí),又何苦把自己套進(jìn)這個(gè)怪圈里呢?當(dāng)然,如果你的開(kāi)源項(xiàng)目原本就是做給某些官老爺看的,那又另當(dāng)別論。
|
所以,我的這位朋友怕也不能怪我刻?。阂皇撬o趕著拿出個(gè)遠(yuǎn)未完善的版本“新年獻(xiàn)禮”,要不是他提前放出“ AOP/IoC ”的衛(wèi)星,要不是他妄稱(chēng)這個(gè)框架“代表民族軟件水平”,或許我還會(huì)夸他的代碼頗有可看之處呢。有一句大家都熟悉的老話(huà),筆者私以為所有投身開(kāi)源者頗可借鑒,在此與諸位共勉:
長(zhǎng)得丑不是你的錯(cuò)……
[1]
這篇
Blog
的原文請(qǐng)看:
http://gigix.blogdriver.com/gigix/474041.html
。
[2] 關(guān)于 IoC 模式和 Dependency Injection 模式,詳見(jiàn) Martin Fowler 的《Dependency Injection與模式IoC容器》 一文。(中譯本發(fā)表于《程序員》 2004 年第3 期。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=276486
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
