@mcakaneo
Я люблю пиццу

Бросать исключение или возвращать коды ошибок/успеха? Является ли исключением то, что метод не может выполнить свою задачу?

Исключения являются для меня самой непонятной темой в программировании(пока что). Даже прочтение кучи информации не помогло мне получить чёткое представление о том как ими ПРАВИЛЬНО пользоваться.
В поле "Суть вопроса" сложно передать суть вопроса? Поэтому нужно прочитать всё что ниже :)
Простите за много букв.

Допустим есть метод transferMoney($amount, User $recipient) и мы считаем, что любой user input является ожидаемым(даже строка с буквами). А он может быть любым из-за того, что пользователь может отправить запрос с любыми данными в теле прямиком на сервер, также некорректные данные может отправить недоработанное клиентское приложение. Проверить отправлен ли вообще параметр amount на сервер и является ли он числом можно где-нибудь выше метода transferMoney(). Но узнать является ли сумма(amount) корректной по-хорошему можно только в браузере/GUI или в Domain слое в самом методе transferMoney(). А поскольку пользователь может обойти и браузер и GUI, то это может сделать только transferMoney(). В transferMoney() нужно извлечь данные пользователя(который пересылает деньги) из БД и проверить есть ли у него такая сумма. Получается в методе transferMoney() есть одна причина неудачи, а что если добавится ещё одна причина, например, пользователю запрещено временно пересылать деньги? Таким образом получается, что из метода в случае неудачи нужно возвратить причину неудачи, этих причин уже может быть две, но при этом они являются вполне ожидаемыми если смотреть на общий контекст(мы ожидаем некорректные данные). Поскольку они ожидаемые, то бросать исключения не логично. В случае если бы была одна причина неудачи, то можно было бы просто вернуть false. Но поскольку их две, то получается, что нужно возвращать коды ошибок(или код успеха в случае успеха) либо возвращать что-то на подобии [false, $error] и [true, null](я подобное видел в очень популярной книге про PHP, только там вместо null была запись из БД).

Но есть и другой подход к проектированию(вроде как): метод transferMoney() является частью ядра приложения, а в Clean Architecture ясно сказано, что ядро это основа вокруг которой строится приложение. То есть, если я правильно понимаю, то transferMoney() не должен подстраиваться под слой повыше, в нём не должно быть никаких предположений, что пользователь введёт что-то правильно или неправильно, он просто должен ожидать только валидные данные, если они неправильные, то это исключительная ситуация. То есть transferMoney() выбросит исключение, которое подымется до Controller'a, и на основе которого Controller отправит 400 Bad Request и причину неудачи. Я правильно размышляю? Или всё таки первый подход к проблеме лучше(первый абзац)?
Я в интернете нашел вот такую вот мысль: "A good rule of thumb is that if a method does not do what its name suggests, it should be considered a method-level failure, resulting in an exception".

Хочу заметить, что я еще не сталкивался с потребностью реализации такого метода, меня сейчас интересует только работа с ошибками.
  • Вопрос задан
  • 1053 просмотра
Решения вопроса 5
Adamos
@Adamos
Пока вы вызываете одну функцию и решаете, что делать с ее ответом, вы не поймете исключений.
Вот когда вам надо будет вызвать функцию, которая вызывает методы класса, которые вызывают методы других классов - вы либо изрисуете себе все стены теми вариантами ошибок, которые каждый из этих методов может вернуть, либо поймете, как это прекрасно - просто поймать исключение, если что-то пошло не так, и не париться с тем, что и где именно.
Ответ написан
index0h
@index0h
PHP, Golang. https://github.com/index0h
Бросать исключение или возвращать коды ошибок/успеха?

Бросать исключение.

Является ли исключением то, что метод не может выполнить свою задачу?

Конечно же.

Но узнать является ли сумма(amount) корректной по-хорошему можно только в браузере/GUI или в Domain слое в самом методе transferMoney().

К фронту не должно быть доверия, данные обязательно нужно проверять.
Аргументы метода лучше проверять И на тип И на граничные значения. Например ваш amount по идее должен быть float + больше, или равен 0. Если amount таковым не является - бросайте исключение. Логика выше должна была это отсечь, еще на этапе валидации данных запроса.

В transferMoney() нужно извлечь данные пользователя(который пересылает деньги) из БД и проверить есть ли у него такая сумма.

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

Получается в методе transferMoney() есть одна причина неудачи, а что если добавится ещё одна причина, например, пользователю запрещено временно пересылать деньги?

Тот же комментарий, что и про баланс.

Поскольку они ожидаемые, то бросать исключения не логично.

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

В случае если бы была одна причина неудачи, то можно было бы просто вернуть false.

Очень хреновая практика. Как вы определите в вызывающем коде, что пошло не так? Что "false"? Баланса не хватает, нельзя делать покупки, или Меркурий в ретрограде?

возвращать что-то на подобии [false, $error] и [true, null]

Для php - это очень кривой подход. По двум причинам:
1. Вы расширяете интерфейс метода, просто так.
2. Вы нагружаете вызывающий код дополнительными обвязками проверок, опять же просто так.
В этом нет смысла так как есть механизм try-catch, который отлично решает это задачу.
Вот пример, допустим ваш transferMoney($amount, User $recipient) должен возвращать объект транзакции, пусть его сигнатура будет:
transferMoney(float $amount, User $recipient): Transaction
Вызывающий код знает, что он обязан в конце получить транзакцию, а если что-то пойдет не так - будет исключение, нет смысла в дополнительных проверках, а что если первый элемент массива false, а что если он true, но второй элемент не null, а что если второй элемент не тот, что ожидалось, а что если первый элемент - строка, и т.д.

transferMoney() не должен подстраиваться под слой повыше, в нём не должно быть никаких предположений, что пользователь введёт что-то правильно или неправильно, он просто должен ожидать только валидные данные, если они неправильные, то это исключительная ситуация.

Верно

То есть transferMoney() выбросит исключение, которое подымется до Controller'a, и на основе которого Controller отправит 400 Bad Request и причину неудачи.

Угу

Или всё таки первый подход к проблеме лучше(первый абзац)?

Первый подход - это путь боли, ошибок и отчаянья.

Вот вам еще чтива: https://github.com/index0h/php-conventions
Ответ написан
glaphire
@glaphire Куратор тега PHP
PHP developer
А поскольку пользователь может обойти и браузер и GUI

Пользователь не должен уметь обходить, он должен иметь gui и/или api)

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

Некоторые ошибки могут быть штатными в ряде случаев (как те же права пользователя) и не надо их эскалировать до исключения. Если пользователь может повлиять на исправление проблемы, то надо ему вернуть полный текст ошибки (нп. он должен пополнить счет, чтобы переслать сумму, поменять настройки или ввести правильные данные), если это чисто server-side ошибка, то естественно не надо показывать детали проблемы пользователю.
Ответ написан
@xfg
Исключения и есть современный способ реакции программы на ошибки, которые приводят к бессмысленности дальнейшего её выполнения. Соответственно, если у пользователя недостаточно средств для перевода, дальнейшее выполнение программы не имеет смысла, необходимо прервать её выполнение выбросив исключение. Когда этого механизма не существовало использовали коды ошибок.

Вы также всегда имеете возможность посмотреть популярные open-source решения и убедиться какой точки зрения придерживается сообщество.
Ответ написан
@freehostua
Работаю в FREEhost.UA
Исключения и сообщения об ошибках решают разные задачи. Если условно разделить приложение на Контроллер, формы проверки входных значений и модель приложения.

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

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

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

Мое мнение такое:
Если ошибка ожидаемая или ошибки должны накапливаться для отображения в диалоге с пользователем, это не исключение.
Если ошибка незапланированная, приводит к немедленному прекращению выполнения алгоритма, значит это исключение. Пример исключения: ошибка взаимодействия с базой данных, ошибка записи на диск, вызов метода запрещенного для текущего состояния объекта.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы