Как стать автором
Обновить
63
0
kotiara @kotiara

Пользователь

Отправить сообщение
Начали за здравие (agiledev, S.O.L.I.D), а закончили за упокой (php manual как эталон дизайна).
Я привел все аргументы «За». Но не услышал ни одного аргумента «Против».
Приведите пример хоть одной ситуации, где возникнут проблемы если в бизнес-логике использовать исключения.
Может быть, я где-то утрировал.
Все-таки бывает, что бизнес логику удобно сделать через исключения, а бывает и неудобно.
Делать надо по уму. Так что бы код было легко читать и поддерживать.
100% правил не бывает здесь делай так, а здесь вот так. Есть общие принципы и практики проверенные временем, но голову на плечах они не заменят.
если этот адаптер не умеет откатывать транзакцию по отпадению конетка (приложения), то глотание ошибок не самая большая проблема
Может быть мы друг друга не понимаем?
Я же написал, почему такое исключение никак обрабатывать не надо (разве что залогировать).
Если rollback будет бросать исключения, то это сделает не возможным (не удобным) его использование в catch.
Да собственно как вы будете его обрабатывать? Коннект к базе уже отвалился и по таймауту транзакция и так откатится. Если вам нужно знать, что rollback не прошел верните false или так.
что-то я запутался, в чем спор?

почему я могу перехватить все исключения вызываемого мной модуля, но не могу перехватить все исключения связанные с файловой системой?

можете catch(fileModuleException $e)

пусть у нас есть модуль А, он использует модули Б, Ц и Д, каждый из которых бросает однотипные исключения (например, не могу открыть файл), но каждый называет его по своему. поэтому модулю А приходится их перехватывать и заворачивать в своё «не могу открыть файл». в результате у нас получается 4 имени для одного и того же исключения. зачем?

ну правильно, чтобы было меньше зависимостей, чтобы в коде оперировать только исключениями модуля А. 4 имени — и что в этом плохого? Использовать в коде надо только одно исключение модуля A

а что если нам нужно перехватывать «проблемы с файлами» и «проблемы с базой» и для этих случаев выполнить один и тот же код, а для других проблем — другой? синтаксис catch( ExcaptionName $e ) тут уже не рулит, ибо эта задача не вписывается в «единую иерархию»

если очень нужно, можно сделать через интерфейсы
interface myDbFileException {}
class fileException extends baseException implements myDbFileException{}
class dbException extends baseException implements myDbFileException{}

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

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

Ну вот в приведенном примере, задача вывести информативное сообщение пользователю. Если нет конекта к базе «сервер временно не доступен», если какой-то файл не читаем «Не возможно открыть файл <имя файла>», если какой-то файл не подходит по формату «Не верный формат файл <имя файла>».

Если действительно в функции есть два исхода true/false и неудача происходит только по одной различимой причине, то подход с return true/false тоже подойдет. Но с другой стороны если, когда-то нужно будет расширить функционал, то придется делать или исключения или собственный механизм работы с ошибками. Вариант с собственным механизмом работы с ошибками мне не нравится. Остаются исключения.
а теперь тебе нужно реализовать артефакт, который в случае недостатка манны для совершения какого-либо действия единократно пополняет её запас и разрушается. но только если действие совершится. как ты его присунешь в свою модель с исключениями?

Ну это будет внутри кода функции которая бросает исключения. Я проверю ману, если не хватает маны, то я проверю есть ли артефакт для пополнения маны, если он есть тогда пополняем (мы работаем в транзакции), если его нет бросаем исключение «Нет маны», в конце функции ловим исключение делаем откат и пробрасываем в исключение дальше.
А какое важнее?
Из-за чего может возникнуть исключение в rollback(), я вижу две причины:
1. отпал коннект к базе, и соответственно транзакция сама откатится по таймауту
2. конец базе
В первом случае ничего страшного не произошло, второй случай — за жизнью базы должен следить отдельный человек/механизм, а не исключения в ролбеках. Вот и получается, что внутри роллбека, нужно заглушить и залогировать исключение.
А работать нужно с первым исключением, которое может нести в себе информацию для вывода на экран.

В ролбеке исключения нужно заглушить, но при этом комит может и даже должен бросать исключения (в случае возникновения исключительных ситуаций).
пусть у нас есть модуль А, он использует модули Б, Ц и Д, каждый из которых бросает однотипные исключения (например, не могу открыть файл), но каждый называет его по своему. поэтому модулю А приходится их перехватывать и заворачивать в своё «не могу открыть файл». в результате у нас получается 4 имени для одного и того же исключения. зачем?

Если в модули Б, Ц и Д бросают однотипные исключения, они делают однотипные операции и нужно задуматься о рефакторинге:
или свести все три модуля к одному или вынести общую часть в один К, а модули Б, Ц и Д будут его использовать; если речь идет о классах, то общую часть можно вынести в один общий родительский класс.
И тогда бросаемое исключение будет «кИсключение».
Если Б, Ц и Д это классы наследуемые от К и нам нужно иметь возможность: как поймать все «кИсключения» разом так и ловить «бИсключения», «цИсключения» отдельно, тогда просто нужно объявить их потомками «кИсключения».

Если же вдруг вам все равно мало возможностей иерархии(дерево), то такой полиморфизм решается интерфейсами.
можно извратится вот так.
Думаю всем понятно, почему так делать не надо
ну так конечно симпатичней. А если нужно передать из функции кроме факта ошибки(return false) еще и детали ошибки, то можно передать скажем ассоциативный массив, в котором будет тип/номер ошибки, меседж, ит.п; но массив это не стандартизация, если несколько разработчиков, то каждый может создавать ключи в массиве кто какие хочет — заменяем массив объектом (инстанс класса ошибки); теперь типы ошибок тоже надо бы стандартизовать — проще простого, вводим константы, чтоб они не висели в глобальном скопе кладем их в класс ошибки. А теперь, хочется еще и иметь возможность передать эту ошибку выше, т.е. в функцию которая вызвала функция…
Так слово за слово. Где-то в конце этих рассуждений, можно прийти к модели очень сильно напоминающую исключения. И понять, что получился кривоватый велосипед, для работы с которым написан мануал, при работе с «велосипедом» он диктует как кодить (обязательно должен быть ретерн в функции, на выходе нужно проверить ни является ли объект инстансом класса ошибки и.т.п). Но если остановится где-то на ранних стадиях, то такой подход имеет право на жизнь. А если вы начинаете что-то изобретать, я бы посоветовал использовать готовое
> Обнаружить пропущенную строку при таком подходе невозможно.
Если вам еще дополнительно, нужно искать непереводимые строки, добавляем код, который логирует
if(!array_key_exists($message, $translate)){
    log($message, 'untranslated string');
    return $message;
} else {
    return $translate[$message];
}
 

> Ну, а на дев сервере нотисы сразу выводятся вместе с трассой (спасибо xdebug) и исправляются при обнаружении.
А если это какой-то сложно воспроизводимый баг и на дев сервере вы его не нашли. Отдали код в продакшин. Там на него натыкается конечный пользователь. Вы ищете этот notice среди не очень информативных сообщений в логе PHP и даже если вы его нашли, не факт, что станет понятно как его воспроизвели. В кастомном логере исключения может быть как минимум trace, так же можно залогировать страницу на которой это произошло, глобальные переменные (если это поможет) ит.п.

Насчет стороннего кода вы правы, могут быть проблемы. Варианты решения
костыль 1. ставим собачку при инклюде стороннего кода и выключаем режим screem
костыль 2. определяем сторонний код по названию файла или класса в errorHandler с помощью debug_backtrace()
решение 3. отказываемся от такого стороннего кода (если есть возможность)
решение 4. отказываемся от такого подхода работы с ошибками (т.е. от моего варианта) — в конце концов надо решить задачу, а не следовать всем правилам
решение 5. если код больше не поддерживается другой командой — достался вашей команде, то можно отрефакторить где надо
Проектирование исключений, это как проектирование API, чем больше хуков заложено, тем больше возможностей у третестороннего разработчика, и соответственно больше времени потратится на разработку и система станет тяжелей (больше классов). Это выбор каждого разработчика, кто как считает правильным в том или ином контексте.
> Я считаю, что указание на вид ошибки должно размещаться не в названии класса исключения, а в свойстве класса
Взято отсюда
Любой объект уже обладает типом. Этим типом является его класс. Внедряя информацию о типе в сам класс, вы можете нарушить принцип открытия/закрытия…

В статье, ссылку на которую вы давали выше:
Как сделать лучшую архитектуру

Принцип открытия-закрытия (Open-Closed Principle) – классы должны быть открыты для расширений, но закрыты для модификаций. Кажется, что это невозможно, однако стоит вспомнить шаблон проектирования Strategy и становится более или менее понятно.


Не находите противоречий?
заворачивать надо там, где собираетесь обработать исключение
Все равно assertion как-то обрабатывать надо, чтобы не изобретать велосипед используем готовую модель. Если где-то assertion не пройдет получаем исключение, а с ним и trace. Тут кому как больше нравится.

Насчет преобразования ошибок, тут надо понимать какая этому альтернатива. Рассмотрим ваш пример
> скажем, не найдена строка локализации — думаете показать пользователю ошибку что её нет лучше, чем просто вывести страницу без неё?
если у вас задумана логика, что если строки локализации нет, то вывести то что есть, так и напишите это в коде:
if(!array_key_exists($message, $translate)){
    return $message;
} else {
    return $translate[$message];
}

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

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность