簡體   English   中英

異常與返回代碼:我們是否會丟失某些東西(同時獲得其他東西)?

[英]Exceptions vs return codes : do we lose something (while gaining something else)?

我的問題很模糊 :o) - 但這里有一個例子:

當我編寫 C 代碼時,我能夠在出現故障時記錄計數器的值:

   <...>
   for ( int i = 0 ; i < n ; i++ )
      if ( SUCCESS != myCall())
         Log( "Failure, i = %d", i );
   <...>

現在,使用異常,我得到這個:

  try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }

當然,可以在 try/catch 語句之外聲明“i”(這就是我正在做的)。 但我不喜歡它 - 我喜歡在使用它們的地方聲明變量,而不是之前。

但也許我在這里錯過了什么。 你有什么優雅的解決方案嗎?

預先感謝 ! 西爾萬。

添加:myCall() 是一個晦澀的 API 調用 - 我不知道它會拋出什么。 另外,我當然可以在每次調用周圍添加一個 Try/Catch 塊,但是我最好使用返回代碼? 然后我會在重要的代碼行周圍添加很多噪音嗎?

怎么樣:

for(int i = 0; i < n; i++)
{
  try
  {
    myCall();
  }
  catch(Exception e)
  {
    Log(String.Format("Problem with {0}", i));
  }
}

我認為你錯了,這並不像許多其他人那樣令人驚訝。

異常不能用於程序流。 再讀一遍,這很重要。

例外是“哇,這不應該發生”的錯誤,您希望在運行時永遠不會看到這些錯誤。 顯然,您會在第一個用戶使用它的那一刻看到它們,這就是為什么您必須考慮它們可能發生的情況,但您仍然不應該嘗試將代碼放入捕獲、處理和繼續,就好像什么也沒發生一樣。

對於這樣的錯誤,您需要錯誤代碼。 如果您將異常用作“超級錯誤代碼”,那么您最終會編寫您提到的代碼 - 將每個方法調用包裝在 try/catch 塊中! 你還不如返回一個枚舉,而不是,它的速度快了很多,並顯著更易於閱讀比垃圾一切錯誤返回碼7行代碼,而不是1(其也更可能是正確的代碼太-看到erikkallen的答復)

現在,在現實世界中,方法通常會在您不希望它們拋出的異常(例如 EndOfFile)中拋出異常,在這種情況下,您必須使用“try/catch wrapper”反模式,但是如果你要設計你的方法,不要在日常錯誤處理中使用異常——只在特殊情況下使用它們。 (是的,我知道很難讓這種設計正確,但很多設計工作也是如此)

我不喜歡“現在,有例外……”的表達。

例外是您在編程中使用它的一種工具- 如果您認為它是最好的選擇,請使用它,否則,請不要使用。

我遵循個人規則,即在內部代碼中不拋出任何可以避免拋出的異常。 對於公開可用的 DLL 的 API,前提條件檢查應保持啟用狀態,如果失敗則觸發異常,是的; 但對於內部邏輯,我很少(如果有的話)在我的設計中包含例外。 相反,當我使用某個函數來記錄它會在發生某些糟糕情況時拋出的情況時,我傾向於立即捕獲異常 - 畢竟這是一個預期的異常。

如果您認為您的非特殊選擇更好 - 堅持下去!

是的。 2件事。

將 try-catch 塊放在有意義的地方。 如果您對 myCall 的異常(以及 i 的值)感興趣,請使用

for ( int i = 0 ; i < n ; i++ )
    try { myCall(); } catch ( Exception exception ) {
        Log( "Failure, i = %d", i );
    }

針對不同的錯誤拋出不同類的對象。 如果您對財務處理中發生的邏輯錯誤感興趣,請拋出財務::邏輯錯誤,而不是 std::exception("error msg") 或其他內容。 這樣你就可以捕捉到你需要的東西。

從我的角度來看,這里有兩件事。

期望異常本身包含關於 i 的值的信息,或者不太具體地關於它被評估的上下文和出了什么問題,這並不令人發指。 舉個簡單的例子,我永遠不會直接拋出InvalidArgumentException 相反,我會確保我將准確的描述傳遞給構造函數,例如


   public void doStuff(int arg) {
      if (arg < 0) {
         throw new InvalidArgumentException("Index must be greater than or equal to zero, but was " + arg);
      }
      ...

這可能不會明確記錄 i 的值,但在大多數情況下,您將能夠了解導致錯誤的輸入的問題。 這也是支持異常鏈的論據——如果你在每個概念級別捕獲、包裝和重新拋出異常,那么每個包裝都可以添加自己的相關變量,這些變量太高而無法被基本的低級錯誤看到或理解.

或者,如果你的myCall函數真的太抽象而無法知道發生了什么,那么我發現在進行調用之前以更高的詳細級別記錄日志效果很好,例如

try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         DebugLog("Trying myCall with i = " + i);
         myCall();
      <...>
   }
   catch ( Exception exception )
   {
      Log( "Failure ! Maybe in myCall() ? Don't know. i's value ? No clue." );
   }
這樣,如果出現問題,您可以檢查您的高詳細調試日志,並在引發異常的調用之前找到i的狀態。

考慮一下 Raymond Chen 的意見以及 Microsoft 的 x64 思維。
((raymond chen 例外)) 作為一個谷歌查詢足以讓你找到他的經典文章“更干凈、更優雅和錯誤——僅僅因為你看不到錯誤路徑並不意味着它不存在.” 和澄清“更干凈,更優雅,更難識別”。
(( x64 異常模型 )) 將您帶到 MSDN 文章“開始編程 64 位 Windows 系統需要知道的一切”,其中包含引用“基於表的異常處理的缺點(相對於基於 x86 堆棧的模型)是從代碼地址查找函數表條目比僅僅遍歷一個鏈表花費更多的時間。好處是函數沒有在每次函數執行時設置一個 try 數據塊的開銷。”
總結一下這句話,在 x64 中,設置一個從未使用過的“捕獲”是免費的或幾乎免費的,但實際上拋出 - 捕獲異常比在 x86 中慢。

如果您拋出的對象可以保存上下文信息,可以告訴您有關錯誤性質的一些信息,那么它會更優雅。

從 istream 派生一個可拋出的對象,您可以使用 >> 將信息流式傳輸到其中。 教對象如何顯示自己<<。

當您檢測到錯誤情況時,在以下級別或 N 個級別以下。 用好的上下文信息填充你的對象,然后扔掉它。 當您捕獲對象時,告訴它在日志文件和/或屏幕和/或您想要的任何位置顯示其上下文信息。

當然,可以在 try/catch 語句之外聲明“i”(這就是我正在做的)。

嗯……如果你真的需要知道i的值,那么這似乎是一個記錄工具——結構化異常處理可能不是最好的方法。 如果您想有條件地處理異常(即僅在調試時),請將try放入循環中。 由於這可能會影響性能(取決於您的環境),因此只能在調試模式下執行此操作。

第一件事,第一。 如果您正在捕獲 Exception,那您就錯了。 您應該捕獲您期望的特定異常。

但除此之外,如果您的異常是由您自己的代碼拋出的,您可以使用智能異常來包含您需要了解的有關該異常的所有數據。

例如,ArrayIndexOutOfBoundsException 將包含尋址的索引。

您可以通過兩種方式獲得更具體的信息。 首先,不要捕捉異常,捕捉特定的異常。 其次,在需要確定哪個函數調用拋出異常的函數中使用多個 try/catch 語句。

在我看來,在這種情況下不應該使用異常,但如果你真的需要它們,我會采用以下方式:

您可以將“i”作為參數傳遞給 myCall(); 函數,如果發生任何錯誤,將拋出一些特殊異常。 像:

public class SomeException : Exception
{
     public int Iteration;

     public SomeException(int iteration) { Iteration = iteration; }
}

循環塊:

try
{
    for(int i = 0; i < n; ++i)
    {
        myCall(i);
    }
}catch(SomeException se)
{
    Log("Something terrible happened during the %ith iteration.", se.Iteration);
}

最后是 myCall() 函數:

void myCall(int iteration)
{
    // do something very useful here

    // failed.
    throw new SomeException(iteration);
}

我們失去了輕松查看代碼如何處理不同地方的故障的可能性。 Raymond Chen 寫了一篇關於它好文章

嗯! 你可以這樣做:

   try
   {
      <...>
      for ( int i = 0 ; i < n ; i++ )
         try {
            myCall();
         } catch(Exception e) {
            println("bloody hell! " + i);
         }
      <...>
   }

我認為 Exceptions 比 Java 展示給你的更酷。 真正有趣的是讓調試器在每個未處理的異常上出現,然后在失敗時查看堆棧,這樣您就可以檢查 i 而無需更改一行代碼。 也就是說,我相信,例外應該是什么。

許多異常處理工具(Delphi 的MadExcept就是其中之一)允許您在捕獲異常時檢索整個堆棧跟蹤。 所以,你會確切地知道它被扔到哪里了。

獲取“i”的常用技術是捕獲異常,並在重新拋出異常之前向其添加額外數據(istream 技術)。 這很少是必要的,但如果你堅持......

您可以從RuntimeException派生並創建自己的mycall()拋出的異常。 然后你可以通過try / catch捕獲它。

暫無
暫無

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

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