Какой дизайн является наиболее предпочтительным: тестовое создание, создание try, создание-catch?

Предположим, что существует операция, которая создает пользователя. Эта операция может завершиться неудачно, если указанная электронная почта или имя пользователя существуют. Если это не удалось, необходимо точно знать, почему. Есть три подхода к этому, поскольку я вижу это, и мне интересно, есть ли явный победитель.

Итак, здесь пользователь класса:

class User
{
    public string Email { get; set; }
    public string UserName { get; set; }
}

И есть три способа выполнения операции create:

Test-Создать

if (UserExists(user)) act on user exists error;
if (UsernameExists(user)) act on username exists error;
CreateUser(user);

UserExists и UsernameExists делают запрос на сервер db для проверки. Эти вызовы снова повторяются в CreateUser, чтобы гарантировать правильность использования API. В случае неудачи проверки я бросаю ArgumentOutOfRangeException в обоих случаях. Таким образом, есть производительность.

Try-Создать

enum CreateUserResultCode
{
    Success,
    UserAlreadyExists,
    UsernameAlreadyExists
}

if (!TryCreate(user, out resultCode))
{
    switch(resultCode)
    {
        case UserAlreadyExists: act on user exists error;
        case UsernameAlreadyExists: act on username exists error;
    }
}

Этот шаблон выполняет проверку только один раз, но мы прибегаем к использованию так называемых кодов ошибок, которые не считаются хорошей практикой.

Create-Поймайте

try
{
    CreateUser(user);
}
catch(UserExistsException)
{
    act on user exists error;
}
catch(UsernameExistsException)
{
    act on username exists error;
}

Здесь я не использую коды ошибок, но теперь мне нужно создать отдельный класс исключения для каждого случая. Это более или менее то, как предполагается использование исключений, но мне интересно, стоит ли создавать отдельное исключение вместо записи enum.

Итак, у нас есть явный победитель, или это скорее вопрос вкуса?

Ответ 1

Итак, у нас есть явный победитель, или это скорее вопрос вкуса?

Первый вариант имеет фундаментальный недостаток - он никогда не будет потокобезопасным или безопасным, если CreateUser полагается на внешние ресурсы, а другие реализации могут возникать между вашими тестами. В общем, я стараюсь избегать этого "шаблона" из-за этого.

Что касается двух других опций - действительно, дело в том, произойдет ли отказ. Если ожидать, что CreateUser завершится неудачно с некоторой нормой, шаблон Try * - это мое предпочтение, поскольку использование исключений по существу становится использованием исключений для потока управления.

Если отказ будет действительно исключительным случаем, исключения будут более понятными.

Ответ 2

Test-Create может вызвать расовые условия, поэтому это не отличная идея. Это также, вероятно, делает дополнительную работу.

Try-Create хорош, если вы ожидаете, что ошибки будут частью нормального потока кода (например, в случае ввода пользователем).

Create-Catch хорош, если ошибки действительно исключительны (поэтому вас не беспокоит производительность).

Ответ 3

Это несколько субъективно, но есть некоторые конкретные плюсы и минусы, которые стоит отметить.

Одним из недостатков подхода, основанного на тестировании, является состояние гонки. Если два клиента попытаются создать одного и того же пользователя примерно в одно и то же время, возможно, им будет как проходить тесты, так и затем попытаться создать такого же пользователя.

Между Try-Create и Create-Catch я предпочитаю Create-Catch, но этот личный вкус. Можно утверждать, что Create-Catch использует исключения для управления потоком, которые обычно не одобряются. С другой стороны, Try-Create требует несколько неудобного параметра output, который можно было бы легко упустить.

Итак, я предпочитаю Create-Catch, но здесь определенно место для обсуждения.

Ответ 4

Вы указали, что UserExists и UsernameExists оба делают вызовы БД. Я предполагаю, что CreateUser также выполняет вызов базы данных.

Почему бы не позволить базе данных обрабатывать ваши проблемы с потоками? Предполагая далее, что в вашей таблице (таблицах) есть соответствующие ограничения, вы можете позволить ей выполнять ошибки в сохраненном вызове proc.

Поэтому я отдам свой голос за Create-Catch.