Лучшие методики обработки исключений

Хорошо разработанный набор блоков кода обработчиков ошибки может сделать программу более устойчивой и менее подверженной сбоям за счет обработки таких ошибок в приложении. В следующем списке содержатся рекомендации по обработке исключений.

  • Необходимо четко представлять, когда следует устанавливать блок try/catch. Например, можно программным путем проверять условие, которое с большой вероятностью может возникать без обработки исключений. В других ситуациях для перехвата ошибочных условий целесообразнее применить обработку исключений.

    В следующем примере показано использование оператора if для проверки закрытия подключения. Этот метод можно использовать вместо создания исключения в случае, если подключение не закрыто.

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

В следующем примере в случае, если подключение не закрыто, создается исключение.

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);
}

Выбираемый метод зависит от того, насколько часто ожидается возникновение данного события. Если событие носит действительно исключительный характер и представляет собой ошибку (например, неожиданно встретившийся конец файла), то лучше применить обработку исключений, потому что при этом в нормальном состоянии выполняется меньшее количество кода. Если событие встречается регулярно, лучше использовать программную проверку ошибки. В этом случае, если появляется исключение, оно будет обрабатываться дольше.

  • Используйте блоки try/finally, выделив с их помощью тот код, который потенциально может явиться источником исключения, и разместите все операторы catch в одном месте. При таком способе оператор try создает исключение, оператор finally закрывает или освобождает ресурсы, а оператор catch обрабатывает исключение, располагаясь в одном месте.

  • Всегда упорядочивайте исключения в блоках catch, размещая их в порядке убывания определенности. При таком методе определенное исключение обрабатывается до его передачи в блок перехвата более общего исключения.

  • Имена классов исключений завершайте словом "Exception". Примеры.

Public Class MyFileNotFoundException
    Inherits Exception
End Class
public class MyFileNotFoundException : Exception
{
}
public ref class MyFileNotFoundException : public Exception
{
};
  • При создании пользовательских исключений необходимо обеспечить доступность метаданных исключений для кода, исполняемого удаленно, включая и те исключения, которые возникают между доменными приложениями. Например, предположим, что домен приложения А создает домен приложения В, который выполняет код, порождающий исключение. Чтобы домен приложения A правильно перехватил и обработал это исключение, он должен иметь возможность найти сборку, содержащую исключение, вызванное доменом приложения B. Если домен приложения B вызывает исключение, содержащееся в его базе приложения, но не в базе домена приложения A, то домен приложения A не сможет найти исключение и среда CLR создаст исключение FileNotFoundException. Чтобы избежать такой ситуации, можно развернуть сборку, содержащую сведения об исключении, двумя способами.

    • Поместите эту сборку в общую базу приложения, совместно используемую обоими доменами приложений

      - или -

    • Если у этих доменов нет общей базы приложения, то подпишите сборку, содержащую сведения об исключении, строгим именем и разверните ее в глобальном кэше сборок.

  • В C# и С++ при создании своих собственных классов исключений используйте по крайней мере три общих конструктора. Пример см. в разделе Практическое руководство. Создание пользовательских исключений.

  • В большинстве случаев используйте предварительно определенные типы исключений. Определяйте новые типы исключений только для программных сценариев. Вводите новый класс исключений, чтобы предоставить программисту возможность выполнить на основе этого класса другие операции с кодом.

  • Для большинства приложений унаследуйте пользовательские исключения от класса Exception. Изначально предполагалось, что пользовательские исключения должны наследовать от класса ApplicationException, однако на практике это не имеет особого значения.

  • В каждое исключение включайте локализованную строку описания. Сообщение об ошибке, отображаемое пользователю, извлекается из строки описания созданного исключения, а не из класса исключения.

  • Используйте грамматически правильные сообщения об ошибке с соответствующей пунктуацией. Каждое предложение в строке описания должно заканчиваться точкой.

  • Обеспечьте программный доступ к свойствам Exception. Дополнительные сведения (кроме строки описания) включайте в исключение только в тех случаях, когда в соответствии со сценарием программирования такие дополнительные сведения могут оказаться полезными.

  • Для наиболее общих и часто встречающихся ошибок следует возвращать пустое значение. Например, метод Open возвращает null, если файл не найден, но создает исключение, если этот файл заблокирован.

  • Разрабатывайте классы таким образом, чтобы исключение никогда не создавалось при нормальном использовании. Например, класс FileStream предоставляет еще одну возможность для определения факта достижения конца файла. Это позволяет избежать создания исключения, создаваемого в случае выполнения чтения после окончания файла. В следующем примере показан способ чтения до конца файла.

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, если значение свойства или вызов метода не соответствуют текущему состоянию объекта.

Создает исключение ArgumentException или класс, производный от ArgumentException, если передаются недопустимые параметры.

Трассировка стека начинается в операторе, породившем исключение, и завершается оператором catch, перехватывающим это исключение. Не забывайте об этом, принимая решение о месте расположения оператора 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 следует создавать исключения.

  • Устраняйте промежуточные результаты при порождении исключения. Вызывающие объекты должны предполагать, что при создании исключения из метода побочные эффекты не возникают.

См. также

Основные понятия

Обработка и создание исключений