ice rabbit programming

[C#] 예외 처리(Exception Handling) 본문

Development/C#

[C#] 예외 처리(Exception Handling)

판교토끼 2021. 3. 28. 23:08

많은 언어에서 예외 처리를 지원하고, C# 또한 마찬가지이다. 

예외 처리가 나온 흐름

어느 정도 익숙해진 개발자라면 try-catch를 이용한 예외 처리가 이미 낯익을 것이다. 그래도 다른 처리 방법과, try-catch를 사용했을 때의 이점을 한 번 살펴보도록 하겠다.

메소드가 실패했음을 전달하는 것은, 3가지 정도 방법을 생각해볼 수 있다.

  1. 반환 값으로 fail을 전달한다.
    예를 들어 실패하면 -1을 return하기로 하는 방식이다. 하지만 이는 실패 값이 연산의 결과인지, fail인지 모호할 수도 있고 오류에 대한 자세한 정보가 없다.
    또한 -1이 fail이라는 로컬 룰을 모든 개발자가 숙지하고 있어야 한다.
    로직적으로 봤을 때에는 정상적인 flow와(try 구문) 오류 처리 flow가(catch) 혼재되어 있고, 사용자에게 메소드의 실패 처리를 갖에할 수 없다.

  2. out 파라미터 사용
    1번 방법의 개선 형태인데, 메소드 반환 값은 성공/실패 여부를 반환하고 연산 결과는 out 파라미터를 사용하는 것이다.
    이전의 모호한 결과는 해결했으나, 실패 처리 강제와 로직 혼재의 단점은 그대로 존재한다. 또한 반환하는 값이 여러 루트라 사용법이 까다롭다는 점도 있겠다.
    Try를 Prefix로 가지는 메소드들이 이러한 방식을 사용한다.

  3. 예외 처리(try-catch)
    메소드 실패 시에 throw로 에러를 던지는 방식이다. 즉, 이 메소드를 호출한 곳에서 처리한다.
    예외를 처리하지 않으면 프로그램이 종료되므로 사용자에게 처리의 강제성이 생긴다.
    또한 정상적인 흐름 로직(tyr)과 오류 처리 로직(catch)가 분리된다.

관례적으로는 사소한 오류(프로그램의 동작에 영향이 미미한)는 반환 값으로, 크리티컬한 오류는 예외처리로 처리한다고 한다.

try-catch 문법 자체는 다른 언어들(Java, Python 등)과 유사하다. 첫 catch부더 차례대로 검사하고, Exception 키워드는 모든 예외를 포괄한다. 모든 예외들이 Exception을 상속받기 때문.

public void temp()
{
  try
  {
    // 정상 로직, 문제가 발생하면 catch 구문으로 throw
  }
  catch(NotFoundException)
  {
    // 제일 먼저 체크
  }
  catch(NoImplementException)
  {
  }
  catch(Exception)
  {
    // 마지막으로 체크
  }
}

위처럼 작성하면 catch문을 순서대로 검사하고, 마지막으로 모든 예외를 거를 수 있게 된다. 만일 Exception을 다른 Exception보다 상위에 작성하게 되면, Exception에서 모든 예외가 걸러지므로 그 뒤의 catch문이 무의미해짐을 염두에 두자.

예외는 Message와 StackTrace(예외의 근원 ~ catch까지의 메소드 흐름에 대한 정보)를 담고 있고, 콘솔 등에 자동으로 찍히게 된다. 물론 Azure App Insights나, 파일로 로그를 기록하게 할 수도 있다.

상술했듯이 throw를 사용해서 해당 메소드 내의 catch 구문에서 처리하지 않고 호출한 곳으로 처리를 넘길 수 있다.
throw; 라고만 작성하면 받은 예외를 그대로 던진다.

Exception Filter(catch-when)가 있는데, status를 통해 상세한 정보에 따라 예외를 분기 처리하는 것이다. 즉, when 뒤가 true일 때만 catch 구문이 실행된다. 이는 catch문의 성능 저하가 있어서, 가벼운 처리(logging 등) 후에 throw할 경우 보통 사용한다.

마지막으로 finally는 예외가 나든 나지 않든 실행되는 구문이다. try 블락을 벗어나면 실행되는데, return을 하거나 throw가 나더라도 이후에 finally가 실행된다. 주로 자원(파일, 네트워크 등의 dispose) 해제 시에 사용된다.

public void temp()
{
  try
  {
    FileStream fs = new FileStream(...);
    // 정상 로직
  }
  catch(Exception)
  {
    throw;
  }
  finally
  {
    fs?.Dispose(); // 자원 해제
  }
}

'Development > C#' 카테고리의 다른 글

[C#] Delegate  (0) 2020.09.05
[C#] Generic  (0) 2020.05.24
[C#] Index와 Range  (0) 2020.05.17
[C#] Property와 Indexer  (0) 2020.05.10
[C#] 메소드  (0) 2020.05.04