1. 소개
이상적인 세상에서는 코드도 완벽해야겠지. 하지만 현실은 아무리 꼼꼼한 개발자라도 언젠가는 예상치 못한 상황을 만나게 돼: 파일을 못 찾거나, 사용자가 숫자 대신 문자열을 입력하거나, 네트워크가 갑자기 끊기거나, 데이터베이스에서 뭔가 이상한 값이 올 수도 있어.
이런 문제들을 잡지 않으면 — 프로그램이 그냥 "크래시"돼버려. 때로는 이상한 에러 메시지랑 stack trace(스택 트레이스)만 남기고 말이야. 이건 사용자나 나중에 지원팀이 원하는 게 아니지. 이런 에러들을 "잡아서" 잘 처리하는 법을 배워야 해: 프로그램을 조용히 종료시키거나, 사용자한테 이해하기 쉬운 메시지를 보여주거나, 심지어 상황을 바로잡을 수도 있지.
try-catch는 컴파일러랑 런타임한테 이렇게 말하는 거야: "이 코드 한 번 실행해봐. 만약 문제가 생기면, 당황하지 말고 내가 처리할 기회를 줘!"
2. try-catch의 기본 문법
전체 구조는 진짜 간단해:
try
{
// 여기서 위험하거나(혹은 위험할 수도 있는) 코드를 작성해
}
catch (ExceptionType variableName)
{
// ExceptionType 타입의 예외가 발생하면 여기서 처리해
}
- try 블록 — "위험 구역"이야. 여기에는 잠재적으로 "터질" 수 있는 작업들을 넣어.
- catch 블록 — 특정 타입의 예외를 잡아. 만약 try 안에서 예외가 발생하면, 바로 첫 번째로 맞는 catch로 점프하고, try 안의 "문제 있는 부분" 뒤에 있는 코드는 실행되지 않아.
예시: 간단한 예외 처리
예를 들어, 지난 강의에서 만든 미니 계산기가 이제 숫자를 나눌 수 있다고 해보자. 근데 사용자가 0을 입력할 수도 있잖아! 에러 처리를 안 하면 어떻게 되는지 보자:
int a = 10;
int b = 0;
int result = a / b; // 쾅! System.DivideByZeroException
Console.WriteLine(result);
프로그램은 DivideByZeroException 에러와 함께 강제 종료돼:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
이제 try-catch로 "고쳐보자":
int a = 10;
int b = 0;
try
{
int result = a / b; // 위험한 연산
Console.WriteLine("나눗셈 결과: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("앗! 0으로 나눌 수 없어: " + ex.Message);
}
이제 프로그램이 "크래시"되지 않아. 콘솔에는 0으로 나눌 수 없다는 메시지가 나와:
앗! 0으로 나눌 수 없어: Attempted to divide by zero.
3. try-catch는 어떻게 동작할까: 단계별로 보기
사실 try 안에는 코드가 몇 줄이든 상관없어. 만약 그 안에서 어떤 동작이 예외를 던지면, 바로 가장 가까운 맞는 catch로 점프해. try 블록에서 에러가 난 이후 코드는 실행되지 않아.
좀 더 큰 예시를 보자:
try
{
Console.WriteLine("시작한다...");
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[1]); // 이건 OK
Console.WriteLine(numbers[5]); // 에러: 배열 인덱스 초과
Console.WriteLine("이 메시지는 더 이상 안 나와!");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("에러: 존재하지 않는 배열 요소에 접근하려고 했어");
}
콘솔 출력은 이렇게 돼:
시작한다...
2
에러: 존재하지 않는 배열 요소에 접근하려고 했어
Process finished with exit code 0.
이 프로그램은 어떻게 동작할까?
- "시작한다..." 메시지가 출력돼.
- 그 다음 numbers[1]을 출력하려고 하는데 — 값은 2야.
- numbers[5]에 도달하면 "사고"가 나고, IndexOutOfRangeException이 "던져져".
- 프로그램은 바로 catch 블록으로 점프하고, Console.WriteLine("이 메시지는 더 이상 안 나와!");는 건너뛰어.
- 콘솔에는 catch에서 쓴 메시지가 나와.
4. 서로 다른 에러 타입을 다르게 처리할 수 있을까?
당연하지! catch 블록을 여러 개 써서, 예외 타입마다 다르게 반응할 수 있어. 예를 들어, 어떤 동작은 파일 읽기랑 관련 있고(FileNotFoundException이 날 수 있음), 다른 건 나눗셈(DivideByZeroException이 날 수 있음)이라면 각각 다르게 처리할 수 있지.
try
{
// 너의 위험한 코드
}
catch (DivideByZeroException)
{
Console.WriteLine("0으로 나누기 에러");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("에러: 배열 인덱스 범위 초과");
}
catch (Exception ex)
{
Console.WriteLine("뭔가 예기치 못한 에러: " + ex.Message);
}
여기서 마지막 catch 블록(Exception 타입)은 "만능 잡이"야. 앞에 있는 블록에 안 맞는 모든 예외를 잡아. 근데 주의! 이걸 제일 위에 쓰면, 그 아래 블록들은 절대 실행되지 않아! 항상 "넓은" catch는 맨 마지막에 둬.
5. 예외 객체는 어떻게 생겼을까?
catch 블록 안에서 변수(예: catch (Exception ex))를 선택적으로 쓸 수 있어. 이 변수에는 에러에 대한 모든 정보가 들어있어: 메시지, 에러 타입, 스택 트레이스, 중첩된 예외 등등.
간단한 예시:
try
{
string? text = null;
Console.WriteLine(text.Length); // 오잉! NullReferenceException
}
catch (NullReferenceException ex)
{
Console.WriteLine("예외 잡았다: " + ex.Message);
Console.WriteLine("스택 트레이스: " + ex.StackTrace);
}
이런 방식은 복잡한 에러를 디버깅할 때 진짜 유용해: 프로그램이 어디서, 왜 "넘어졌는지" 항상 볼 수 있거든.
6. 에러를 잡고 계속 진행하기
실제, 특히 사용자용 프로그램에서는 에러가 세상의 끝이 아니야. 누가 파일 경로를 잘못 입력할 수도 있지. 그렇다고 프로그램이 멘붕하면서 재시작할 필요는 없어. 대신, 그냥 다시 시도해보라고 안내하면 돼.
아래 예시는 간단하지만 실전에서 잘 쓰는 방법이야: 파일 읽기를 반복문으로 감싸고, 뭔가 잘못되면 에러 메시지를 보여주고 다시 시도하게 해. 당황하지 말고, 빨간 화면도 없이.
bool success = false;
while (!success)
{
Console.Write("파일 이름을 입력하세요: ");
string fileName = Console.ReadLine() ?? "";
try
{
string content = File.ReadAllText(fileName);
Console.WriteLine("파일을 성공적으로 읽었어!");
success = true; // 만세!
}
catch (FileNotFoundException)
{
Console.WriteLine("에러: 파일을 찾을 수 없어. 다시 시도해봐.");
}
}
여기서 핵심은 — 예외를 그냥 잡는 게 아니라, 제대로 처리하는 거야: 프로그램이 크래시되지 않게 하고, "다시 제대로 해보자" 모드로 전환해. 사용자는 친절한 안내를 받고, 프로그램은 두 번째 기회를 얻고, 넌 칭찬과 존경을 받지!
7. try-catch 쓸 때 흔한 실수들
실수 1: 전체 코드를 하나의 만능 try-catch로 감싸기.
이게 편해 보여 — try 하나, catch (Exception) 하나, 그러면 "다 안전하다" 싶지. 근데 실제로는 상황을 통제할 수 없게 돼. 어디서 에러가 났는지, 어떤 에러에 반응하는지 알 수 없어. 결국 신뢰성 대신 진단 불가 상태가 돼버려.
실수 2: 예외를 잡고 아무것도 안 하기.
실전 예시:
try
{
// 뭔가 위험한 작업
}
catch
{
// 조용...
}
이런 코드는 말 그대로 "뭔가 잘못됐네... 뭐, 신경 안 써"라는 뜻이야. 위험해: 프로그램이 잘못된 상태로 계속 돌아갈 수도 있고, 에러가 났다는 사실조차 모를 수도 있어. 최소한 메시지라도 출력하거나, 로그라도 남겨. 빈
catch는 절대 쓰지 마!
실수 3: 너무 넓은 예외 타입을 잡기.
Exception을 잡으면서, 사실 FormatException이나 FileNotFoundException이 날 수 있다는 걸 알면서도 그렇게 하면, 구체적인 문제에 제대로 대응할 기회를 놓쳐. catch가 좁고 정확할수록, 프로그램이 예외 상황에서 더 예측 가능하고 똑똑하게 동작해.
8. 예외를 이렇게 쓰면 안 되는 경우
가끔 초보자들이 예외를... 그냥 일반적인 로직 제어 도구처럼 쓰기도 해. 예를 들면:
try
{
if (x < 0) throw new Exception("x는 >= 0이어야 해");
}
catch (Exception)
{
// 상태 롤백, 계속 진행...
}
이렇게 하면 안 돼! 예외는 예상치 못한, 드문 상황(파일 읽기 에러, 네트워크 장애 등등)에 쓰는 도구야. 사용자 데이터 유효성 검사 같은 평범한 체크에는 쓰지 마. 그런 경우엔 조건문(if, else)을 쓰고, 뭔가 잘못됐으면 특별한 값을 반환해.
GO TO FULL VERSION