РЕДАКТУВАТИ 20.11.2009 :
Я просто читав цю статтю MSDN про покращення продуктивності керованого коду, і ця частина нагадала мені це питання:
Вартість продуктивності кидання винятку є значною. Хоча структурована обробка винятків є рекомендованим способом обробки помилок, переконайтеся, що ви використовуєте винятки лише у виняткових обставинах, коли виникають умови помилок. Не використовуйте винятки для регулярного регулювання потоку.
Звичайно, це лише для .NET, і воно також спрямоване спеціально на тих, хто розробляє високопродуктивні додатки (як я); отже, це очевидно не універсальна істина. Тим не менше, нас є багато розробників .NET, тому я відчув, що це варто зазначити.
РЕДАГУВАТИ :
Добре, перш за все, давайте зрозуміємо одне: я не маю наміру битися з кимось із-за питання про виступ. Загалом, насправді я схильний погодитися з тими, хто вважає передчасну оптимізацію гріхом. Однак дозвольте лише зазначити два моменти:
Плакат вимагає об'єктивного обґрунтування загальноприйнятої думки, що винятки слід використовувати економно. Ми можемо обговорювати читабельність та правильний дизайн все, що хочемо; але це суб’єктивні питання з людьми, готовими сперечатися з будь-якої сторони. Я думаю, що плакат про це знає. Справа в тому, що використання винятків для управління потоком програм часто є неефективним способом здійснення дій. Ні, не завжди , але часто . Ось чому розумна порада - вживати винятки економно, як і хороша порада - їсти червоне м’ясо або економно пити вино.
Існує різниця між оптимізацією без поважних причин та написанням ефективного коду. Наслідком цього є те, що існує різниця між написанням чогось надійного, якщо не оптимізованого, і просто неефективного. Іноді я думаю, що коли люди сперечаються через такі речі, як обробка винятків, вони насправді просто розмовляють один з одним, оскільки вони обговорюють принципово різні речі.
Для ілюстрації моєї думки розглянемо наступні приклади коду 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–10 000 разів повільніше, ніж альтернативні підходи, які настільки ж читаються - якщо не більше.
Ось чому я включив приклад GetNine()методу нижче. Справа не в тому, що це нестерпно повільно чи неприпустимо повільно; це те, що це повільніше, ніж повинно бути ... без поважних причин .
Знову ж таки, це лише два приклади. З звичайно буде раз , коли падіння продуктивності використання винятків не ця важка (Павла право, в кінці кінців, це залежить від реалізації). Все, що я кажу, це: давайте розглянемося з фактами, хлопці - у випадках, подібних до наведеного вище, кидання та вловлювання винятку є аналогічним GetNine(); це просто неефективний спосіб зробити те, що можна легко зробити краще .
Ви запитуєте обгрунтування, ніби це одна з тих ситуацій, коли всі стрибають на ноги, не знаючи чому. Але насправді відповідь очевидна, і я думаю, ви її вже знаєте. Обробка винятків має жахливі результати.
Добре, можливо, це добре для вашого особливо ділового сценарію, але умовно кажучи , кидання / ловлення винятку створює значно більші витрати, ніж це потрібно у багатьох, багатьох випадках. Ви це знаєте, я це знаю: здебільшого , якщо ви використовуєте винятки для управління потоком програм, ви просто пишете повільний код.
Ви можете також запитати: чому цей код поганий?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
Б'юся об заклад, що якщо ви профільіруете цю функцію, ви виявите, що вона виконує цілком прийнятно швидко для вашого типового бізнес-додатку. Це не змінює того факту, що це жахливо неефективний спосіб досягнення чогось, що можна зробити набагато краще.
Ось що люди мають на увазі, коли говорять про винятки "зловживання".