2014-03-25 91 views
0

現在我們有一套所謂的'服務' - 類,它們具有共同簽名的方法:它們的結果類型有一個屬性T?錯誤,其中T是枚舉。 對於每種方法,我們都有一個單獨的枚舉值,併爲特定方法定義了一組值。異常與錯誤代碼

只要我們使用這些服務的方法的最後一個地方是控制器的操作,這些工作就會很好,這些錯誤會返回給客戶端,並由javascript處理。

但有時候我們想編寫一些調用其他服務方法的方法,這是我似乎有問題的地方。 假設我們有服務的方法A(),它有AError類型的錯誤。這個A()方法在內部調用方法B(),它有一個BError類型的錯誤。

首先,我們必須將可能的BError映射到AError。 也有可能忘記檢查B的錯誤,它的存在將保持不可見。

對於cource,我知道使用異常來指示方法失敗是很常見的。 現在所有的控制器都有一個過濾器來攔截未處理的異常,並返回一個只有單個屬性的錯誤值爲'InternalServerError'的錯誤。 但是,如果我們開始使用異常,我們將失去一個我認爲重要的特性:現在,一個可能的方法錯誤集在其簽名中明確指定,並且在我們使用異常的情況下會丟失。 我知道在xml文檔中有一個標籤來列出異常類型,但它只是文檔,不會被編譯器檢查。

另外我不明白如何在我們的代碼上使用異常: 假設我們有一些方法首先檢查訂單的狀態。現在,如果訂單狀態對當前操作無效,則返回「InvalidOrderStatus」錯誤。 如果我們使用異常,我們可以創建一個異常InvalidOrderStatusException,但我們怎麼能知道我們內部調用的代碼會拋出它?我們也可以創建一個助記規則:方法A應該有錯誤類型AError,並且在它內部應該拋出一些通用異常(比方說,ErrorException<>),通過這個AError進行參數化。我們可以截取所有A的調用中的通用異常ErrorException<AError>並觀察其錯誤代碼。但是這不會被編譯器檢查:方法A可以拋出任何其他異常,或ErrorException<>,但通過其他錯誤代碼進行參數化。

所以我的問題是:什麼是最好的方法a)總是知道什麼樣的異常方法可以拋出,它可以返回哪種錯誤,以及b)不能忘記觀察方法的結果錯誤?

+0

您可以添加一些自定義或預定義的屬性來標記可能的異常,但爲什麼您需要知道它們? – lavrik

+0

如果我將錯誤代碼轉換爲異常,它們將是業務異常,並且無論如何我必須將它們捕獲到控制器中並轉換爲錯誤代碼,這些代碼稍後將由JavaScript處理程序或服務器響應消耗。而在JavaScript中,我可以爲不同的錯誤代碼提供不同的錯誤消息。 –

回答

0

這個怎麼樣的東西交換枚舉AError

class ErrorHolder<T> // T would be AError, BError 
{ 
    T ErrorCode {get;} 
    object[] InnerErrors {get;} 
    // other payload could go here like inner exceptions etc. 
} 

因此你有可能以某種方式檢查所列舉的錯誤代碼,再加上你可以添加你需要的任何有效載荷。

+0

這就是我們現在使用它的方式。每個方法的結果都是一個實現接口的類IHaveError {T?錯誤{get; set;}} –

+0

讓結果具有「ErrorHolder」類型的屬性,它本身包含枚舉。 – Onur

+0

這種方法有什麼好處?我應該在InnerErrors中存儲什麼?如果內部調用方法的錯誤(如同樣的ErrorHolders或異常),我認爲這不是一個好主意,因爲調用者不僅必須知道如何處理其直接調用者的錯誤,而且也要知道他們的調用者的錯誤,因此變得高度依賴關於他們的變化。 –

0

創建一些基本的異常與所需的行爲,提供基礎mechanizm處理,處理和轉換爲結果被髮送到JavaScript。這並不意味着你需要知道你的方法的所有possbile例外列表(由於可能的非業務例外,這種列表總是會是謊言)。因此,派生異常可以僅僅是錯誤代碼的替代,包含特定的消息和其他數據(錯誤代碼:))。正如你所說 - 「一套可能的方法錯誤在其簽名中明確規定」,恕我直言,這不是一個重要的功能。您應該將「面向對象」看作是一般的異常處理(在HandleError(ExecuteService())等控制器方法代碼級別或操作篩選級別)。此外,您的錯誤代碼可能不會出現例外情況,但會顯示一些成功或失敗狀態的「執行結果」,但它不是一種例外行爲,如「未找到實體」,而是服務的預期結果。在這種情況下,我使用下面的代碼

public class ExecutionResult 
{ 
    public ExecutionResult() : this(null) 
    { 
    } 
    public ExecutionResult(ExecutionResult result) 
    { 
     if (result != null) 
     { 
      Success = result.Success; 
      Errors = new List<ErrorInfo>(result.Errors); 
     } 
     else 
     { 
      Errors = new List<ErrorInfo>(); 
     } 
    } 
    private bool? _success; 
    public bool Success 
    { 
     get { return _success ?? Errors.Count == 0; } 
     set { _success = value; } 
    } 
    public IList<ErrorInfo> Errors { get; private set; } 
} 

/*T is for result (any business object)*/ 
public class ExecutionResult<T> : ExecutionResult 
{ 
    public ExecutionResult() : this(null) 
    { 
    } 
    public ExecutionResult(T result) : this(null) 
    { 
     Value = result; 
    } 
    public ExecutionResult(ExecutionResult result) 
     : base(result) 
    { 
     var r = result as ExecutionResult<T>; 
     if (r != null) 
     { 
      Value = r.Value; 
     } 
    } 
    public T Value { get; set; } 
} 
+0

假設我們有一個非常複雜的控制器動作,它在很多其他方法中調用,其中一些可以拋出一個業務異常(我說的一個業務異常,例如,它應該向用戶顯示爲一條消息,如'未找到訂單','訂單無效狀態','禁止'等)。我怎麼能不探索這個動作的所有調用樹,向我們的Web開發人員提供一個可能的錯誤代碼列表,以便她能夠處理它們,爲每個人展示獨特的消息?錯誤代碼雖然有前面提到的幾個缺點,但它提供了列出可能的錯誤的方法 –

+0

我明白了,所有可能的錯誤都是軟件規範的一部分。你也可以使用一些屬性來引發異常並通過一些單元測試生成錯誤列表,這些屬性可以用來查找這個屬性的用法。 – lavrik

0

所以我的問題是:什麼是一個最好的方式)總是知道什麼樣的 例外方法可以拋出什麼樣的錯誤,它可以返回,並b)不能忘記觀察方法的結果錯誤?

爲了解決「一」:

很難在編譯時做到這一點。但是你可以在運行時通過反射來完成。請參閱ErrorHandlerFor<T>類中的靜態enumValues字段。

爲了解決「B」,你可以做這樣的:

簡單:不是一個switch通話結束後,你準備錯誤處理程序(以前在case份)lambda表達式,並把所有的人在ErrorHandlerFor<T>類中,並將其傳遞給該函數。這增加了對功能是否繼續或中止給予反饋的額外好處。

你也可以認爲這是這樣的:

假設你想給一些工作的研究員。這項工作可能會以多種方式失敗。

傳統上,你給這個傢伙工作,一直等到它完成,可能有錯誤。如有必要,您可以處理這些錯誤。

現在你給這個傢伙一些「電話號碼」,以便在發生某些錯誤時調用。如果工作能夠繼續下去或者需要中止,那麼電話的答案甚至可以指導家庭成員。

enum AError 
{ 
    AError1, 
    AError2, 
    AError3, 
    AError4, 
    AError5, 

} 


delegate bool SingleErrorHandlerDelegate<T>(T error, object someOtherPayload); 

interface IHandle<T> 
{ 
    bool Handle(T error, object someOtherPayload); // return true if handled; 
} 

class ErrorHandlerFor<T> : IHandle<T> 
{ 
    private Dictionary<T, SingleErrorHandlerDelegate<T>> handlers; 
    private static T[] enumValues = Enum.GetValues(typeof(T)).Cast<T>().ToArray(); 
    public ErrorHandlerFor(IEnumerable<KeyValuePair<IEnumerable<T>, SingleErrorHandlerDelegate<T>>> handlers) 
     : this(handlers.SelectMany(h => h.Key.Select(key => new KeyValuePair<T, SingleErrorHandlerDelegate<T>>(key, h.Value)))) 
    { 
    } 

    public ErrorHandlerFor(IEnumerable<KeyValuePair<IEnumerable<T>, SingleErrorHandlerDelegate<T>>> handlers, SingleErrorHandlerDelegate<T> fallbackHandler) 
     : this(handlers.SelectMany(h => h.Key.Select(key => new KeyValuePair<T, SingleErrorHandlerDelegate<T>>(key, h.Value))), fallbackHandler) 
    { 
    } 



    public ErrorHandlerFor(IEnumerable<KeyValuePair<T, SingleErrorHandlerDelegate<T>>> handlers) 
    { 
     this.handlers = new Dictionary<T, SingleErrorHandlerDelegate<T>>(); 
     foreach (var handler in handlers) 
     { 
      Debug.Assert(handler.Value != null); 
      this.handlers.Add(handler.Key, handler.Value); 
     } 

     checkHandlers(); 
    } 

    public ErrorHandlerFor(IEnumerable<KeyValuePair<T, SingleErrorHandlerDelegate<T>>> handlers, SingleErrorHandlerDelegate<T> fallbackHandler) 
    { 
     this.handlers = new Dictionary<T, SingleErrorHandlerDelegate<T>>(); 
     foreach (var handler in handlers) 
     { 
      Debug.Assert(handler.Value != null); 
      this.handlers.Add(handler.Key, handler.Value); 
     } 
     foreach (var enumValue in enumValues) 
     { 
      if (this.handlers.ContainsKey(enumValue) == false) 
      { 
       this.handlers.Add(enumValue, fallbackHandler); 
      } 
     } 

     checkHandlers(); 
    } 



    private void checkHandlers() 
    { 
     foreach (var enumValue in enumValues) 
     { 
      Debug.Assert(handlers.ContainsKey(enumValue)); 
     } 
    } 

    public bool Handle(T error, object someOtherPayload) 
    { 
     return handlers[error](error: error, someOtherPayload: someOtherPayload); 
    } 
} 



class Test 
{ 
    public static void test() 
    { 
     var handler = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError3, AError.AError5,}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } 
      ); 

     var result = Services.foo(handler); 


     var incompleteHandlerButWithFallbackThatWillPassTheTest = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError5}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } 
      // AError.AError3 is not handled! => will go in fallback 
      , (AError error, object payload) => { Console.WriteLine(@"could not handle error in fallback!"); return false; } 
      ); 

     var result2 = Services.foo(incompleteHandlerButWithFallbackThatWillPassTheTest); 


     var incompleteHandlerThatWillBeDetectedUponInstantiation = new ErrorHandlerFor<AError>(
      new[]{ 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError1, AError.AError2, AError.AError4,}, 
       (AError error, object payload) => { Console.WriteLine(@"handled error 1, 2 or 4!"); return true;} 
       ), 
       new KeyValuePair<IEnumerable<AError>, SingleErrorHandlerDelegate<AError>>(
       new []{AError.AError3}, 
       (AError error, object payload) => { Console.WriteLine(@"could not handle error 3 or 5!"); return false;} 
       ), 
      } // AError.AError5 is not handled! => will trigger the assertion! 
      ); 

    } 

} 


class Services 
{ 
    public static Result foo(IHandle<AError> errorHandler) 
    { 
     Debug.Assert(errorHandler != null); 

     // raise error... 
     var myError = AError.AError1; 
     var handled = errorHandler.Handle(error: myError, someOtherPayload: "hello"); 
     if (!handled) 
      return new Result(); 

     // maybe proceed 

     var myOtherError = AError.AError3; 
     errorHandler.Handle(error: myOtherError, someOtherPayload: 42); //we'll return anyway in this case... 
     return new Result(); 

    } 



    public class Result 
    { 

    } 
} 
相關問題