최선의 예외 처리 구현 방법

잘 디자인된 일련의 오류 처리 코드 블록을 사용하면 응용 프로그램이 관련 오류를 처리하게 되므로 프로그램이 중단되거나 크래시될 가능성이 줄어듭니다. 다음 목록 내용은 최선의 예외 처리 구현 방법을 제안한 것입니다.

  • try/catch 블록을 설정할 시점을 알아야 합니다. 예를 들면,발생 가능성이 있는 조건을 예외 처리를 사용하지 않고 프로그래밍 방식으로 검사할 수 있습니다. 그 외의 상황에서는 예외 처리를 사용하여 오류 조건을 catch하는 것이 좋습니다.

    다음 예제에서는 if 문을 사용하여 연결이 끊어졌는지를 검사합니다. 연결이 끊어지지 않은 경우 예외를 throw하는 대신 이 방법을 사용할 수 있습니다.

If conn.State <> ConnectionState.Closed Then
    conn.Close()
End IF
if (conn.State != ConnectionState.Closed)
{
    conn.Close();
}
if (conn->State != ConnectionState::Closed)
{
    conn->Close();
}

다음 예제에서는 연결이 끊어지지 않은 경우 예외가 throw됩니다.

Try
    conn.Close()
Catch ex As InvalidOperationException
    Console.WriteLine(ex.GetType().FullName)
    Console.WriteLine(ex.Message)
End Try
try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex.GetType().FullName);
    Console.WriteLine(ex.Message);
}
try
{
    conn->Close();
}
catch (InvalidOperationException^ ex)
{
    Console::WriteLine(ex->GetType()->FullName);
    Console::WriteLine(ex->Message);
}

어떤 방법을 선택할 것인지는 해당 이벤트의 발생 빈도에 따라 달라집니다. 예를 들어, 예기치 못한 파일 끝(EOF)처럼 이벤트가 예외이면서 오류이면 일반적인 경우 예외 처리를 사용하는 것이 실행되는 코드 양이 적으므로 더 좋습니다. 이벤트가 정기적으로 발생하면 프로그래밍 방법으로 오류를 검사하는 것이 더 좋습니다. 이런 경우에는 발생한 예외의 처리 시간이 오래 걸립니다.

  • 잠재적으로 예외를 생성할 수 있고 catch 문을 한 위치에 집중시킬 수 있는 코드에는 try/finally 블록을 사용합니다. 이런 방법으로 try 문은 예외를 생성하고, finally 문은 리소스를 닫거나 할당을 취소하며, catch 문은 한 위치에서 예외를 처리합니다.

  • 항상 가장 특정한 예외부터 가장 일반적인 예외의 순서로 catch 블록에 예외를 지정합니다. 이렇게 하면 특정 예외가 더 일반적인 catch 블록으로 전달되기 전에 먼저 처리됩니다.

  • 예외 클래스의 이름 뒤에는 "Exception"을 붙입니다. 예를 들면 다음과 같습니다.

Public Class MyFileNotFoundException
    Inherits Exception
End Class
public class MyFileNotFoundException : Exception
{
}
public ref class MyFileNotFoundException : public Exception
{
};
  • 사용자 정의 예외를 만들 때에는 여러 응용 프로그램 도메인에서 예외가 발생할 때 뿐만 아니라 원격에서 실행되는 코드에서도 해당 예외에 대한 메타데이터를 사용할 수 있도록 만들어야 합니다. 예를 들어, 응용 프로그램 도메인 A에서 예외를 throw하는 코드를 실행하는 응용 프로그램 도메인 B를 만든다고 가정합니다. 응용 프로그램 도메인 A에서 예외를 정확하게 catch하고 처리하려면 응용 프로그램 도메인 B에서 throw된 예외를 포함하는 어셈블리를 찾을 수 있어야 합니다. 응용 프로그램 도메인 B에서 응용 프로그램 도메인 A의 응용 프로그램 기본 구조가 아닌 자신의 기본 구조에 있는 어셈블리에 포함된 예외를 throw하면 응용 프로그램 도메인 A에서는 해당 예외를 찾을 수 없으며 공용 언어 런타임은 FileNotFoundException을 throw합니다. 이러한 상황을 방지하기 위해 예외 정보가 포함된 어셈블리를 다음과 같은 두 가지 방법으로 배포할 수 있습니다.

    • 해당 어셈블리를 두 응용 프로그램 도메인이 공유하는 공통 응용 프로그램 기본 구조에 넣습니다.

      -또는-

    • 도메인이 공통 응용 프로그램 기반 구조를 공유하지 않을 경우, 예외 정보가 포함된 어셈블리를 강력한 이름으로 지정한 다음, 이 어셈블리를 전역 어셈블리 캐시에 배포합니다.

  • C# 및 C++에서는 사용자의 고유한 예외 클래스를 만들 때 적어도 세 개의 공통 생성자를 사용합니다. 예제를 보려면 방법: 사용자 정의 예외 만들기를 참조하십시오.

  • 대부분의 경우에는 미리 정의된 예외 형식을 사용합니다. 프로그래밍 시나리오에 대해서만 새 예외 형식을 정의합니다. 새 예외 클래스를 파생시키면 프로그래머가 해당 예외 클래스를 기반으로 다른 동작을 수행하는 코드를 사용할 수 있습니다.

  • 대부분의 응용 프로그램에서는 Exception 클래스에서 사용자 지정 예외를 파생시킵니다. 원래는 사용자 지정 예외를 ApplicationException 클래스에서 파생시켜야 하는 것으로 생각했지만 실제로는 이로 인해 얻는 이점이 그다지 크지 않은 것으로 밝혀졌습니다.

  • 모든 예외에는 지역화된 설명 문자열을 포함시킵니다. 사용자가 오류 메시지를 볼 때는 예외 클래스에서 파생된 설명이 아니라 throw된 예외의 설명 문자열에서 파생된 오류 메시지가 표시됩니다.

  • 문법적으로 올바른 오류 메시지와 구두점을 사용합니다. 설명 문자열의 각 문장의 뒤에는 마침표가 있어야 합니다.

  • 프로그래밍 액세스에는 Exception 속성을 제공합니다. 추가 정보가 필요한 프로그래밍 시나리오에 대해서만 예외에 설명 문자열 이외의 별도 정보를 포함시키십시오.

  • 아주 일반적인 오류의 경우에는 null을 반환합니다. 예들 들어 Open은 파일이 없으면 null을 반환하지만 파일이 잠겨 있으면 예외를 throw합니다.

  • 일반적인 사용에서는 예외가 throw되지 않도록 클래스를 디자인합니다. 예를 들어 FileStream 클래스에서는 또 다른 방법을 사용하여 파일 끝에 도달했는지를 결정할 수 있습니다. 이렇게 하면 파일 끝 이후의 부분을 읽을 때 오류가 throw되는 것을 방지할 수 있습니다. 다음 예제에서는 파일 끝까지 읽는 방법을 보여 줍니다.

Class FileRead
    Public Sub ReadAll(fileToRead As FileStream)
        ' This if statement is optional
        ' as it is very unlikely that
        ' the stream would ever be null.
        If fileToRead Is Nothing Then
            Throw New System.ArgumentNullException()
        End If

        Dim b As Integer

        ' Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin)

        ' Read each byte to the end of the file.
        For i As Integer = 0 To fileToRead.Length - 1
            b = fileToRead.ReadByte()
            Console.Write(b.ToString())
            ' Or do something else with the byte.
        Next i
    End Sub
End Class
class FileRead
{
    public void ReadAll(FileStream fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == null)
        {
            throw new System.ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead.Length; i++)
        {
            b = fileToRead.ReadByte();
            Console.Write(b.ToString());
            // Or do something else with the byte.
        }
    }
}
class FileRead
{
public:
    void ReadAll(FileStream^ fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == nullptr)
        {
            throw gcnew System::ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead->Seek(0, SeekOrigin::Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead->Length; i++)
        {
            b = fileToRead->ReadByte();
            Console::Write(b.ToString());
            // Or do something else with the byte.
        }
    }
};

개체의 현재 상태가 속성 설정이나 메서드 호출에 제공되지 않으면 InvalidOperationException을 throw합니다.

잘못된 매개 변수가 전달되면 ArgumentException에서 파생된 ArgumentException이나 클래스를 throw합니다.

스택 추적은 예외를 throw한 문에서 시작하여 예외를 catch한 catch 문까지 수행됩니다. throw 문을 사용할 위치를 결정할 때 이러한 점에 주의하십시오.

예외 작성기 메서드를 사용합니다. 클래스는 구현된 여러 위치에서 동일한 예외를 throw하는 것이 일반적입니다. 코드를 많이 사용하지 않으려면 예외를 만들어 반환하는 도우미 메서드를 사용합니다. 예를 들면 다음과 같습니다.

Class FileReader
    Private fileName As String


    Public Sub New(path As String)
        fileName = path
    End Sub

    Public Function Read(bytes As Integer) As Byte()
        Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
        If results Is Nothing
            Throw NewFileIOException()
        End If
        Return results
    End Function

    Function NewFileIOException() As FileReaderException
        Dim description As String = "My NewFileIOException Description"

        Return New FileReaderException(description)
    End Function
End Class
class FileReader
{
    private string fileName;

    public FileReader(string path)
    {
        fileName = path;
    }

    public byte[] Read(int bytes)
    {
        byte[] results = FileUtils.ReadFromFile(fileName, bytes);
        if (results == null)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException NewFileIOException()
    {
        string description = "My NewFileIOException Description";

        return new FileReaderException(description);
    }
}
ref class FileReader
{
private:
    String^ fileName;

public:
    FileReader(String^ path)
    {
        fileName = path;
    }

    array<Byte>^ Read(int bytes)
    {
        array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
        if (results == nullptr)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException^ NewFileIOException()
    {
        String^ description = "My NewFileIOException Description";

        return gcnew FileReaderException(description);
    }
};

또는 예외의 생성자를 사용하여 예외를 만듭니다. ArgumentException과 같은 전역 예외 클래스에는 이 방법이 더 적합합니다.

  • 오류 코드 또는 HRESULT를 반환하는 대신 예외를 throw합니다.

  • 예외를 throw할 때 중간 결과를 정리합니다. 호출자가 메서드에서 예외가 throw될 때 의도하지 않은 결과가 발생하지 않는다고 가정할 수 있어야 합니다.

참고 항목

개념

예외 처리 및 Throw