Чистый код. Создание, анализ и рефакторинг (Мартин) - страница 44

>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 ничего другого быть не должно (как в предыдущем примере).