понедельник, 22 мая 2017 г.

Оборонительное программирование посредством проверки декораторов

Проверяете ли вы входные параметры своих методов на достоверность? Я нет. Раньше да, но больше нет. Я просто позволяю своему коду вылетать с нулевым указателем (null pointer) и другими исключениями, когда параметры недействительны. Это может показаться нелогичным, но только в начале. Я предлагаю вместо этого использовать декораторы.

Давайте посмотрим на этот довольно типичный пример Java:


class Report {
  void export(File file) {
    if (file == null) {
      throw new IllegalArgumentException(
        "File is NULL; can't export."
      );
    }
    if (file.exists()) {
      throw new IllegalArgumentException(
        "File already exists."
      );
    }
    // Export the report to the file
  }
}

Довольно оборонительный код, не так ли? Если мы удалим эти проверки, код будет намного короче, но он будет ломаться с довольно запутанными сообщениями, если NULL предоставляется клиентом. Более того, если файл уже существует, наш отчет будет автоматически его перезаписывать. Довольно опасно, правда?

Да, мы должны защищаться!

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

Во-первых, должен быть интерфейс Report:

interface Report {
  void export(File file);
}

Затем класс, реализующий основные функции:

class DefaultReport implements Report {
  @Override
  void export(File file) {
    // Export the report to the file
  }
}

И, наконец, ряд декораторов, которые будут защищать нас:

class NoWriteOverReport implements Report {
  private final Report origin;
  NoWriteOverReport(Report rep) {
    this.origin = rep;
  }
  @Override
  void export(File file) {
    if (file.exists()) {
      throw new IllegalArgumentException(
        "File already exists."
      );
    }
    this.origin.export(file);
  }
}

Теперь у клиента есть гибкость создания сложного объекта у декораторов, которые выполняют свои конкретные задачи. Основной объект будет делать отчетность, в то время как декораторы будут проверять параметры:

Report report = new NoNullReport(
  new NoWriteOverReport(
    new DefaultReport()
  )
);
report.export(file);

Чего мы добиваемся при таком подходе? Прежде всего: мелкие объекты. А меньшие объекты всегда означают более высокую поддерживаемость кода. Наш класс DefaultReport всегда будет оставаться маленьким, независимо от того, сколько проверок мы можем изобрести в будущем. Чем больше вещей нам нужно проверить, тем больше будет оформляющих декораторов, которые мы создадим. Все они будут небольшими и сплоченными. И мы сможем собрать их в разных вариантах.

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

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

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

Комментариев нет:

Отправить комментарий