5
Jun

DI: SRP to the rescue!

Ten post jest częścią cyklu o Dependency Injection.


Zanim zajmiemy się faktycznym wstrzykiwaniem zależności, to najpierw musimy mieć co wstrzykiwać! Statycznej klasy nie wstrzykniemy przecież, jakkolwiek byśmy wstrzykiwać nie chcieli. Na początek zatem: zidentyfikujmy składowe procesu rejestracji użytkownika, które w ogólnie nie powinny znajdować się w kontrolerze.

Pewniakiem jest tutaj walidacja poprawności adresu e-mail. Nie dość, że bardzo ładnie da się to zamknąć w osobną klasę, to jeszcze możemy wymyślić inne, bardziej skomplikowane i wiarygodne, implementacje tej samej logiki.

No to siup (tag demo2.1 w repo):

public class EmailValidator
{
    const string EMAIL_REGEX = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}";

    public bool Validate(string email)
    {
        return Regex.IsMatch(email, EMAIL_REGEX);
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo2.1/src/app/EmailValidator.cs)

Zwykłe cut/paste do nowej klasy, a z kontrolera zniknęło już trochę (choć niewiele) brzydoty:

public class UsersController
{
    public void RegisterUser(string email)
    {
        // check if email is valid
        if (new EmailValidator().Validate(email) == false)
        {
            throw new ArgumentException("Invalid email address");
        }

(https://github.com/maniserowicz/di-talk/blob/demo2.1/src/app/UsersController.cs)

Nie mamy już tajemniczego regexa.

Nie mamy już operacji na tekście – mamy wiele mówiące wywołanie metody Validate() na klasie EmailValidator.

Dodatkowa korzyść? Możemy tego walidatora obłożyć testami!

public class EmailValidatorTests
{
    readonly EmailValidator _validator;
    string _email;

    public EmailValidatorTests()
    {
        _validator = new EmailValidator();
    }

    bool execute()
    {
        return _validator.Validate(_email);
    }

    [Fact]
    public void validates_gmail_email_address_with_dot()
    {
        _email = "my.email@gmail.com";

        bool result = execute();

        Assert.True(result);
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo2.1/src/app.Tests/EmailValidatorTests.cs)

Test właściwie mówi sam za siebie. A to tylko początek, powinno się tam oczywiście dorzucić ich więcej, testując różne kombinacje możliwych danych wejściowych. Można by się chwilę porozwodzić na temat struktury tej klasy, ale to nie temat na teraz.

Ale! Mimo że mamy tylko jeden test, który oczywiście powinien przechodzić, bo przecież podany adres już na pierwszy rzut oka jest prawidłowy… wywala się. “Mamo, dlaczemu???”

Dlatemu, że programista mający za zadanie implementację ficzera “waliduj adres e-mail” wpisał w guglach “regex to validate e-mail address” i wkleił do kodu pierwszy lepszy wynik – tak jak ja, przygotowując te dema. Gdy spojrzymy jeszcze raz na wykorzystane wyrażenie regularne to zauważymy, że ono nie zna pojęcia “mała litera”. Fix jest prosty, trzeba sprawić, aby regex akceptował litery inne niż wielkie, i śmiga!

Przechodzimy zatem na tag demo2-finish, gdzie regex jest już poprawiony:

const string EMAIL_REGEX = @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}";

I idziemy dalej, tym razem biorąc na warsztat proces generowanie linków aktywacyjnych. Wiemy, że ten link musi zawierać przekazany adres e-mail. Musi również zawierać unikatowy dla usera token. Zróbmy więc to TDD-way! Najpierw testy klasy, którą zaraz napiszemy:

public class ActivationLinkGeneratorTests
{
    readonly ActivationLinkGenerator _generator;
    string _token = "a-token";
    string _email = "some-email";

    public ActivationLinkGeneratorTests()
    {
        _generator = new ActivationLinkGenerator();
    }

    string execute()
    {
        return _generator.GenerateLink(_token, _email);
    }

    [Fact]
    public void generates_link_using_given_token()
    {
        string result = execute();

        Assert.Contains("token=a-token", result);
    }

    [Fact]
    public void generates_link_using_given_email()
    {
        string result = execute();

        Assert.Contains("email=some-email", result);
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo2-finish/src/app.Tests/ActivationLinkGeneratorTests.cs)

A teraz sama klasa:

public class ActivationLinkGenerator
{
    public string GenerateLink(string token, string email)
    {
        return string.Format(
            "http://myapp.com/confirm?email={0}&token={1}"
            , email, token
        );
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo2-finish/src/app/ActivationLinkGenerator.cs)

Kontroler znowu został trochę odchudzony, ale wklejać go po raz kolejny nie będę, do obejrzenia tu: https://github.com/maniserowicz/di-talk/blob/demo2-finish/src/app/UsersController.cs.

Ale to przecież nie wszystkie korzyści z tego kroku. Teraz mamy więcej klas, i te dodatkowe klasy są malutkie. Można by nawet rzec: mikroskopijne. Dzięki temu są “ogarnialne” już na pierwszy rzut oka. Wiadomo co robią, wiadomo jak robią. I wiadomo, że dobrze robią, bo mamy do nich testy! Ba, już na tym etapie wykryliśmy i naprawiliśmy buga, nawet nie uruchamiając aplikacji! Pomyślcie co trzeba byłoby zrobić, żeby tego buga wykryć wcześniej… pomijając nawet fakt, że przecież jedyne co mamy to goła dllka.

A co dalej? Kolejny odcinek;).

Autor

Maciej Aniserowicz

Maciej Aniserowicz
"Procent"
developer / architect

MVP
MCP

Search
Facebook
Twitter
Archiwum
Kategorie
© Copyright 2008-2014 Maciej Aniserowicz. All rights reserved. Running on WordPress.