簡體   English   中英

使用回調而不是拋出異常?

[英]Using callbacks instead of throwing exceptions?

理念

我正在考慮使用回調而不是在C#/ .NET中拋出異常。

優點和缺點

優點是

  • 沒有隱藏的goto像未經檢查的異常的控制流
  • 更清晰的代碼,特別是如果涉及多個例外
  • 拋出的異常記錄在方法簽名中,並且調用者被迫考慮處理異常,但可以輕松地傳遞應用程序范圍的異常處理程序,“UnhandledExceptionHandler”或null。 因此它們有點像“軟”檢查異常但更易於維護,因為異常可以通過重載方法拋出,或者異常可以通過不再在異常處理程序上調用“句柄”來消除。
  • 適用於異步調用
  • 異常處理程序可以處理在不同位置拋出的幾個異常
  • 明確應該處理哪些例外。 普通方式拋出異常仍可用於您不希望像“NotImplementedException”那樣處理的異常。

缺點是

  • 不是C#和.NET的慣用語
  • throw方法必須通過立即返回一個返回值來中斷控制流程。 如果返回類型是值類型,則這很困難。
  • (見下面的問題)

我可能錯過了一些關鍵的缺點,因為我想知道為什么不使用它。 我錯過了什么缺點?

例:

代替

void ThrowingMethod() {
    throw new Exception();
}

void CatchingMethod() {
    try {
         ThrowingMethod();
    } catch(Exception e) {
         //handle exception
    }
}

我會做

void ThrowingMethod(ExceptionHandler exceptionHandler) {
    exceptionHandler.handle(new Exception());
}

void CatchingMethod() {
     ThrowingMethod(exception => */ handle exception */ );
}

delegate void ExceptionHandler(Exception exception);

在某處定義並且“handle(...)”是一個檢查null的擴展方法,檢索堆棧跟蹤,如果在拋出異常時根本沒有異常處理程序,則可能拋出“UnhandledException”。


在之前未拋出異常的方法中拋出異常的示例

void UsedToNotThrowButNowThrowing() {
   UsedToNotThrowButNowThrowing(null);
}

//overloads existing method that did not throw to now throw
void UsedToNotThrowButNowThrowing(ExceptionHandler exceptionHandler) {
    //extension method "handle" throws an UnhandledException if the handler is null
    exceptionHandler.handle(exceptionHandler);
}

返回值的方法示例

TResult ThrowingMethod(ExceptionHandler<TResult> exceptionHandler) {
        //code before exception
        return exceptionHandler.handle(new Exception()); //return to interrupt execution
        //code after exception
    }

TResult CatchingMethod() {
     return ThrowingMethod(exception => */ handle exception and return value */ );
}

delegate TResult ExceptionHandler<TResult>(Exception exception);

首先,您需要將這些處理程序的開銷傳遞給應用程序中的幾乎所有方法。 這是一個非常重量級的依賴關系,以及在構建應用程序之前做出的決定。

其次,存在處理系統拋出異常和來自第三方程序集的其他異常的問題。

第三,異常是指在拋出程序時暫停程序的執行,因為它確實是“異常”,而不僅僅是可以處理的錯誤,允許繼續執行。

可擴展性。

正如@mungflesh正確地指出你必須通過這些處理程序 我的第一個問題不是開銷而是可伸縮性 :它會影響方法簽名。 它可能會導致與我們在Java中使用已檢查異常相同的可伸縮性問題(我不知道C#,我只做C ++和一些Java)。

想象一下一個深度為50次調用的調用堆棧(沒有什么極端的東西,IMO)。 有一天,一個變化出現了,並且沒有拋出的鏈中的一個被調用者變成了一個現在可以拋出異常的方法。 如果它是未經檢查的異常,您只需更改頂級代碼即可處理新錯誤。 如果它是已檢查的異常或您應用了您的想法,則必須通過調用鏈更改所有涉及的方法簽名。 不要忘記簽名更改傳播:您更改這些方法的簽名,您必須在調用這些方法的其他地方更改代碼,可能會生成更多簽名更改。 簡而言之,規模很差。 :(


這是一些偽代碼,顯示了我的意思。 使用未經檢查的異常,您可以通過以下方式處理深度為50的callstack中的更改:

f1() {
  try {    // <-- This try-catch block is the only change you have to make
    f2();  
  }
  catch(...) {
    // do something with the error
  }
}

f2() { // None of the f2(), f3(), ..., f49() has to be changed
  f3();
}

...

f49() {
  f50();
}

f50() {
  throw SomeNewException; // it was not here before
}

使用您的方法處理相同的更改:

f1() {
  ExceptionHandler h;
  f2(h);
}

f2(ExceptionHandler h) { // Signature change
  f3(h); // Calling site change
}

...

f49(ExceptionHandler h) { // Signature change
  f50(h); // Calling site change
}

f50(ExceptionHandler h) {
  h.SomeNewException(); // it was not here before
}

所涉及的所有方法(f2 ... f49)現在都有一個新簽名,並且呼叫站點也必須更新(例如f2()變為f2(h)等)。 請注意, f2...f49甚至不應該知道這個更改,但是,他們的簽名和調用站點都必須更改。


換句話說:所有中間調用現在必須處理錯誤處理程序,即使它是一個他們甚至不知道的細節。 使用未經檢查的異常,可以隱藏這些詳細信息。


未經檢查的異常確實“隱藏得像控制流”,但至少它們可以很好地擴展。 毫無疑問,可以迅速導致難以維護的混亂......

雖然+1,一個有趣的想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM