Uma função pura é aquela que:
- Vai sempre dar o mesmo resultado dado os mesmos argumentos
- Não possui efeitos colaterais observáveis (por exemplo, alterações de estado)
Suponha que estamos escrevendo algum código para lidar com o login do usuário, onde queremos verificar se o nome de usuário e a senha fornecidos estão corretos e impedir que o usuário efetue login se houver muitas tentativas falhas. Em um estilo imperativo, nosso código pode ficar assim:
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;
}
É bastante claro que essa não é uma função pura:
- Essa função nem sempre dará o mesmo resultado para uma determinada combinação
username
e password
o resultado também depende do registro do usuário armazenado no banco de dados.
- A função pode alterar o estado do banco de dados, ou seja, tem efeitos colaterais.
Observe também que, para testar esta unidade, precisamos zombar de duas chamadas ao banco de dados, FindUser
e RecordFailedLoginAttempt
.
Se refatorássemos esse código para um estilo mais funcional, poderíamos acabar com algo assim:
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;
}
Observe que, embora a UserLogin
função ainda não seja pura, a UserLoginPure
função agora é pura e, como resultado, a lógica de autenticação do usuário principal pode ser testada em unidade sem a necessidade de zombar de dependências externas. Isso ocorre porque a interação com o banco de dados é tratada mais acima na pilha de chamadas.