SummaryOfEstimatedWorkloadForRequestType,
{30240=>[
ActualWorkloadForReqeustWithId_30240,
EstimatedWorkloadForRequestWithId_30240],
30241=>[
ActualWorkloadForReqeustWithId_30241,
EstimatedWorkloadForRequestWithId_30241]
}
SummaryOfActualWorkloadForTicketType,
SummaryOfEstimatedWorkloadForTicketType,
{20000=>[
ActualWorkloadForTicketWithId_2000,
EstimatedWorkloadForTicketWithId_2000],
}
]
}
這個糟糕的數據結構使得數據的編碼與解碼邏輯在直觀上非常冗長乏味,可讀性很差。
集成測試
希望我已經讓你認識到這段代碼確實是非常復雜的。如果在開始重構之前讓我首先解開這團亂麻,然后理解每一行代碼的意圖,那我非瘋了不可。為了保持我的心智健全,我決定采用一種自頂向下的方式來理解代碼邏輯。也就是說,我決定首先嘗試一下這個系統的功能,進行一些調試,以了解系統的整體情況,而不是一上來就直接閱讀代碼,并試圖從中推斷出代碼的邏輯。
我所使用的方法與編寫測試代碼完全相同,傳統的方法是編寫小段的測試代碼以驗證每一段代碼路徑,如果每一段測試都通過,那么當所有的代碼路徑組織在一起之后,方法能夠按照預期方式工作的機會就很高了。但這種傳統方式在這里行不通, ResourceBreakdownService簡直就是一個“上帝類”,如果我僅憑著對系統整體情況的一些了解就對這個類進行分解,很可能會造成很多問題 – 在遺留系統的每個角落里都有可能隱藏著眾多不為人知的秘密。
我編寫了以下這個簡單的測試,它反映了我對整個系統的理解:
public void testResourceBreakdown(){
Resource resource=createResource();
List requests=createRequests();
assignRequestToResource(resource, requests);
List tickets=createTickets();
assignTicketToResource(resource, tickets);
Map result=new ResourceBreakdownService().search(resource);
verifyResult(result,resource,requests,tickets);
}
注意一下verifyResult()這個方法,我首先循環式地將result的內容打印出來,以找出其中的結構,隨后verifyResult()方法根據這個結構對結果進行驗證,確保其中包含了正確的數據:
private void verifyResult(Map result, Resource rsc, List requests,
List tickets){
assertTrue(result.containsKey(rsc.getId()));
// in this simple test case, actual workload is empty
UtilizationBean emptyActualLoad=createDummyWorkload();
List resourceWorkLoad=result.get(rsc.getId());
UtilizationBean scheduleWorkload=calculateWorkload(rsc,requests);
assertEquals(emptyActualLoad,resourceWorkLoad.get(0));
assertEquals(scheduleWorkload,resourceWorkLoad.get(1));
Map requestDetailWorkload = (Map)resourceWorkLoad.get(3);
for (Request request : requests) {
assertTrue(requestDetailWorkload.containsKey(request.getId());
UtilizationBean scheduleWorkload0=calculateWorkload(rsc,request);
assertEquals(emptyActualLoad,requestDetailWorkload.get(request.getId()).get(0));
assertEquals(scheduleWorkload0,requestDetailWorkload.get(request.getId()).get(1));
}
// omit code to check tickets
...
}
用臨時方案繞過障礙
以上測試用例看起來簡單,但實際卻隱含了許多復雜性。首先,ResourceBreakdownService().search方法與運行時緊密相關,它需要訪問數據庫、其它服務,或許還有些不為人知的依賴項。而且和許多遺留系統一樣,這個系統也沒有建立任何單元測試的架構。為了訪問運行時服務,唯一的選擇就是啟動整個系統,這不僅造成巨大的開銷,而且也帶來了很大的不便。
ServerMain類啟動了整個系統的服務端功能,這個類也是個老古董了,你完全可以從中觀察到它的進化過程。這個系統的編寫時間已經超過10年了,當時還沒有Spring、Hibernate這些東西,JBoss和Tomcat也才剛剛冒頭。因此那些勇敢的先驅們不得不手工打造了許多工具,他們創建了一個自制的集群、一個緩存服務、一個連接池以及其它許多東西。之后他們在某種程度上引入了JBoss和Tomcat(但不幸的是他們仍然保留了那些手工藝品,導致了現在的代碼中存在著兩種事務管理機制以及三種連接池)。
我決定將ServerMain復制到TestServerMain類中,但運行TestServerMain.main()方法產生了以下失敗信息:
org.springframework.beans.factory.BeanInitializationException: Could not load
properties; nested exception is
java.io.FileNotFoundException: class path resource [database.properties] cannot
be opened because it does not exist
at
org.springframework.beans.factory.config.PropertyResourceConfigurer.
postProcessBeanFactory(PropertyResourceConfigurer.java:78)
好吧,它還挺靈活!我隨意拿了個database.properties文件,把它放到測試類的文件夾中并再次運行測試。但這一次程序又拋出了下面的異常:
java.io.FileNotFoundException: .\server.conf (The system cannot find the file specified)
at java.io.FileInputStream.open(Native Method)
原文轉自:http://www.infoq.com/cn/articles/refactoring-legacy-applications