函数样式如何帮助模拟依赖项?


10

摘自《 Java Magazine》最近一期对Kent Beck的采访:

Binstock:让我们讨论微服务。在我看来,微服务的测试优先会变得复杂,因为某些服务要正常运行就需要大量其他服务。你同意吗?

贝克:上一堂大班或上几堂小班,似乎是一组折衷方案。

Binstock:是的,除了我猜,在这里,您必须使用大量的模拟程序,以便能够建立一个可以测试给定服务的系统。

贝克:我不同意。如果它是命令式样式,则必须使用很多模拟。在一种功能样式中,外部依赖项在调用链中被聚集在一起,那么我认为这不是必需的。我认为您可以从单元测试中获得很多覆盖。

他什么意思?函数样式如何使您摆脱对外部依赖的嘲笑?



1
如果他们专门讨论Java,我怀疑其中的大部分讨论都是没有根据的。Java实际上并没有它所需要的那种支持来支持所描述的功能编程。哦,可以,您可以使用实用程序类或Java 8 Lambdas对其进行仿真,但是...令人毛骨悚然。
罗伯特·哈维

Answers:


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. 对于给定usernamepassword组合,此函数将不会总是给出相同的结果,因为结果还取决于存储在数据库中的用户记录。
  2. 该功能可以更改数据库的状态,即具有副作用。

还要注意,为了对该功能进行单元测试,我们需要模拟两个数据库调用FindUserRecordFailedLoginAttempt

如果要将此代码重构为更具功能性的样式,我们可能会得到如下所示的结果:

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:我要说的是,函数风格显然将纯函数与具有副作用的代码分开,就像您在示例中所做的那样。换句话说,功能代码仍然会产生副作用。
Giorgio

函数方法应返回一个带有结果和一个用户的对,因为尝试失败时,Result.FailedAttempt是具有与原始数据相同的新用户的结果,但它又有一次失败尝试,而纯函数执行作为参数给用户带来副作用。
上升黑暗

我对先前评论的最后部分的更正:“并且纯函数不会给用户带来作为参数给出的副作用”。
上升黑暗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.