Я бы предпочел исключения для всех случаев ошибки, кроме случаев, когда сбой является ожидаемым безошибочным результатом функции, которая возвращает примитивный тип данных. Например. поиск индекса подстроки в большей строке обычно возвращает -1, если не найден, вместо вызова NotFoundException.
Возвращение недопустимых указателей, которые могут быть разыменованы (например, вызывает исключение NullPointerException в Java), недопустимо.
Использование нескольких различных числовых кодов ошибок (-1, -2) в качестве возвращаемых значений для одной и той же функции обычно является плохим стилем, поскольку клиенты могут выполнить проверку "== -1" вместо "<0". </p>
Здесь нужно иметь в виду эволюцию API с течением времени. Хороший API позволяет изменять и расширять поведение при сбоях несколькими способами, не нарушая работу клиентов. Например. если дескриптор ошибок клиента проверен на 4 случая ошибок, и вы добавляете пятое значение ошибки в свою функцию, обработчик клиента может не проверить это и не прервать работу. Если вы возбуждаете исключения, это обычно упрощает миграцию клиентов на более новую версию библиотеки.
Еще одна вещь, которую следует учитывать, - это когда вы работаете в команде, где следует четко обозначить всем разработчикам такие решения. Например. «Исключения для вещей высокого уровня, коды ошибок для вещей низкого уровня» очень субъективны.
В любом случае, когда возможно более одного тривиального типа ошибки, исходный код никогда не должен использовать числовой литерал, чтобы вернуть код ошибки или обработать его (вернуть -7, если x == -7 ... ), но всегда именованная константа (верните NO_SUCH_FOO, если x == NO_SUCH_FOO).