Обработка ошибок

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

private void open_file( string filename ) 
{
  try 
  {
    string text;
    size_t size;
    FileUtils.get_contents( filename, out text, out size );
    this.text_view.buffer.set_text( text, (int) size );
  } 
  catch( Error e ) 
  {
    stderr.printf( "Ошибка: %s\n", e.message );
  }
}

В конструкции try {} catch {} не видно никаких отличий от аналогов из C++, C# и Java. Особенности обнаруживаются на более низком уровне – в реализации. В библиотеке GLib имеется средство управления исключениями времени выполнения GError. Vala преобразует его в более привычную "современную" форму, но сам по себе GError изначально был предназначен для обработки так называемых восстановимых ошибок времени выполнения, о которых ничего не известно до начала выполнения, и которые не приводят к аварийному завершению программы. Поэтому не следует напрямую использовать GError для тех исключительных ситуаций, которые можно "предсказать", например, передача отрицательного числа в функцию, которая требует параметр только со значением, большим нуля.

При возникновении необходимости в написании специфического кода для обработки исключений общая схема работы точно такая же, как при использовании любого другого языка из "C-группы". Все функции, являющиеся потенциальными источниками исключений, определяются с ключевым словом throws. Везде, где это необходимо в коде, записываются команды генерации исключений throw. Вызовы потенциально "опасных" функций помещаются в блоки try-catch.

Отличия проявляются при определении типов обрабатываемых исключений (ошибок). Исключения обладают тремя характеристиками: домен (domain), код (code) и сообщение (message). Домен определяет тип ошибки. Ближайший аналог – подкласс класса Exception в языке Java. Каждый домен исключения может содержать один или несколько кодов ошибок:

errordomain IOError     // имя домена
{
  FILE_NOT_FOUND        // код ошибки
}

Таким образом, можно определить не только обобщённый тип возникающей проблемы, но и конкретную ошибку. Что касается сообщений, то мы уже встречались с ними – это текст, выводимый при возникновении исключительной ситуации.

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

Всё, сказанное выше, можно проиллюстрировать следующим примером:

errordomain ErrType01
{
  ERROR_CODE_01
}
 
errordomain ErrType02
{
  ERROR_TYPE_02
}
 
public class MyClass : GLib.Object
{
  public static void myfunc_throw() throws ErrType01, ErrType02
  {
    throw new ErrType01.ERROR_CODE_01( "Ошибка с кодом 01" );
  }
 
  public static void myfunc_catch() throws ErrType02
  {
    try
    {
      myfunc_throw();
    }
    catch( ErrType01 e )
    {
      // Здесь обрабатываются ошибки из домена ErrType01
    }
    finally
    {
      // Освобождение ресурсов и прочие необходимые операции
    }
  }
 
  public static int main( string[] args )
  {
    try
    {
      myfunc_catch();
    }
    catch( ErrType02 e )
    {
      // Здесь обрабатываются ошибки из домена ErrType02
    }
    return 0;
  }
}

Здесь метод myfunc_throw может генерировать исключения из обоих доменов, определённых в программе. Метод myfunc_catch способен генерировать исключения только второго типа, поэтому обязан обеспечить обработку ошибок из домена ErrType01. В свою очередь, метод main обеспечивает обработку всех ошибок, которые могут возникать при работе метода myfunc_catch.

Last updated