Например что использовать, когда нужно создавать колонку в таблице только в том случае, если такой колонки в ней нет? Я могу, как написать код, который будет проверять, существует ли колонка, и лишь потом добавлять без ошибок, так могу и без проверки пытаться добавить колонку, обернув метод в try-catch (если есть — перехватится исключение если нет — колонка добавится). Результат работы будет одинаковым. И таких примеров можно привести массу, например можно проверять файлы на существование, и только потом делать копирование, а можно перехватывать исключения. Какой метод более грамотный, или правильный?
Ответ Исключения позволяют сделать код чище и понятнее, поскольку с их помощью можно разделить выполнение действий и обработку ошибок. В книге Мартина «Чистый код» этот аспект описан самым первым. При этом, по моему опыту, надо именно разделять код: выносить блок try/catch в отдельный метод. Программист, который будет разбираться с вашей программой, скажет вам «спасибо». Эта конструкция весьма громоздка, даже в простой форме try/finally, и в середине большого метода озадачит кого угодно. Второй плюс исключений в том, что они позволяют передавать дополнительную информацию. Функция atoi из C ничего не могла сказать о том, почему именно не удалось конвертировать строку в целое число. int result result = atoi("123") /* в result 123 */ . . . result = atoi("foo") /* что в result? */ В таких языках, как Java и C# вы можете добавить в свой класс исключений необходимые свойства, сочетая какие-нибудь коды ошибок с контекстами вызова и ещё чем-нибудь. Пример: . . . catch (SqlException e) { Console.WriteLine("Ошибка '{2}' в строке {0} процедуры {1}", e.LineNumber, e.Procedure, e.Message) } . . . Разработчики, которые давно и прочно перешли на исключения, делают свой код ещё чище, не возвращая никаких кодов ошибок, в частности, пресловутого null: // Что будет, если в хранилище нет пользователя с указанным userid? // Вернёт null или сгенерирует исключение? User user = userRepository.GetById(userId) В настоящее время считается, что правильнее создавать исключение (за аргументами снова отсылаю к книге «Чистый код»). Если метод используется для проверки наличия пользователя, его рекомендуют переделать в форму TryX. Она неуклюжа, но уже привычна, по крайней мере, для программистов .NET: User user if (userRepository.TryGetById(userId, out user)) { . . . } Что более ценно, она однозначна: смотря на код, вы не думаете: «а что, если такого пользователя нет?» Теперь посмотрим на ситуацию с другой стороны — а когда не надо использовать исключения? На мой взгляд, тогда, когда они затрудняют понимание кода. Если ситуация не исключительная, тогда в тексте программы должна быть обычная проверка. Например, приложение для нового документа генерирует имена Untitled.foo, Untitled1.foo, Untitled2.foo и т.д. Та ситуация, что файл с таким именем уже существует, является вполне обычной, не исключительной, поэтому и реализовать код корректнее с помощью обычной проверки: public string GetNewDocumentName(string prefix) { var filename = prefix + ".foo" if (!File.Exists(filename)) return filename int suffix = 0 do { filename = prefix + (++suffix).ToString() + ".foo" } while (File.Exists(filename)) return filename } Этот код не только быстрее, чем аналогичный с использованием исключений, но, что важнее, понятнее другим программистам, потому что неявно передаёт им дополнительную информацию: эта штука будет случаться регулярно, и мы к этому готовы. А вот, например, невероятная ситуация, что в папке скопилось 2 миллиарда untitled-файлов — несомненное исключение. public string GetNewDocumentName(string prefix) { var filename = prefix + ".foo" if (!File.Exists(filename)) return filename int suffix = 0 do { if (suffix == int.MaxValue) throw new InvalidOperationException("You're crazy!") filename = prefix + (++suffix).ToString() + ".foo" } while (File.Exists(filename)) return filename } Такой код выглядит более запутанным. К счастью, мы можем часть проверок возложить на компилятор C#: public string GetNewDocumentName(string prefix) { var filename = prefix + ".foo" if (!File.Exists(filename)) return filename int suffix = 0 do checked { filename = prefix + (suffix++).ToString() + ".foo" } while (File.Exists(filename)) return filename } Откуда возникает ещё одно правило: код можно сделать чище, если знать язык, платформу, библиотеку, и опираться на их исключения. Выше я написал, что исключения выполняются медленнее, чем проверки, и хочу уточнить свою мысль: не надо опираться на производительность при принятии решения. Правильная передача смысла другому программисту, чистота кода — то, к чему следует стремится. Разница в производительности, хотя и существует, никогда не была настолько большой, чтобы пользователи её замечали. Ну, если только вы не пишите код для самого вложенного цикла в каком-нибудь графическом движке.