Нужны ли checked исключения в современных языках программирования
Что проверяют
Вопрос дискуссионный, единственно верного ответа у него нет. Проверяют аргументацию: знаете ли вы механику checked-исключений, видите ли, чем именно они мешают, и понимаете ли, почему более новые языки от них отказались.
Как отвечать
Сначала факты. Checked это всё, что наследует Exception, но не RuntimeException: компилятор требует либо catch, либо throws в сигнатуре. Идея хорошая, ошибка становится частью контракта метода, и не заметить её нельзя.
Проблемы у неё практические:
- плохо дружат с функциональными интерфейсами: типы из
java.util.functionне объявляютthrows, поэтому вызовFiles.readStringвнутриmapне компилируется и лямбда обрастает обёрткой; - текут сквозь слои: либо
throwsрасползается по всему стеку вызовов, либо ошибку заворачивают вRuntimeExceptionради сохранения сигнатуры; - провоцируют пустой
catch, а проглоченная ошибка хуже упавшей.
Отсюда и практика: у Kotlin и Scala checked-исключений нет, в C# их не было никогда, а Rust и функциональные языки выражают ошибку типом результата, то есть значением, а не отдельным каналом.
Разумная позиция звучит так: разделяйте ожидаемые ошибки и баги программы. Для первых уместны checked или тип результата, для вторых только unchecked. В своём коде по умолчанию unchecked, а checked там, где вызывающий действительно может что-то сделать.
Где спотыкаются
Говорят, что checked-исключения медленнее. Стоимость исключения целиком в сборе стектрейса и одинакова для обоих видов, а checked отличаются только проверкой на этапе компиляции.
Ставят throws Exception на каждом методе или пишут пустой catch. Контракт превращается в шум, а настоящая причина сбоя всплывает позже и в другом месте, уже без следов.
Оборачивают в RuntimeException, не передав исходное исключение как cause. Стектрейс обрывается на обёртке. Соседняя ошибка: поймать, залогировать и бросить дальше, из-за чего один сбой попадает в лог по три раза.