In che modo lo stile funzionale aiuta a deridere le dipendenze?


10

Dall'intervista a Kent Beck in un recente numero di Java Magazine:

Binstock: parliamo di microservizi. Mi sembra che testare prima i microservizi diventerebbe complicato, nel senso che alcuni servizi, per funzionare, avranno bisogno della presenza di un sacco di altri servizi. Sei d'accordo?

Beck: Sembra la stessa serie di scambi di avere una grande classe o molte piccole classi.

Binstock: Giusto, tranne che immagino, qui devi usare un sacco di beffe per essere in grado di impostare un sistema con il quale puoi testare un determinato servizio.

Beck: Non sono d'accordo. Se è in uno stile imperativo, devi usare un sacco di beffe. In uno stile funzionale in cui le dipendenze esterne sono raccolte insieme in alto nella catena delle chiamate, non penso che sia necessario. Penso che puoi ottenere molta copertura dai test unitari.

Che cosa intende? In che modo lo stile funzionale può liberarti dal deridere dipendenze esterne?



1
Se stanno discutendo specificamente di Java, sospetto che gran parte di quella discussione sia controversa. Java non ha davvero il tipo di supporto di cui ha bisogno per prestarsi al tipo di programmazione funzionale descritta. Oh, certo, puoi usare le classi di utilità o forse Java 8 Lambdas per simularlo, ma ... blecch.
Robert Harvey,

Risposte:


8

Una funzione pura è quella che:

  1. Fornirà sempre lo stesso risultato dati gli stessi argomenti
  2. Non ha effetti collaterali osservabili (ad esempio cambiamenti di stato)

Supponiamo di scrivere del codice per gestire l'accesso dell'utente, in cui vogliamo verificare che il nome utente e la password forniti siano corretti e impedire all'utente di accedere se ci sono troppi tentativi falliti. In uno stile imperativo il nostro codice potrebbe apparire così:

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;
}

È abbastanza chiaro che questa non è una funzione pura:

  1. Questa funzione non fornirà sempre lo stesso risultato per una data usernamee una passwordcombinazione poiché il risultato dipende anche dal record dell'utente memorizzato nel database.
  2. La funzione può cambiare lo stato del database, ovvero ha effetti collaterali.

Inoltre, per testare l'unità questa funzione dobbiamo prendere in giro due chiamate al database FindUsere RecordFailedLoginAttempt.

Se dovessimo riformattare questo codice in uno stile più funzionale potremmo finire con qualcosa di simile a questo:

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;
}

Si noti che sebbene la UserLoginfunzione non sia ancora pura, la UserLoginPurefunzione ora è una funzione pura e di conseguenza la logica di autenticazione dell'utente principale può essere testata dall'unità senza dover deridere eventuali dipendenze esterne. Questo perché l'interazione con il database è gestita più in alto nello stack di chiamate.


La tua interpretazione è: stile imperativo = microservizi statefull e stile funzionale = microservizi stateless ?
k3b,

@ k3b Ordinamento di, ad eccezione del bit relativo ai micro servizi. Molto semplicemente lo stile imperativo implica la manipolazione dello stato mentre lo stile funzionale utilizza funzioni pure senza manipolazione dello stato.
Justin il

1
@Justin: Direi che lo stile funzionale separa chiaramente le funzioni pure dal codice con effetti collaterali, come hai fatto nel tuo esempio. In altre parole, il codice funzionale può ancora avere effetti collaterali.
Giorgio,

L'approccio funzionale dovrebbe restituire una coppia con un risultato e un utente, poiché in caso di tentativo fallito, Result.FailedAttempt è il risultato con un nuovo utente con gli stessi dati dell'originale, tranne per il fatto che ha un altro tentativo fallito e una funzione pura ha indurre effetti collaterali sull'utente forniti come parametro.
risingDarkness

correzione per l'ultima parte del mio commento precedente: "e una funzione pura NON induce effetti collaterali sull'utente che viene dato come parametro".
risingDarkness
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.