遺留代碼經常是腐臭的,每個優秀的開發者都想把它重構。而進行重構的一個理想的先決條件是,它應該包含一組單元測試用例,以避免產生回歸缺陷。但是為遺留代碼編寫單元測試可不是件容易的事,因為它經常是一團糟。要想為遺留代碼編寫有效的單元測試,你大概得先把它重構一下。但要重構它,你又需要單元測試來確保你沒有破壞任何功能。這種狀況相當于要回答是先有雞還是先有蛋。這篇文章通過分享一個我曾參與過的真實案例,描述了一種可以安全地重構遺留代碼的方法。
問題描述
在這篇文章中,我將用一個真實案例來描述測試與重構遺留系統的有效實踐。這個例子的代碼由Java編寫,不過這個實踐對其它語言也是適用的。我將原始場景稍做了些改動以免誤傷無辜,并稍做簡化以便讓它更容易被理解。這篇文章所介紹的實踐幫助我重構了近期我所參與的一個遺留系統。
這篇文章并不打算介紹單元測試與重構的基本技巧。你可以通過閱讀相關書籍以學習該主題的更多內容,如Martin Fowler的《重構:改善既有代碼的設計》及Joshua Kerievsky的《重構與模式》。相對而言,這篇文章的內容將描述一些真實場景中的復雜性,我也希望它能夠為解決這些復雜性提供一些有用的實踐。
在這個案例中我將描述一個虛構的資源管理系統,其中資源指的是可指派給其任務的某個人??梢詾槟硞€資源指派一個HR票據(ticket)或者IT票據,也可以為某個資源指派一個HR請求或IT請求。資源經理可以記錄某個資源處理某項任務的預計時間,而資源本身可以記錄他們在某個票據或請求上工作的實際時間。
可以用餅圖的方式表示資源的使用情況,圖中同時顯示了預計時間與實際花費的時間。
相關廠商內容
BPMS基于RDF/OWL快速元數據倉儲,重用流程開發周期所有資產
QCon上海2013精彩回顧:GitHub Peter Bell:"首先,殺死所有產品Owner"
QCon上海2013精彩回顧:Twitter工程VP Raffi:"分解Twitter"
QCon北京2014大會正式啟動,面向社會征集演講話題
QCon上海2013精彩回顧:LinkedIn Sam Shah:"如何將數據變為產品"
相關贊助商
QCon全球軟件開發大會(北京站)2014,4月25-27日,誠邀蒞臨。
好像不太復雜嘛?不過,真實的系統能夠為資源分配多種類型的任務,當然從技術上講這也不是多么復雜的設計。但當我初次看到系統的代碼時,我感覺自己似乎看到了一件老古董,從中看得出代碼是如何從開始逐步進化的(或者不如說是退化的)。在一開始,這一系統僅能用來處理請求,之后才加入了處理票據以及其它類型任務的功能。某位工程師開始編寫代碼以處理請求:首先從數據庫中獲取數據,隨后按照餅圖的方式顯示數據。他甚至沒有考慮過要將信息組織為合適的對象:
class ResourceBreakdownService {
public Map search (Session context) throws SearchException{
//omitted twenty or so lines of code to pull search criteria out of context
and verify them, such as the below:
if(resourceIds==null || resourceIds.size ()==0){
throw new SearchException(“Resource list is not provided”);
}
if(resourceId!=null || resourceIds.size()>0){
resourceObjs=resourceDAO.getResourceByIds(resourceIds);
}
//get workload for all requests
Map requestBreakDown=getResourceRequestsLoadBreakdown (resourceObjs,startDate,
finishDate);
return requestBreakDown;
}
}
我相信你肯定被這段代碼里的壞味道嚇到了吧?比方說,你大概很快就會發現search并不是一個有意義的名稱,還有應該使用Apache Commons類庫中的CollectionUtils.isEmpty()方法來檢測一個集合,此外你大概也會疑惑該方法返回的Map對象到底包含了些什么?
別著急,壞味道陸續有來。接下來的一位工程師繼承了先人的衣缽,按照相同的方式對票據進行了處理,以下就是修改后的代碼:
// get workload for all tickets
Map ticketBreakdown =getResourceRequestsLoadBreakdown(resourceObjs,startDate,
finishDate,ticketSeverity);
Map result=new HashMap();
for(Iterator i = resourceObjs.iterator(); i.hasNext();) {
Resource resource=(Resource)i.next();
Map requestBreakdown2=(Map)requestBreakdown.get(resource);
List ticketBreakdown2=(List)ticketBreakdown.get(resource);
Map resourceWorkloadBreakdown=combineRequestAndTicket(requestBreakdown2,
ticketBreakdown2);
result.put(resource,resourceWorkloadBreakdown)
}
return result;
先不管那糟糕的命名、失衡的代碼結構以及其它任何代碼美觀度上的問題了。這段代碼中最壞的味道就是它返回的Map對象了,這個Map對象完全是個黑洞,里面塞滿了各種數據,但又不會提示你里面究竟包含的是什么。我只能編寫了一些調試代碼,將Map中的內容循環打印出來后,才看懂了它的數據結構。
在這個示例中,{} 代表一個Map,=> 代表健值映射,而[] 代表一個集合:
{resource with id 30000=> [
SummaryOfActualWorkloadForRequestType,
原文轉自:http://www.infoq.com/cn/articles/refactoring-legacy-applications