이번 글에서는 예외(Exception) 처리에 대해서 알아보도록 하겠습니다.

예외 처리란 '프로그램이 처리되는 동안 특정한 문제가 일어났을 때 처리를 중단하고 다른 처리를 하는 것' 이라고 위키 사전(예외 처리)에 기술되어 있는데요. 맞는 말입니다. 이걸 좀 더 이해하기 쉽게 설명하면 이런 식으로도 표현이 가능할 것 같네요. '오류가 날 것 같은 곳(부실한 곳)을 미리 방지하는 것(보수 공사라고 해두죠)'.


... 쓰고 보니 같은 말이네요..


어쨌든! 예외 처리. 설명 들어가도록 하겠습니다.

프로그래밍을 하면서 예외를 보는 것은 어려운 일이 아닙니다. 이러다가 예외가 발생하기도 하고, 저러다가 예외가 발생하기도 합니다. 예외 중에서도 대표적으로 발생하는 예외들은 이런 예외들이 있습니다.

NullReferenceException

참조 개체가 초기화되지 않은 상태에서 참조된 경우에 발생

OverflowException

값의 범위를 초과하는 값을 대입했을 때 발생

ArgumentException

매개 변수가 잘못된 경우에 발생

InvalidCastException

캐스팅(형 변환)이 잘못된 경우에 발생

FormatException

형식이 잘못된 경우에 발생


위의 예외 말고도, 굉장히 많은 예외 클래스들이 있지만 모두 설명하기엔 제가 모르는 것이 너무나도 많아서 안될듯 합니다. (털썩)


일단, 기본적인 예외 처리 구조는 다음과 같습니다.

try {
    // 오류(=예외)가 발생할 만한 코드
} catch {
    // 오류(=예외) 발생 시에 수행될 코드
} finally {
    // 오류 발생에 상관 없이 마지막에 수행될 코드
}


이 예외 처리의 구조는 if의 else문처럼 catch 를 중복하여 처리할 수 있습니다. 이런 식으로 말이죠.

try {
    // 오류(=예외)가 발생할 만한 코드
} catch (/* 하위 예외 클래스 */ NullReferenceException nr) {
    // 오류(=예외) 발생 시에 수행될 코드
} catch (/* 중간 예외 클래스 */ SystemException se) {
    // 오류(=예외) 발생 시에 수행될 코드
} catch (/* 최상위 예외 클래스 */ Exception ex) {
    // 오류(=예외) 발생 시에 수행될 코드
} finally {
    // 오류 발생에 상관 없이 마지막에 수행될 코드
}


* 여기서 매우 중요히 짚고 넘어가야 할 것은 다중 catch문을 사용하여 여러 개의 예외에 대한 처리를 하려고 할 때는 최하위 예외 클래스->하위 예외 클래스->중간 예외 클래스->상위 예외 클래스->최상위 예외 클래스(Exception 클래스) 식으로 점점 높은 곳으로 올라가는 단계로 작성해야 합니다.

여기서 말하는 단계라 함은 상속을 말합니다.


Tip!

상속의 단계라 함은 상속을 받고, 상속을 해주는 것으로 나뉘게 됩니다.

상속을 해주는 클래스의 경우 상위에 위치하게 되고 상속을 받는 클래스의 경우엔 하위에 위치하게 됩니다.

이걸 코드로 설명하면 이런식으로 되게 됩니다.

class A {
}
class B : A {
}
class C : B {
}
class D : C {
}
class E : D {
}
class AA : A {
}


위 코드에서의 단계롤 표시하면 A 클래스가 가장 최상위에 존재하고 B, AA가 그 다음, C, D, E 순입니다.

A (최상위)

- B            AA

- C

- D

- E (최하위)


이런식으로 표시가 되고, B 클래스와 AA 클래스는 같은 위치에 위치하게 됩니다.



그리고 try ~ catch 문에서의 finally 문은 생략이 가능하고 단일 catch 문의 경우에는 예외 클래스를 생략이 가능합니다.

try {
    
} catch /* (Exception ex) 생략 가능 */ {
    
} /* finally {
    생략 가능    
} */


이걸 주석을 제거하고 표시하면 이렇게 간단하게 변하게 됩니다.

하지만 이런 경우엔 예외의 정보를 가져오기가 힘들어지죠... 어떻게든 가져올 수는 있겠지만, 바로 그 정보를 받아올 수는 없게되겠죠.

try {
    
} catch {
    
}



그리고 예외 처리의 가장 이상적인 사용 방법은 try ~ catch 문으로 싸는 블록(코드)이 짧아야 합니다.

좋은 예:

UInt32 i = 0;
try {
    i--;
} catch (Exception ex) {
    Console.WriteLine("예외 발생!! 메세지: {0}", ex.Message);
}


나쁜 예:

try {
    UInt32 i = 0;
    i--;
} catch (Exception ex) {
    Console.WriteLine("예외 발생!! 메세지: {0}", ex.Message);
}


예외 처리는 오류로 인한 프로그램의 흐름이 뜻하지 않는대로 가게 되는 것을 방지하고자 오류 발생 시에는 프로그래머가 의도한 대로 흘러가도록 하는 것이므로 오류가 발생한 부분이나 오류가 발생할 것 같은 부분만을 try ~ catch 로 감싸는 것이 위에서 예를 든 것처럼 가장 이상적인 방법입니다.



이 예제에서는 try ~ catch ~ finally 의 흐름 제어를 보여줍니다.

Int32 a = 100;
Int32 b = 0;
Int32 c;
try {
    // 0 으로 나눔. 예외가 발생하게 됩니다.
    c = a / b;
} catch (ArithmeticException ae) {
    Console.WriteLine("{0} 예외 발생! 메세지: {1}", ae.GetType().Name, ae.Message);
} catch (Exception ex) {
    Console.WriteLine("예외 발생!! 메세지: {0}", ex.Message);
} finally {
    // try 및 catch 블록의 흐름이 끝나고 예외 발생에 상관 없이 수행되는 코드
    Console.WriteLine("try ~ catch 문의 finally 부분입니다.");
    Console.WriteLine("이 부분은 마지막에 반드시 호출되게 됩니다.");
    
    c = 12345;
}

Console.WriteLine("과연 c의 값은?? {0}", c);
Console.ReadKey(true);


예제 결과:


위의 코드의 설명대로 흐름은 

try -> 예외 발생 -> catch -> finally

이렇게 흘러가게 되었습니다.

반대로 예외가 발생하지 않는다면 흐름은 이렇게 흘러가게 됩니다.

try -> finally



그리고, 사용자가 예외를 발생시키는 방법에 대해서도 알려드리도록 하겠습니다.

예외를 발생시키는 것은 매우 간단합니다. throw 키워드만 알고있으면 끝!


throw (Exception 클래스 혹은 파생된 클래스)

사용 방법은 두 가지가 있습니다.


하나는 예외 처리 부분(catch)의 예외 클래스를 throw 하는 것이고,

다른 하나는 개체를 하나 생성하고 생성된 개체를 throw 하는 것입니다.

첫 번째 방법은 ... 하는 이유가 없지 않나 싶습니다 ㅋ....


첫 번째 방법:

Int32 a = 100;
Int32 b = 0;
Int32 c;
try {
    // 0 으로 나눔. 예외가 발생하게 됩니다.
    c = a / b;
} catch (ArithmeticException ae) {
    throw ae;
} catch (Exception ex) {
    Console.WriteLine("예외 발생!! 메세지: {0}", ex.Message);
} finally {
    // try 및 catch 블록의 흐름이 끝나고 예외 발생에 상관 없이 수행되는 코드
    Console.WriteLine("try ~ catch 문의 finally 부분입니다.");
    Console.WriteLine("이 부분은 마지막에 반드시 호출되게 됩니다.");
    
    c = 12345;
}


두 번째 방법:

Int32 a = 100;
Int32 b = 0;
Int32 c;
try {
    // 0 으로 나눔. 예외가 발생하게 됩니다.
    c = a / b;
} catch (ArithmeticException ae) {
    throw new DivideByZeroException("0으로 나눴습니다.");
} catch (Exception ex) {
    Console.WriteLine("예외 발생!! 메세지: {0}", ex.Message);
} finally {
    // try 및 catch 블록의 흐름이 끝나고 예외 발생에 상관 없이 수행되는 코드
    Console.WriteLine("try ~ catch 문의 finally 부분입니다.");
    Console.WriteLine("이 부분은 마지막에 반드시 호출되게 됩니다.");
    
    c = 12345;
}



그리고 마지막으로 사용자 정의 예외 클래스를 작성하는 법을 알려드리도록 하겠습니다.

사용자 정의 예외 클래스는 반드시 이런 구조를 가져야 합니다.

class MyException : Exception {
    // 추가적인 메서드, 속성 정의
}


throw 키워드에 사용할 수 있는 개체는 반드시 Exception 클래스 혹은 Exception 클래스를 상속하는 클래스이여야 하기 때문입니다.


throw 및 예외 발생에 대한 MSDN 문서


긴 글 읽어주시느라  고생했습니다! 

감사합니다!

+ Recent posts