Дизайн API: действительно ли «отказоустойчивость» хороша? - PullRequest
5 голосов
/ 21 июля 2010

Я собрал много полезных ответов и нашел свой собственный ответ ниже


Например, я пишу API Foo, который требует явной инициализации и завершения. (Должен быть независимым от языка, но я использую C ++ здесь)

class Foo
{
public:
    static void InitLibrary(int someMagicInputRequiredAtRuntime);
    static void TermLibrary(int someOtherInput);
};

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

Какой лучший способ сообщить об этом моему абоненту? Я могу придумать два пути:

  1. Внутри InitLibrary, у меня assert некоторая статическая переменная, которая обвинит моего абонента в инициации дважды.
  2. Внутри InitLibrary я проверяю некоторую статическую переменную и молча прерываю работу, если моя библиотека уже инициализирована.

Метод # 1 явно явный, а метод # 2 делает его более удобным для пользователя. Я думаю, что метод № 2, вероятно, имеет тот недостаток, что мой вызывающий не будет знать о том факте, что InitLibrary не следует вызывать дважды.

Каковы были бы плюсы / минусы каждого подхода? Есть ли более умный способ подорвать все это?

Редактировать

Я знаю, что пример здесь очень надуманный. Как указал @daemon, я должен инициализировать себя и не беспокоить звонящего. Однако на практике есть места, где мне нужно больше информации для правильной инициализации (обратите внимание на использование имени моей переменной someMagicInputRequiredAtRuntime). Это не ограничивается инициализацией / завершением, но другими случаями, когда существует дилемма, должен ли я выбрать «цитируй-и-процитируй« ошибка, терпимая »» или неудачно.

Ответы [ 9 ]

9 голосов
/ 21 июля 2010

Я бы определенно пошел на заход на посадку 1 вместе с легким для понимания исключением и хорошей документацией , которая объясняет, почему это не удается . Это заставит вызывающего абонента осознавать, что это может произойти, и вызывающий класс может легко обернуть вызов в оператор try-catch, если необходимо.

Сбой молча, с другой стороны, заставит ваших пользователей поверить в то, что второй вызов был успешным (без сообщений об ошибках, без исключений), и, таким образом, они будут ожидать установки новых значений. Поэтому, когда они пытаются сделать что-то еще с Foo, они не получают ожидаемых результатов. И почти невозможно понять, почему, если у них нет доступа к вашему исходному коду.

4 голосов
/ 21 июля 2010

Serenity Prayer (модифицировано для интерфейсов)

     SA,  grant me the assertions 
     to accept the things devs cannot change 
     the code to except the things they can, 
     and the conditionals to detect the difference

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

3 голосов
/ 21 июля 2010

Хороший подход состоит в том, чтобы иметь фабрику, которая создает объект инициализированной библиотеки (для этого вам нужно заключить библиотеку в класс).Многократные вызовы на фабрику создают разные объекты.Таким образом, метод initialize не будет частью открытого интерфейса библиотеки, и фабрика будет управлять инициализацией.

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

2 голосов
/ 21 июля 2010

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

В общем, я думаю, что более полезно думать о состоянии, чем о действии.Чтобы использовать аналогию, попытка открыть как «запись нового» файла, который уже открыт, должна либо завершиться неудачей, либо привести к закрытию-стиранию-открытию.Он не должен просто выполнять no-op, так как программа будет ожидать записи в пустой файл, время создания которого совпадает с текущим временем.С другой стороны, попытка закрыть файл, который уже закрыт, как правило, не должна рассматриваться как ошибка, поскольку желательно, чтобы файл был закрыт.

Кстати, часто полезно иметь пробную версию.метода, который может вызвать исключение.Было бы хорошо, например, иметь Control.TryBeginInvoke для таких вещей, как процедуры обновления (если потокобезопасное свойство элемента управления изменяется, обработчик свойства хотел бы, чтобы элемент управления обновлялся, если он все еще существует, но на самом деле не будетне забывайте, что элемент управления удаляется; немного утомительно, когда не удается избежать исключения первого шанса, если элемент управления закрывается при обновлении его свойства).

1 голос
/ 23 июля 2010

Полагаю, один из способов преодолеть эту дилемму - выполнить оба лагеря.В Ruby есть предупреждающий переключатель -w, для пользователей gcc он настроен на -Wall или даже -Weffc++, а Perl имеет режим taint.По умолчанию они «просто работают», но более осторожный программист может сам включить эти строгие настройки.

Одним примером против подхода «всегда жалуйся на малейшую ошибку» является HTML.Представьте себе, как был бы разочарован мир, если бы все браузеры лаяли на любые хаки CSS (такие как рисование элементов в отрицательных координатах).

После рассмотрения множества отличных ответов я пришел к такому выводу: когда кто-тосадится, мой API в идеале должен "просто работать".Конечно, для участия в любой области ему необходимо работать на уровне абстракций на один или два уровня ниже, чем проблема, которую он пытается решить, а это значит, что мой пользователь должен рано или поздно узнать о моих внутренних элементах.Если он будет использовать мой API достаточно долго, он начнет расширять пределы, и слишком большие усилия, чтобы «спрятать» или «инкапсулировать» внутреннюю работу, станут только неприятностью.

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

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

1 голос
/ 21 июля 2010

В вашем классе есть личная статическая переменная-счетчик. Если это 0, тогда выполните логику в Init и увеличьте счетчик. Если он больше 0, просто увеличьте счетчик. В терминах выполните обратное, уменьшите до 0, затем выполните логику.

Другой способ - использовать Singleton pattern , вот пример на C ++.

0 голосов
/ 21 июля 2010

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

0 голосов
/ 21 июля 2010

Если эта библиотека является статическим классом (тип библиотеки без состояния), почему бы не поместить вызов Init в инициализаторе типа?Если это инстанцируемый тип, поместите вызов в конструктор или в метод фабрики, который обрабатывает инстанцирование.
Не разрешайте публичный доступ к функции Init.

0 голосов
/ 21 июля 2010

Если ваш язык не позволяет этой ошибке появляться статически, велика вероятность, что ошибка появится только во время выполнения. В зависимости от использования вашей библиотеки, это означает, что ошибка не возникнет намного позже разработки. Возможно только при отправке (опять же, зависит от многих).

Если нет опасности в молчаливом поедании ошибки (которая в любом случае не является настоящей ошибкой, поскольку вы ее ловите до того, как произойдет что-то опасное), тогда я бы сказал, что вы должны молча поесть. Это делает его более удобным для пользователя.

Если, однако, someMagicInputRequiredAtRuntime меняется от вызова к вызову, я бы по возможности вызывал ошибку или, предположительно, библиотека не будет функционировать должным образом («Я инициализировал библиотеку со значением 42, но она ведет себя так, как будто инициируется с 11!? ").

...