Как функциональный стиль помогает с насмешливыми зависимостями?

10

Из интервью с Кентом Беком в недавнем выпуске журнала Java:

Бинсток: давайте обсудим микросервисы. Мне кажется, что первое тестирование на микросервисах усложнилось бы в том смысле, что некоторым службам для функционирования потребуется присутствие целого ряда других служб. Ты согласен?

Бек: Кажется, что это один и тот же набор компромиссов в отношении одного большого класса или множества маленьких классов.

Бинсток: Да, за исключением того, что, я полагаю, здесь вы должны использовать очень много издевательств, чтобы иметь возможность настроить систему, с помощью которой вы можете протестировать данный сервис.

Бек: Я не согласен. Если это в императивном стиле, вы должны использовать много издевательств. В функциональном стиле, где внешние зависимости собираются вместе высоко в цепочке вызовов, тогда я не думаю, что это необходимо. Я думаю, что вы можете получить много освещения от модульных тестов.

Что он имеет в виду? Как функциональный стиль может освободить вас от насмешек над внешними зависимостями?

Дэн
источник
1
Если они специально обсуждали Java, я подозреваю , что большая часть этой дискуссии является спорным. Java на самом деле не имеет такой поддержки, которая ему нужна для описания описываемого функционального программирования. О, конечно, вы можете использовать служебные классы или, возможно, Java 8 Lambdas для имитации, но ... blecch.
Роберт Харви
1
Связанный: softwareengineering.stackexchange.com/q/5757
Роберт Харви

Ответы:

8

Чистая функция является тот , который:

  1. Буду всегда дают тот же результат , учитывая те же аргументы
  2. Не имеет видимых побочных эффектов (например, изменения состояния)

Предположим, мы пишем некоторый код для обработки входа пользователя, где мы хотим проверить правильность введенных имени пользователя и пароля и предотвратить вход пользователя в систему, если слишком много неудачных попыток. В императивном стиле наш код может выглядеть так:

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

Довольно ясно, что это не чистая функция:

  1. Эта функция не всегда дает тот же результат для заданного usernameи passwordкомбинации , как результат также зависит от записи пользователя , хранящейся в базе данных.
  2. Функция может изменять состояние базы данных, то есть имеет побочные эффекты.

Также обратите внимание, что для модульного тестирования этой функции нам нужно смоделировать два вызова базы данных, FindUserи RecordFailedLoginAttempt.

Если бы мы реорганизовали этот код в более функциональный стиль, мы могли бы получить что-то вроде этого:

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

Обратите внимание, что хотя UserLoginфункция все еще не является чистой, UserLoginPureфункция теперь является чистой функцией, и в результате логика аутентификации пользователя может быть проверена модульно, без необходимости имитировать какие-либо внешние зависимости. Это связано с тем, что взаимодействие с базой данных обрабатывается выше стека вызовов.

Джастин
источник
Ваша интерпретация: императивный стиль = состояние микросервисов и функциональный стиль = состояние без микросервисов ?
k3b
@ k3b В некотором роде, за исключением небольшого количества микросервисов. Очень просто императивный стиль включает манипулирование состоянием, в то время как функциональный стиль использует чистые функции без манипулирования состоянием.
Джастин
1
@Justin: я бы сказал, что функциональный стиль четко отделяет чистые функции от кода с побочными эффектами, как вы сделали в своем примере. Другими словами, функциональный код все еще может иметь побочные эффекты.
Джорджио
Функциональный подход должен возвращать пару с результатом и пользователем, потому что при неудачной попытке Result.FailedAttempt является результатом с новым пользователем с теми же данными, что и у оригинала, за исключением того, что у него есть еще одна неудачная попытка, и чистая функция делает вызвать побочные эффекты на пользователя, который указан в качестве параметра.
повышение темноты
исправление для последней части моего предыдущего комментария: «и чистая функция НЕ вызывает побочных эффектов для пользователя, который указан в качестве параметра».
восходяшая темнота