[C#] 예외 처리에 try catch를 사용하는 것이 가장 좋은 방법

선임 개발자라고 주장하는 누군가의 동료의 코드를 유지하면서 종종 다음 코드가 표시됩니다.

try
{
  //do something
}
catch
{
  //Do nothing
}

또는 때때로 다음 try catch블록 과 같은 로그 파일에 로깅 정보를 작성합니다.

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);
}

그들이 한 일이 최선의 방법인지 궁금합니다. 내 생각에 사용자가 시스템에서 일어나는 일을 알아야하기 때문에 혼란 스럽습니다.

조언 좀주세요



답변

내 예외 처리 전략은 다음과 같습니다.

  • 에 연결하여 처리되지 않은 모든 예외 를 포착하려면 Application.ThreadException event다음을 결정하십시오.

    • UI 응용 프로그램의 경우 : 사과 메시지 (winforms)로 사용자에게 팝업
    • 서비스 또는 콘솔 응용 프로그램의 경우 파일 (서비스 또는 콘솔)에 기록

그런 다음 항상 외부 에서 실행되는 모든 코드를 묶 습니다 try/catch.

  • Winforms 인프라에 의해 발생 된 모든 이벤트 (로드, 클릭, 선택 변경 …)
  • 타사 구성 요소에서 발생한 모든 이벤트

그런 다음 ‘try / catch’로 묶습니다.

  • 모든 내가 그 작업을 항상 작동하지 않을 수 있습니다 알고 (IO 작업을 잠재적 인 제로 부문 …로 계산). 그런 경우에, 나는 ApplicationException("custom message", innerException)실제로 일어난 일을 추적하기 위해 새로운 것을 던졌습니다.

또한 예외를 올바르게 정렬 하기 위해 최선을 다합니다 . 다음과 같은 예외가 있습니다.

  • 사용자에게 즉시 보여줘야합니다
  • 계단식 문제를 피하기 위해 일을 모으기 위해 추가 처리가 필요합니다 (예 : 채우기 finally중 섹션 에 .EndUpdate TreeView넣기)
  • 사용자는 신경 쓰지 않지만 무슨 일이 있었는지 아는 것이 중요합니다. 그래서 나는 항상 그것들을 기록합니다.

    • 이벤트 로그에서
    • 또는 디스크의 .log 파일에

응용 프로그램 최상위 오류 처리기에서 예외를 처리하기 위해 일부 정적 메서드디자인 하는 것이 좋습니다 .

나는 또한 다음과 같은 노력을 강요한다.

  • 기억 ALL 예외는 최상위까지 버블된다 . 예외 핸들러를 어디에나 둘 필요는 없습니다.
  • 재사용 가능하거나 깊은 호출 함수는 예외를 표시하거나 기록 할 필요가 없습니다. 자동으로 버블 링되거나 예외 처리기의 일부 사용자 지정 메시지와 함께 다시 발생합니다.

마지막으로 :

나쁜:

// DON'T DO THIS, ITS BAD
try
{
    ...
}
catch
{
   // only air...
}

쓸모없는:

// DONT'T DO THIS, ITS USELESS
try
{
    ...
}
catch(Exception ex)
{
    throw ex;
}

캐치없이 마침내 시도하는 것은 완벽하게 유효합니다.

try
{
    listView1.BeginUpdate();

    // If an exception occurs in the following code, then the finally will be executed
    // and the exception will be thrown
    ...
}
finally
{
    // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT
    listView1.EndUpdate();
}

내가 최상위에서하는 일 :

// i.e When the user clicks on a button
try
{
    ...
}
catch(Exception ex)
{
    ex.Log(); // Log exception

    -- OR --

    ex.Log().Display(); // Log exception, then show it to the user with apologies...
}

일부 호출 된 함수에서 수행하는 작업 :

// Calculation module
try
{
    ...
}
catch(Exception ex)
{
    // Add useful information to the exception
    throw new ApplicationException("Something wrong happened in the calculation module :", ex);
}

// IO module
try
{
    ...
}
catch(Exception ex)
{
    throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex);
}

예외 처리 (사용자 지정 예외)와 많은 관련이 있지만, 명심하려고하는 규칙은 간단한 응용 프로그램에 충분합니다.

다음은 포착 된 예외를 편안한 방식으로 처리하는 확장 메소드의 예입니다. 그것들은 서로 연결될 수있는 방식으로 구현되며, 사용자 자신의 예외 처리를 추가하는 것은 매우 쉽습니다.

// Usage:

try
{
    // boom
}
catch(Exception ex)
{
    // Only log exception
    ex.Log();

    -- OR --

    // Only display exception
    ex.Display();

    -- OR --

    // Log, then display exception
    ex.Log().Display();

    -- OR --

    // Add some user-friendly message to an exception
    new ApplicationException("Unable to calculate !", ex).Log().Display();
}

// Extension methods

internal static Exception Log(this Exception ex)
{
    File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n");
    return ex;
}

internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error)
{
    MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img);
    return ex;
}


답변

모범 사례는 예외 처리 가 문제를 숨기지 않아야한다는 것 입니다. 이것은 try-catch블록이 매우 드 물어야 함을 의미합니다 .

try-catch의미를 사용하는 것이 3 가지 상황이 있습니다 .

  1. 알려진 예외는 항상 가능한 한 낮게 처리하십시오 . 그러나 예외가 예상되는 경우 일반적으로 먼저 예외를 테스트하는 것이 좋습니다. 예를 들어 구문 분석, 형식 지정 및 산술 예외는 거의 항상 논리 검사보다 먼저 처리하는 것이 좋습니다 try-catch.

  2. 예외에 대한 작업 (예 : 트랜잭션 로깅 또는 롤백)이 필요한 경우 예외를 다시 발생시킵니다.

  3. 알 수없는 예외는 항상 가능한 한 높게 처리하십시오. 예외를 소비하고 다시 발생시키지 않아야하는 유일한 코드는 UI 또는 공용 API 여야합니다.

원격 API에 연결한다고 가정 해 봅시다. 여기서 특정 오류가 발생하고 그러한 상황에서 문제가 있음을 알고 있으므로이 경우 1입니다.

try
{
    remoteApi.Connect()
}
catch(ApiConnectionSecurityException ex)
{
    // User's security details have expired
    return false;
}

return true;

예상하지 못한 다른 예외는 발견되지 않습니다.

이제 데이터베이스에 무언가를 저장하려고한다고 가정하십시오. 실패하면 롤백해야하므로 사례 2가 있습니다.

try
{
    DBConnection.Save();
}
catch
{
    // Roll back the DB changes so they aren't corrupted on ANY exception
    DBConnection.Rollback();

    // Re-throw the exception, it's critical that the user knows that it failed to save
    throw;
}

우리는 예외를 다시 던진다는 것을 명심하십시오. 높은 코드는 여전히 무언가 실패했음을 알아야합니다.

마지막으로 우리는 UI를 가지고 있습니다-여기서 우리는 처리되지 않은 예외를 완전히 원하지 않지만, 그것들을 숨기고 싶지는 않습니다. 다음은 사례 3의 예입니다.

try
{
    // Do something
}
catch(Exception ex)
{
    // Log exception for developers
    WriteException2LogFile(ex);

    // Display message to users
    DisplayWarningBox("An error has occurred, please contact support!");
}

그러나 대부분의 API 또는 UI 프레임 워크에는 사례 3을 수행하는 일반적인 방법이 있습니다. 예를 들어 ASP.Net에는 예외 세부 정보를 덤프하는 노란색 오류 화면이 있지만 프로덕션 환경에서는보다 일반적인 메시지로 대체 될 수 있습니다. 이를 수행하는 것은 많은 코드를 절약 할뿐만 아니라 오류 로깅 및 표시가 하드 코딩이 아닌 구성 결정이어야하므로 모범 사례입니다.

이것은 모두 사례 1 (알려진 예외)과 사례 3 (일회성 UI 처리)이 더 나은 패턴을 가짐을 의미합니다 (예상 된 오류 또는 손 오류 처리를 UI로 전달하지 않음).

예를 들어 트랜잭션 범위 ( using블록 동안 커밋되지 않은 트랜잭션을 롤백하는 블록)와 같이 사례 2도 더 나은 패턴으로 대체 될 수 있으므로 개발자가 모범 사례 패턴을 잘못 이해하기가 더 어려워집니다.

예를 들어 대규모 ASP.Net 응용 프로그램이 있다고 가정합니다. 오류 로깅은 ELMAH 를 통해 가능하며 , 오류 표시는 유익한 로컬 YSoD 및 프로덕션에서 현지화 된 메시지 일 수 있습니다. 데이터베이스 연결은 모두 트랜잭션 범위 및 using블록을 통해 이루어질 수 있습니다 . 단일 try-catch블록이 필요하지 않습니다 .

TL; DR : 모범 사례는 실제로 try-catch블록 을 전혀 사용하지 않는 것 입니다.


답변

예외는 차단 오류 입니다.

우선, 모범 사례는 블로킹 오류가 아닌 한 모든 종류의 오류에 대해 예외를 throw하지 않아야합니다 .

오류가 차단 되면 예외를 처리하십시오. 예외가 이미 발생하면 예외이기 때문에 숨길 필요가 없습니다. 사용자에게 알리십시오 (모든 예외를 UI에서 사용자에게 유용한 것으로 다시 포맷해야합니다).

소프트웨어 개발자로 당신의 임무는 방지하기 위해 노력하는 예외적 인 경우 일부 매개 변수 또는 런타임 상황은 예외가 종료 될 수 있습니다. 즉, 예외를 음소거해서는 안되지만 피해야 합니다.

예를 들어 일부 정수 입력에 잘못된 형식이있을 수있는 경우 int.TryParse대신을 사용하십시오 int.Parse. “실패하면 단순히 예외를 던지십시오”라고 말하는 대신이 작업을 수행 할 수있는 경우가 많이 있습니다.

예외를 던지는 것은 비싸다.

결국 예외가 발생하면 로그에 예외를 작성하는 대신 예외가 발생하면 모범 사례 중 하나는 첫 번째 예외 처리기에서 예외를 포착하는 것입니다 . 예를 들면 다음과 같습니다.

  • ASP.NET : Global.asax Application_Error
  • 기타 : AppDomain.FirstChanceException 이벤트 .

내 입장은 로컬 try / catch가 예외를 다른 것으로 번역 할 수있는 특별한 경우를 처리하거나 매우, 매우, 매우, 매우 특별한 경우 (라이브러리 버그) 전체 버그를 해결하기 위해 음소거 해야하는 관련이없는 예외를 던짐).

나머지 경우 :

  • 예외를 피하십시오.
  • 이것이 가능하지 않은 경우 : 첫 번째 예외 처리기.
  • 또는 AOP (PostSharp aspect)를 사용하십시오.

일부 의견에 @thewhiteambit에 대답하는 중 …

@thewhiteambit는 말했다 :

예외는 치명적인 오류가 아니라 예외입니다! 때로는 오류조차되지 않지만 치명적 오류를 고려하는 것은 예외가 무엇인지 완전히 이해하지 못하는 것입니다.

우선 예외가 어떻게 오류가 될 수 없습니까?

  • 데이터베이스 연결이 없습니다 => 예외.
  • 일부 유형으로 구문 분석 할 수없는 잘못된 문자열 형식 => 예외
  • JSON을 구문 분석하려고 시도하고 입력이 실제로 JSON => 예외가 아닌 동안
  • null객체가 예상되는 동안 인수 => 예외
  • 일부 라이브러리에는 버그가 있습니다 => 예기치 않은 예외가 발생합니다.
  • 소켓 연결이 있고 연결이 끊어집니다. 그런 다음 메시지를 보내려고합니다 => 예외

우리는 예외가 발생했을 때 1k 건을 나열 할 수 있으며 결국 가능한 모든 사례가 오류 가 될 것 입니다.

예외 하루가 끝나면 진단 정보를 수집하는 객체이기 때문에 . 메시지가 있고 문제가 발생했을 때 발생합니다.

예외적 인 경우가 없으면 아무도 예외를 던지지 않습니다. 예외 가 발생하면 일단 오류가 발생 하기 때문에 try / catch 사용을 시도 하지 않고 제어 흐름을 구현하기위한 예외가 발생하면 응용 프로그램 / 서비스가 예외적 인 경우에 입력 한 작업을 중지합니다 .

또한 Martin Fowler (Jim Shore)가 출판 한 페일-패스트 패러다임 을 확인하는 것이 좋습니다 . 이것은 얼마 전에이 문서를 읽기 전에도 예외를 처리하는 방법을 항상 이해 한 방법입니다.

[…] 그들을 고려하십시오 치명적 오류는 예외가 무엇인지에 대한 완전히 잘못된 이해입니다.

일반적으로 예외 일부 작업 흐름을 차단 하고 사람이 이해할 수있는 오류로 변환하기 위해 처리됩니다. 따라서 예외는 실제로 응용 프로그램 / 서비스의 완전한 충돌을 피하고 문제가 있음을 사용자 / 소비자에게 알리기 위해 오류 사례를 처리하고 처리하는 더 나은 패러다임 인 것 같습니다.

@thewhiteambit에 대한 추가 답변

예를 들어, 데이터베이스 연결이 누락 된 경우 프로그램은 예외적으로 로컬 파일에 계속 기록하고 데이터베이스를 다시 사용할 수있게되면 변경 사항을 데이터베이스에 보낼 수 있습니다. 기본 영어를 Parse ( “1,5”)로 시도 할 때 실패하고 완전히 해석되는 독일어 해석으로 다시 시도하는 것과 같이 예외에 대한 언어 로컬 해석을 사용하여 유효하지 않은 문자열 대 숫자 캐스팅을 다시 구문 분석하려고 시도 할 수 있습니다. 점 대신 구분 기호로 쉼표를 사용하기 때문에 좋습니다. 이러한 예외는 차단되어서는 안되며 예외 처리 만 필요합니다.

  1. 데이터를 데이터베이스에 유지하지 않고 앱이 오프라인에서 작동 할 수있는 경우 예외를 사용해서는 안됩니다.이를 사용 하여 제어 흐름을 구현 try/catch하는 것은 안티 패턴으로 간주됩니다. 오프라인 작업은 가능한 유스 케이스이므로 데이터베이스에 액세스 할 수 있는지 여부를 확인하기 위해 제어 플로우를 구현하고 도달 할 수 없을 때까지 기다리지 않습니다 .

  2. 파싱 것은 또한 예상되는 경우입니다 ( 예외적하지 CASE ). 이것을 예상하면 예외를 사용하여 흐름을 제어하지 마십시오! . 자신의 문화가 무엇인지 알기 위해 사용자로부터 일부 메타 데이터를 얻고이를 위해 포맷터를 사용합니다! .NET은이 환경과 다른 환경도 지원하며 응용 프로그램 / 서비스의 문화권 별 사용을 기대하는 경우 숫자 형식을 피해야하기 때문에 예외가 발생합니다 .

처리되지 않은 예외는 보통 오류가되지만 예외 자체는 codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors가 아닙니다.

이 기사는 저자의 의견이나 관점 일뿐입니다.

Wikipedia는 또한 articule author (s)의 의견 일 수 있기 때문에 나는 그것이 교리 라고 말하지는 않지만 일부 기사에서 예외 기사에 의한 코딩이 무엇을 말하는지 확인하십시오 .

[…] 프로그램을 계속하기 위해 발생하는 특정 오류를 처리하기 위해 이러한 예외를 사용하는 것을 예외로 코딩이라고합니다. 이 안티 패턴은 성능 및 유지 관리면에서 소프트웨어를 빠르게 저하시킬 수 있습니다.

또한 어딘가에 말합니다 :

잘못된 예외 사용법

예외에 의한 코딩은 종종 잘못된 예외 사용과 함께 소프트웨어의 추가 문제로 이어질 수 있습니다. 고유 한 문제에 대해 예외 처리를 사용하는 것 외에도 예외가 발생한 후에도 잘못된 예외 사용은 코드를 실행하여 더 많은 문제를 해결합니다. 이 불량한 프로그래밍 방법은 많은 소프트웨어 언어의 goto 방법과 유사하지만 소프트웨어의 문제가 감지 된 후에 만 ​​발생합니다.

솔직히, 나는 소프트웨어를 개발할 수 없다고 믿어 유스 케이스를 심각하게 생각하지 않습니다. 알고 있다면 …

  • 데이터베이스가 오프라인 상태가 될 수 있습니다 …
  • 일부 파일을 잠글 수 있습니다 …
  • 일부 형식이 지원되지 않을 수 있습니다 …
  • 일부 도메인 확인이 실패 할 수 있습니다 …
  • 앱이 오프라인 모드에서 작동해야합니다.
  • 어떤 유스 케이스라도

당신은 그것에 대한 예외를 사용하지 않습니다 . 당신은 것입니다 지원 일반 제어 흐름을 사용하여 이러한 사용 사례를.

예기치 않은 사용 사례가 다루지 않으면 예외가 발생 하기 때문에 코드가 빠르게 실패 합니다. 예외는 예외적 인 경우 이기 때문 입니다.

반면에, 때로는 예상 예외를 던지는 예외적 인 경우 를 다루지 만 제어 흐름을 구현하기 위해 예외를 던지지는 않습니다. 일부 유스 케이스를 지원하지 않거나 코드가 특정 인수 또는 환경 데이터 / 속성에서 작동하지 않는다는 것을 상위 계층에 알리고 싶기 때문입니다.


답변

코드에서 발생한 일에 대해 사용자에게 걱정해야 할 유일한 경우는 문제를 피하기 위해 할 수 있거나해야 할 일이 있는지 여부입니다. 양식의 데이터를 변경할 수있는 경우, 문제를 피하기 위해 버튼을 누르거나 응용 프로그램 설정을 변경 한 다음 알려주십시오. 그러나 사용자가 피할 수없는 경고 나 오류는 제품에 대한 신뢰를 잃게합니다.

예외 및 로그는 최종 사용자가 아닌 개발자를위한 것입니다. 각 예외를 잡을 때해야 할 올바른 일을 이해하는 것이 황금률을 적용하거나 응용 프로그램 전체의 안전망에 의존하는 것보다 훨씬 좋습니다.

마인드 코딩은 오로지 잘못된 코딩입니다. 이러한 상황에서 더 나은 것이 있다고 생각하면 좋은 코딩에 투자했지만 이러한 상황에서 일반적인 규칙을 찍지 말고 무엇을 던질 이유와 무엇을 이해 해야하는지 알 수 있습니다. 당신은 그것에서 복구 할 수 있습니다.


답변

나는 이것이 오래된 질문이라는 것을 알고 있지만 여기에 아무도 MSDN 기사를 언급하지 않았으며 실제로 나를 위해 정리 한 문서였으며 MSDN에는 매우 좋은 문서 가 있습니다. 다음 조건에 해당하면 예외를 잡아야합니다.

  • 예외가 발생하는 이유를 잘 알고 있으며 FileNotFoundException 오브젝트를 발견 할 때 사용자에게 새 파일 이름을 입력하도록 프롬프트하는 것과 같은 특정 복구를 구현할 수 있습니다.

  • 보다 구체적인 새 예외를 작성하고 던질 수 있습니다.

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch(System.IndexOutOfRangeException e)
    {
        throw new System.ArgumentOutOfRangeException(
            "Parameter index is out of range.");
    }
}
  • 추가 처리를 위해 예외를 전달하기 전에 부분적으로 처리하려고합니다. 다음 예에서 catch 블록은 예외를 다시 발생시키기 전에 오류 로그에 항목을 추가하는 데 사용됩니다.
    try
{
    // Try to access a resource.
}
catch (System.UnauthorizedAccessException e)
{
    // Call a custom error logging procedure.
    LogError(e);
    // Re-throw the error.
    throw;
}

전체 ” 예외 및 예외 처리 “섹션과 예외에 대한 모범 사례를 읽는 것이 좋습니다 .


답변

더 좋은 방법은 두 번째 방법 (예외 유형을 지정하는 방법)입니다. 이것의 장점은 코드에서 이러한 유형의 예외가 발생할 수 있다는 것입니다. 이 유형의 예외를 처리하고 있으며 다시 시작할 수 있습니다. 다른 예외가 발생하면 코드에서 버그를 찾는 데 도움이되는 무언가 잘못되었음을 의미합니다. 응용 프로그램이 결국 중단되지만 누락해야 할 버그가 있다는 것을 알게 될 것입니다.


답변

예외를 제외하고 다음을 시도합니다.

먼저, 0으로 나누기, IO 연산 등과 같은 특수 유형의 예외를 포착하고 이에 따라 코드를 작성합니다. 예를 들어, 값의 신뢰성에 따라 0으로 나누거나 (중간 계산 (인수가 아닌) 계산에서 0으로 나누는 간단한 계산기) 또는 예외를 자동으로 처리 할 수 ​​있습니다. 처리를 계속합니다.

그런 다음 나머지 예외를 잡아서 기록하려고합니다. 가능하면 코드 실행을 허용하고, 그렇지 않으면 사용자에게 오류가 발생했음을 알리고 오류 보고서를 우편으로 보내도록 요청하십시오.

코드에서는 다음과 같습니다.

try{
    //Some code here
}
catch(DivideByZeroException dz){
    AlerUserDivideByZerohappened();
}
catch(Exception e){
    treatGeneralException(e);
}
finally{
    //if a IO operation here i close the hanging handlers for example
}