Что использовать правильней: if(), или try-catch?

1,00
р.
Например что использовать, когда нужно создавать колонку в таблице только в том случае, если такой колонки в ней нет?
Я могу, как написать код, который будет проверять, существует ли колонка, и лишь потом добавлять без ошибок, так могу и без проверки пытаться добавить колонку, обернув метод в 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 }
Откуда возникает ещё одно правило: код можно сделать чище, если знать язык, платформу, библиотеку, и опираться на их исключения.
Выше я написал, что исключения выполняются медленнее, чем проверки, и хочу уточнить свою мысль: не надо опираться на производительность при принятии решения. Правильная передача смысла другому программисту, чистота кода — то, к чему следует стремится. Разница в производительности, хотя и существует, никогда не была настолько большой, чтобы пользователи её замечали. Ну, если только вы не пишите код для самого вложенного цикла в каком-нибудь графическом движке.