2011-12-22 67 views
1

我正在編寫一個Web應用程序,其中使用異常來處理錯誤情況。通常情況下,我發現自己寫的助手這樣的:作爲函數參數設計模式的Scala異常

def someHelper(...) : Boolean {...} 

,然後用它是這樣的:

if (!someHelper(...)){ 
    throw new SomeException() 
} 

這些例外代表相同的參數無效的事情,處理時,他們發出一個有用的錯誤消息的用戶,例如

try { 
    ... 
} catch { 
    case e: SomeException => "Bad user!" 
} 

這是一個合理的方法嗎?我怎麼能將這個異常傳入輔助函數並將它拋出?我在構建這種功能的類型時遇到了困難。

回答

6

我大多數時間使用Either,沒有例外。我通常會使用異常,就像你已經做過的或者類似的方式一樣,當控制流程必須走回頭路,回到某個遙遠的點時,否則就沒有什麼明智的做法。然而,當異常還算可以在本地處理,我會代替

def myMethod(...): Either[String,ValidatedInputForm] = { 
    ... 
    if (!someHelper(...)) Left("Agree button not checked") 
    else Right(whateverForm) 
} 

,然後當我把這種方法,我可以

myMethod(blah).fold({ err => 
    doSomething(err) 
    saneReturnValue 
}, { form => 
    foo(form) 
    form.usefulField 
}) 
Left(err)

或比賽VS Right(form),或各種其他的東西。

如果我不想處理錯誤在那裏,而是要處理的返回值,我

myMethod(blah).right.map{ form => 
    foo(form) 
    bar(form) 
} 

,我會得到一個Either與錯誤信息不變爲Left,如果是錯誤消息,或者{ foo(form); bar(form) }的結果爲Right(如果沒有問題)。您還可以使用flatMap鏈接錯誤處理,例如如果你想執行就這麼遠,正確的價值觀額外的檢查,並拒絕其中的一些,你可以

myMethod(blah).right.flatMap{ form => 
    if (!checkSomething(form)) Left("Something didn't check out.") 
    else Right(form) 
} 

它的這種處理,這使得使用Either更方便(通常是更好的表現,如果異常是常見的)比例外,這就是我使用它們的原因。

(事實上,在很多情況下,我不關心爲什麼出事了,只出了問題,在這種情況下,我只使用一個Option

1

沒有什麼特別之處傳遞一個異常實例的一些方法:

def someMethod(e: SomeException) { 
    throw e 
} 
someMethod(new SomeException) 

但我不得不說,我得到一個非常不同的感覺,你的整個想法只是氣味。如果您想驗證用戶輸入,只需編寫驗證器,例如UserValidator這將有一些像isValid方法來測試用戶輸入並返回一個布爾值,你也可以在那裏實現一些消息。例外是真正用於不同的目的。

+0

咦?例外是準確的這種情況!以便與用戶通信的應用程序部分可與系統的其他部分分離。如果'someHelper'是用於轉換源自用戶的數據的本地有用函數,那麼肯定不應該知道如何與用戶通信以顯示錯誤消息。爲了驗證它,通信模塊可能不應該知道對'someHelper'數據的要求。 – Ben 2011-12-22 04:02:45

+0

被警告。創建異常會填充堆棧跟蹤,這可能是一項昂貴的操作。如果你走這條路線,按名稱傳遞異常,所以它只在你使用它時創建:'def someMethod(e:=> Exception)'。 – leedm777 2011-12-22 04:34:58

+0

@Ben我的意思是,這些異常不是用來保存應用程序的狀態信息,而是通過異常實例傳遞(而不是拋出)並用邏輯圍繞它們看起來非常像這樣 – 2011-12-22 05:49:58

1

處理你想要做的事情的兩種最常見的方法是讓助手自己創建並拋出一個異常,或者你正在做的是:讓調用代碼檢查結果,然後拋出一個有意義的例外,如果需要。

我從來沒有見過一個庫,你傳遞你希望助手拋出的異常。正如我所說的on another answer,簡單地實例化一個異常有一個令人驚訝的實際成本,如果你在整個代碼中遵循這個模式,你可能會看到一個整體性能問題。這可以通過使用by-name parameters來緩解,但是如果您忘記將=>置於幾個關鍵功能中,則會出現難以追蹤的性能問題。

在一天結束時,如果您希望幫助者拋出異常,那麼幫助者本身已經知道它想拋出什麼樣的異常是有道理的。如果我不得不在A和B之間選擇:

def helperA(...) { if (stuff) throw new InvalidStuff() } 

def helperB(..., onError: => Exception) { if (stuff) throw onError } 

我會每次選A。

現在,如果我不得不在A和你現在擁有的東西之間進行選擇,那就是折騰了。這真的取決於上下文,你試圖與幫手完成什麼,他們可能會如何使用,等等。

最後一點,在這種情況下命名是非常重要的。如果你使用返回碼輔助線路,你的助手應該有問題名稱,例如isValid。如果你有異常投擲助手,他們應該有動作名稱,比如validate。甚至可能會強調它,比如validate_!

0

另一種可選擇的方法你可以檢查出scalaz驗證器,它爲這種情況提供了很大的靈活性(例如,我應該在錯誤時崩潰,在最後累積錯誤並報告或完全忽略它們?)。 A few examples可能會幫助您決定這是否適合您。

如果您發現很難找到圖書館的方式,this answer給出了一些介紹性材料的一些指示;或退房。