Comment le style fonctionnel aide-t-il à se moquer des dépendances?

10

Extrait de l'interview de Kent Beck dans un récent numéro de Java Magazine:

Binstock: Parlons des microservices. Il me semble que le test d'abord sur les microservices se compliquerait en ce sens que certains services, pour fonctionner, auront besoin de la présence de tout un tas d'autres services. Êtes-vous d'accord?

Beck: Il semble que le même ensemble de compromis sur le fait d'avoir une grande classe ou beaucoup de petites classes.

Binstock: D'accord, sauf je suppose, ici, vous devez utiliser énormément de simulacres pour pouvoir mettre en place un système par lequel vous pouvez tester un service donné.

Beck: Je ne suis pas d'accord. Si c'est dans un style impératif, vous devez utiliser beaucoup de simulacres. Dans un style fonctionnel où les dépendances externes sont collectées en haut de la chaîne d'appels, je ne pense pas que ce soit nécessaire. Je pense que vous pouvez obtenir beaucoup de couverture grâce aux tests unitaires.

Que veut-il dire? Comment le style fonctionnel peut-il vous libérer des moqueries des dépendances externes?

Dan
la source
1
S'ils discutent spécifiquement de Java, je soupçonne qu'une grande partie de cette discussion est théorique. Java n'a pas vraiment le type de support dont il a besoin pour se prêter au type de programmation fonctionnelle décrit. Oh, bien sûr, vous pouvez utiliser des classes utilitaires ou peut-être Java 8 Lambdas pour le simuler, mais ... blecch.
Robert Harvey

Réponses:

8

Une fonction pure est celle qui:

  1. Donne toujours le même résultat avec les mêmes arguments
  2. N'a pas d'effets secondaires observables (par exemple des changements d'état)

Supposons que nous écrivons du code pour gérer la connexion utilisateur, où nous voulons vérifier que le nom d'utilisateur et le mot de passe fournis sont corrects et empêcher l'utilisateur de se connecter s'il y a trop de tentatives infructueuses. Dans un style impératif, notre code pourrait ressembler à ceci:

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;
}

C'est assez clair que ce n'est pas une fonction pure:

  1. Cette fonction ne donnera pas toujours le même résultat pour une donnée usernameet passwordcombinaison que le résultat dépend également de l'enregistrement de l' utilisateur stocké dans la base de données.
  2. La fonction peut changer l'état de la base de données, c'est-à-dire qu'elle a des effets secondaires.

Notez également que pour tester unitaire cette fonction, nous devons simuler deux appels de base de données, FindUseret RecordFailedLoginAttempt.

Si nous devions refactoriser ce code dans un style plus fonctionnel, nous pourrions nous retrouver avec quelque chose comme ceci:

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;
}

Notez que bien que la UserLoginfonction ne soit toujours pas pure, la UserLoginPurefonction est maintenant une fonction pure et, par conséquent, la logique d'authentification principale de l'utilisateur peut être testée unitaire sans avoir besoin de se moquer des dépendances externes. En effet, l'interaction avec la base de données est gérée plus haut dans la pile des appels.

Justin
la source
Votre interprétation est-elle de style impératif = microservices étatiques et de style fonctionnel = microservices sans état ?
k3b
@ k3b Sorte de, sauf pour le bit sur les micro services. Le style impératif implique très simplement la manipulation de l'état tandis que le style fonctionnel utilise des fonctions pures sans manipulation de l'état.
Justin
1
@Justin: Je dirais que le style fonctionnel sépare clairement les fonctions pures du code avec des effets secondaires, comme vous l'avez fait dans votre exemple. En d'autres termes, le code fonctionnel peut toujours avoir des effets secondaires.
Giorgio
L'approche fonctionnelle doit renvoyer une paire avec un résultat et un utilisateur, car en cas d'échec, Result.FailedAttempt est le résultat avec un nouvel utilisateur avec les mêmes données que l'original, sauf qu'il a une autre tentative échouée et qu'une fonction pure ne le fait pas. induire des effets secondaires sur l'utilisateur qui sont donnés en paramètre.
riseDarkness
correction pour la dernière partie de mon commentaire précédent: "et une fonction pure n'induit PAS d' effets secondaires sur l'utilisateur qui est donnée en paramètre."
riseDarkness