Комментарии 57
Кстати, документация Java дает интересное определение для Unchecked Exception.
These are exceptional conditions that are internal to the application, and that the application usually cannot anticipate or recover from. These usually indicate programming bugs, such as logic errors or improper use of an API
Когда я учил Java, то там было написано немного иначе. RuntimeException - это ошибка программиста. В нормальной оттестированной программе их не должно быть. Примеры: выход за границу массивы, обращение к полю у переменной null и т. д. Конкретно в случае с классом User из статьи исключение IllegalArgumentException может возникать только из-за того, что не было адекватной проверки пользовательского ввода с формированием сообщений, понятных пользователю, и отображению их в интерфейсе.
CheckedException - это исключение, которое может возникнуть в правильно написанной программе. Это не ошибка алгоритма и не ошибка программиста. Например: ошибка сети, отсутствие файла, недостаточно прав для выполнения действия и т. д. Именно поэтому CheckedException всегда надо обрабатывать и всегда указывать в throws.
В Spring Framework да, там все CheckedException внутри фреймворка оборачиваются в RuntimeException. В Spring философия отличается от философии чистой Java. Там считают, что CheckedException - это зло. Их не должно быть. Почему так - не могу сказать. Возможно, у них были свои причины.
Не увидел противоречия Вашего определения и цитаты.
Почему так - не могу сказать
Потому что языковых средств не хватает. Например, вот хочу я обработать все ошибки бд в каком-то фильтре/мидлваре – я не могу как-то пометить свои хендлеры, что они кидают DbException, а мидлварь - что она его обрабатывает(чтобы на меня заругались, если вдруг я забыл добавить мидлварь). А большинство ошибок именно так и стоит обрабатывать в спринге... Выразительности физически не хватает.
Хм, я вот лично пишу игрушки на джаве и не считаю нужным как то особо обрабатывать ошибки обработки ресурсов (например загрузка геометрии) или чтения с диска. Просто прокидываю IOException в метод загрузки пула ресурсов и там его ловлю, бросая RuntimeException. Особой необходимости обрабатывать конкретное поведение (например подменять модель на надпись Error) нет, но при желании можно унаследоваться от Unchecked исключения и если логика загрузки ресурсов предусматривает, его перехватывать.
Пробовал я писать активно используя Checked exceptions, сломался на том что все стандартные functional interfaces определены без всяких Checked exceptions. Так что извините, но неудобно
Мы в проекте использовали следующее соглашение: методы можно объявлять либо без throws, либо с throws Exception - и больше никак (т.е. никаких IOException, MyCustomException в описаниях нет, и быть не может). Дальше правила жизни:
Если ты вызвал функцию которая throws Exception, но не хочешь/не можешь делать error recovery (99% случаев) - пометь свою функцию throws Excepton, и живи дальше (очень похоже на то, к чему в конце-концов пришел C++).
Если ты хочешь сделать error recovery - то лови только те исключения, которые ты можешь/хочешь обработать именно на этом уровне.
Категорически запрещается ловить и проглатывать произвольное исключение. Разрешается (хотя и не поощряется) подход catch+rethrow - обычно это паттерн catch+log+rethrow. Загромождает логи, но иногда нужно для более быстой локализации проблем.
На верхнем уровне есть обработчик исключений который их логирует, и в зависимости от типа - или терминирует приложение, или поток, или только логирует и надеется что оно как-то само рассосется.
Если ты вызвал throws-функцию из какого-нибудь интересного типа а-ля Runnable - убери руки от клавиатуры, и подумай что ты делаешь! Либо у тебя в твоем Runnable должен быть аналог обработки исключений верхнего уровня (как минимум, залогировать). А как максимум - обсуждать с лидами архитектурную проблему: скорее всего где-то кто-то будет ждать побочный эффект нашего runnalbe, который никогда не наступит. И тут нужна асинхронная обработка ошибок - что есть отдельная песня и отдельный свод правил.
Если делать совсем правильно - то в архитектуре приложения со слоями, каждый слой должен ловить любой Exception который произошел в нижних слоях, и транслировать его в логический эксепшн выраженный в терминах, которыми оперирует именно этот слой. А в качестве "причины" - передавать as-is эксепшн из нижележащего слоя. Например - слой отвечающий за витрину магазина делает запрос в сервис который определяет динамическую цену. Тот лезет, скажем, в базу и получает transaction error. Идеологически правильно будет перехватить этот Transaction Error в сервисе динамической цены и обернуть его в бизнес-эксепшн DynamicPriceNotAvailableException(..., underlyingException). Тогда сервис витрины может хоть как-то сделать recovery из этой ситуации: то ли показать backup статическую цену, то ли пропустить товар, то ли сделать что-то еще. Однако, такие заморочки нужны, ну скажем в 10% случаев когда что-то кидается.
Обязательная оговорка - речь идет о системе не mission-critical класса. Если вы пишете софт для кардиостимулятора или контроллера тормозов - правила относительно исключений и их обработки будут жестче - сильно жестче!
Отказываться от исключений и возвращаться к return value - я считаю нерациональным.
Перехват эксепшена из нижнего слоя это маст хев, потому что не очень приятно видеть какой нибудь apache http exeption при вызове сервиса
Очень сильно зависит от требований к приложению, ИМХО. Если в конце этих перехватов надо записать лог и показать пользователю "Произошла непредвиденная ошибка. Попробуйте еще раз. Trace-id=..." - то все эти перехваты и перевыбросы другого типа становятся явным излишеством. Поэтому мы перехватывали и перевыбрасывали только там, где были явные требования к error recovery.
Не обязательно из-за recovery. Иногда от сервиса прилетает к примеру IOException без дополнительной информации, только вызывающий метод, обладает информацией, что бы нормально записать в лог, что произошло.
Ну вот я же выше писал паттерн "catch+log+rethrow"! Это он и есть: поймали внизу иерархии, залогировали (с инфой которая есть в этом месте), и перебросили дальше. :-)
Cause в помощь в этом случае. Для логов stack trace на верхнем уровне.
Stack trace не всегда достаточно, все равно может потеряться контекст, в котором было брошено исключение. Какие то данные, которые собирались для метода, выбросившего исключение, бизнескейс, имя файла хотя бы, его размер и путь, запись которого вызвало IOException исключение. Исключения выброшенные каким ни будь библиотечными функциями, часто содержат информацию слишком низкого уровня. Типа "у вас ошибка в строке 17", а для анализа хотелось бы эту самую строку в логе иметь.
либо с throws Exception - и больше никак
Выглядит так, что если убрать это правило то ничего не изменится.
Если у вас Control Flow построен на еxception'ах, то у вас бардак в консерватории, а не в Java. Правильное использование exception'ов это про прерывание исполнения, это значит что операция пользователя не может быть завершена успешно (с ожидаемым результатом или за ожидаемое время), а значит меняется и оговоренное поведение (приложение может вернуть ошибку и сложить лапки или запустить процедуру деградации/восстановления функциональности и т.д.), то есть тут уже не так важно сколько времени тратится на сбор стека и восстановление консистентности. Тогда в идеальном случае весь ваш критический путь исполнения будет оптимизирован через предсказание ветвлений.
Насчет проверок условий тут конечно вопрос к изначальному дизайну. Контракты с предусловиями/постусловиями описаны достаточно давно, но в Java их не завезли, имеем то что имеем.
AsynchronousChannel , но помойму прикол в том что если будет отвязка то нужен контроль, потомучто можно нечайно запустить больше ведь чем положено, а значит пользователю чтобы не думать, разрабу надо учесть этот нюанс, последовательного запуска штук, в 24 еще есть join(так же Runtime exec - выкидывает депрекейтед, но это норм есть ProcessBuilder, еще есть виртуальные потоки, еще есть Structured Concurrency - 499, остальное впринципе можно кинуть в диспатч например в свинге есть свои штуки по асинхронности - действительно отвязывает от интерфейса, но помойму это и хорошо и плохо проверки то всё равно придётся делать, тут самое наивное именно что не использовать асинк, а просто прагматично ждать завершения, ну будет зависесть от выбора - я так сделал - просто жду меня устраивает), но и в 24 есть ускорение в виде векторов, по минимальным показателям - минимальным 24 даёт прирост ну 10% точно по сравнению с 17 версией
Assert в java есть, для дебага но это же просто другой синтаксис для исключений
Во многих языках программирования контракты реализуются с помощью assert . Assert по умолчанию компилируются в режиме выпуска в C/C++ и аналогичным образом деактивируются в C# [ 8 ] и Java.
Используйте assert, чтобы документировать предположения и проверить корректность логики во время разработки.
Не полагайтесь на assert в рабочем продакшене для критически важных проверок.
используете ли вы исключения для управления логикой своей программы?
А что, в Java исключения достаточно легковесны и сравнимы по производительности с обычными вызовами функций и т.п. ? По крайней мере в плюсах это дорогое удовольствие.
Исключения везде дороги, в большинстве случаев они опираются на систему исключений, предоставляемые ОС, требуют параллельного стека и т.п.
Поэтому есть прекрасное правило: исключения только для исключительных событий, для остального используйте ошибки и статусы. К сожалению, далеко не во всех штатных фреймворках используется этот принцип, хотя тут, скорее, просто приоритет отдают удобству использования, а не оптимальности.
Сами по себе они относительно недорогие, больше всего времени там занимает сбор стектрейса. В свежих версиях Java сборку стектрейса оптимизировали, добавили lazy создание фреймов и StackWalker, но это все равно не панацея. Есть конечно опции JVM типа -XX:-OmitStackTraceInFastThrow и специальный конструктор для создания исключения без стектрейса, но я бы не советовал сильно полагаться на первое и злоупотреблять вторым без понимания зачем вам это надо. В случае если вы кидаете и перехватывает exception в рамках одного метода это работает также быстро как goto (при условии соблюдения ряда специфичных условий, типа отсутствия synchronized).
Подробнее можно почитать тут https://shipilev.net/blog/2014/exceptional-performance/
Спасибо, интересно.
если вы кидаете и перехватывает exception в рамках одного метода это работает также быстро как goto
С точки зрения использования для логики приложения (как предлагается в статье) - масштаб мелковат, но в принципе оптимизация полезная. Теоретически и в плюсах можно, проанализировав try/catch/throw в рамках метода (вместе с заинлайненным кодом), сделать просто переход на обработчик, не знаю, делают ли такое реально компиляторы.
конечно использую при записи в файл ), процесс еще провожу с try
одна из моих плохих практик последних наверно, просто беру STDOUT_FILENO в С и пишу в поток сообщение, но файлы всё равно проверяю
У меня другая парадигма, не на Яве. Исключение - это непредсказуемая ошибка, о которой никто заранее не подумал. Никакая иная реакция на исключение, кроме завершения всей программы и перезапуска - недопустима. Любые предсказуемые события, типа хотел открыть файл, а он не открывается - реализуются не исключениями.
Это освобождает компилятору большое поле для манёвра. Компилятор наконец-то может рассчитывать на то, что последовательный блок кода будет выполнен последовательно. Ему не нужно опасаться, что в любой момент точка исполнения из середины блока улетит пёс знает куда. (Ну а если улетит, то корректность кода уже не важна).
Не говоря уже, что за 20 лет стажа я так и не понял, как грамотно писать программы в среде, где любая функция может выбросить неизвестно какое исключение, потому что оно пришло из пятого уровня вложенности.
эти случаи могут быть описаны в ключевых стандартах один из них например посикс, например как проводить процесс, должны быть соблюдены многие моменты, вызывая write ресурс может быть не доступен, а записывая в не туда например не проверяя операций с памятью без ексепшена, приводило бы к некоторым моментам, да где-то нету ексепшенов, но если есть потребность в стабильной библиотеке где нет обновлений они скорее нужны чем нет, это как типо нан - лучше реализовать и проверять делимое на 0(тут тоже стандарт IEEE_754), ну и забавный пример, было несколькно раз стэк трейс кинуть если не загружается ОС, предположим просто застынет а предположим кидаем стек трейс, как бы результат очевиден если это связано с ексепшенами
Кажется вы смешиваете несколько парадигм в одну.
Если вы пишете код в функциональном стиле / используете project-reactor, то ваш вариант TypedError / Flux/Mono.
Если же вы пишете код в классическом стиле, то в исключениях нет ничего плохого. Указанные вами минусы честно говоря притянуты за уши.
Частое игнорирование проверяемых исключений и опасность забыть, поймать непроверяемые.
Исключения надо обрабатывать на том уровне, на котором вы можете что-то сделать.
Например, валидация данных, указанных пользователем - проверяем корректность данных - кидаем ошибку в случае неверных данных - отдаем ошибку клиенту.
К слову про SneakyThrows - это вредная аннотация и в продакшен коде ее не должно быть ровно по той причине, что вы указали.
Гигантские блоки try/catch прячут реальный метод, откуда бросилось исключение
1) Немного непонятно про гигантские блоки. Никто не мешает вам разбить большой метод на более мелкие. Это не имеет отношение к исключениям.
2) С чего бы исключения прятали метод откуда они брошены? Как раз наоборот, исключения с их стектрейсом показывают откуда они брошены. В отличие от TypedError, которые представляют собой просто сообщение об ошибке.
Сомнительный перформанс при большом количестве исключений.
Как уже упомянули другие комментаторы, если вы используете исключения для control-flow, то у нас для вас плохие новости: вы неправильно используете исключения. Исключения по своему имени - это исключительная ситуация, которая в обычном исполнении программы не должна возникать.
Еще о плюсах исключений. Конечно, это все вкусовщина, но для меня конструкция try-with-resources автоматического закрытия ресурсов выглядит проще и понятней чем тот огород из flatMap / doOnFinally, что приходится городить при использовании project-reactor.
А вот в Kotlin компилятор не заставляет обрабатывать исключения.
Никто не мешает вам разбить большой метод на более мелкие. Это не имеет отношение к исключениям.
Как раз try/catch несколько мешает. Нельзя просто так взять и выделить в функцию блок, кидающий исключения, их нужно либо объявить либо обработать.
на случай если цепочки .map().flatmap() от функций, способных выбросить исключения, вызывают положительные эмоции, могу порекомендовать обратить внимание на функциональную библиотеку для ямы vavr.io Там как раз есть подходящая для этих целей монада Try
Из серии "каждый уважающий себя ... доложен написать свою реализацию..." (я уже два раза писал). Вопрос, почему не существует стандартной...
Если вы пишите сервис на спринге, то проверяемые исключения действительно могут досаждать, потому что в случае чего, у вас автоматом откатится транзакция и "юзер подождет". Но если вы пишите систему реального времени, допустим, систему управления воротами, где используете низкоуровневый ввод вывод, то просто так прокинуть исключение на верхний хендлер уже не получится - вас люди сожрут. Тут проверяемые исключения как раз очень сильно помогают контроллировать ошибочные ситуации и не гадать где что может стрельнуть. Как раз поэтому в гошке и сделали иаку проверку на ошибки - она не для очередного круда, а для низкоуровнего взаимодействия, чтобы информировать об ошибках явно.
Исключения нужно изолировать на каждом уровне, а не пробрасывать ioexeption из глубин преисподнии.
Вообще, на языке нужно программтровать так, как на нем принято. А самопальные обертки над результатом в конечном итоге приведут к лишней головной боли и легаси, оставшееся от гениального старожила. Не нравится такой подход, пишите на языке, где принято возвращать обертки.
В сервисе на Котлин со спринг проверяемые исключения могут досаждать тем, что транзакция не откатиться при из возникновения а Котлин не заставляет их ловить.
Вообще, на языке нужно программировать так, как на нем принято.
Хорошее, вроде как, замечание. Если встать в позицию школьного учителя вместо того чтоб пытаться понять, зачем оно так было "принято". Кроме того, само "принято" со временем меняется. В случае с Java это явно видно за 30 лет. Из-за этого нередко приходится "танцевать на одной ножке" для обеспечения обратной совместимости, и это в общем нормально: всё заранее уточнить и предусмотреть невозможно. Легаси, оставшееся от гениального старожила, потом окажется фактическим кривым прототипом новой функциональности языка: один раз и навсегда написать невозможно, а люди склонны упрощать себе жизнь, избегая необходимости творить копипасту.
Я люблю исключения ради стек трейса. Когда исключение бросается и потом как-нибудь прилетает в лог, то в логах всегда видно строчку, откуда ошибка пришла и через что она прострелила.
Все эти ошибки (привет, Раст) выглядят очень хорошо, пока ты не начинаешь искать источник проблемы. Не то место, где ты разименовываешь значение, а там, где эта ошибка была создана.
Стандартные исключения — это то, за что я готов платить производительностью.
Я из мира C#, и вопросы там возникают похожие. Механизма Checked Exceptions там нет. Я разрабатываю по следующему принципу: контракт класса, метода или интерфейса формируется как сигнатурой этого метода, так и ожидаемыми исключениями (что-то подобное Checked Exceptions). В моем случае ожидаемые исключения отражены не в синтаксисе языка, как в Java, а в документации к коду. Это не так удобно, как в Java, но что поделать.
Возьмем реальный пример: предположим, что нужно спроектировать достаточно примитивный интерфейс Chat со следующими методами: sendMessage(text, replyTo): string и свойством messages: string[]. В контексте нашего приложения важно уведомлять пользователя об ошибке отправки сообщения. Поэтому к сигнатуре метода sendMessage(text, replyTo) прикрепляется маркер throws ChatMessageDeliveryException — эта ошибка становится частью контракта.
ChatMessageDeliveryException может быть выброшена в ряде случаев, которые разработчик интерфейса интерпретирует как ожидаемые: нет интернета, получатель добавил отправителя в чёрный список, отправитель заблокирован и т. д. Можно выразить это черед одно общее исключение, можно через исключение под кейс, а можно через какой-то enum - зависит от потребности клиента. Таким образом, если sendMessage(text, replyTo) выбросит любое другое исключение, это будет считаться ошибкой разработки и должно логироваться как ошибка, а также отображаться в соответствующих алертах. ChatMessageDeliveryExceptionтак же может быть залогирован на уровне предупреждения вызывающей стороной.
Таким образом, на клиенте (SPA, Mobile, Desktop) срабатывает принцип Dependency Inversion: клиент описывает интерфейс, с которым он хочет работать, — тот, который он понимает, какие ошибки ожидает и умеет обрабатывать (какие исключения для него являются Checked). Затем на уровне инфраструктуры реализуется адаптер для этого интерфейса. Это значит, что клиент самостоятельно решает, что для него Cheched, а что Unchecked.
Если говорить о сервере, то чем ближе мы к самому высокому уровню (обычно это уровень Controller / Use Case: отправить сообщение, ответить на сообщение, начать новый чат), тем для меньшего числа ошибок вызывающий код действительно может выполнить какой-то recovery. Все Checked Exceptions будут описывать бизнес-прерывания и преобразовываться в HTTP-статус, отличный от 500. А все Unchecked Exceptions будут залогированы как ошибки, и клиенту вернётся ответ со статусом 500, указывающий, что клиент не может исправить эту проблему. Это значит, что не нужно на самом высоком уровне отлавливать ошибку подключения к базе данных; ее нужно отловить в инфраструкторном конвеере, где это соединение открывается, там же должна быть предпринята попытка recovery, если она предусмотрена.
В итоге получается, что чем более «общий» модуль (соединение с базой данных, вебсокет соединение и другой I/O), тем больше исходов в нём будет интерпретировано как Checked Exception и тем меньше как Unchecked Exception. Для «узкого» модуля зависимость обратно пропорциональна (Controllers / Use Cases / Clients). Получается достаточно сбалансированно.
Важно подчеркнуть, что Checked Exceptions необязательны в этой схеме, можно везде использовать Unchecked Exception, если вы не хотите получить «гарантию» обработки на уровне компилятора.
Исключения и нуллы - вечная боль Java и JVM- языков.
А вы как все валидацию проводите если не кидаете исключения?
Есть же обычно метод, в который приходит что то, а в конце проверка, лежит ли что нибудь в мапе ошибок. Если лежит - шли нафиг пользователя с его "Войной и Миром" в пароле
Просто вернуть код ошибки никак?
Сколько там современная жисонина в среднем имеет полей? Не дофига ли кодов ошибок? И получается - если какие то поля обязательны, то тоже делать коды на них?
Никто не мешает вернуть подробную информацию в отдельной структурке - всё равно дешевле, чем кидать исключение.
Ну так этим и занимаются же Хендлеры исключений. Целую аннотацию же придумали в том же Спринге @RestControllerAdvice над классом и @ExceptionHandler над методами.
Плюс
Typed Errors: Метод явно декларирует варианты результатов и мы обязаны их обрабатывать.
Почему это плюс именно Typed Errors, ведь при использовании Checked Exception метод точно так же декларирует варианты результатов и мы точно так же обязаны их обработать?
Причём при Checked Exception декларируется не просто один тип для ошибки (к тому же обычно общий), а есть возможность указать несколько типов ошибки с возможностью обработать каждый отдельный тип определённым образом.
Плюс
Typed Errors: Видно, какие методы возвращают ошибку среди большого количества вызовов.
Почему это плюс именно Typed Errors, ведь при использовании Checked Exception это видно на столько же хорошо?
Минус
Exceptions: Сомнительный перформанс при большом количестве исключений.
Это действительно минус, но:
Исключение потому и исключение что оно не должно быть частым случаем.
Исключения не должны использоваться для обычного управления потоком выполнения - только для исключительных ситуаций.
Самая дорога часть это сбор стэка, но если до него не доводить (или отключить его сбор), то стоимость резко снижается, в то время как в случае с
Typed Errorsдаже в случае успешной ветки выполнения есть необходимость в создании инстансов обёрток что тоже не бесплатно, при том что по успешной ветке выполнение по определению идёт чаще.
И в итоге у Typed Errors количество плюсов уменьшается с 3 до 1, а у Exceptions количество минусов уменьшается с 3 до 2 (да и те весьма сомнительны и уже обсуждались выше в комментах).
Посидев 5 лет на питоне могу сказать что checked exception это офигенно. Громоздко или нет - пофиг, лучше громоздко чем разваливается на каждый чих. А за ломбок просто руки отрывать надо имхо
Про ломбок согласен. Был сильно удивлен наличием такой "фичи". И еще больше удивлен, что достаточно уважаемые "гуру" и преподаватели проталкивают ее неокрепшим умам.
Если не затруднит, разверните мысль, что конкретно Вам не нравится в использовании ломбок? Декларативно, наглядно, меньше кода, код проще читать. Да, есть Record, но кода, написанного до - вагон и маленькая тележка.
Если код (геттеры-сеттеры-конструкторы-билдеры) может быть сгенерирован IDE, то его лучше вообще не писать, имхо. На производительность ломбок не влияет, отладке не мешает, и, в случае необходимости, всегда можно развернуть аннотацию в конкретный метод.
Ломбок это имхо один из лучших примеров нафиг не нужной библиотеки которая вносит в код потенциально опасную магию. Которая может лопнуть в неявном месте, особенно если выхлоп ломбока используют другие либы. Я не против датаклассов и прочего сахара, но это должна быть фича языка как в питоне или котлине, а не левая библиотека.
Если код можно сгенерить ide - имхо его нужно генерить ide. Писать на джаве и противиться написанию ещё пары методов как-то странно, язык достаточно развесистый.
Ну и отладке он таки мешает
Понял, потенциальная опасность сломаться для Вас перевешивает удобство чтения. Спасибо!
Магии (в смысле неявного кода) в каком-нибудь фреймворке типа Spring на порядок больше, нежели в в этой библиотеке. А в SpringBoot еще больше. Но использовать повсеместно их это не мешает.
Может быть, когда-нибудь ломбок и включат в JDK, частично или полностью, а может быть нет, а предложат альтернативу, как те же Records.
Вот потому, что Java достаточно многословна, и хочется писать немного лаконичней, желательно сейчас, а не в светлом будущем, и на Java, а не котлине или питоне. Пара методов на десяток полей в десятке DTO - так и набегает тут пара тысяч строк, там пара тысяч строк, без которых вполне можно обойтись.
потенциальная опасность сломаться для Вас перевешивает удобство чтения
Потенциальная возможность неожиданно сломаться в неизвестном месте когда несколько сортов магии решат встать в круг как ежики в анекдоте.
Магии (в смысле неявного кода) в каком-нибудь фреймворке типа Spring на порядок больше, нежели в в этой библиотеке. А в SpringBoot еще больше. Но использовать повсеместно их это не мешает.
Про шпринг я и не спорю. Как раз с чудесной SpringBootApplication один раз поимел веселья когда внезапно развалился контекст потому что неиспользуемый (но загруженный) hibernate вступил в конфликт хрен его помнит с чем. Это вообще весёлая аннотация, одного вопроса что она делает достаточно чтоб раскрутить собеседование по шпрингу на пару часов, ну её нафиг.
Но с фреймворками, по сравнению с ломбоком - вопрос баланса. Условно говоря я не могу нажать в идее три кнопки и сгенерить даже малую часть шпринга. Ну, не, много чего можно обойти или вообще не использовать, но стоимость разработки фреймворк снижает прилично. Ломбок не снижает по сути ничего кроме вызова кодогенератора и пары килобайт в гите.
Исходный пример - правильный. Если исходные параметры метода - неверные, метод должен выбросить runtime исключение и прекратить работу.
Смысл этого кода - в недопущении выполнения кода с неправильными параметрами.
Словосочетание "Защитное программирование" вам что-нибудь говорит?
Смысл защитного программирования состоит в том, что выполнение метода с неправильными параметрами - намного опаснее и страшнее, чем вывод сообщения о Runtime exception в рантайме и (о ужас!) на проде. Вы же банкиры! Вы должны понимать, что выдача сообщения на формочке в браузере - намного пустячная проблема, чем перевод миллиона хрен знает кому со счета клиента. Или для вас, быдлокодеров, самое страшное - runtime exception на экране пользователя?
Многие (джуны, да и много сеньоров) говорят "а что, если вызывающий метод (разработчик, конечно) подаст на вход null или неверные параметры?
Так не надо говно (извиняюсь за выражение) подавать на вход методу. Подача дряни в метод - грех вызывающего метода.
Вызываемый метод не должен чистить или исправлять параметры. Его задача - проверить параметры на ограничения и выполнить свою работу. Либо прервать выполнение. Какие ограничения? Об этом надо писать в доке метода, и эти доки надо читать, перед тем, как писать вызов метода.
Формируйте правильные параметры перед вызовом метода. Если эти параметры формируются комбинацией или вычислением ваших входных параметров - проанализируйте и примите решение, вызывать или нет метод. Если эти параметры приходят извне, и вы не проверили перед выполнением своего метода - также примите решение, вызывать или нет.
Использование возвращаемых результатов работы функции - вообще порочная практика, поскольку возвращает нас во времена Си, когда не было исключение и приходилось либо возвращать значения с результатом true/false, либо возвращать вместо данных, например количество чего-то, отрицательный результат, и потом идти в функции Win-API и вытаскивать код и текст ошибки. Писали на Си? Ну так попробуйте, поработайте без механизма исключений. Если у нас огромный стек вызова, и каждый метод возвращает свою структуру с результатом, как вы возвратите из глубины результат? будете переупаковывать результат от вызываемого метода в свой? А если вызываемых методов в вашем десяток, и каждый со своей структурой результата?
Мое мнение - не надо изобретать ахинею. Умные люди придумали механизм исключений. Этот механизм нужно знать, любить и пользоваться. Ну и на уровне менеджмента бить по рукам тех, кто пихает в параметры вызываемых методов всякую дрянь.
при использовании линтеров с кастомными правилами, настроенными на подобные кейсы
Привыкли полагаться на универсальный встроенный "линтер" под названием "статическая проверка типов", а тут вдруг его оказывается принципиально недостаточно. Ну придётся поднапрячься и настроить дополнительную проверку...
Если глобально и коротко, то потому, что подход к разработке встроенной стандартной библиотеки Java и подход к разработке прикладной коммерческой программы - разные
Дополнительная причина - что синтаксис языка Java вынуждает писать много boilerplate-кода вокруг исключений. Тут и предыдущий абзац "в строку", потому что именно в стандартной библиотеке такое терпимо и даже, по соображениям надёжности, потребно, а в прикладной программе в основном засоряет код, закапывая "основной путь" в глубине обработчиков исключений.
Мне кажется, я нашёл ключевой ответ: потому что они увеличивают связность кода. Этим ответом покрывается и конкретизируется также предыдущий мой ответ. В случае гигантской монолитной (как ныне модно выражаться) библиотеки это нужно, в случае много-микро-модульной системы это создаёт лишние зависимости между модулями.
Информация
- Дата регистрации
- Дата основания
- 1996
- Численность
- 5 001–10 000 человек
- Местоположение
- Россия
Почему Exceptions в Java не взлетели — и что с этим делать в 2025 году