Функция возвращает true / false против void при успешном завершении и выдает исключение при сбое

22

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

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

Это хорошая практика или лучше возвращать объект (с логическим значением внутри, необязательным сообщением об ошибке и перечислением ошибок)?

Accollativo
источник
59
Правда и ложь хорошо сочетаются. Пустота и исключение хорошо сочетаются. Правда и исключения идут вместе, как кошки и налоги. Возможно, когда-нибудь я прочитаю твой код. Пожалуйста, не делай этого со мной.
Candied_Orange
4
Мне очень нравятся шаблоны, в которых возвращается объект, который заключает в себе либо успех, либо неудачу. Например, Rust имеет Result<T, E>где Tрезультат в случае успеха и Eошибка в случае неудачи. Создание исключения (может быть - не очень уверенно в Java) может быть дорогостоящим, поскольку включает в себя разматывание стека вызовов, но создание объекта Exception обходится дешево. Очевидно, придерживайтесь установленных шаблонов языка, который вы используете, но не объединяйте логические значения и исключения, подобные этому.
Дэн Кладовая
4
Будьте осторожны здесь. Вы можете быть направлены вниз по кроличьей норе. Когда ваша программа способна справиться с проблемой, обычно проще просто обнаружить и справиться с ней, проверив предварительные условия. Вы редко поймаете исключение, изучите его данные в коде и попытаетесь снова после устранения проблемы. Поэтому редко бывает очень полезно иметь очень много типов исключений. Если вы пытаетесь регистрировать проблемы, это имеет больше смысла, но не ожидайте, что вы напишете несколько блоков catch со значительным количеством кода в них.
jpmc26
4
@DanPantry В Java стек вызовов проверяется при создании исключения, а не при его создании . fillInStackTrace();вызывается в супер-конструктор, в классеThrowable
Саймон Форсберг

Ответы:

35

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

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

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

Килиан Фот
источник
7
Если это ожидаемый результат для запроса на загрузку файла, я был бы удивлен исключением, брошенным мне в лицо (особенно если в сигнатуре метода есть возвращаемое значение).
hoffmale
1
Обратите внимание, что причина, по которой он расширяет стандартное исключение, заключается в том, что многие (возможно, даже большинство) абонентов не будут писать код, чтобы попытаться решить проблему. В результате большинство вызывающих абонентов захотят просто «поймать эту большую группу исключений» без необходимости перечислять их все явно.
jpmc26
4
@gbjbaanb Вот почему исключения должны использоваться, когда действительно существует неустранимая ситуация. Когда вы пытаетесь открыть файл и файл не может быть прочитан, это хороший случай для исключения. Когда вы проверяете существование файла, необходимо использовать логическое значение, потому что вполне ожидаемо, что файла там может не быть.
Малкольм
21
Килиан говорит, что в рекурсивном лозунге «исключения предназначены для исключительных ситуаций», исключительные ситуации - это когда функция не может выполнить свое назначение. Если функция предназначена для чтения файла, и файл не может быть прочитан , это исключительное . Если функция должна сообщать вам, существует ли файл (не говоря уже о потенциальном TOCTOU), то файл, не существующий , не является исключением, поскольку функция все еще может делать то, что должна делать. Если функция должна утверждать, что файл существует, то он не существует, является исключительным, потому что это то, что означает «assert».
Стив Джессоп
10
Обратите внимание, что единственная разница между функцией, которая сообщает вам, существует ли файл, и функцией, которая утверждает, что файл существует, заключается в том, является ли случай несуществующего файла исключительным. Иногда люди говорят, что это свойство ошибки, независимо от того, является ли оно «исключительным». Это не свойство условия ошибки, это объединенное свойство условия ошибки и цели функции, в которой оно возникает. В противном случае мы никогда не поймаем исключения.
Стив Джессоп
31

Нет абсолютно никаких причин возвращаться trueв случае успеха, если вы не вернетесь falseв случае неудачи. Как должен выглядеть код клиента?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

В этом случае вызывающему в любом случае нужен блок try-catch, но тогда он может лучше написать:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Таким образом, trueвозвращаемое значение совершенно не имеет значения для вызывающей стороны. Так что просто продолжайте метод void.


В общем, есть три способа создания режимов сбоев.

  1. Верните true / false
  2. Использовать void, выбрасывать (проверено) исключение
  3. вернуть промежуточный результат объекта.

Возврат true/false

Это используется в некоторых старых API, в основном в стиле c. Недостатки - obviuos, вы понятия не имеете, что пошло не так. PHP делает это довольно часто, что приводит к такому коду:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

В многопоточных контекстах это еще хуже.

Бросить (проверено) исключения

Это хороший способ сделать это. В некоторые моменты вы можете ожидать сбой. Java делает это с помощью сокетов. Основным предположением является то, что вызов должен быть успешным, но все знают, что некоторые операции могут завершиться неудачей. Разъемы подключения среди них. Таким образом, вызывающий абонент вынужден справиться с ошибкой. это хороший дизайн, потому что он гарантирует, что вызывающая сторона действительно обрабатывает сбой, и дает вызывающему абоненту элегантный способ справиться с ошибкой.

Возвращаемый результат объекта

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

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Хорошая, чистая логика для звонящего.

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

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

Polygnome
источник
5

Это действительно сводится к тому, является ли отказ исключительным или ожидаемым .

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

Если, с другой стороны, ошибки являются обычным явлением, вы должны оптимизировать их для разработчика, который их проверяет, а try-catchпредложения являются несколько более громоздкими, чем серия if/elifили switch.

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

Xiong Chiamiov
источник
1
В моем случае, как кто-то указал, должна быть исключительная ошибка, из-за которой пользователь API не может загрузить файл.
Accollativo
4

Не возвращайте логическое значение, если вы никогда не собираетесь возвращаться false. Просто сделайте метод voidи задокументируйте, что он выдаст исключение ( IOExceptionподходит) при сбое.

Причина этого заключается в том, что если вы возвращаете логическое значение, пользователь вашего API может заключить, что он / она может сделать это:

if (fileUpload(file) == false) {
    // Handle failure.
}

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

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}
JeroenHoek
источник
3

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

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

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

ccControl
источник
1
Перечисление было просто идеей, чтобы помочь пользователю API обрабатывать различные типы ошибок без разбора сообщения об ошибке. Так что с вашей точки зрения в моем случае лучше вернуть enum и добавить enum для «OK».
Accollativo