<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Sung in Blog

               一些技術文章 & 一些生活雜碎

    本文用eclipse的自動重構功能對一個程序實例進行重構,目的是探索Eclipse自動重構可以在多大程度上輔助重構這個過程。程序實例使用《Refactoring:Improving the Design of Existing Code》一書中的例子。
      
      Eclipse的自動重構功能能夠很好地支持各種程序元素的重命名,并自動更新相關的引用。Eclipse能夠支持方法、字段在類之間移動,并自動更新引用。Eclipse較好地支持內聯字段、函數的更新替換。Eclipse較好地支持抽取方法、變量等程序元素。
      
      重構的過程是一個不斷嘗試和探索的過程。Eclipse的重構支持撤銷和重做,并且能夠預覽重構結果,這些是很實用的功能。
      
      Eclipse的重命名、抽取方法、移動、內聯功能、更改方法特征符等代碼結構級別的重構方法,是比較成熟同時也值得使用的功能。至于設計結構上的重構,eclipse還不能很好地支持。但是作者相信,自動重構的理念應該是"工具輔助下的重構工作",人仍然承擔大部分重構工作。
      
      一、預備工作
      
      本文使用《Refactoring:Improving the Design of Existing Code》一書第一章的例子。重構前的代碼及每一步重構后的代碼見附件。讀者最好配合《Refactoring:Improving the Design of Existing Code》一書閱讀本文。
      
      Eclipse使用如下版本:
       
      同時安裝了中文語言包。
      
      二、重構第一步:分解并重組statement()
      
      目的:
      
      1、 把statement()函數中的swich語句提煉到獨立的函數amountFor()中。
      
      2、 修改amountFor()參數命名
      
      重構方法:
      
      Extract Method
      Rename Method
      
      方法:
      
      1、選中swich語句的代碼塊,在右鍵菜單中選擇"重構/抽取方法",出現參數對話框。Eclipse自動分析代碼塊中的局部變量,找到了兩個局部變量:each和thisAmount。其中,each只是在代碼塊中被讀取,但thisAmount會在代碼塊中被修改。按照重構Extract Method總結出來的規則,應該把each當作抽取函數的參數、thisAmount當作抽取函數的返回值。然而Eclipse并不做區分,直接把這兩個變量當作抽取新方法的參數,如圖。
       
      我們的目的是把在抽取函數中不會被修改的each作為參數;會被修改的thisAmount作為返回值。解決的辦法是,把 double thisAmount = 0; 這行代碼移到switch語句的上面,變成這樣:
      
      double thisAmount = 0;
      switch(each.getMovie().getPriceCode()){
      case Movie.REGULAR:
      thisAmount += 2;
      if(each.getDaysRented()>2)
      thisAmount += (each.getDaysRented()-2)*1.5;
      break;
      
      case Movie.NEW_RELEASE:
      thisAmount += each.getDaysRented()*3;
      break;
      
      case Movie.CHILDRENS:
      thisAmount += 1.5;
      if(each.getDaysRented()>3)
      thisAmount += (each.getDaysRented()-3)*1.5;
      break;
      }
      
      選中這段代碼,在右鍵菜單中選擇"重構/抽取方法",eclipse這次變得聰明點了,如圖。
       
      選擇"預覽"按鈕預先查看重構后的結果,符合我們最初的目的。
       
      選擇"確定"按鈕,重構后的代碼片斷如下:
      
      public String statement() {
      double totalAmount = 0;
      int frequentRenterPoints = 0;
      Enumeration rentals = _rentals.elements();
      String result = "Rental Record for " + getName() + "\n";
      
      while(rentals.hasMoreElements()){
      Rental each = (Rental)rentals.nextElement();
      
      double thisAmount = amountFor(each);
      
      frequentRenterPoints ++;
      if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE &&each.getDaysRented()>1)
      frequentRenterPoints ++;
      
      result += "\t" + each.getMovie().getTitle() + "\t" +String.valueOf(thisAmount) + "\n";
      totalAmount += thisAmount;
      }
      
      result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
      result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
      
      return result;
      }
      
      /**
      * @param each
      * @return
      */
      private double amountFor(Rental each) {
      double thisAmount = 0;
      switch(each.getMovie().getPriceCode()){
      case Movie.REGULAR:
      thisAmount += 2;
      if(each.getDaysRented()>2)
      thisAmount += (each.getDaysRented()-2)*1.5;
      break;
      
      case Movie.NEW_RELEASE:
      thisAmount += each.getDaysRented()*3;
      break;
      
      case Movie.CHILDRENS:
      thisAmount += 1.5;
      if(each.getDaysRented()>3)
      thisAmount += (each.getDaysRented()-3)*1.5;
      break;
      }
      return thisAmount;
      }
      
      2、選中amountFor()的參數each,在右鍵菜單中選擇"重構/重命名",在對話框中輸入新的名稱:aRental,選擇確定,amountFor()中所有each的引用全部被替換成新的名稱。用同樣的辦法修改amountFor()中的局部變量thisAmount為result。重構后的amountFor()代碼如下:
      
      /**
      * @param aRental
      * @return
      */
      private double amountFor(Rental aRental) {
      double result = 0;
      switch(aRental.getMovie().getPriceCode()){
      case Movie.REGULAR:
      result += 2;
      if(aRental.getDaysRented()>2)
      result += (aRental.getDaysRented()-2)*1.5;
      break;
      
      case Movie.NEW_RELEASE:
      result += aRental.getDaysRented()*3;
      break;
      
      case Movie.CHILDRENS:
      result += 1.5;
      if(aRental.getDaysRented()>3)
      result += (aRental.getDaysRented()-3)*1.5;
      break;
      }
      return result;
      }
      
      三、重構第二步:搬移"金額計算"代碼
      
      目的:
      
      1、 將函數amountFor()轉移到Rental類中,并更名為getCharge()。
      
      2、 更新并替換所有對amountFor()的引用。
      
      重構方法:
      
      Move Method
      Change Method signatrue
      Inline Method
      Inline Temp
      
      方法:
      
      1、選中函數amountFor()的定義,在右鍵菜單中選擇"重構/移動",顯示參數設置對話框。把新方法名改成getCharge。按下"確定"按鈕,Customer Class中的amountFor()函數被移動到Rental Class中,并更名為:getCharge()。
       
      同時eclipse自動在Customer的amountFor()函數中添加一行對新函數的"委托"代碼:
      
      private double amountFor(Rental aRental) {
      return aRental.getCharge();
      }
      
      這行代碼會產生編譯錯誤,原因是amountFor()的private型被傳遞到了新的方法中:
      
      /**
      * @param this
      * @return
      */
      private double getCharge() {
      ……
      }
      
      2、繼續重構!選中getCharge()方法,在右鍵菜單中選擇"重構/更改方法特征符",彈出參數選擇對話框,把訪問修飾符從private改成public。Eclipse的編譯錯誤提示自動消失。
       
      3、回到Customer類,把所有對amountFor()引用的地方替換成直接對getCharge()的引用。選中Customer類的函數amountFor(Rental aRental),在右鍵菜單中選擇"重構/內聯",出現參數選擇對話框。
       
      選擇"確認"按鈕,引用amountFor()的地方被替換成對getCharge()的引用。
      
      public String statement() {
      ……
      double thisAmount = each.getCharge();
      ……
      }
      
      4、除去臨時變量thisAmount。
      
      選中變量thisAmount,在右鍵菜單中選擇"重構/內聯",重構預覽窗口如下,可見達到了重構的目的。按下"確認"按鈕重構代碼。
       
      statement()代碼:
      
      public String statement() {
      double totalAmount = 0; // 總消費金額
      int frequentRenterPoints = 0; // 常客積點
      Enumeration rentals = _rentals.elements();
      String result = "Rental Record for " + getName() + "\n";
      
      while(rentals.hasMoreElements()){
      Rental each = (Rental)rentals.nextElement(); //取得一筆租借記錄
      
      // add frequent renter points(累加 常客積點)
      frequentRenterPoints ++;
      // add bouns for a two day new release rental
      if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
      frequentRenterPoints ++;
      
      // show figures for this rental(顯示此筆租借數據)
      result += "\t" + each.getMovie().getTitle() + "\t" +
      String.valueOf(each.getCharge()) + "\n";
      totalAmount += each.getCharge();
      }
      
      // add footer lines(結尾打印)
      result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
      result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
      
      return result;
      }
      
      四、重構第三步:提煉"常客積點計算"代碼
      
      目的:提取"常客積點計算"代碼并放在Rental類中,"常客積點計算"代碼如下。
      
      public String statement() {
      ……
      // add frequent renter points
      frequentRenterPoints ++;
      // add bouns for a two day new release rental
      if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
      frequentRenterPoints ++;
      ……
      }
      
      重構后的代碼如下:
      
      frequentRenterPoints += each.getFrequentRenterPoints();
      
      重構方法:
      
      Extract Method
      Move Method
      Change Method signatrue
      Inline Method
      
      方法:
      
      1、 首先,抽取代碼到獨立的函數中。
      
      用"抽取方法"重構代碼,函數名:getFrequentRenterPoints。很遺憾,eclipse的不能生成諸如:frequentRenterPoints += getFrequentRenterPoints(Rental aRental); 的代碼。原因是執行自增操作的局部變量frequentRenterPoints要出現在等式右邊,因此抽取函數getFrequentRenterPoints()一定要把frequentRenterPoints作為參數。手工修改函數和對函數的引用,重構后的代碼如下:
      
      public String statement() {
      ……
      while(rentals.hasMoreElements()){
      ……
      frequentRenterPoints += getFrequentRenterPoints(each);
      ……
      }
      ……
      }
      
      /**
      * @param each
      * @return
      */
      private int getFrequentRenterPoints(Rental each) {
      if((each.getMovie().getPriceCode())==Movie.NEW_RELEASE && each.getDaysRented()>1)
      return 2;
      else
      return 1;
      }
      
      2、 把getFrequentRenterPoints()移動到Rental類中。
      
      3、 對getFrequentRenterPoints()"更改方法特征符"為public。
      
      4、 對Customer的函數getFrequentRenterPoints()執行內聯操作,重構目標完成。
      
      五、重構第四步:去除臨時變量(totalAmount和frequentRenterPoints)
      
      目的:去除臨時變量(totalAmount和frequentRenterPoints)
      
      方法:
      
      1、 分析totalAmount和frequentRenterPoints的定義和引用結構如下:
      
      // 聲明和定義
      double totalAmount = 0;
      int frequentRenterPoints = 0;
      ……
      // 在循環中修改
      while(rentals.hasMoreElements()){
      ……
      frequentRenterPoints += each.getFrequentRenterPoints();
      ……
      totalAmount += each.getCharge();
      ……
      }
      ……
      // 在循環外使用
      result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
      result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
      ……
      
      上述兩個變量在循環體外面定義和使用,在循環中被修改,運用Replace Temp with Query方法去除這兩個臨時變量是一項稍微復雜的重構。很遺憾,eclipse目前不支持這樣的重構。
      
      2、手工修改代碼。
      
      六、重構第五步:運用多態取代與價格相關的條件邏輯
      
      目的:
      
      1、 把Rental類中的函數getCharge()移動到Movie類中。
      
      2、 把Rental類中的函數getFrequentRenterPoints()移動到Movie類中。
      
      重構方法:
      
      Move Method
      Inline Method
      
      方法:
      
      1、 選中Rental類中的函數getCharge(),右鍵菜單選中"重構/移動",eclipse提示找不到接收者,不能移動。原因在于這行語句:
      
      switch(getMovie().getPriceCode()){//取得影片出租價格
      
      選中getMovie(),右鍵菜單選中"重構/內聯",確定后代碼成為:
      
      switch(_movie.getPriceCode()){ //取得影片出租價格
      
      選中getCharge(),執行"重構/移動"后,函數被移動到Movie類中。然而這只是部分達成了重構目的,我們發現,移動后的代碼把Rental作為參數傳給了getCharge(),手工修改一下,代碼變成:
      
      class Movie ……
      /**
      * @param this
      * @return
      */
      public double getCharge(int _daysRented) {
      double result = 0;
      switch(getPriceCode()){ //取得影片出租價格
      case Movie.REGULAR: // 普通片
      result += 2;
      if(_daysRented>2)
      result += (_daysRented-2)*1.5;
      break;
      
      case Movie.NEW_RELEASE: // 新片
      result += _daysRented*3;
      break;
      
      case Movie.CHILDRENS: // 兒童片
      result += 1.5;
      if(_daysRented>3)
      result += (_daysRented-3)*1.5;
      break;
      }
      return result;
      }
      
      class Rental……
      /**
      * @param this
      * @return
      */
      public double getCharge() {
      return _movie.getCharge(_daysRented);
      }
      
      2、用同樣的步驟處理getFrequentRenterPoints(),重構后的代碼:
      
      class Movie ……
      /**
      * @param frequentRenterPoints
      * @param this
      * @return
      */
      public int getFrequentRenterPoints(int daysRented) {
      if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1)
      return 2;
      else
      return 1;
      }
      class Rental……
      /**
      * @param frequentRenterPoints
      * @param this
      * @return
      */
      public int getFrequentRenterPoints(int daysRented) {
      if((getPriceCode())==Movie.NEW_RELEASE && daysRented>1)
      return 2;
      else
      return 1;
      }
      
      七、重構第六步:終于……我們來到繼承
      
      目的:對switch語句引入state模式。
      
      方法:
      
      很遺憾,不得不在這里提前結束eclipse的自動重構之旅。Eclipse幾乎不能做結構上的重構。也許Martin Fowler在書中呼喚的自動重構工具止于"工具輔助下的重構工作"這一理念。藝術是人類的專利,編程藝術的夢想將持續下去。
      
      感興趣的讀者可以查看手工重構的最后一步代碼。將重構進行到底!
      
      附錄:eclipse支持的重構方法(摘自eclipse中文幫助)
      
     

    posted on 2005-11-02 15:42 Sung 閱讀(753) 評論(0)  編輯  收藏 所屬分類: Eclipse
    主站蜘蛛池模板: 亚洲中文字幕无码爆乳AV| 国产又大又粗又硬又长免费| 亚洲精品乱码久久久久久蜜桃不卡| 亚洲偷自拍另类图片二区| 大地资源在线观看免费高清| 亚洲国产成a人v在线观看| 中文字幕影片免费在线观看| 亚洲1234区乱码| 永久免费毛片在线播放| 亚洲精品国产国语| 国产又粗又猛又爽又黄的免费视频 | 国产特黄一级一片免费| 色久悠悠婷婷综合在线亚洲| 日本免费A级毛一片| 亚洲高清视频在线观看| 1000部啪啪毛片免费看| 日韩亚洲国产高清免费视频| 最近中文字幕免费mv视频8| 亚洲国产成人无码AV在线| 国产成人免费一区二区三区| 国产精品午夜免费观看网站| 久久久久无码精品亚洲日韩 | 久久成人免费播放网站| 亚洲美女自拍视频| 最新免费jlzzjlzz在线播放| 无码色偷偷亚洲国内自拍| 亚洲亚洲人成综合网络| 一级毛片**不卡免费播| 亚洲婷婷第一狠人综合精品| 四虎永久成人免费| 日本一区午夜艳熟免费| 亚洲国产熟亚洲女视频| 亚洲成A人片在线观看中文| 久久久久久免费一区二区三区 | 亚洲男人天堂2017| 免费观看黄网站在线播放| 真人无码作爱免费视频| 亚洲Av无码专区国产乱码DVD| 国产乱码免费卡1卡二卡3卡| 深夜久久AAAAA级毛片免费看| 人人狠狠综合久久亚洲88|