Pull to refresh

Принцип самурая

Designing and refactoring *
В мире разработки софта существует много идей и «метафор», позаимствованных из других, казалось бы, не сильно связанных с программированием областей. Можно вспомнить паттерны проектирования, позаимствованные у архитекторов, или понятие «технического долга», пришедшее из финансовой индустрии, да и «эффектом второй системы» страдают проектировщики любых систем, а не только программных (*). Все это упрощает коммуникацию между разработчиками или между разработчиками и заказчиками, а также упрощает понимание той или иной проблемы в разработке ПО.

Еще одной метафорой, или скорее принципом разработки, является «принцип самурая», призванный описать «контракт» между функцией и вызывающим ее кодом и заключается в следующем. Любая функция, реализующая некоторую единицу работы должна следовать тому же кодексу чести «бусидо», по которому живет любой самурай. Так, самурай не будет выполнять никаких заданий, противоречащих его «кодексу чести» и если к нему подойти с «непристойным» предложением, то он снесет вам башку раньше, чем вы успеете глазом моргнуть. Но если уж самурай возьмется за дело, то можно быть уверенным в том, что он доведет его до конца (**).




Тем же принципам должна следовать и любая открытая функция. Если ей «подсунули» неадекватные входные данные, нарушающие ее предусловие (то есть ее кодекс чести), то она должна четко сказать об этом с помощью исключения (это аналог сноса башки, ведь вы сами в этом виноваты). Но если уж аргументы валидны и она вызывается в корректном состоянии, то вызывающий код может быть уверенным в результате: функция либо завершится успешно, либо «упадет». Функция, как и самурай должны следовать принципу «сделай или умри», но если самурай, чтобы избежать позора, делает себе сэппуку, то функция, если она не в состоянии выполнить свою работу, должна «упасть» с исключением.

Этот, казалось бы, нехитрый принцип дает ответы на многие непростые вопросы обработки исключений. Нужно ли проверять аргументы функции и что делать, если они некорректны? Нужно ли глотать исключения, которые происходят во внутренностях этой функции? Нужно ли возвращать null или пустой список, если что-то пошло не так и функция не может выполнить свою работу? (***)

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

Из этого принципа следует, что «глотать» исключения – это плохо, поскольку вы, фактически, скрываете свои проблемы от глаз вызывающего кода и не даете внешнему коду возможности узнать об этом. Не нужно брать на себя слишком многого, пусть голова болит у вашего «клиента», что делать с «вашим телом» (то есть с исключением), когда вы не справились со своей задачей и решили последовать принципам самурая.

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

public SomeEntry ReadEntryById(int id)
{
  try
  {
    // Читаем SomeEntry из базы данных
  }
  catch (Exception)
  {
    // Ядрёна кочарыжка! Как же вызывающему коду узнать,
    // была ли ошибка, или записи с таким id нет в базе?
    return null;
  }
}


* This source code was highlighted with Source Code Highlighter.


Бывают случаи, когда функция может перехватывать исключение и не пробрасывать его вызывающему коду; это может быть функция самого высокого уровня и вызывающего кода может не быть. В остальных же случаях, функция может самостоятельно попытаться восстановиться после возникновения ошибки, но, в конечном счете, если эта попытка не увенчается успехом, то у нее не останется другого выхода, кроме как последовать принципу самурая – «сделать свою работу или умереть».

--------------

(*) За подробностями о том, что это за метафоры, обращайтесь к соответствующим заметкам: «Технический долг» и «Эффект второй системы», соответственно.

(**) В большинстве статей о «принципе самурая» говорится только о второй части соглашения, что, дескать, любой метод, как и истинный самурай, должны либо выполнить свою работу, либо умереть. Но вызывающий код не является начальником самурая или его императором, которого самурай должен слушаться беспрекословно. Самурай, как и функция, не должны выполнять задания, противоречащие их «кодексу чести»; в отношениях между функцией и вызывающим кодом важно, чтобы обе стороны выполняли свои соглашения.

(***) Принцип самурая не является революцией в разработке ПО; этому принципы следуют уже давно, причем некоторые делают это достаточно формальным образом с помощью проектирования по контракту. Если вы хотите познакомиться с понятиями предусловия и постусловия, то можно начать со статьи «Как не надо писать код», или же обратиться к целой серии статей, посвященных теме проектирования по контракту.
Tags:
Hubs:
Total votes 64: ↑60 and ↓4 +56
Views 6.1K
Comments Comments 40