16

I am trying to find out the advantages and disadvantages for a method with multiple result values.

For example I'm using a login-method. If the login was successful, it will pass, otherwise I need to know why it failed.

1. Return true or false (Not enough information)

bool Login(string user, string password);

2. Return true, if it was successful, otherwise throw an exception

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

3. Return nothing. Throw an exception, if it wasn't successful

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

4. Return an enum value

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

"Login" is just one example case. I would like to know what the advantages and disadvantages of the different implementations is, and for which cases they are more or less appropriate.

soerface
  • 6,417
  • 6
  • 29
  • 50
Jan
  • 317
  • 2
  • 7
  • 1 and 2 are obviously not worthy of consideration. #3 is in the vein of how things are usually done, but the only person equipped to recognize that perhaps #4 is more appropriate in your case is you. – Jon Apr 02 '14 at 09:47
  • API is synchronous or async ? – Sriram Sakthivel Apr 02 '14 at 09:49
  • synchronous, sorry :) – Jan Apr 02 '14 at 09:50
  • The phrase "Exceptions are for exceptional errors" should guide you against #2 or #3. #4 is acceptable in the case where there may be multiple reasons for failure, and #1 is acceptable when there is only I reason for failure. In my own library I created a class ResultString which contains a bool for success/failure and a string for details to be shown to user. This allows the function to determine the best description of the failure. – Dr Herbie Apr 02 '14 at 09:52
  • [Exceptions vs. status returns](https://nedbatchelder.com/text/exceptions-vs-status.html) – tchelidze May 03 '17 at 13:33

4 Answers4

5

Definitely not exceptions. A failed login is hardly an "exceptional" case and it just a normal course of logic for the application. If you use an exception then you'll always have to wrap logging in with an exception handler for no other reason than to handle the case of a failed login. That seems like the very definition of using exceptions for logic flow, which isn't right.

If you need to return specific information (which isn't always necessary from a login function, but might be in your case), #4 seems reasonable. You could take it a step further and make it an object:

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

Or, depending on the logic for it, an immutable struct instead of a class. (Make sure the struct is immutable, mutable structs are just asking for trouble.) The point being that you can apply all sorts of logic and functionality on the result object itself, which seems to be the direction you're heading.

David
  • 208,112
  • 36
  • 198
  • 279
4

You'll get more opinionated answers, If I were doing I'll combine 3 & 4. Throw LoginFailedException with an enum saying why.

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
}

Reason to choose exception option: Assume you choose return value only approach, users of your api(may be client, or other developers) can get a chance to overlook the API.

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

Who knows they have to do like this

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

So, IMO throw exception in face saying I can't process your login request due to so and so reason. Make it simple.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • 6
    I like this approach, although it feels wrong to throw an exception in what, imho, seems like a common scenario. I feel like Exceptions should be used in 'exceptional' scenarios where something is wrong on a deeper level. – Moeri Apr 02 '14 at 10:12
  • 4
    @Moeri I believe this is right way, login failure is not a usual condition it is an **exceptional** condition, consider you're choosing just an Enum apprroach, and you develop a public API, what if the user doesn't check for the return value and just calls `Login();` and assumes logged in.? Throw it to face saying heyy something gone wrong(same as sqlserver does via SqlException) – Sriram Sakthivel Apr 02 '14 at 10:17
  • @Moeri Exceptions are there to replace `C-Style` status codes and `GetLastError()`'s. How would you implement idea of `Inner Exception` with that approach ? *case when error occurs two or more layers away* – tchelidze May 03 '17 at 12:20
4

Of course it depends on the certain situation but let me provide a couple of my subjective comments here:

  1. I'm sure such cases should be avoided. The name of the method points that it just performs any operation like "Login". According to the method name we can't expect any result here. Is you'd like the method to return bool value it's much better to name it like IsLoggedIn(userName). In addition you'll never know if you need to extend the set of values being returned. So the enum is much better here also taking in account that the aim of the value is reflected in the enum name rather than simple bool.

  2. The same as above. Exceptions here help to stop the whole execution hierarchy (which can of course contain more than 1 method in call stack) instead of just returning the result and let the caller to make appropriate decision. More flexible solution as for me. In the current case I'd use exceptions for parameters validation only. Situations like "wrong user name/password" are not exceptional. They are normal from the use cases point of view. null parameter or wrong parameter format are exceptional cases.

  3. If you don't need the method to return a value, that is the way to go. Do not forget you shouldn't use exceptions as a navigation. I mean UserSuccessfullyCreatedException or so.

  4. As I mentioned above, that is the best way as for me. The single point is not to place validation exceptions as a enum values. You have exceptions for it.

So enum result plus exceptions for validation is a way to go.

If you'd like to collect all the errors during the method execution you'll probably like to create special LoginOperationResult class to wrap all the information (including validation errors` occured during the method execution.

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.
}
Sergey Metlov
  • 25,747
  • 28
  • 93
  • 153
1

I usually use this approach in my projects:

signature:

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

usage:

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

You could expand this to return your Enum:

signature:

enum LoginResult
{
    Successful
    UnknownUser,
    WrongPassword
}

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

usage:

User user;
LoginResult loginResult;
if((loginResult = userService.TryLogin(username, password, out user)) == LoginResult.Successful)
{
    // do stuff with user
}
else 
{
    // do stuff with loginResult
}
Moeri
  • 9,104
  • 5
  • 43
  • 56