Ответ, на мой взгляд, зависит!
Все подходы, которые вы упомянули, имеют конкретные варианты использования, где каждый из них доминирует. Давайте возьмем один за другим. (И критик в конце)
Асинхронное приложение с обратными вызовами
Хотя обратные вызовы рассматриваются как старые и не модные, они дают нам гибкость того, что я могу назвать «множественными возвращаемыми значениями».
getUserByName(name, callback) {
// Caller can check the `err` and `status` to decide what to do next.
return callback(err, status, user);
}
- Здесь выдача ошибок не будет работать должным образом. То есть вы не можете отловить сгенерированные ошибки с помощью try-catch. Конечно, вы можете «вернуть» пользовательскую ошибку. Но это кажется бессмысленным, имея возможность вернуть «статус» отдельно.
- Возвращение нулевого объекта или RORO не имеет смысла с дополнительным параметром
status
. Вы можете предпочесть избавиться от status
в пользу нулевого объекта или RORO.
Асинхронное приложение с обещаниями / async-await
Здесь будут выбрасывать ошибки. Если вы используете async-await, перехват try-catch получит ошибку. Или, если вы используете обещания как есть, выгода цепочки обещаний получит.
Синхронное приложение
Бросание ошибок и использование try-catch работает, как и ожидалось. Тем не менее, это будет редкий случай в реальном приложении, которое использует любую стороннюю службу.
Однако , существует проблема с использованием исключений в конце концов. Хотите ли вы рассматривать сценарий «без данных» как ошибку или нет, зависит от бизнес-логики.
- Если вы хотите просто сказать конечному пользователю, что такого пользователя нет, тогда использование исключения будет иметь смысл.
- Если вы хотите продолжить создание нового пользователя, то это может не иметь смысла. Вам нужно будет четко указать, была ли ошибка вызвана «отсутствием данных» или «ошибкой драйвера», «тайм-аутом» и т. Д. По моему опыту, использование подобных исключений довольно легко приводит к ошибкам.
Таким образом, решением этой проблемы будет использование пользовательской ошибки. С другой стороны, в вызываемом и вызывающем абоненте будет слишком много кода котельной пластины. И это может даже вызвать путаницу в семантическом значении логики. С другой стороны, слишком частое использование пользовательских ошибок может вызвать проблемы несогласованности и со временем привести к сложному обслуживанию приложения. (Это полностью зависит от того, как развивается приложение. Например, если разработчики часто меняются, разные люди будут иметь разные мнения о том, какими должны быть эти пользовательские ошибки)
Мнение ответа
Увидев хорошие приложения и крайне не поддерживаемые монолиты, я отдам вам свои личные предпочтения. Но имейте в виду, что это не может быть окончательным решением.
Примечание: это (самоуверенное) решение основано на таких фактах, как
- Требуется количество кода для шаблона
- Семантическое значение кода
- Легко для нового разработчика начать работу над приложением
- ремонтопригодность
Не используйте ошибки для обработки сценариев без данных. вместо этого, проверьте явно для «пустых» данных и обрабатывать случай отдельно.
Я буду образец RORO здесь, с небольшой разницей. Я возвращаю объект, но получаю параметры вместо объекта. Это зависит от количества параметров. Если число параметров слишком велико, то может иметь смысл получить объект. Но, по моему мнению, это может быть признаком плохого решения для бизнеса / архитектуры .
Рассмотрим следующий пример входа пользователя.
- Если при входе возникает ошибка, я показываю конечному пользователю «ошибочное» состояние.
- Если пользователь был успешно выбран, я регистрирую пользователя в
- Если пользователя там нет, я создаю нового пользователя. (при условии, что у меня достаточно данных для запроса само по себе)
Псевдокод для этого может выглядеть так:
// Controller code
async signIn(name, email) {
try {
let user = await userDao.getUserByUserName(name);
if (!user.exists) {
user = await userDao.createUser(name, email);
}
return user;
} catch(e) {
// handle error
}
}
или как это,
// Controller code
async signIn(name, email) {
try {
let result = await userDao.getUserByUserName(name);
if (!result.user) {
result = await userDao.createUser(name, email);
}
return result.user;
} catch(e) {
// handle error
}
}
Хорошим примером использования пользовательских ошибок может быть различие в том, произошла ли ошибка при получении пользователя или при создании пользователя.
Основное преимущество использования status
(либо в качестве параметра обратного вызова, либо в качестве свойства возвращаемого объекта) заключается в том, что вы можете использовать его для разработки остальной части приложения в качестве конечного автомата ( Сценарий без данных является только одним таким состоянием. В сложном бизнесе может быть много состояний, основанных на пользовательских типах, частичном извлечении и т. д.).