Una funzione pura è una che:
- Will sempre darà lo stesso risultato con gli stessi argomenti
- Non ha effetti collaterali osservabili (ad esempio cambiamenti di stato)
Supponiamo di scrivere del codice per gestire l'accesso 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 come questo:
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:
- Questa funzione non darà sempre lo stesso risultato per una determinata combinazione
username
e password
poiché il risultato dipende anche dal record utente memorizzato nel database.
- La funzione può cambiare lo stato del database, cioè ha effetti collaterali.
Nota inoltre che per testare questa funzione è necessario prendere in giro due chiamate al database, FindUser
e RecordFailedLoginAttempt
.
Se dovessimo ridefinire questo codice in uno stile più funzionale potremmo ritrovarci 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 funzione UserLogin
non sia ancora pura, la funzione UserLoginPure
è ora una funzione pura e, di conseguenza, la logica di autenticazione dell'utente principale può essere sottoposta a test dell'unità senza la necessità di prendere in giro eventuali dipendenze esterne. Questo perché l'interazione con il database viene gestita più in alto nello stack di chiamate.