Возвращаемая стоимость лучшей практики vs Exception vs Enum

Я пытаюсь выяснить преимущества и недостатки метода с несколькими значениями результата.

Например, я использую метод login. Если логин был успешным, он пройдет, в противном случае мне нужно знать, почему он не удался.

1. Вернуть true или false (недостаточно информации)

bool Login(string user, string password);

2. Возвращаем true, если это было успешно, иначе выведите исключение

public class UnknownUserException : Exception { }
public class WrongPasswordException : Exception { }
bool Login(string user, string password);

3. Не возвращай ничего. Выбросить исключение, если оно не было успешным

public class UnknownUserException : Exception { }
public class WrongPasswordException : Exception { }
void Login(string user, string password);

4. Возвращает значение перечисления

enum LoginResult
{
    Successful
    UnknownUser,
    WrongPassword
}
LoginResult Login(string user, string password);

"Вход" - это всего лишь один пример. Я хотел бы знать, каковы преимущества и недостатки различных реализаций и для каких случаев они более или менее уместны.

Ответ 1

Вы получите более упрямые ответы. Если бы я делал, я объединил 3 и 4. Throw LoginFailedException с перечислением, объясняющим почему.

void Login(string user, string password);//Or return a bool(redundant though)

class LoginFailedException : ApplicationException
{
    public LoginFailReason Reason {get; private set;}
    public LoginFailedException(LoginFailReason reason)
    {
       this.Reason = reason;
    }
}

enum LoginFailReason
{
    UnknownUser,
    WrongPassword
}

Причина выбора варианта исключения: Предположим, что вы выбираете только метод возврата, пользователи вашего api (может быть, клиент или другие разработчики) могут получить возможность игнорировать API.

instance.Login(user, password);
var accountInfo = instance.GetAccountInfo();//Assuming logged in; going to explode

Кто знает, что они должны это делать

if(instance.Login(user, password) == LoginResult.Successful))
{
    var accountInfo = instance.GetAccountInfo();
}

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

Ответ 2

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

Если вам нужно вернуть определенную информацию (что не всегда необходимо из функции входа, но может быть в вашем случае), # 4 кажется разумным. Вы можете сделать это еще дальше и сделать его объектом:

public class LoginResult
{
    // an enum for the status
    // a string for a more specific message
    // a valid user object on successful login
    // etc.
}

Или, в зависимости от логики для него, неизменяемая структура вместо класса. (Убедитесь, что структура неизменна, изменяемые структуры просто задают проблемы.) Смысл в том, что вы можете применять всевозможные логику и функциональные возможности для самого объекта результата, который, по-видимому, является направлением, в котором вы направляетесь.

Ответ 3

Конечно, это зависит от определенной ситуации, но позвольте мне представить пару моих субъективных комментариев здесь:

  • Я уверен, что таких случаев следует избегать. Имя метода указывает, что он просто выполняет любую операцию, например "Вход". В соответствии с именем метода мы не можем ожидать никакого результата. Вы хотите, чтобы метод возвращал значение bool намного лучше, чтобы назвать его как IsLoggedIn(userName). Кроме того, вы никогда не узнаете, нужно ли расширять набор возвращаемых значений. Таким образом, enum намного лучше здесь, также принимая во внимание, что цель значения отражается в имени enum, а не просто bool.

  • То же, что и выше. Исключения здесь помогают остановить всю иерархию выполнения (которая, конечно, может содержать более одного метода в стеке вызовов) вместо того, чтобы просто возвращать результат и позволить вызывающему принять соответствующее решение. Более гибкое решение для меня. В текущем случае я бы использовал исключения только для проверки параметров. Ситуации типа "неправильное имя пользователя/пароль" не являются исключительными. Они являются нормальными с точки зрения использования. null параметр или неправильный формат параметра - исключительные случаи.

  • Если вам не нужен метод для возврата значения, это путь. Не забывайте, что вы не должны использовать исключения в качестве навигации. Я имею в виду UserSuccessfullyCreatedException или так.

  • Как я уже упоминал выше, это лучший способ для меня. Единственная точка - не размещать исключения проверки как значения enum. У вас есть исключения для этого.

Так что enum результат плюс исключения для проверки - это путь.

Если вы хотите собрать все ошибки во время выполнения метода, вам, вероятно, захочется создать специальный класс LoginOperationResult для обертывания всей информации (включая ошибки проверки) во время выполнения метода.

class OperationResult
{
    public OperationStatus Status { get; set; }

    public IEnumerable<ValidationError> Errors { get; set; }
    // or list of exceptions
}

class LoginOperationResult : OperationResult
{
    // Login result specific data.
}

enum OperationStatus
{
    Success,
    Denied,
    ValidationFailed,
    // etc.
}

Ответ 4

Обычно я использую этот подход в своих проектах:

Подпись:

bool TryLogin(string username, string password, out User user);

использование:

User user;
if(userService.TryLogin(username, password, out user)))
{
    // do stuff with user
}
else 
{
    // show "login failed"
}

Вы можете развернуть это, чтобы вернуть свой Enum:

Подпись:

enum LoginResult
{
    Successful
    UnknownUser,
    WrongPassword
}

LoginResult TryLogin(string username, string password, out User user);

использование:

User user;
LoginResult loginResult;
if((loginResult = userService.TryLogin(username, password, out user)) == LoginResult.Successful)
{
    // do stuff with user
}
else 
{
    // do stuff with loginResult
}