За время моей трудовой деятельности я участвовал как в проектах использующих исключения, так и в проектах использующих коды ошибок. Сейчас я больше склоняюсь ко второму подходу, так как исключения действительно часто становятся этаким неявным goto, кроме того использование их в многопоточном и асинхронном коде становится болью. С другой стороны, отказ от проверок кодов возврата тоже плохо. Давайте пофантазируем как можно было бы организовать идеальную обработку ошибок. Я пока придумал объеденить оба подхода через дополнительную сущность — ну пусть будет поток выполнения (ExecutionFlow), экземпляр которого имеет каждый поток. Данная сущность неявно присутствует в вызове каждой функции, хранит стек вызовов и флажок ошибки. Проще проиллюстрировать псевдокодом:
int plus2([неявно ExecutionFlow executionFlow], int x)
{
if (x < 10)
return RangeError("bla-bla"); // Выход из функции и установка ошибки executionFlow.setError(RangeError("bla-bla"))
return x + 2; // Просто выход и возврат значения, неявно оборачивается в ReturnValue<int>
}
void foo() { ... }
Вызывающий код:
var y = foo(5);
if (y.success) // проверка, также устанавливает в объекте ReturnValue<int> и ExecutionFlow флажок checked = true
int z = bar(y); // неявно приводится к int, если перед приведением не проверить success, то программа пишет стек и завершается
if (!foo().success)
print(executionFlow.stackTrace());
var error = executionFlow.error();
print(error.message())
return error
foo(); // не проверили success, флажок не установился, значит при вызове следующей функции (любой) будет стек и аварийное завершение
Как считаете имеет право на жизнь? Есть ли языки с таким подходом к обработке ошибок?