W jaki sposób styl funkcjonalny pomaga w wyśmiewaniu zależności?

10

Z wywiadu z Kentem Beckem w najnowszym wydaniu Java Magazine:

Binstock: Porozmawiajmy o mikrousługach. Wydaje mi się, że pierwsze testowanie mikrousług byłoby skomplikowane w tym sensie, że niektóre usługi, aby funkcjonować, będą wymagały obecności całej gamy innych usług. Czy sie zgadzasz?

Beck: Wydaje się, że to ten sam zestaw zawodów związanych z posiadaniem jednej dużej klasy lub wielu małych klas.

Binstock: Racja, chyba że chyba, tutaj musisz użyć okropnej liczby prób, aby móc skonfigurować system, w którym możesz przetestować daną usługę.

Beck: Nie zgadzam się. Jeśli jest to imperatywny styl, musisz użyć wielu kpin. W funkcjonalnym stylu, w którym zależności zewnętrzne są gromadzone razem wysoko w łańcuchu połączeń, nie sądzę, że jest to konieczne. Myślę, że można uzyskać dużo zasięgu z testów jednostkowych.

Co on ma na myśli? Jak styl funkcjonalny może uwolnić cię od kpienia z zewnętrznych zależności?

Dan
źródło
1
zobacz Omów to $ {blog}
gnat
1
Jeśli omawiają konkretnie Javę, podejrzewam, że znaczna część tej dyskusji jest dyskusyjna. Java tak naprawdę nie ma takiego wsparcia, jakiego potrzebuje, aby nadać się do opisywanego programowania funkcjonalnego. Och, oczywiście, możesz użyć klas narzędzi lub Lambdas Java 8 do symulacji, ale ... pomieszaj.
Robert Harvey
1
Powiązane: softwareengineering.stackexchange.com/q/5757
Robert Harvey

Odpowiedzi:

8

Czysta funkcja to taka, która:

  1. Będzie zawsze dawać ten sam wynik, biorąc pod uwagę te same argumenty
  2. Nie ma żadnych obserwowalnych skutków ubocznych (np. Zmian stanu)

Załóżmy, że piszemy kod do obsługi logowania użytkownika, w którym chcemy sprawdzić, czy podana nazwa użytkownika i hasło są prawidłowe, i uniemożliwić zalogowanie się użytkownika, jeśli będzie zbyt wiele nieudanych prób. W trybie rozkazującym nasz kod może wyglądać następująco:

bool UserLogin(string username, string password)
{
    var user = _database.FindUser(username);
    if (user == null)
    {
        return false;
    }
    if (user.FailedAttempts > 3)
    {
        return false;
    }
    // Password hashing omitted for brevity
    if (user.Password != password)
    {
        _database.RecordFailedLoginAttempt(username);
    }
    return true;
}

Jest całkiem jasne, że nie jest to czysta funkcja:

  1. Ta funkcja nie zawsze daje taki sam wynik dla danej kombinacji usernamei passwordkombinacji, ponieważ wynik zależy również od rekordu użytkownika przechowywanego w bazie danych.
  2. Funkcja może zmienić stan bazy danych, tzn. Ma skutki uboczne.

Zauważ też, że aby przetestować tę funkcję, musimy wykpić dwa wywołania bazy danych FindUseri RecordFailedLoginAttempt.

Gdybyśmy zmienili kod na bardziej funkcjonalny, moglibyśmy otrzymać coś takiego:

bool UserLogin(string username, string password)
{
    var user = _database.FindUser(username);
    var result = UserLoginPure(user, password);
    if (result == Result.FailedAttempt)
    {
        _database.RecordFailedLoginAttempt(username);
    }
    return result == Result.Success;
}

Result UserLoginPure(User user, string pasword)
{
    if (user == null)
    {
        return Result.UserNotFound;
    }
    if (user.FailedAttempts > 3)
    {
        return Result.LoginAttemptsExceeded;
    }
    if (user.Password != password)
    {
        return Result.FailedAttempt;        
    }
    return Result.Success;
}

Zauważ, że chociaż UserLoginfunkcja ta nadal nie jest czysta, UserLoginPurejest ona teraz funkcją czystą, w wyniku czego logika uwierzytelniania podstawowego użytkownika może być testowana jednostkowo bez konieczności kpienia z zewnętrznych zależności. Jest tak, ponieważ interakcja z bazą danych jest obsługiwana wyżej w stosie wywołań.

Justin
źródło
Czy twoja interpretacja: imperatywny styl = stanowe mikrousługi i funkcjonalny styl = bezpaństwowe mikrousługi ?
k3b
@ k3b Coś w rodzaju, z wyjątkiem trochę o mikro usługach. Po prostu styl imperatywny obejmuje manipulowanie stanem, podczas gdy styl funkcjonalny wykorzystuje czyste funkcje bez manipulowania stanem.
Justin
1
@Justin: Powiedziałbym, że styl funkcjonalny wyraźnie oddziela czyste funkcje od kodu z efektami ubocznymi, tak jak to zrobiłeś w swoim przykładzie. Innymi słowy, kod funkcjonalny może nadal mieć skutki uboczne.
Giorgio
Podejście funkcjonalne powinno zwrócić parę z wynikiem i użytkownikiem, ponieważ w przypadku nieudanej próby Wynik.FailedAttempt jest wynikiem dla nowego użytkownika z tymi samymi danymi co oryginał, z wyjątkiem tego, że ma jeszcze jedną nieudaną próbę i czysta funkcja wywoływać skutki uboczne dla użytkownika podane jako parametr.
rośnie Ciemność
korekta ostatniej części mojego poprzedniego komentarza: „a czysta funkcja NIE wywołuje skutków ubocznych dla użytkownika podanych jako parametr”.
rośnie Ciemność