Как уже показывают ваши примеры, каждый такой случай должен оцениваться отдельно, и между «исключительными обстоятельствами» и «контролем потока» существует значительный спектр серого, особенно если ваш метод предназначен для многократного использования и может использоваться в совершенно разных схемах. чем это было изначально разработано. Не ожидайте, что мы все здесь согласимся с тем, что означает «не исключение», особенно если вы немедленно обсудите возможность использования «исключений» для реализации этого.
Мы также можем не согласиться с тем, какой дизайн делает код более легким для чтения и поддержки, но я предполагаю, что у разработчика библиотеки есть четкое личное представление об этом, и ему нужно только сбалансировать его с другими вовлеченными соображениями.
Краткий ответ
Следуйте своим внутренним чувствам, кроме случаев, когда вы разрабатываете довольно быстрые методы и ожидаете возможности непредвиденного повторного использования.
Длинный ответ
Каждый будущий абонент может свободно переводить между кодами ошибок и исключениями по своему усмотрению в обоих направлениях; это делает два подхода к проектированию практически эквивалентными, за исключением производительности, удобства отладки и некоторых контекстов ограниченной функциональной совместимости. Обычно это сводится к производительности, поэтому давайте сосредоточимся на этом.
Как правило, ожидайте, что выбрасывание исключения в 200 раз медленнее, чем обычное возвращение (на самом деле, в этом есть существенная разница).
Как еще одно практическое правило, выбрасывание исключения часто позволяет получить более чистый код по сравнению с самыми грубыми магическими значениями, поскольку вы не полагаетесь на то, что программист переводит код ошибки в другой код ошибки, поскольку он проходит через несколько уровней клиентского кода в направлении точка, в которой достаточно контекста для последовательной и адекватной обработки. (Особый случай: здесь, null
как правило, лучше, чем у других магических ценностей, из-за его склонности к автоматическому переводу себя NullReferenceException
в случае некоторых, но не всех типов дефектов; обычно, но не всегда, довольно близко к источнику дефекта. )
Так какой урок?
Для функции, которая вызывается всего несколько раз в течение жизненного цикла приложения (например, инициализация приложения), используйте все, что дает вам более понятный и понятный код. Производительность не может быть проблемой.
Для одноразовой функции используйте все, что дает более чистый код. Затем выполните некоторое профилирование (при необходимости вообще) и измените исключения на коды возврата, если они входят в число предполагаемых верхних узких мест на основе измерений или общей структуры программы.
Для дорогой многоразовой функции используйте все, что дает вам более чистый код. Если вам, по сути, всегда приходится проходить в обе стороны по сети или анализировать XML-файл на диске, затраты на создание исключения, вероятно, незначительны. Гораздо важнее не потерять детали любого сбоя, даже не случайно, чем быстро вернуться из «неустранимого отказа».
Бережливая многоразовая функция требует больше размышлений. Используя исключения, вы вызываете что-то вроде 100-кратного замедления для вызывающих, которые увидят исключение при половине их (многих) вызовов, если тело функции выполняется очень быстро. Исключения по-прежнему остаются вариантом дизайна, но вам придется предоставить альтернативу с низкими накладными расходами для абонентов, которые не могут себе этого позволить. Давайте посмотрим на пример.
Вы перечисляете отличный пример того Dictionary<,>.Item
, что, собственно говоря, изменилось от возврата null
значений к выбрасыванию KeyNotFoundException
между .NET 1.1 и .NET 2.0 (только если вы хотите считать Hashtable.Item
его практическим неуниверсальным предвестником). Причина этого «изменения» здесь не без интереса. Оптимизация производительности типов значений (не больше бокса) сделало оригинальное магическое значение ( null
) недоступным; out
параметры просто вернут небольшую часть затрат на производительность. Это последнее соображение производительности совершенно незначительно по сравнению с накладными расходами на бросание a KeyNotFoundException
, но дизайн исключений здесь все же лучше. Почему?
- Параметры ref / out несут свои затраты каждый раз, а не только в случае «отказа»
- Любой, кому небезразлично, может позвонить
Contains
перед любым обращением к индексатору, и этот шаблон читается совершенно естественно. Если разработчик хочет, но забывает звонить Contains
, никакие проблемы с производительностью не могут закрасться; KeyNotFoundException
достаточно громко, чтобы быть замеченным и исправленным.