2010-07-28 61 views
47

比方說,我們有一個在MVC應用程序:拋出異常時,沒有指定的情況下,可以處理

public JsonResult ChangePassword 
    (string username, string currentPassword, string newPassword) 
{ 
    switch (this.membershipService.ValidateLogin(username, currentPassword)) 
    { 
     case UserValidationResult.BasUsername: 
     case UserValidationResult.BadPassword: 
      // abort: return JsonResult with localized error message   
      // for invalid username/pass combo. 
     case UserValidationResult.TrialExpired 
      // abort: return JsonResult with localized error message 
      // that user cannot login because their trial period has expired 
     case UserValidationResult.Success: 
      break; 
    } 

    // NOW change password now that user is validated 
} 

membershipService.ValidateLogin()返回UserValidationResult枚舉改變了用戶的密碼系統中的一個函數,被定義爲:

enum UserValidationResult 
{ 
    BadUsername, 
    BadPassword, 
    TrialExpired, 
    Success 
} 

作爲一個防禦性的程序員,我會改變上述ChangePassword()方法拋出一個異常,如果有一個無法識別的UserValidationResult值從ValidateLogin()回:

public JsonResult ChangePassword 
    (string username, string currentPassword, string newPassword) 
{ 
    switch (this.membershipService.ValidateLogin(username, currentPassword)) 
    { 
     case UserValidationResult.BasUsername: 
     case UserValidationResult.BadPassword: 
      // abort: return JsonResult with localized error message   
      // for invalid username/pass combo. 
     case UserValidationResult.TrialExpired 
      // abort: return JsonResult with localized error message 
      // that user cannot login because their trial period has expired 
     case UserValidationResult.Success: 
      break; 
     default: 
      throw new NotImplementedException 
       ("Unrecognized UserValidationResult value."); 
      // or NotSupportedException() 
      break; 
    } 

    // Change password now that user is validated 
} 

我一直認爲模式就像最後一個片段的最佳實踐。例如,如果一個開發人員現在要求用戶嘗試登錄時要求,如果出於這種或那種商業原因,他們是否會首先聯繫業務?所以UserValidationResult的定義更新爲:

enum UserValidationResult 
{ 
    BadUsername, 
    BadPassword, 
    TrialExpired, 
    ContactUs, 
    Success 
} 

開發商改變了ValidateLogin()方法的身體恢復新枚舉值(UserValidationResult.ContactUs)適用的時候,卻忘記更新ChangePassword()。在交換機中沒有例外的情況下,用戶仍然可以在他們的登錄嘗試甚至不需要首先驗證時更改密碼!

難道只是我,或者是這default: throw new Exception()一個好主意嗎?我幾年前就看到了它,並且始終(在通過它之後)認爲這是最佳做法。

回答

61

我總是拋出一個異常,在這種情況下。考慮使用InvalidEnumArgumentException,在這種情況下可以提供更豐富的信息。

+2

Oooh很好,以前沒見過這個Exception衍生物。謝謝:) – CantSleepAgain 2010-07-28 03:04:51

+0

有趣的是,我希望它是在Visual Studio中創建一個新類時默認包含的名稱空間之一。 – Davy8 2010-07-28 03:10:35

+2

只有當承受枚舉是一個參數時,它纔會在語義上正確。情況並非總是如此:http://stackoverflow.com/questions/13645149/what-is-the-correct-exception-to-throw-for-unhandled-enum-values – 2013-08-21 09:01:39

3

跟你有什麼是好的,雖然break語句後,它永遠不會被擊中,因爲當一個異常被拋出未處理該線程的執行將停止。

+0

但它會*編譯*不破? – CantSleepAgain 2010-07-28 02:56:02

+0

我很肯定它會編譯,就像它會編譯,如果你有一個沒有「break」的return語句。 – Davy8 2010-07-28 02:58:09

+0

基於ChaosPandion的類似答案,你必須正確。謝謝。這就是我在記事本中編寫代碼所得到的結果:) – CantSleepAgain 2010-07-28 03:10:48

0

我從來不會在switch語句中包含的throw語句之後添加中斷。不用擔心的是煩人的「檢測不到的代碼」警告。所以是的,這是一個好主意。

+0

聽起來像耶穌拉莫斯是正確的(當然你)。謝謝 - 我做了幾次迭代,忽略了投擲之後不必要的突破。榮譽:) – CantSleepAgain 2010-07-28 03:08:59

0

我覺得這樣的做法是非常有用的(例如,見Erroneously empty code paths)。如果你可以在釋放的代碼中編譯默認值,比如assert,那更好。這樣在開發和測試過程中就有了額外的防禦性,但在發佈時不會產生額外的代碼開銷。

+0

我不認爲這是一個好主意,忽略例外。你提到的開銷是可以忽略的,讓你的代碼忽略未處理的值可能會產生更糟的副作用。例如,它可能是您以後不應該刪除文件的唯一情況。如果您忽略它,則會丟失數據。 – 2013-08-21 09:04:42

0

你說你非常防守,我可能會說這太過分了。其他開發者是不是要測試他們的代碼?當然,當他們做最簡單的測試時,他們會看到用戶仍然可以登錄 - 所以,他們會意識到他們需要修復的東西。你所做的並不可怕或不對,但如果你花了很多時間去做,那可能太過分了。

+3

我不同意。代碼*的調用者應該*測試所有的代碼路徑,但* * * * * * *將是兩個不同的東西。即使進行了測試,如果您處於不進行單元測試的環境中,可能需要更多時間才能確切查明導致問題的原因。 – Davy8 2010-07-28 03:02:11

+1

實際上,這不一定會被單元測試捕獲,因爲如果測試是在新的枚舉值存在之前編寫的,那麼它很可能不會被測試,並且調用方法的測試可能會將此類作爲模擬對象,所以它只能在集成測試中被捕獲,在這種情況下,拋出的異常會提醒可能錯過的錯誤(通常情況下,事情並不總是經過徹底測試),並給出錯誤的確切位置。 – Davy8 2010-07-28 03:07:32

+0

你也不能確定每個從事項目工作的人都知道(或者至少在當時記得)每個地方都實施了某些東西。拋出一個異常不會過度。 – rossisdead 2010-07-28 03:29:16

2

我總是喜歡做的正是你所擁有的,雖然我平時拋出ArgumentException,如果它是在傳遞一個說法,但我挺喜歡NotImplementedException更好,因爲錯誤可能是一個新的case語句應該是相當添加比調用者應該將參數更改爲支持的參數。

3

枚舉的生活時,「遠」,從它的使用,但在枚舉是非常接近並專用於特定功能的情況下,好像有點多,我曾經使用過這種做法的具體實例。

很可能,我懷疑調試斷言失敗可能更合適。

http://msdn.microsoft.com/en-us/library/6z60kt1f.aspx

注意第二個代碼示例...

+1

「調試斷言失敗」與拋出的異常有什麼不同?只是負責報告失敗的代碼不會使其成爲發佈代碼?當你可以拋出異常時爲什麼斷言失敗 - 是不是異常點? – CantSleepAgain 2010-07-28 03:04:01

+0

如果交換機忽略的選項是期望的行爲,則調試斷言失敗不會導致代碼在內置釋放模式下失敗。 – 2010-07-28 03:07:09

+1

所以你說你寧願看到一個System.Diagonstics.Debug.Assert()比拋出的異常?只是想跟着你。在#debug中聲明失敗的問題是,在發佈中,ChangePassword()仍會更改密碼。有了一個例外,他們會得到一個HTTP 500.哪一個更好是一個優秀和困難的答案。 – CantSleepAgain 2010-07-28 03:15:35

0

對於一個Web應用程序,我寧願使用默認方式生成結果並顯示一條錯誤消息,要求用戶聯繫管理員並記錄錯誤,而不是引發可能導致用戶意義不大的異常。因爲我可以預測,在這種情況下,返回值可能不是我期望的,我不會認爲這是真正的特殊行爲。

而且,導致額外的枚舉值你的使用情況不應該引入一個錯誤,您的特定方法。我的期望是該用例僅適用於登錄 - 這可能會在ChangePassword方法可以合法調用之前發生,大概是因爲您想在更改密碼之前確保該用戶已登錄 - 您應該實際上從來沒有將ContactUs的值作爲密碼驗證的返回值。該用例將指定如果聯繫結果返回,則認證失敗,直到結果條件被清除。如果不是這樣,我希望你會對系統的其他部分應該如何在這種情況下作出反應有所要求,並且你會編寫測試來強制你改變這個方法中的代碼以符合這些測試,處理新的返回值。

+0

「我的期望是該用例僅適用於登錄 - 在之前會發生之前可能會合法調用ChangePassword方法,可能是因爲您想在更改密碼之前確保該用戶已登錄 - 你永遠不應該看到ContactUs值作爲密碼驗證的回報。「我試圖想到一個很好的例子,但是你是對的,這個特定的情況不應該發生,如果一切正常架構。我應該更多地考慮一個更好的例子,但我認爲這一點仍然存在,不是嗎? – CantSleepAgain 2010-08-04 03:21:09

0

驗證運行的制約通常是一件好事,所以是的,最好的做法,以「快速失敗」的原則幫助和檢測到錯誤條件時,而不是繼續默默地你停止(或記錄)。有些情況並非如此,但給予switch語句,我懷疑我們沒有經常看到它們。

要精心,switch語句總是可以通過if/else語句塊替換。從這個角度看它,我們看到了扔VS在默認開關的情況下不要亂扔大約相當於這個例子:

if(i == 0) { 


    } else { // i > 0 


    } 

VS

if(i == 0) { 


    } else if (i > 0) { 


    } else { 
     // i < 0 is now an enforced error 
     throw new Illegal(...) 
    } 

第二個例子通常被認爲是因爲更好如果違反了錯誤約束而不是在潛在的錯誤假設下繼續執行,則會失敗。

相反,如果我們想:

if(i == 0) { 


    } else { // i != 0 
     // we can handle both positve or negative, just not zero 
    } 

然後,我懷疑在實踐中最後一種情況下可能會顯示爲一個if/else語句。因爲switch語句經常類似於第二個if/else塊,所以通常是最佳實踐。

一些更多的考慮是值得的: - 考慮多態的方法或枚舉的方法來完全取代switch語句,如:Methods inside enum in C# - 如果拋是最好的,在其他的答案注意到喜歡使用一個特定的異常鍵入避免鍋爐板代碼,例如:InvalidEnumArgumentException

相關問題