重構遺留程序的一次案例學習

發表于:2013-12-20來源:InfoQ作者:Chen Ping點擊數: 標簽:重構
重構遺留程序的一次案例學習.遺留代碼經常是腐臭的,每個優秀的開發者都想把它重構。而進行重構的一個理想的先決條件是,它應該包含一組單元測試用例,以避免產生回歸缺陷。但是為遺留代碼編寫單元測試可不是件容易的事,因為它經常是一團糟。要想為遺留代碼編寫有效的

  遺留代碼經常是腐臭的,每個優秀的開發者都想把它重構。而進行重構的一個理想的先決條件是,它應該包含一組單元測試用例,以避免產生回歸缺陷。但是為遺留代碼編寫單元測試可不是件容易的事,因為它經常是一團糟。要想為遺留代碼編寫有效的單元測試,你大概得先把它重構一下。但要重構它,你又需要單元測試來確保你沒有破壞任何功能。這種狀況相當于要回答是先有雞還是先有蛋。這篇文章通過分享一個我曾參與過的真實案例,描述了一種可以安全地重構遺留代碼的方法。

  問題描述

  在這篇文章中,我將用一個真實案例來描述測試與重構遺留系統的有效實踐。這個例子的代碼由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

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97