Czysta funkcja to taka, która:
- Będzie zawsze dawać ten sam wynik, biorąc pod uwagę te same argumenty
- 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:
- Ta funkcja nie zawsze daje taki sam wynik dla danej kombinacji
username
i password
kombinacji, ponieważ wynik zależy również od rekordu użytkownika przechowywanego w bazie danych.
- 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 FindUser
i 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ż UserLogin
funkcja ta nadal nie jest czysta, UserLoginPure
jest 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ń.