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
usernamee passwordo 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, FindUsere 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 UserLoginfunção ainda não seja pura, a UserLoginPurefunçã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.