Использовать необработанные исключения вместо Contains()?

StackOverflow https://stackoverflow.com/questions/8348

  •  08-06-2019
  •  | 
  •  

Вопрос

Представьте, что объект, с которым вы работаете, имеет коллекцию других объектов, связанных с ним, например, коллекцию элементов управления в WinForm.Вы хотите проверить наличие определенного объекта в коллекции, но в коллекции нет Contains() способ.Есть несколько способов справиться с этим.

  • Реализуйте свой собственный Contains() метод путем перебора всех элементов в коллекции, чтобы увидеть, является ли один из них тем, что вы ищете.По-видимому, это подход "наилучшей практики".
  • Недавно я наткнулся на некоторый код, где вместо цикла была попытка получить доступ к объекту внутри инструкции try следующим образом:
try  
{  
    Object aObject = myCollection[myObject];  
}  
catch(Exception e)  
{  
    //if this is thrown, then the object doesn't exist in the collection
}

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

Это было полезно?

Решение

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

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

Например, исключение может быть вызвано тем, что объект не существует в коллекции, или потому, что сама коллекция равна нулю, или потому, что вы не можете преобразовать myCollect[MyObject] в AObject.

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

Это пара хороших статей о том, когда и где обычно считается приемлемым создавать исключения:

Мне особенно нравится эта цитата из второй статьи:

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

Другие советы

Общее эмпирическое правило состоит в том, чтобы избегать использования исключений для потока управления, если только обстоятельства, которые вызовут исключение, не являются "исключительными" - например, крайне редкими!

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

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

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

Однако, если вы просто проверяете существование объекта и обнаруживаете, что его там нет, это не исключение.Использование исключения в этом случае некорректно.

Анализ производительности среды выполнения зависит от используемой фактической коллекции и метода ее поиска.Хотя это не должно иметь значения.Не позволяйте иллюзии оптимизации ввести вас в заблуждение и заставить писать запутанный код.

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

Редактировать:Я согласен с комментариями Райана Фокса об исключительном случае, они идеальны

Что касается производительности, то она зависит от индексатора в коллекции.C # позволяет вам переопределить оператор индексатора, поэтому, если он выполняет цикл for, подобный методу contains, который вы бы написали, тогда он будет таким же медленным (возможно, на несколько наносекунд медленнее из-за try / catch...но беспокоиться не о чем, если только сам этот код не находится в огромном цикле).

Если индексатор равен O(1) (или даже O(log(n))...или что-нибудь быстрее, чем O (n)), тогда решение try / catch, конечно, было бы быстрее.

Кроме того, я предполагаю, что индексатор выдает исключение, если он возвращает null, вы, конечно, могли бы просто проверить наличие null и не использовать try / catch .

В общем, использование обработки исключений для программного потока и логики является плохой практикой.Лично я считаю, что в последнем варианте недопустимо использование исключений.Учитывая особенности языков, обычно используемых в наши дни (таких как Linq и лямбды в C #, например), нет причин не написать свой собственный метод Contains().

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

Исключения должны быть исключительными.

Что-то вроде "Коллекция отсутствует, потому что база данных выпала из-под нее" является исключительным

Что-то вроде "ключ отсутствует" - это нормальное поведение для словаря.

Для вашего конкретного примера коллекции элементов управления winforms Controls в собственности есть ContainsKey метод, который вы должны использовать.

Там нет никакого ContainsValue потому что при работе со словарями / хэш-таблицами нет быстрого способа, кроме перебора всей коллекции, проверки, присутствует ли что-то, так что вам действительно не рекомендуется этого делать.

Что касается ТОГО, ПОЧЕМУ Исключения должны быть исключительными, то речь идет о двух вещах

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

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

Взгляните на этот пост в блоге Кшиштофа: http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx

Исключения следует использовать для передачи сообщений об ошибках, но их не следует использовать в качестве управляющей логики (особенно когда существуют гораздо более простые способы определения условия, такие как Contains ).

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

Последнее является приемлемым решением.Хотя я бы определенно уловил конкретное исключение (ElementNotFound?) , которое коллекция выдает в этом случае.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top