CodeGym /행동 /C# SELF /예외와 그 계층 구조 알아보기

예외와 그 계층 구조 알아보기

C# SELF
레벨 13 , 레슨 0
사용 가능

1. 소개

예를 들어 두 숫자를 나누는 계산기를 만든다고 해보자. 다 잘 되다가 누군가 0으로 나누려고 하면:

int a = 10;
int b = 0;
int result = a / b; // 펑!
0으로 나누면 예외가 발생해

이 순간 프로그램이 "크래시"되고, 사용자는 무서운 에러 메시지를 보게 돼. 이게 바로 예외 상황이야 — "정상"적인 프로그램 흐름에서는 일어나면 안 되는 일이고, 특별한 처리가 필요해.

예외.NET이 네 프로그램에 뭔가 비정상적인 일이 생겼다고 알려주고, 실행을 멈추거나 특별하게 처리해야 한다는 신호를 주는 특별한 메커니즘이야.

런타임 에러(예: 0으로 나누기, 없는 파일에 접근, 또는 — 익숙한 — null 참조 해제)가 발생하면 .NET이 "예외 객체"를 "던져"(throw) 줘. 그러면 이걸 "잡을"(catch) 수 있는 핸들러를 찾게 돼. 그 핸들러가 뭘 해야 할지 알고 있겠지.

왜 "에러 코드"를 반환하지 않을까?

에러 코드를 반환할 수도 있지 — 옛날 언어들(Pascal, C, 그리고 몇몇 현대 API들)도 그렇게 해. 근데 이건 불편하고 위험해: 에러 코드를 체크하는 걸 까먹기 쉽고(다들 잘 될 거라 믿으니까), 어디서 문제가 생겼는지 찾기도 엄청 힘들어. 예외를 쓰면 모든 에러를 한 곳에서 잡고 유연하게 처리할 수 있어서, 코드가 온통 if-check로 더러워지지 않아.

2. 예외 "던지기"

예외는 런타임 에러가 나면 자동으로 발생할 수도 있고, throw 키워드로 직접 던질 수도 있어:


int[] arr = new int[2];
arr[10] = 5; // 예외가 자동으로 발생함 System.IndexOutOfRangeException

throw new Exception("완전 대참사!"); // Exception 예외를 직접 던짐

네 프로그램이 완벽하게 설계됐다고 생각해도, 항상 예상 못한 일이 생길 수 있어: 사용자가 파일을 닫거나, 인터넷이 끊기거나, 누가 컴퓨터 전원을 꺼버릴 수도 있지(거의 그런 느낌).

다행히도, .NET은 예외를 "던지는" 기능뿐 아니라, 예외를 잡고 처리하는 방법도 제공해 — 이건 좀 있다가 다뤄볼게.

3. 예외의 생애주기: throw에서 catch까지

코드에서 뭔가 안 좋은 일이 생기면, .NET이 "비상 절차"를 시작해:

  1. 예외 객체를 만든다 (예: IndexOutOfRangeException).
  2. 그걸 던진다 (throw 키워드로).
  3. 핸들러를 찾는다: 콜스택을 아래에서 위로(현재 메서드에서 호출한 쪽으로) 훑으면서, 던진 예외 타입에 맞는 catch 블록을 찾음.
  4. 핸들러를 찾으면catch 블록 안에서 실행이 계속돼.
  5. 아무 핸들러도 없으면 — 프로그램이 비정상 종료되고, 에러 디테일이 담긴 "콜스택"을 보여줘.

이건 마치 계단에서 공이 튀는 것처럼 — 누가 받아줄 때까지 콜스택을 계속 튀어다녀.

4. .NET의 예외 계층 구조

"부모-자식" 트리

.NET(대부분의 OOP 언어처럼)에서는 예외가 클래스 형태로 구현돼 있고, 서로 상속받으면서 계층 구조를 이뤄.

모든 예외의 공통 부모는 System.Exception이야.


System.Object
    └─ System.Exception
         ├─ System.SystemException
         │     ├─ System.NullReferenceException
         │     ├─ System.IndexOutOfRangeException
         │     ├─ System.DivideByZeroException
         │     ├─ System.OutOfMemoryException
         │     └─ ... 그리고 기타 등등
         ├─ System.IO.IOException
         │     ├─ System.IO.FileNotFoundException
         │     ├─ System.IO.DirectoryNotFoundException
         │     └─ ...
         ├─ System.ArgumentException
         │     ├─ System.ArgumentNullException
         │     └─ System.ArgumentOutOfRangeException
         └─ (네가 직접 만든 Exception 클래스들)
.NET 예외 계층 구조 간단 도식

왜 클래스일까?

클래스 계층 구조 덕분에 한 번에 여러 "종류"의 문제를 잡을 수 있어 — 예를 들어 함수 인자 관련 에러(ArgumentException 및 그 자식들) 전부를 한 번에 catch할 수 있지. 그리고 네가 직접 새로운 에러 타입을 추가할 수도 있어(이건 다음 강의에서 다룰 거야).

.NET에서 자주 만나는 예외들

  • NullReferenceExceptionnull인 객체의 메서드나 프로퍼티에 접근하려고 할 때. 익숙하지?
  • DivideByZeroException — 0으로 나누기.
  • IndexOutOfRangeException — 배열 범위를 벗어남.
  • ArgumentException, ArgumentNullException, ArgumentOutOfRangeException — 잘못된 메서드 파라미터.
  • FileNotFoundException, IOException — 파일 작업 중 문제.
  • InvalidOperationException — 객체의 현재 상태에서 연산이 불가능할 때.
  • FormatException — 데이터 포맷이 잘못됨(예: "abcd"를 숫자로 바꾸려 할 때).

5. 예시: 다양한 예외가 "터지는" 코드

직접 코드를 보면서 어떤 예외가 발생하는지 확인해보자.

예시 1: NullReferenceException

string? str = null;
Console.WriteLine(str.Length); // 여기서 "펑!" — str이 null임

출력:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

예시 2: DivideByZeroException

int a = 42;
int b = 0;
int c = a / b; // 위험! 0으로 나눔

출력:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

예시 3: IndexOutOfRangeException

int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // 인덱스 5는 없음

여기서 인덱스가 범위를 벗어나서, 출력은 다음과 같아:

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.

예시 4: FileNotFoundException

using System.IO;

string content = File.ReadAllText("secret.txt");

"secret.txt"라는 파일이 없으면, 대충 이런 메시지가 나와:

Unhandled exception. System.IO.FileNotFoundException: Could not find file '/Users/zapp/RiderProjects/ConsoleApp1/ConsoleApp1/bin/Debug/net9.0/secret.txt'.

예시 5: FormatException

string input = "십삼";
int number = int.Parse(input); // 문자열이 숫자가 아님

출력:

Unhandled exception. System.FormatException: The input string '십삼' was not in a correct format.

6. 이걸 어떻게 써먹을까?

예외가 어떻게 동작하는지 이해하는 건 진짜 중요해. 이건 그냥 "이론을 위한 이론"이 아니라, 네 프로그램의 안정성에 직접적으로 영향을 주는 실전 스킬이야. 에러를 제대로 처리할 줄 알면, 프로그램이 첫 번째 에러에서 "펑!" 하고 죽지 않고, 사용자에게 뭘 잘못했는지 친절하게 알려줄 수 있어. 그럼 훨씬 더 신뢰할 수 있고, 프로페셔널하고, 사람 친화적인 프로그램이 되지.

그리고 이건 단순히 편의성만의 문제가 아니야. 면접에서도 예외 처리 메커니즘, Exception 타입, 그리고 .NET에서 이게 어떻게 동작하는지 자주 물어봐. 그러니까 이걸 잘 이해하면 취업에도 플러스야.

게다가, 실제 프로젝트에서 예외는 계속 만나게 될 거야. 파일, 네트워크, 데이터베이스, 외부 라이브러리, 프레임워크 — 전부 예외 시스템을 적극적으로 써. 그러니까 미리 제대로 알아두면 나중에 당황할 일 없지.

7. 예외 처리에서 흔히 하는 실수

실수 1: 예외 자체를 무시함.
어떤 초보(가끔은 고수도)들은 예외를 귀찮거나 쓸데없는 걸로 생각해. 아예 무시하거나, 더 심하게는 코드를 catch { }로 감싸놓고 아무것도 안 해. 그러면 프로그램이 "조용히" 이상한 상태로 계속 돌아가고, 문제 원인을 찾을 수가 없어.

실수 2: 예외와 일반 에러를 헷갈림.
모든 에러를 try-catch로 잡아야 하는 건 아니야. 예를 들어 사용자가 잘못된 데이터를 입력했다면, FormatException에 기대지 말고 직접 먼저 체크하는 게 좋아. 예외는 진짜 예외적인 상황에 쓰는 거고, 일상적인 건 if로 처리하는 게 맞아.

실수 3: 예외 계층 구조를 잘못 이해함.
너무 넓은 클래스(Exception)로 잡으려고 하면, 원래 처리하지 말아야 할 에러까지 다 잡아버려. 반대로 너무 좁은 타입(IndexOutOfRangeException, NullReferenceException)만 잡으면, 다른 예외는 놓칠 수 있어. .NET의 예외 계층 구조를 잘 이해하고, 뭘 처리할지 명확히 해야 해.

그리고 제일 중요한 건: C#에서 예외는 사고나 "끝장" 신호가 아니라, 그냥 특별한 실행 흐름으로 전환하는 메커니즘이야. 잘만 쓰면 진짜 강력한 도구야.

2
과제
C# SELF, 레벨 13, 레슨 0
잠금
예외 발생 간단 시연
예외 발생 간단 시연
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION