Como o estilo funcional ajuda na simulação de dependências?

10

Da entrevista com Kent Beck em uma edição recente da Java Magazine:

Binstock: Vamos discutir microsserviços. Parece-me que o primeiro teste em microsserviços se tornaria complicado no sentido de que alguns serviços, para funcionar, precisarão da presença de vários outros serviços. Você concorda?

Beck: Parece o mesmo conjunto de trocas sobre ter uma classe grande ou muitas classes pequenas.

Binstock: Certo, exceto, eu acho, aqui você precisa usar uma imensa quantidade de zombarias para poder configurar um sistema pelo qual você pode testar um determinado serviço.

Beck: Eu discordo. Se for um estilo imperativo, você precisará usar muitas zombarias. Em um estilo funcional em que as dependências externas são coletadas no alto da cadeia de chamadas, não acho necessário. Eu acho que você pode obter muita cobertura dos testes de unidade.

O que ele quer dizer? Como o estilo funcional pode libertá-lo de zombar de dependências externas?

Dan
fonte
11
consulte Discutir este $ {blog}
gnat
11
Se eles estão discutindo Java especificamente, suspeito que grande parte dessa discussão é discutível. Java realmente não tem o tipo de suporte necessário para se prestar ao tipo de programação funcional descrita. Ah, claro, você pode usar classes de utilitários ou talvez o Java 8 Lambdas para simulá-lo, mas ... blecch.
Robert Harvey
11
Relacionados: softwareengineering.stackexchange.com/q/5757
Robert Harvey

Respostas:

8

Uma função pura é aquela que:

  1. Vai sempre dar o mesmo resultado dado os mesmos argumentos
  2. 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:

  1. 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.
  2. 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.

Justin
fonte
A sua interpretação é: microservices imperative-style = statefull e style-funcional = microservies sem estado ?
K3b
@ k3b Mais ou menos, com exceção dos micro serviços. Muito simplesmente, o estilo imperativo envolve a manipulação do estado, enquanto o estilo funcional usa funções puras sem manipulação do estado.
Justin
11
@ Justin: Eu diria que o estilo funcional separa claramente funções puras do código com efeitos colaterais, como você fez no seu exemplo. Em outras palavras, o código funcional ainda pode ter efeitos colaterais.
Giorgio
A abordagem funcional deve retornar um par com um resultado e um usuário, porque, em uma tentativa com falha, Result.FailedAttempt é o resultado com um novo usuário com os mesmos dados que o original, exceto que ele tem mais uma tentativa com falha e uma função pura faz induzir efeitos colaterais ao usuário, dados como parâmetro.
risingDarkness
correção para a última parte do meu comentário anterior: "e uma função pura NÃO induz efeitos colaterais ao usuário, dados como parâmetro".
risingDarkness