Refactoring Exercise: The Single Responsibility Principle vs Needless Complexity

October 6th, 2008

Ray Houston has written this post on his blog named Single-Responsibility Versus Needless Complexity. His post contains the following code sample of which he suspects that it possibly violates the Single Responsibility Principle:

public bool Login(string username, string password) { var user = userRepo.GetUserByUsername(username); if(user == null) return false; if (loginValidator.IsValid(user, password)) return true; user.FailedLoginAttempts++; if (user.FailedLoginAttempts >= 3) user.LockedOut = true; return false; }

Because I’m still sitting at home slowly recovering from surgery (almost a month now), and because I’m terribly bored by now, I thought I’d just try to refactor this code. The first step I took was to isolate the validation of the user in a separate private method like so:

private bool IsValid(User user, String password) { if(user == null) return false; return loginValidator.IsValid(user, password); }

This slightly simplifies the code of the Login method:

public bool Login(string username, string password) { var user = userRepo.GetUserByUsername(username); if (IsValid(user, password)) return true; user.FailedLoginAttempts++; if (user.FailedLoginAttempts >= 3) user.LockedOut = true; return false; }

Next thing I noticed was the use of the FailedLoginAttempts and the LockedOut properties, which appear to be part of the User class. This is something I talked about in Properties – A False Sense of Encapsulation. Let us apply Tell, Don’t Ask and move this code into the User class like so:

private const Int32 MaximumFailedLoginAttempts = 3 public void FailsToLogin() { this.failedLoginAttempts += 1; if(MaximumFailedLoginAttemptsExceeded()) LockOut(); } private Boolean MaximumFailedLoginAttemptsExceeded() { return this.failedLoginAttempts >= MaximumFailedLoginAttempts; } private void LockOut() { this.lockedOut = true; }

This eliminates the need of the properties I just mentioned, which expose private data of the User class (at least for the Login story). The code of the Login method now becomes fairly small:

public bool Login(string username, string password) { var user = userRepo.GetUserByUsername(username); if(false == IsValid(user, password)) { user.FailedToLogin(); return false; } return true; }

Besides good judgement, SRP is also about organizing complexity so that other developers/readers know where to look for it.

Now get those torches lit and flame away.

Jan Van Ryswyck Esoterica

  1. October 6th, 2008 at 13:50 | #1

    Very nice! That makes good sense to move that directly to the User class so that it doesn’t leak out.

  2. October 6th, 2008 at 13:56 | #2

    Pfew! Glad you like it :-) .

  3. Mark Holtman
    October 6th, 2008 at 14:11 | #3

    I’m not seeing where you passed the ‘password’ into the IsValid method. Originally, password was available in the scope of the all encompassing method. When you factored the validation logic out, you’d need to pass the password in as well as the user to IsValid().

    Is that right, or did I miss something?

  4. October 6th, 2008 at 14:36 | #4

    Really nitpicky point, but MaximumFailedLoginAttemptsExceeded is poorly named since it does a “>=” comparison. Should rename to MaximumFailedLoginAttemptsReached or something.

  5. Tony
    October 6th, 2008 at 14:50 | #5

    thanks. I’ve been struggling with the tell don’t ask principle. Your article cleared that up.

  6. October 6th, 2008 at 15:25 | #6

    I like it too. Shouldn’t IsValid take a password parameter? Also, what’s the login validator for? Is that to provide app services like dealing with hashed password and salt?

    I had a play with a couple of ideas, although I’m not sure I like either of them, but fun to throw em out there…

    This one makes the login attempt an explicit class.

    public bool Login(string username, string password)
    {
    var user = userRepo.GetByUsername(username);

    if(user == null)
    return false;

    var attempt = new LoginAttempt(user, password);
    return attempt.Succeeded;
    }

    Another one use the Null object pattern. This looks weird though, which is good enough reason not to use it?

    public bool Login(string username, string password)
    {
    var user = userRepo.GetByUsername(username) ?? User.Null;
    return new LoginAttempt(user, password).Succeeded;
    }

  7. October 6th, 2008 at 15:48 | #7

    You guys are right on the ‘password’ thing. Will correct it right away. That’s what you get when refactoring in Notepad2. No Resharper support.

    @jdn: Your function name is a lot better. Thanks!

  8. October 6th, 2008 at 15:57 | #8

    Given the fact that I forgot the password argument to the IsValid method, maybe its better to rename this method to ‘Authenticate’ or something?

  9. SteveP
    October 6th, 2008 at 17:36 | #9

    A few random thoughts. (Just finished reading “Clean Code” as well, awesome book.)

    Argument validation, generally I try to move this as deep as possible to avoid issues. loginValidator takes a user name & password. Should it ever validate a null (or empty) user name successfully? This would essentially get rid of IsValid().

    This would change Login to:
    public bool Login(string username, string password)
    {
    var user = userRepo.GetUserByUsername(username);
    bool isValid = loginValidator.IsValid(user, password))

    if ( !isValid) // or (false = isValid)
    {
    user.FailedToLogin();
    }

    return isValid;
    }

    generally I’m a stickler for 1-way-in, 1-way-out.

  10. SteveP
    October 6th, 2008 at 17:41 | #10

    ack, scratch that. Checking the returned user reference for null, then validating the user against PW. ^_^ I shouldn’t read code before noon. *sigh*

Comment pages
Comments are closed.