[C# 7.1] 3. 예외 처리

RisingJade의 개발기록·2022년 5월 30일
0

C# 7.1 정리

목록 보기
3/4

5.3 예외

예외는 프로그램을 실행했을 때 비정상적으로 종료하는 것을 뜻한다.
예외가 발생하면 개발자는 예외 메시지로부터 오류의 원인을 찾을 수 있다.

5.3.1 예외 타입

CLR에 의해 전달되는 예외는 그 자체도 타입(Type)의 인스턴스다.

예를 들어 system.NullReferenceExceptionSystem.IndexOutOfRangeException 예외는 클래스의 이름이고, 이 두 예외는 BCL의 하나인 mscorlib.dll에 정의 되어 있다.

직접 예외 타입을 만든느 것도 가능하며 조건은 System.Exception을 상속받는 것이다. 추가적인 관례는 다음과 같다.

  • 응용 프로그램 개발자가 정의하는 예외는 System.Exception을 상속받은 System.ApplicationException을 상속 받는다.
  • 접미사로 Exception을 클래스명에 추가한다.
  • CLR에서 미리 정의된 예외는 System.SystemException을 상속받는다.

System.Exception 타입의 주요 멤버

  • Message: 인스턴스 프로퍼티: 예외를 설명하는 메시지 반환
  • Source: 인스턴스 프로퍼티: 예외를 발생시킨 응용 프로그램의 이름을 반환
  • StackTrace: 인스턴스 프로퍼티: 예외가 발생된 메서드의 호출 스택을 반환한다.
  • ToString: 인스턴스 메소드: Message, StackTrace 내용을 포함하는 문자열을 반환한다.

5.3.1 예외 처리기

예외가 발생한 경우 CLR의 기본 처리 과정은 예외 메시지를 출력하고 프로그램을 종료하는 것이다. 하지만 이는 개발자가 의도한 동작이 아닐 수 있다. 대부분의 경우 프로그램이 강제로 종료되기보다는 예외 상황을 사용자에게 알리고 프로그램은 여전히 계속 실행되기를 원할 것이다. 그걸 해결하기 위해 예외가 발생할 수 있는 코드를 미리 try/catch로 묶어 둬야 한다.

int div = 0;

try
{
	int temp = 10 / div;
    Console.WriteLine("try test");//여기까지 실행 안된다.
}
catch
{
	Console.WriteLine("catch");//"catch"가 콘솔창에 뜬다.
}

위의 예시를 보면 0으로 나누었기 때문에 System.DivideByZeroException을 발생시키지만 try/catch를 통해 예외를 처리하겠다고 지정했으므로 프로그램이 예외로 인해 종료되지 않고 catch블럭으로 넘어가게 된다. 이때, 에러 뒷부분은 따로 실행되지 않기 때문에 에러가 발생해도 실행시키고 싶은 코드가 있다면 finally구문을 사용하자

try
{
	int temp = 10 / div;
    Console.WriteLine("try test");//여기까지 실행 안된다.
}
catch(System.DivideByZeroException)
{
	Console.WriteLine("catch");//"catch"가 콘솔창에 뜬다.
}
catch(System.NullReferenceException)
{...}

위와 같이 catch의 구문에 예외 타입을 지정하여 원하는 예외만 잡을 수 도 있다.

5.3.4 예외 발생

예외를 처리하는 것 뿐만 아니라 임의로 발생시키는 것 또한 가능하다. 이를 위해 C#은 throw예약어를 제공한다.
사용법은 예외 타입의 인스턴스를 생성한 후 그것을 throw에 전달하면 끝이다.

string txt = Console.ReadLine();
if(txt != "123")
{
	ApplicationException ex = new ApplicationException("암호가 틀립니다.");
    throw ex;//예외 발생
}

Console.WriteLine("올바른 암호");

참고로 catch블록에서 throw를 쓰는 경우 예외 객체 없이 단독으로 사용 가능하며 예외 객체포함해서 쓰는 것보다 조금 더 예외발생 시 많은 정보를 준다.

try{...//error발생}
catch(System.Exception ex)
{
	throw // or throw ex
}

위처럼 예외 객체를 전달안 할 때 throw시 예외에 호출 스택이 끝까지 남는다.

5.3.6 올바른 예외 처리

만약 위에서 부터 차례대로 하나라도 원하는 값이 나오지 않을경우 예외처리를 해야한다고 가정하면 아래와 같이 짤 수 있다.

if(LogText(aText) == false){
	return;
}
if(LogText(bText) == false){
	return;
}
...

이것을 try catch를 이용하면 좀 더 깔끔하게 만들 수 있다.

try
{
	LogTextWithException(aText);//예외 발생시 아래 코드 실행 없이 바로 실행
    LogTextWithException(bText);//예외 발생시 아래 코드 실행 없이 바로 실행
    ...
}
catch{...//예외 처리}

이렇게 예외를 사용해서 많은 에러를 효율적으로 탐지하고 실수를 예방할 수 있지만 try/catch를 남용해서는 안된다.
만약 예외를 사용하라는 규칙만 있다면 예외 발생을 남용하여 습관적으로 try/catch를 만들게 되고 이는 "예외를 먹는(swallowing exceptions)" 상황을 유발한다. 이는 실제로 프로그램에 문제가 있지만 외부에 아무런 문제 현상이 나타나지 않는 것을 뜻한다.

try
{
	LogTextWithException(aText);//예외 발생시 아래 코드 실행 없이 바로 실행
    LogTextWithException(bText);//예외 발생시 아래 코드 실행 없이 바로 실행
    ...
}
catch
{
//Empty catch문, 이럴 경우 에러가 발생해도 아무 조치도 안하게 되어 문제가 발생한다.
}

"따라서, try/catch는 스레드 단위마다 단 한 번만 전역적으로 사용하자."

또한 예외 처리는 예외 발생한 경우 처리가 매우 무겁기 때문에 조심히 사용하자.

예외 처리가 무겁기 때문에 마이크로소프트는 Parse 메서드를 out인자를 사용해 개선한 TryParse등을 BCL에 포함시키기도 하였다. 이 메소드는 문자열이 숫자로 바꿀 수 있는 경우에만 out 형식의 인자에 숫자값을 담고, 메서드 실행이 성공했는지 여부만 반환할 뿐 예외는 발생시키지 않아 빠르다.

예외 처리 규칙 정리

  • 적어도 공용(public) 메서드에 한해서는 인자값이 올바른지 확인하고, 올바른 인자가 아니라면 예외를 발생시킨다.
  • 예외를 범용적으로 catch하는 것은 스레드마다 하나만 둔다. 그 외에는 catch구문에 반드시 예외 타입을 적용한다.
  • try/finally 조합은 언제든 사용할 수 있다.
  • 성능상 문제가 발생할 수 있는 경우, 즉 호출 시 예외가 대략으로 발생하는 메서드가 있다면 예외 처리가 없는 메서드를 함께 제공한다.
profile
언제나 감사하며 살자!

0개의 댓글