Выбрать главу

if (attributeExists("username")) {

  setAttribute("username", "unclebob");

  ...

}

Используйте исключения вместо возвращения кодов ошибок

Возвращение кодов ошибок функциями-командами является неочевидным нарушением принципа разделения команд и запросов. Оно поощряет использование команд в предикатных выражениях if:

if (deletePage(page) == E_OK)

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

if (deletePage(page) == E_OK) {

  if (registry.deleteReference(page.name) == E_OK) {

    if (configKeys.deleteKey(page.name.makeKey()) == E_OK){

      logger.log("page deleted");

    } else {

      logger.log("configKey not deleted");

    }

  } else

{

    logger.log("deleteReference from registry failed");

  }

} else {

  logger.log("delete failed");

  return E_ERROR;

}

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

try {

  deletePage(page);

  registry.deleteReference(page.name);

  configKeys.deleteKey(page.name.makeKey());

}

catch (Exception e) {

  logger.log(e.getMessage());

}

Изолируйте блоки try/catch

Блоки try/catch выглядят весьма уродливо. Они запутывают структуру кода и смешивают обработку ошибок с нормальной обработкой. По этой причине тела блоков try и catch рекомендуется выделять в отдельные функции.

public void delete(Page page) {

  try {

    deletePageAndAllReferences(page);

  }

  catch (Exception e) {

    logError(e);

  }

}

private void deletePageAndAllReferences(Page page) throws Exception {

  deletePage(page);

  registry.deleteReference(page.name);

  configKeys.deleteKey(page.name.makeKey());

}

private void logError(Exception e) {

  logger.log(e.getMessage());

}

В этом примере функция delete специализируется на обработке ошибок. В этой функции легко разобраться, а потом забыть о ней. Функция deletePageAndAllReferences специализируется на процессе полного удаления страницы. Читая ее, можно не обращать внимания на обработку ошибок. Таким образом, код нормального выполнения отделяется от кода обработки ошибок, а это упрощает его понимание и модификацию.

Обработка ошибок как одна операция

Функции должны выполнять одну операцию. Обработка ошибок — это одна операция. Значит, функция, обрабатывающая ошибки, ничего другого делать не должна. Отсюда следует, что если в функции присутствует ключевое слово try, то оно должно быть первым словом в функции, а после блоков catch/finally ничего другого быть не должно (как в предыдущем примере).

Магнит зависимостей Error.java

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

public enum Error {

  OK,

  INVALID,

  NO_SUCH,

  LOCKED,

  OUT_OF_RESOURCES,

  WAITING_FOR_EVENT;

}

Подобные классы называются магнитами зависимостей; они должны импортироваться и использоваться многими другими классами. При любых изменениях перечисления Error все эти классы приходится компилировать и развертывать заново[18]. Это обстоятельство создает негативную нагрузку на класс Error. Программистам не хочется добавлять новые ошибки, чтобы не создавать себе проблем со сборкой и развертыванием. Соответственно, вместо добавления новых кодов ошибок они предпочитают использовать старые.

Если вместо кодов ошибок использовать исключения, то новые исключения определяются производными от класса исключения. Их включение в программу не требует перекомпиляции или повторного развертывания[19].

вернуться

18

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

вернуться

19

Пример принципа открытости/закрытости (OCP) [PPP02].