РЕДАКТИРОВАТЬ 11/20/2009 :
Я только что прочитал эту статью MSDN о повышении производительности управляемого кода , и эта часть напомнила мне этот вопрос:
Затраты на выполнение броска исключения значительны. Хотя структурированная обработка исключений является рекомендуемым способом обработки состояний ошибок, убедитесь, что вы используете исключения только в исключительных случаях, когда возникают условия ошибок. Не используйте исключения для регулярного потока управления.
Конечно, это только для .NET, и оно также предназначено специально для тех, кто разрабатывает высокопроизводительные приложения (такие как я); так что это явно не универсальная правда Тем не менее, среди нас есть много разработчиков .NET, поэтому я чувствовал, что это стоит отметить.
EDIT
Хорошо, во-первых, давайте прямо скажем: я не собираюсь вступать в драку с кем-либо из-за вопроса производительности. В целом, на самом деле, я склонен согласиться с теми, кто считает преждевременную оптимизацию грехом. Однако позвольте мне сделать два замечания:
Постер просит дать объективное обоснование общепринятому мнению, что исключения следует использовать с осторожностью. Мы можем обсуждать удобочитаемость и правильное оформление всего, что хотим; но это субъективные вопросы, когда люди готовы спорить с обеих сторон. Я думаю, что плакат знает об этом. Дело в том, что использование исключений для управления потоком программ часто является неэффективным способом действий. Нет, не всегда , но часто . Вот почему разумно советовать экономно использовать исключения, точно так же, как это хороший совет: есть красное мясо или пить вино экономно.
Существует разница между оптимизацией без веской причины и написанием эффективного кода. Следствием этого является то, что существует разница между написанием чего-то надежного, если не оптимизированного, и просто неэффективного. Иногда я думаю, что когда люди спорят о таких вещах, как обработка исключений, они на самом деле просто говорят друг за другом, потому что они обсуждают принципиально разные вещи.
Чтобы проиллюстрировать мою точку зрения, рассмотрим следующие примеры кода C #.
Пример 1. Обнаружение неверного ввода пользователя
Это пример того, что я бы назвал исключением злоупотребление .
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
Этот код для меня нелепый. Конечно это работает . Никто не спорит с этим. Но это должно быть примерно таким:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Пример 2. Проверка наличия файла
Я думаю, что этот сценарий на самом деле очень распространен. Это, конечно, кажется намного более "приемлемым" для многих людей, поскольку имеет дело с файловым вводом / выводом:
string text = null;
string path = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
inputChecksOut = true;
} catch (FileNotFoundException) {
path = GetInput();
}
}
Это кажется достаточно разумным, верно? Мы пытаемся открыть файл; если его там нет, мы ловим это исключение и пытаемся открыть другой файл ... Что с этим не так?
Ничего, правда. Но рассмотрим эту альтернативу, которая не не выдает никаких исключений:
string text = null;
string path = GetInput();
while (!File.Exists(path)) path = GetInput();
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
Конечно, если бы эффективность этих двух подходов была на самом деле одинаковой, это действительно было бы чисто доктринальной проблемой. Итак, давайте посмотрим. Для первого примера кода я составил список из 10000 случайных строк, ни одна из которых не представляла правильное целое число, а затем добавил допустимую целочисленную строку в самый конец. Используя оба вышеупомянутых подхода, это были мои результаты:
Использование try
/ catch
блока: 25,455 секунд
Использование int.TryParse
: 1,637 миллисекунд
Для второго примера я сделал в основном то же самое: составил список из 10000 случайных строк, ни одна из которых не была допустимым путем, затем добавил допустимый путь в самый конец. Это были результаты:
Использование блока try
/ catch
: 29,989 секунд
Использование File.Exists
: 22,820 миллисекунд
Многие люди ответили бы на это словами: «Да, ну, бросить и поймать 10 000 исключений крайне нереально; это преувеличивает результаты». Конечно, это так. Разница между выдачей одного исключения и обработкой неверного ввода самостоятельно не будет заметна для пользователя. Факт остается фактом: использование исключений в этих двух случаях от 1000 до более 10000 раз медленнее , чем альтернативные подходы, которые столь же удобочитаемы - если не больше.
Вот почему я включил пример метода GetNine()
ниже. Нельзя сказать, что это невыносимо медленно или недопустимо медленно; это то, что он медленнее, чем должен быть ... без веской причины .
Опять же, это всего лишь два примера. Из курса будут времена, когда снижение производительности при использовании исключений не будет таким серьезным (Павел прав; в конце концов, это зависит от реализации). Все, что я говорю: давайте посмотрим правде в глаза, ребята - в случаях, подобных приведенному выше, создание и отлов исключений аналогичны GetNine()
; это просто неэффективный способ сделать что-то , что можно легко сделать лучше .
Вы просите обоснование, как будто это одна из тех ситуаций, когда все запрыгнули на подножку, не зная, почему. Но на самом деле ответ очевиден, и я думаю, вы уже это знаете. Обработка исключений имеет ужасные характеристики.
ОК, может быть, это подходит для вашего конкретного бизнес-сценария, но условно говоря , бросая / перехватывая исключение, вносит гораздо больше накладных расходов, чем необходимо во многих, многих случаях. Вы знаете это, я знаю это: большую часть времени , если вы используете исключения для управления потоком программ, вы просто пишете медленный код.
Вы могли бы также спросить: почему этот код плох?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
Могу поспорить, что если вы профилируете эту функцию, то обнаружите, что она работает довольно приемлемо быстро для вашего типичного бизнес-приложения. Это не меняет того факта, что это ужасно неэффективный способ сделать что-то, что можно сделать намного лучше.
Это то, что люди имеют в виду, когда говорят об исключении "злоупотребления".