¿Cómo ayuda el estilo funcional con las burlas de dependencias?

10

De la entrevista con Kent Beck en un reciente número de la revista Java:

Binstock: Discutamos microservicios. Me parece que probar primero en microservicios se volvería complicado en el sentido de que algunos servicios, para funcionar, necesitarán la presencia de muchos otros servicios. ¿Estás de acuerdo?

Beck: Parece el mismo conjunto de intercambios sobre tener una clase grande o muchas clases pequeñas.

Binstock: Correcto, excepto que supongo, aquí tienes que usar una gran cantidad de simulacros para poder configurar un sistema mediante el cual puedas probar un servicio determinado.

Beck: no estoy de acuerdo. Si tiene un estilo imperativo, debe usar muchas simulaciones. En un estilo funcional donde las dependencias externas se recopilan juntas en la parte superior de la cadena de llamadas, entonces no creo que sea necesario. Creo que puede obtener mucha cobertura de las pruebas unitarias.

¿Qué quiere decir? ¿Cómo puede el estilo funcional liberarlo de burlarse de las dependencias externas?

Dan
fuente
1
ver Discutir esto $ {blog}
mosquito
1
Si están discutiendo Java específicamente, sospecho que gran parte de esa discusión es discutible. Java realmente no tiene el tipo de soporte que necesita para prestarse al tipo de programación funcional que se describe. Ah, claro, puedes usar clases de utilidad o quizás Java 8 Lambdas para simularlo, pero ... blecch.
Robert Harvey
1
Relacionado: softwareengineering.stackexchange.com/q/5757
Robert Harvey

Respuestas:

8

Una función pura es aquella que:

  1. Será siempre el mismo resultado dado los mismos argumentos
  2. No tiene efectos secundarios observables (por ejemplo, cambios de estado)

Supongamos que estamos escribiendo algún código para manejar el inicio de sesión del usuario, donde queremos verificar que el nombre de usuario y la contraseña proporcionados sean correctos y evitar que el usuario inicie sesión si hay demasiados intentos fallidos. En un estilo imperativo, nuestro código podría verse así:

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

Está bastante claro que esta no es una función pura:

  1. Esta función no siempre dará el mismo resultado para un determinado usernamey passwordcombinación como el resultado también depende del registro de usuario almacenada en la base de datos.
  2. La función puede cambiar el estado de la base de datos, es decir, tiene efectos secundarios.

También tenga en cuenta que para realizar una prueba unitaria de esta función, debemos simular dos llamadas a la base de datos, FindUsery RecordFailedLoginAttempt.

Si tuviéramos que refactorizar este código en un estilo más funcional, podríamos terminar con algo así:

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

Tenga en cuenta que aunque la UserLoginfunción aún no es pura, UserLoginPureahora es una función pura y, como resultado, la lógica de autenticación del usuario central puede probarse en la unidad sin necesidad de burlarse de ninguna dependencia externa. Esto se debe a que la interacción con la base de datos se maneja más arriba en la pila de llamadas.

Justin
fuente
¿Su interpretación es: imperativo-estilo = microservicios con estado y funcional-estilo = microservicios sin estado ?
k3b
@ k3b Más o menos, excepto el bit sobre micro servicios. Muy simplemente, el estilo imperativo implica la manipulación del estado, mientras que el estilo funcional utiliza funciones puras sin manipulación del estado.
Justin
1
@Justin: Diría que el estilo funcional separa claramente las funciones puras del código con efectos secundarios, como lo hizo en su ejemplo. En otras palabras, el código funcional aún puede tener efectos secundarios.
Giorgio el
El enfoque funcional debería devolver un par con un resultado y un usuario, porque en un intento fallido, Result.FailedAttempt es el resultado con un nuevo usuario con los mismos datos que el original, excepto que tiene un intento fallido más y una función pura inducir efectos secundarios en el usuario que se da como parámetro.
risingDarkness
corrección para la última parte de mi comentario anterior: "y una función pura NO induce efectos secundarios en el usuario que se da como parámetro".
risingDarkness