重構:仔細檢視 改進程式碼

importnew發表於2013-06-26

  我建議你進行一個練習:當你第二天早晨開始工作的時候,重新審視你的專案原始碼,嘗試發現進行重構的機會。就算你的領導沒有要求,仍然去做。因為在工作中,你希望有一些激動人心的時候。

  重構是一門改變已經正常的工作的藝術。但是重構是需要理由的。可能是設計改進,效能問題,安全形色或者很多其他的原因。改程式序的技術缺點是要冒風險的,儘管能夠使其更穩定,並且可能能夠提高你自己今後的生產力。

  這不是為了公司或者領導好,而是為了自己。為什麼?因為問題都是逐漸累積的,到達一個程度之後,你會失去對程式碼的控制。你將會面對苦於交付結果的境地,最終可能毀掉你的職業生涯。

  好吧,讓我們從一個更加積極的角度來看這個問題。在重構過程中你會學習到很多,並且很快意識到你產出了比之前更好的程式碼。重構越多,你就越聰明,慢慢累積直到你達到創新的級別。但是這意味著什麼,你又如何知道你達到了呢?

  當你發現一個明顯的機會來對你當前的工作進行改進,並且你意識到至今為止還沒有別人做過這樣的事情(至少你找不到)。這不容易,這不容易,但這常發生在你不斷重複做一件事情,並且你能夠發現你如何能夠使得同樣的事情做得又快又好。讓我用一個真實的故事來說明。

  例子是在Java中進行字串連線:一個經典問題,曾經在過去數年困擾了大量專家,但在今天可能已經被忽略了。在JDK1.5之前,儘管可讀性高而且簡單,但使用“+”來進行字串連線可能產生效率極低的程式碼。之後,“+”操作符被換成了StringBuffer,從而真正改進了連線。使用“+”越多,則在記憶體中的String和StringBuffer例項越多,為了管理所有的物件花費的時間也越多。因此,開發者們被推動使用StringBuffer而忽略“+”。看下面的例子:

 String title = "Mr.";

String name = "John";

String familyName = "Smith"; 

String message = "Dear " + title + " " +

                 name + " " + familyName + ",";

  開發者習慣於這樣寫程式碼,但是現在被推動這麼做:

StringBuffer sb = new StringBuffer();

sb.append("Dear ");

sb.append(title);

sb.append(" ");

sb.append(name);

sb.append(" ");

sb.append(familyName);

sb.append(",");

  你可能同意第一個例子可讀性高於第二個。開發者使用“+”進行字串連線時很自然的,所以丟棄這樣的形式不合適。好訊息是,編譯人員做了一些事情來維護這樣的習慣,確保了JDK1.5會優化連線方法。代替執行緒安全的StringBuffer,他們建立了一個叫做StringBuilder(非執行緒安全,但更快)的類,並且他們確保能夠像第一個例子中那樣,使用一個例項就能處理所有的連線。這是一個很重要的進步,因為他們兼顧了簡潔而不單純是技術性。第一個例子在編譯時會自動轉換成如下情況:

StringBuilder sb = new StringBuilder();

sb.append("Dear ").append(title).append(" ")

  .append(name).append(" ").append(familyName)

  .append(",");

  但是,在一些複雜邏輯程式碼中的字串連線還是需要使用StringBuilder,因為編譯器還沒有那麼智慧,例如:

List<Student> students = studentBean.findStudents();

String intro = "The following students were approved:\n";

String listedNames = "";

String separator = "";

for(Student student: students) {

  if(student.approved()) {

    if(!listedNames.isEmpty()) {

      separator = ", ";

    }

    listedNames += separator + student.getName();

  }

}

String msg = intro + listedNames;

messengerBean.sendMessage(msg);

  如下寫法會更有效率:

List<Student> students = studentBean.findStudents();

String intro = "The following students were approved:\n";

StringBuilder listedNames = new StringBuilder();

String separator = "";

for(Student student: students) {

  if(student.approved()) {

    if(!listedNames.length() > 0) {

      separator = ", ";

    }

    listedNames.append(separator)

               .append(student.getName());

  }

}

String msg = intro + listedNames.toString();

messengerBean.sendMessage(msg);

  呃!你注意到有什麼奇怪的東西了麼?可能一眼看起來不是很明顯,但是看看他們是如何在定義分隔符之前檢查listedNames是否為空的。String類在JDK1.6中有一個可讀性很好的方法isEmpty(),但是StringBuilder仍然使用相對比較老的方式。為什麼他們不將StringBuilder 和 StringBuffer改為同樣的方式呢?

  在核心庫開發的郵件列表中討論了這個問題,沒有發現什麼明顯原因導致他們之前沒有這麼做。可能只是忘記了。要感謝大規模的重構,試圖改進效率低下的字串連線方法,才能發現這樣的不一致。我相信在明年推出的Java8中會有時間修復這個問題。只要在介面CharSequence加入isEmpty()方法,這樣就能使得所有實現都變得同樣優雅。

  這可能是一件簡單的事情,但是Java是一個嚴格審查下的複雜的語言,所以每個細節都會帶來很大的影響。所以,做一些重構,發現一些改進程式碼的機會,同樣也能夠改進你使用的程式語言。讓我們一起推動Java進步吧!

  英文原文:Java Code Geeks

相關文章