예외(Exception)란?

예외란 무엇일까요? 단어적으로 생각하면

에러나 그런 비슷한게 아닌가 싶은데요.

 

보통 기초 과정의 tutorial에서는 에러처리

비슷하게 쉽게 설명을 하는 편입니다.

 

try 와 catch 문을 사용해서 런타임 오류를

처리하는 정도 아이디어도 충분히

쓸만한 방법입니다. 다만 C#의 예외는

상당히 심오한 시스템입니다.

어떻게 보면 이런게 메모리 관리와 함께

가상머신(CLR)이 있는 플랫폼에서

사용할 수 있는 첨단의 프로그래밍입니다.

 

사실... C#이 자바를 카피했다는 의심을

많이 받는데 Exception 클래스 계층과

try catch finalize 문법 자체도 거의

똑같기 때문에 자바의 뒤에 나온 이상

좀 따라했다는 느낌은 지우기 어렵습니다.

 

그러나 의도적으로 따라했다기 보다는

가상머신을 만든 이상 Exception 클래스를

다루는 방식은 OOP 언어라면 비슷할 것이다 -

라고 보면 당연한 문법으로 볼 수도 있습니다.

 

Python 언어도 키워드만 다를 뿐

try except raise finally 같은 키워드가

거의 비슷한 흐름으로 사용됩니다.

자바스크립트도 try catch 문이 있습니다.

OOP언어가 비슷한 것은 가상화 수준이

비슷하기 때문입니다. 

 

Exception 을 다루는 것은 다른 문법과

달리 이론적 내용도 조금 필요합니다.

Exception 클래스의 족보에는 어느정도

자주 발생하는 Runtime Exception에

대한 클래스가 미리 준비되어 있습니다.

 

스트림이나 파일에 관련한 IOException,

Type에 관한 Format Exception 등

런타임에서 자주 발생해서 예측이 가능한

예외들은 클래스가 있습니다.

 

그렇지만 기본 클래스가 세상의 모든

예외를 담는 것은 불가능합니다.

각 회사가 사용하는 비즈니스 로직의

특정한 예외는 프로그래머가 구현해야합니다.

 

 

또 예외를 사용해야 하는지 아니면

다른 방법으로 처리(handling)해야

하는지도 결정해야 합니다.

 

프로그램이 Exception을 처리하지 않으면

최종적으로 CLR이 처리하는데 이는

현재의 코드를 잠시 중단하고

Exception handling 에 제어를 넘겨야

하므로 try catch throw 이것들을

많이 사용할 수록 성능에 영향을 미칩니다.

 

쉬운 예를 들면 컴퓨터는 숫자를 0으로

나눌 수 없습니다. 그런데 런타임에

사용자의 입력으로 인해 0으로

나눴다면 이 명령어는 CPU가

처리할 수 없습니다. 이 때 발생하는

예외는 DivideByZeroException 입니다.

C#의 소스코드에 아무런 조치를 취하지

않아도 CLR이 Exception 을 발생시킵니다.

 

아래 예제는 10을 0으로 나누려는 시도에

예외를 발생시킵니다.

 

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("숫자를 입력하시오.");
            int div = Convert.ToInt32(Console.ReadLine());
            int num = 10 / div;

            Console.WriteLine("10 나누기 {0}은 {1}", div, num);
        }
    }
}

[결과]

숫자를 입력하시오.
0
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero. at ConsoleApp1.Program.Main(String[] args) in C:\Users\masterkay\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 12

 

이렇게 예외로 종료하면 그 뒤의 코드들은

실행되지 않습니다. 비정상 종료같은 거지요.

 

try catch 문은 try 블록안에서 발생한

예외를 처리합니다. catch의 Exception e는

예외처리 클래스 e의 인스턴스 입니다.

인스턴스에는 예외가 발생하며 기록된

메시지가 저장되어 있습니다.

 

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {

            int div = 1;
            int num = 0;

            try
            {
                Console.WriteLine("숫자를 입력하시오.");
                div = Convert.ToInt32(Console.ReadLine());
                num = 10 / div;
            } catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.WriteLine("10 나누기 {0}은 {1}", div, num);
        }
    }
}

 

이렇게 처리하면 예외처리를 하고도

다음 라인으로 코드가 진행이 됩니다.

숫자를 입력하시오.
0
Attempted to divide by zero.
10 나누기 0은 0

 

 

예외의 성격에 따라 발생하는

클래스가 다릅니다. 숫자를 입력해야하는데

문자를 입력하면 format exception 입니다.

숫자를 입력하시오.
g
Input string was not in a correct format.
10 나누기 1은 0

e.ToString() 에는 Exception 클래스의

이름과 상세 사항이 기록됩니다.

 

Exception 은 보통의 코드 흐름과는

다른데 Exceptoin 객체를 생성하고

Stack을 추적하기 때문에 그만큼

시간이 지체됩니다. 그래서 굳이

필요없는 곳에 try catch 문을 넣어서

예외 발생을 유도하는 것은 상당히

불필요한 방법입니다.

 

위의 try 문에 div가 0이 들어오면

if문으로 필터링을 하면 Exception의

발생이 없이도 divide by zeo 런타임

오류를  방지할 수 있습니다.

 

            try
            {
                Console.WriteLine("숫자를 입력하시오.");
                div = Convert.ToInt32(Console.ReadLine());

                if(0==div)
                {
                    Console.WriteLine("0으로 나눌 수 없습니다");
                }
                else
                {
                    num = 10 / div;
                }

            } catch(Exception e)
            {

                Console.WriteLine(e.StackTrace);
            }

 

 

한가지 예를 더 들어 보겠습니다.

file 을 여는 것은 스트림을 읽는 것 입니다.

 

그런데 파일이 없거나 접근이 불가하면?

스트림을 읽을 수 없어서 FileNotFoundException이

발생합니다. try 문에 나머지 코드는 건너 뛰고

catch문으로 이동해서 오류 메시지를

출력하고 끝까지 실행합니다.

 

using System;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string file = "someFile.txt";

            try
            {
                TextReader reader = new StreamReader(file);
                Console.WriteLine(reader.ReadLine());
                reader.Close();
            }
            catch(Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }

            Console.WriteLine("- Done!");
        }
    }
}

 

   at System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)
   at System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
   at System.IO.StreamReader..ctor(String path)
   at ConsoleApp1.Program.Main(String[] args) in C:\Users\masterkay\source\repos\ConsoleApp1\ConsoleApp1\Program.cs:line 14
- Done!

 

Exception 의 발생 구조는 아래와 같습니다.

(Fundamentals of Computer Programming with C# 발췌)

 

exception 예외처리 C#

하위 메소드의 스택에서 발생한 Exception은

핸들러를 찾을 때 까지 위로 올라갑니다.

최종적으로는 .NET CLR 이 핸들하게 되있습니다.

 

런타임 에러가 나도 메시지를 내면서

종료를 하는 이유는 기본으로

.NET CLR 이 핸들링을 하기 때문입니다.

 

요약

대충 겉핥기로 예외를 다뤄봤습니다만,

예외의 어려움은 세상에 수많은 오류가

있듯이 예외의 종류는 무한정입니다.

 

그러므로 모든 예외를 처리하기 위하는

방식의 프로그래밍은 의미가 없습니다.

 

핵심적인 예외를 처리해주고

특수한 경우는 일반 예외 클래스 (Exception)에

맡기고 별도로 추적해야 합니다.

 

예외를 사용하지 않아도 되는 상황에서는

정상적인 코딩을 하도록 합니다.

위의 그림에서 보듯이 핸들러를 호출하고

예외를 해결하기 위해

상위 메소드로 계속 올라가는 과정은

그만큼 시간과 비용이 소모됨을 의미합니다.

 

프로그램의 목적이 예외를 처리하기

위한 것은 아니니까 판단을 잘해야 합니다.

 

Exception 에 대한 내용은 단순히

에러 처리에 관한 내용뿐 아니라

파일 등 시스템의 자원관리에 까지

연관이 있습니다. 메모리와 자원을 릴리스하는

IDisposable 인터페이스 등도 같이 사용하니까

연관된 주제에서도 보는게 좋습니다.

공유하기

facebook twitter kakaoTalk kakaostory naver band