[Scala] KeepRetry……爛掉了就一直重開

話說上個月換了工作,這次的工作主要是寫 Node.JS 的。說實話我之前對 Node.JS 是只有聽說過這東西,但因為我本身就偏好靜態型別的程式語言以及向來對 JavaScript 沒啥好感,加上相當喜歡而且用 Scala 也用的很順手了,所以對 Node.JS 並沒有特別研究,甚至在接這個工作之前連 Node.JS 的 HelloWorld 都沒寫過,只隱約知道一些它的特性和設計典範而已。

而只寫了這一個半月的 Node.JS,當然還是領悟不到什麼高深的技巧的,甚至還遇到了效能上的瓶頸而一知查不出原因,只好先和主管商良用不知道為什麼跑起來楞是快了十倍的 Scala 版本先擋著。

不過另一方面,這一個月也發現 Node.JS 下有一些常見的做法是我以前從未想過的,其中一個就是關於網頁程式和 HTTP Server 之間的關係。在以前由於都是寫些 PHP / JSP 之類的東西,所以在我的印象裡,HTTP Server 應該是要很 robust 的,而應用端的程式碼就算爛到無比地爛,也不應該讓 HTTP Server 死掉。

但另一方面,Node.JS 的 HTTP Server 就某方面來說是和應用層綁在一起,所以自己程式寫得太爛讓 HTTP Server 死掉也不是啥罕見的事,甚至後來讀 Framework 文件的時候還發現了原來有像 pm2 這種專門幫忙讓程式掛掉就重啟的管理程式,這才發現原來也有這種做法--管他的,反重掛掉了重試就是。

正好我上面講的那隻擋著用的 Scala 程式必須常駐,就算有任何外界的 Exception 發生,例如網路斷線什麼的,也應該不斷的重試,只要錯誤原因一排除就要繼續上線。

於是想了一下之後,決定寫個簡單的流程控制,讓某段程式碼可以就算丟了 Exception 出來,也會不斷的重試。

還好,Scala 是個相當有彈性的程式語言,要實作出這樣的流程制一點也不難,幾行程式碼就寫完了:

object KeepRetry {

  def apply(waitBeforeRetryInSec: Int)(block: => Any) {
    try {
      block
    } catch {
      case e: Exception =>
        e.printStackTrace()
        println("Error encountered, wait 1 second to retry...")
        Thread.sleep(waitBeforeRetryInSec * 100)
        apply(block)
    }
  }

}

有了這段程式碼之後,就可以這樣用:

KeepRetry(2) {
  def getStringFromNetwork =  {
    // 從網路取得字串
    throw new Exception("連線逾時")
  }
  println(getStringFromNetwork)
}

這樣 KeepRetry 裡的區段,如果執行成功就會只執行一次,但如果失敗的話,就會每隔兩秒重新試一次,直到成功為止。

這段程式碼可以這看起來很像程式語言內建的流程控制,是因為 Scala 提供了這些東西:

  1. 當寫 A(x) 的時候,實際上是呼叫 A.apply(x)
  2. Curried Function,函式可以有多個參數列表,而最後一個參數列表若只有一個參數,呼叫的時候可以用大括弧取代小括弧。
  3. Call by name 參數(這個例子中的 block),他在函式被呼叫的當下不會被計算(執行),而是進到函式,真的遇到這個參數的名子的時候才會執行,而且每碰到一次就會執行一次。

剛開始學 Scala 的時候,看到這些東西都不太知道為什麼要提供這些功能,後來才知道原來 Scala 實作這些東西,都是大大的有道理的,而若不是這些看似很難得才會用到的功能,我也不會對 Scala 愈寫愈愛。XD

回響