閱讀JDK源碼後,我有了優化它的衝動!

  • 2019 年 11 月 13 日
  • 筆記

讀源碼時的思考

最近在看concurrent包下執行緒池的源碼,當我看到ThreadPoolExecutor類的時候,發現了JDK源碼的一個問題。以下是ThreadPoolExecutor類的addWorker方法的程式碼片段:

boolean workerStarted = false;  boolean workerAdded = false;  Worker w = null;  try {      w = new Worker(firstTask);      final Thread t = w.thread;      if (t != null) {          final ReentrantLock mainLock = this.mainLock;          mainLock.lock();          try {              int rs = runStateOf(ctl.get());                if (rs < SHUTDOWN ||                  (rs == SHUTDOWN && firstTask == null)) {                  if (t.isAlive()) // precheck that t is startable                      throw new IllegalThreadStateException();                  workers.add(w);                  int s = workers.size();                  if (s > largestPoolSize)                      largestPoolSize = s;                  workerAdded = true;              }          } finally {              mainLock.unlock();          }          if (workerAdded) {              t.start();              workerStarted = true;          }      }  } finally {      if (! workerStarted)          addWorkerFailed(w);  }  return workerStarted;

這段程式碼的功能是完全沒有問題的,但是如果使用衛語句,程式碼的可讀性就會更高了。那麼什麼是衛語句呢?

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

什麼是衛語句?

條件表達式通常有兩種表現形式,第一種形式是:所有分支都屬於正常行為;第二種形式則是:條件表達式提供的答案中只有一種是正常行為,其他都是不常見的情況。這兩類條件表達式有不同的用途,這一點應該通過程式碼表現出來。

如果兩條分支都是正常行為,就應該使用形如if…else…的條件表達式;如果某個條件極其罕見,就應該單獨檢查該條件,並在該條件為真時立刻從函數中返回。這樣的單獨檢查常常被稱為「衛語句」(guard clauses)。只看概念乾巴巴的,不好理解,我們來舉兩個例子。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

條件檢查替換

這是一個計算員工薪資的方法,其中以特殊規則處理駐外員工和退休員工的薪資。這些情況不常有,但的確會偶爾出現。

public double getSalary() {      double result;      if (this.isSeparated) {//駐外員工          result = this.separatedSalary();      } else {          if (this.isRetired) {//退休員工              result = this.retiredSalary();          } else {//正常員工              result = this.normalSalary();          }      }      return result;  }

這段程式碼中,非正常情況的檢查掩蓋了正常情況的檢查,所以應該用衛語句來取代這些條件檢查,以提高程式清晰度。對於每個檢查,放進一個衛語句。衛語句要不就從函數中返回,要不就拋出一個異常。

  public double getSalary() {      if (this.isSeparated) {//衛語句          return this.separatedSalary();      }      if (this.isRetired) {//衛語句          return this.retiredSalary();      }      return this.normalSalary();  }

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

反轉條件替換

這是一個已知長寬高求長方體體積的方法,但有個特殊的需求:高大於0時,列印萬貓學社。(驚不驚喜?意不意外?突不突兀?變不變態?是的,有時候我們接到的需求就是這樣的。)程式碼是這樣的:

public double getVolume(double length, double width, double height) {      double result = 0.0;      if (height > 0.0) {          System.out.println("萬貓學社");          if (length > 0.0 && width > 0.0) {              result = length * width * height;          }      }      return result;  }

還是用衛語句替換條件檢查,但是我們需要將相應的條件反轉過來,也就是做邏輯非運算。

public double getVolume(double length, double width, double height) {      if (height <= 0.0) {//衛語句,!(height > 0)          return 0.0;      }      System.out.println("萬貓學社");      if (length <= 0.0 || width <= 0.0) {//衛語句,!(length > 0 && width > 0)          return 0.0;      }      return length * width * height;  }

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

為什麼要使用衛語句?

衛語句的精髓是:給某一條分支以特別的重視。如果使用if…else…結構,你對if分支和else分支的重視是同等的。這樣的程式碼結構傳遞給閱讀者的消息就是:各個分支有同樣的重要性。

衛語句就不同了,它告訴閱讀者:「這種情況很罕見,如果它真的發生了,請做一些必要的整理工作,然後退出。」如果對方法剩餘部分不再有興趣,當然應該立刻退出。引導程式碼的閱讀者去看一個沒有用的else區段,只會妨礙他們的理解。用了衛語句以後,程式碼更容易被理解,被維護。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

優化JDK程式碼

看了上面的講解,addWorker方法的程式碼片段就可以優化為:

boolean workerStarted = false;  boolean workerAdded = false;  Worker w = null;  try {      w = new Worker(firstTask);      final Thread t = w.thread;      if (t == null) {//衛語句          return workerStarted;      }        final ReentrantLock mainLock = this.mainLock;      mainLock.lock();      try {          int rs = runStateOf(ctl.get());            //衛語句          if (rs > SHUTDOWN || (rs == SHUTDOWN && firstTask != null)) {              return workerStarted;          }          if (t.isAlive())// precheck that t is startable              throw new IllegalThreadStateException();          workers.add(w);          int s = workers.size();          if (s > largestPoolSize)              largestPoolSize = s;          workerAdded = true;      } finally {          mainLock.unlock();      }      if (workerAdded) {          t.start();          workerStarted = true;      }    } finally {      if (!workerStarted)          addWorkerFailed(w);  }  return workerStarted;

修改後的程式碼,理解起來是不是更容易了?假如再加入新的功能,可以更容易修改程式碼。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

結語

這段JDK源碼在功能上沒有任何問題,架構設計也堪稱完美,不過我認為在可讀性上還是可以優化的。類似這樣的嵌套條件表達式的程式碼在JDK源碼中不止這一處,可能是作者當時沒有考慮到使用衛語句,或者沒有像我這樣的吹毛求疵。希望大家在自己編碼過程中,也可以盡量使用衛語句取代嵌套條件表達式,以提高程式碼的清晰度和可維護性。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

參考文獻:《重構:改善既有程式碼的設計》