푸터 콘텐츠로 바로가기
Iron Academy Logo
C# 배우기
C# 배우기

다른 카테고리

C# 예외 처리

Tim Corey
59분 46초

예외 처리는 견고한 애플리케이션 개발에 있어 매우 중요한 요소입니다. Tim Corey의 " C#에서 예외 처리 - 언제, 어디서, 어떻게 처리해야 하는가 "라는 제목의 비디오는 예외가 무엇인지, 어떻게 처리해야 하는지, 그리고 어디에서 처리해야 하는지에 대한 자세한 설명을 제공합니다.

이 글에서는 Tim Corey의 영상을 활용하여 C#에서의 예외 처리 방법을 설명합니다. 이는 개발자가 프로그램 실행 중에 발생하는 오류 및 예외 상황을 관리할 수 있도록 해주는 강력한 기능입니다. C#은 try, catch, finally 블록을 사용하여 런타임 오류를 처리하고, 예외를 기록하고, 프로그램 흐름을 유지하는 구조화된 방법을 제공합니다.

소개

팀은 많은 개발자들이 예외와 그 처리 방식에 대해 잘못된 견해를 가지고 있다고 설명하며 이야기를 시작합니다. 그는 더욱 견고한 애플리케이션을 만들기 위해 예외가 무엇인지, 어디서 어떻게 적절하게 처리해야 하는지를 이해하는 것이 중요하다고 강조합니다.

데모 콘솔 애플리케이션 구축하기

Tim은 예외 처리를 시연하기 위해 Visual Studio 2017에서 콘솔 애플리케이션을 만듭니다. 그는 새로운 주제를 테스트할 때 콘솔 애플리케이션을 사용하는 것을 권장하는데, 그 이유는 콘솔 애플리케이션이 최소한의 설정만 필요하고 사용하기 쉽기 때문입니다.

using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}
using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}

클래스 라이브러리 생성

Tim은 서로 다른 메서드들이 서로를 호출하는 실제 시나리오를 시뮬레이션하기 위해 솔루션에 클래스 라이브러리를 추가합니다.

Csharp Exception Handling 1 related to 클래스 라이브러리 생성

그는 기본 클래스를 삭제하고 DemoCode라는 이름의 새 클래스를 생성합니다.

public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}
public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}

DemoCode 클래스는 서로를 호출하는 메서드를 포함하고 있으며, 최종적으로 주어진 위치에 따라 배열에서 숫자를 가져옵니다.

예외 시뮬레이션

팀은 해당 애플리케이션이 성공 사례보다는 실패 사례를 보여주기 위한 것이라고 설명합니다. 그는 GrandparentMethod에 유효하지 않은 위치를 전달하여 범위를 벗어난 예외를 발생시킵니다.

DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");

위 코드를 유효하지 않은 위치로 실행하면 IndexOutOfRangeException이 발생합니다. Tim은 Visual Studio 디버거가 문제를 강조 표시하고 예외에 대한 자세한 정보를 제공하는 방법을 보여줍니다.

try-catch를 잘못 사용하는 방법

팀은 개발자들이 try-catch 블록을 처음 배울 때 흔히 저지르는 실수를 설명합니다. 그들은 예외가 발생할 것으로 예상되는 코드 블록 전체를 감싸는 경우가 많은데, 이는 부적절한 처리로 이어질 수 있습니다.

try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}
try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}

팀은 이러한 접근 방식이 예외를 숨기고 잘못된 가정을 바탕으로 실행을 계속하기 때문에 문제가 있다고 지적합니다. 예를 들어, 기본값으로 0을 반환하는 것은 적절하지 않을 수 있으며 추가적인 문제를 야기할 수 있습니다.

올바른 예외 처리

팀은 예외 처리가 애플리케이션의 예상치 못한 상태에 대한 중요한 정보를 제공한다고 강조합니다. 애플리케이션이 적절한 처리 없이 이러한 상태로 계속 작동하면 추가적인 오류 및 데이터 손상이 발생할 수 있습니다.

예외를 그냥 넘기는 대신, 적절하게 처리하는 것이 필수적입니다. 더 나은 접근 방식은 다음과 같습니다.

try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}

예외를 다시 던짐으로써 문제가 전파되어 필요한 경우 더 높은 수준에서 처리될 수 있도록 보장할 수 있습니다.

사용자에게 유용한 정보 제공

팀은 일부 예외는 애플리케이션이 충돌하지 않고도 정상적으로 처리될 수 있지만, 사용자에게 유용한 피드백을 제공하는 것이 중요하다고 설명합니다. 예를 들어, 작업을 다시 시도할 수 있는 옵션이 포함된 메시지 상자나 알림을 표시하는 것입니다.

추가 정보: 스택 트레이스

Tim은 예외 객체의 StackTrace 속성을 사용하여 예외가 발생한 위치에 대한 자세한 정보를 얻는 방법을 보여줍니다. 여기에는 클래스, 메서드 및 줄 번호가 포함되어 있어 디버깅에 매우 유용합니다.

try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}

StackTrace 속성은 호출 스택의 전체 추적 정보를 제공하여 개발자가 문제 발생 위치를 정확하게 파악할 수 있도록 도와줍니다.

트라이-캐치의 적절한 위치 선정

팀은 예외를 제대로 처리하는 것은 단순히 예외를 잡아내는 것뿐만 아니라 try-catch 블록을 어디에 배치해야 하는지 아는 것도 중요하다고 설명합니다. 핵심은 예외를 적절하게 처리할 수 있을 만큼 충분한 컨텍스트가 있는 수준에 try-catch 블록을 배치하는 것입니다.

부적절한 배치 사례

try-catch 블록을 호출 스택 깊숙이 배치하면 상위 수준 작업의 컨텍스트를 알 수 없기 때문에 예외를 효과적으로 처리하지 못하는 경우가 많습니다.

// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}
// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}

올바른 배치 예시

try-catch 블록을 최상위 레벨, 예를 들어 사용자 인터페이스나 애플리케이션의 진입점에 배치하면 작업의 전체 컨텍스트를 고려하여 예외를 처리할 수 있습니다.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

이렇게 하면 사용자에게 더 자세한 정보를 제공하고 애플리케이션을 계속 실행할지 아니면 종료할지 결정할 수 있습니다.

스택 추적 정보

팀은 예외 진단에 있어 스택 추적 정보의 중요성을 강조합니다. 스택 트레이스는 예외가 발생한 위치와 예외로 이어진 일련의 메서드 호출 과정을 보여주는 자세한 호출 기록을 제공합니다.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

이 출력은 예외가 발생한 정확한 위치와 코드에서 예외가 발생한 경로를 보여주므로 디버깅 및 문제 해결이 더 쉬워집니다.

핸들링 로직 데모

팀은 적절한 수준에서 논리를 다루는 방법을 보여줍니다. 예를 들어, 데이터베이스 연결을 열고 닫는 역할을 하는 메서드는 리소스가 적절하게 관리되도록 예외 처리를 해야 합니다.

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}

이 예시에서는 예외가 발생할 경우 데이터베이스 연결이 제대로 닫히지 않아 리소스 누수가 발생할 가능성이 있습니다. try-catch 블록을 추가하면 예외가 발생하더라도 연결이 닫히도록 할 수 있습니다.

finally 블록을 사용하기

Tim은 예외 발생 여부와 관계없이 특정 코드가 실행되도록 하는 finally 블록을 소개합니다. 이는 데이터베이스 연결을 닫는 것과 같은 리소스 정리 작업에 특히 유용합니다.

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}

finally 블록은 try 및 catch 블록 다음에 실행되어 예외가 발생하더라도 연결이 닫히도록 합니다.

던지기 진술

팀은 예외를 호출 스택 상위로 전달하기 위해 예외를 다시 던지는 것이 중요하다고 설명합니다. 이를 통해 상위 레벨 핸들러가 예외를 적절하게 처리할 수 있습니다.

catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}

throw를 사용하여 예외를 다시 던집니다. 전체 스택 트레이스가 보존되어 디버깅에 유용한 컨텍스트를 제공합니다.

예외 처리를 제대로 하는 방법

팀은 예외가 호출 스택을 통해 어떻게 위로 전달되는지 보여줍니다. 각 메서드는 try-catch 블록을 확인하고 예외를 처리하거나 호출자에게 전달합니다.

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

이 예제에서 GrandparentMethod는 예외를 포착하고, 로그에 기록한 다음, 다시 예외를 던집니다. 콘솔 애플리케이션의 최상위 try-catch 블록은 예외를 처리하고 오류 메시지와 스택 추적을 표시합니다.

예외 처리 시 흔히 저지르는 실수

Tim은 개발자들이 예외를 처리할 때 흔히 저지르는 몇 가지 실수를 지적합니다.

  1. throw 사용 예시:

    • 스택 트레이스를 다시 작성하여 중요한 컨텍스트를 잃게 됩니다.

    • 예:
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
  2. 새로운 예외 발생시키기 :

    • 사용자 지정 메시지가 포함된 새 예외를 생성하지만 원래 스택 추적 정보는 손실됩니다.

    • 예:
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }

원래 스택 트레이스를 유지하면서 새 예외 생성하기

팀은 원래 스택 트레이스를 유지하면서 새로운 예외를 생성하는 방법을 설명합니다. 이는 원래 오류의 맥락을 유지하면서 보다 의미 있는 오류 메시지나 다른 예외 유형을 제공하려는 경우에 유용할 수 있습니다.

catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}

원래 예외(예)를 내부 예외로 전달하면 디버깅에 필수적인 원래 스택 추적을 유지할 수 있습니다.

스택 추적 정보 보존

Tim은 새 예외를 생성할 때 원래 예외의 메시지와 스택 추적에 액세스하는 방법을 보여줍니다.

catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}

이렇게 하면 스택 위로 발생하는 예외에 새로운 메시지와 원래 예외에 대한 세부 정보가 모두 포함됩니다.

내부 예외를 순회하기

Tim은 모든 내부 예외를 순회하여 메시지와 스택 추적을 추출하는 방법을 제공합니다.

catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}
catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}

이 루프는 각 내부 예외를 순회하며 스택 추적을 출력하여 모든 계층의 예외를 처리합니다.

서로 다른 예외 상황을 다르게 처리하기

Tim은 여러 개의 catch 블록을 사용하여 다양한 유형의 예외를 처리하는 방법에 대해 설명합니다. 이를 통해 예외 유형에 따라 특정 처리를 수행할 수 있습니다.

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

이 예제에서는 ArgumentException을 특별히 처리하여 사용자 지정 메시지를 출력하는 반면, 다른 모든 예외는 예외 메시지와 스택 추적을 출력하는 일반 핸들러를 사용합니다.

Csharp Exception Handling 2 related to 서로 다른 예외 상황을 다르게 처리하기

다중 캐치 블록에서 순서의 중요성

팀은 여러 개의 캐치 블록을 사용할 때 순서가 중요하다는 점을 강조합니다. 가장 구체적인 예외 사항부터 먼저 처리하고, 그 다음으로 일반적인 예외 사항을 처리해야 합니다.

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

특정 예외를 처리하는 블록보다 일반적인 예외 처리 블록이 먼저 나타나면, 일반적인 예외 처리 블록이 모든 예외를 처리하게 되어 특정 예외를 처리하는 블록은 실행되지 않고 컴파일 오류가 발생합니다.

결론

Tim Corey의 C# 예외 처리에 대한 고급 비디오 가이드에서는 새로운 예외 생성, 스택 추적 유지, 여러 개의 catch 블록을 효과적으로 사용하는 방법 등 중요한 기술을 다룹니다. 개발자들은 그의 모범 사례를 따르면 예외를 적절하게 처리하고 유용한 디버깅 정보를 제공하는 견고한 애플리케이션을 만들 수 있습니다.

Hero Worlddot related to C# 예외 처리
Hero Affiliate related to C# 예외 처리

사랑하는 것을 공유하여 더 많은 수익을 얻으세요

당신은 .NET, C#, Java, Python, 또는 Node.js를 다루는 개발자를 위한 콘텐츠를 만드나요? 당신의 전문성을 추가 수입으로 전환하세요!

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해