Введение в программирование

Обработка ошибок и исключения

Методы обработки ошибок

Содержание

Традиционные методы

Содержание

Пример: Комплексные числа (1)

  • class Complex {
        private final double re;
        private final double im;
        public Complex(double re, double im) {
            this.re = re;
            this.im = im;
        }
        …
    

Пример: Комплексные числа (2)

  • class Complex {
        …
        Complex divide(Complex c) {
            double d = c.re * c.re + c.im * c.im;
            return new Complex(
                (re * c.re + im * c.im) / d,
                (im * c.re - re * c.im) / d
            );
        }
    

Методы обработки ошибок

  • Не обрабатывать
  • Коды возврата
  • Установка флага ошибки
  • Вызов метода обработки ошибки
  • Завершение программы

Возврат несуществующего значения

  • Изнутри
    if (Math.abs(d) < EPS) {
        return null;
    }
    
  • Снаружи
    Complex c = a.divide(b);
    if (c != null) {
        // Ok
    } else {
        // Обработка ошибки
    }
    

Код возврата

  • Изнутри
    • if Abs(d) < EPS {
          return nil, fmt.Errorf("math: division by zero %g", f)
      }
      ...
      return value, nil
      
  • Снаружи
    • d, err := Divide(a, b)
      if err != nil {
          // Ok
      } else {
          // Обработка ошибки
      }
      

Установка флага ошибки

  • Изнутри
    if (Math.abs(d) < EPS) {
        Complex.error = true;
        return this;
    }
    
  • Снаружи
    Complex c = a.divide(b);
    if (!Complex.error) {
        // Ok
    } else {
        // Обработка ошибки
    }
    

Вызов метода обработки ошибок

  • Изнутри
    if (Math.abs(d) < EPS) {
        return Complex.divisionByZeroError(a, b);
    }
    
  • Снаружи
    Complex.setDivisionByZeroHandler(...);
    
    Complex c = a.divide(b);
    // Можно ли доверять c?
    

Завершение программы

  • Изнутри
    if (Math.abs(d) < EPS) {
        System.exit(0);
        return this;
    }
    
  • Снаружи
    // Без права на ошибку
    Complex c = a.divide(b);
    

Возврат обёрнутого значения (1)

  • Изнутри
    • if (Math.abs(d) < EPS) {
          return Result.error(DBZ, "Division by zero");
      }
      ...
      return Result.success(value);
      
  • Снаружи
    • switch (a.divide(b)) {
          case Success(d) -> {
              // Ok
          }
          case Error(t, message) -> {
              // Обработка ошибки
          }
      }
      

Возврат обёрнутого значения (2)

  • Комбинаторы
    • a.divide(b)
          .onSuccess(d -> d.divide(c))
          .recover(t, message) -> Complex.ZERO);
      

Исключения

Содержание

Применение исключений (1)

  • Определение исключения
    class DBZComplexException extends Exception {
        private final Complex dividend;
        public DBZComplexException(Complex d) {
            this.dividend = d;
        }
        public Complex getDividend() {
            return dividend;
        }
    }
    

Применение исключений (2)

  • Изнутри
    Complex divide(Complex c)
        throws DBZComplexException
    {
        …
        if (Math.abs(d) < EPS) {
            throw new DBZComplexException(d);
        }
        …
    

Применение исключений (3)

  • Снаружи
    try {
        Complex c = a.divide(b);
        // Безопасное использование результата.
    } catch (DBZComplexException e) {
        // Обработка ошибки
    }
    
  • Или
    Complex calculate(…) throws DBZComplexEx… {
        Complex c = a.divide(b);
        // Безопасное использование результата.
    

Исключения в Java

Содержание

Причины ошибок

Пример исключения

  • AssertionError
    • Неожиданное состояние программы
  • Варианты использования
    • throw new AssertionError(message) – недостижимое состояние
    • assert statement [ : message]; – проверка утверждения

Иерархия исключений в Java

Проверяемые исключения

  • Поймать
    try {
        // …
    } catch (*Exception e) {
        // Обработка
    }
    
  • Объявить, что метод может бросить исключение
    void method() throws *Exception { 

Собственная иерархия

  • Exception
    • ComplexException
      • DBZComplexException
      • LogZeroComplexException
  • Пример
    try {
        // throws LogZeroComplexException
    } catch (ComplexException e) {
        // Обработка
    }
    

Информация об исключениях

Реализация исключения

  • public ComplexException extends Exception {
        public ComplexException(String message) {
            super(message);
        }
        public ComplexException(
             String message,
             Throwable cause
        ) {
            super(message, cause);
        }
    }
    

Конструкция try-catch-finally

  • try {
        // Действия
    } catch (*Exception e) {
        // Обработка исключения
    } catch (*Exception e) {
        // Обработка исключения
    } finally {
        // Действия при выходе
    }
    

Применение исключений в коде

Содержание

Шаблоны использования

Содержание

Выделение кода обработки ошибок

  • Ошибки обрабатываются там, где для этого достаточно информации
    try {
        f();
    } catch (*Exception e) {
    }
    
    f() { … g(); … }
    
    g(){ … throw new *Exception(…); … }
    

Управление ресурсами

// Получение ресурса
try {
    // Действия с ресурсом
} finally {
    // Освобождение ресурса
}

Управление на исключениях

  • try {
        int index = 0;
        while (true) {
            System.out.println(a[index++]);
        }
    } catch (IndexOutOfBoundsException e) {
    }
    

Игнорирование исключений

  • Полное игнорирование
    try {
        ...
    } catch (*Exception e) { }
    
  • Запись в лог / на консоль
    try {
        ...
    } catch (*Exception e) {
        e.printStackTrace();
    }
    

Ловушки базовых исключений

  • try {
        ...
    } catch (RuntimeException e) {
        // ?
    } catch (Exception e) {
        // ??
    } catch (Throwable e) {
        // ???
    }
    

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

Содержание

Проверяемые и непроверяемые

  • Что пользователь может сделать с исключением?
    • Ничего ⇒ непроверяемое исключение
    • Что-то осмысленное ⇒ проверяемое исключение

Сохранение инкапсуляции

  • Пользователи не должны знать об устройстве класса
  • Обертывание исключений
    • В непроверяемые
    • В проверяемые
  • Игнорирование исключений

Обертывание исключений

  • Правильно
    try { ...
    } catch (IOException e) {
        throw new APISpecificException(message, e);
    }
    
  • Неправильно
    try { ...
    } catch (IOException e) {
        e.printStackTrace();
        throw new APISpecificException(message);
    }
    

Try с ресурсами (1)

  • Синтаксис
    try (
        Type var1 = …;
        Type var2 = …;
        …
    ) {  … }
    
  • Пример
    try (InputStream is = new FileInputStream("in")) {
        System.out.println(is.read());
    }
    

Try с ресурсами (2)

Одинаковая обработка

  • Было
    • try {
          …
      } catch (SQLException e) {
          throw new APISpecificException("Access error", e);
      } catch (IOException e) {
          throw new APISpecificException("Access error", e);
      }
      
  • Стало
    • try {
          …
      } catch (IOException | SQLException e) {
          throw new APISpecificException("Access error", e);
      }
      

Гарантии безопасности

Содержание

Пример с исключениями

  • а() вызывает b() и ловит исключение
  • b() вызывает c()
  • p() вызывает q()
  • x() бросает исключение

Типы гарантии безопасности

  • Отсутствие гарантий
    • No exceptional safety
  • Отсутствие утечек
    • No-leak guarantee
  • Слабые гарантии
    • Weak exceptional safety
  • Сильные гарантии
    • Strong exceptional safety
  • Гарантия отсутствия исключений
    • No throw guarantee

Пример: Обеспечение гарантий

  • Класс
    class CloneablePair {
        Cloneable first;
        Cloneable second;
    }
    
  • Метод
    /** Копирует пару. 
        Элементы пары клонируются. */
    void copyOf(CloneablePair p)
    

Отсутствие утечек

  • Память – не ресурс
    void copyOf(CloneablePair p) {
        first = p.first.clone();
        second = p.second.clone();
    }
    

Отсутствие утечек с ресурсами

  • Отмена выделения ресурса
    void copyOf(CloneablePair p) {
        first = p.first.clone();
        try {
            second = p.second.clone();
            try { // Другие действия }
            catch (*Exception e) { 
                second.close();
                throw e;
            }
        } catch (*Exception e) {
            first.close();
            throw e;
        }
    }
    

Слабые гарантии

  • Корректное, но неизвестное состояние
  • Сначала – сделать
    • Потом – присвоить
  • void copyOf(CloneablePair p) {
        Cloneable newFirst = p.first.clone();
        second = p.second.clone();
        first = newFirst;
    }
    

Слабые гарантии с ресурсами

  • Отмена выделения ресурса
    void copyOf(CloneablePair p) {
        Cloneable newFirst = p.first.clone();
        try {
            second = p.second.clone();
            try { // Другие действия }
            catch (*Exception e) { … }
            first = newFirst;
        } catch (*Exception e) {
            newFirst.close();
            throw e;
        }
    }
    

Сильные гарантии

  • Состояние не изменяется
    • Cloneable newFirst = p.first.clone();
      try {
          Cloneable newSecond = p.second.clone();
          try { 
              // Другие действия
              first = newFirst; second = newSecond;
          } catch (*Exception e) {
              newSecond.close(); 
              throw e;
          }
      } catch (*Exception e) {
          newFirst.close(); 
          throw e;
      }
      

Заключение

Содержание

Ссылки

Вопросы

???