我喜欢编写纯函数的想法,但是我很难理解将它们组合起来以生成可测试代码的方法。我习惯于提取类然后适当地存根,并且觉得我缺少对函数式编程的一些关键见解。
这是我从当前面临的问题中精简的示例。
我想列出一个日期列表并将其过滤为符合“机会”标准的日期。
在 C# 中,它看起来像:
static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
{
return dates.Where(ds => HasOpportunity(ds)).ToList();
}
static bool HasOpportunity(List<DateTime> dates)
{
var threshold = 0.05D;
var current = OpportunityProbability(dates, DateTime.Now);
var previous = OpportunityProbability(dates, DateTime.Now.Subtract(TimeSpan.FromDays(30)));
return previous >= threshold && current < threshold;
}
static double OpportunityProbability(List<DateTime> dates, DateTime endDate)
{
// does lots of math
return 0.0D;
}
所以在提示我们OpportunityProbability
知道如何测试。我遇到的麻烦是在HasOpportunity
链条上(Opportunities
)。
我知道如何测试的唯一方法HasOpportunity
是存根,OpportunityProbability
但我做不到。而且我不想创建假数据来满足OpportunityProbability
测试的设计HasOpportunity
。因此,即使这两个函数都是纯函数,它也是不可测试的,我觉得它的设计很糟糕。
因此我觉得我正在设计糟糕的功能代码:)
我关心HasOpportunity
的主要是布尔测试。给定两个双精度数和一个阈值,进行比较并返回结果。为了获得这两个双打,它使用了一个需要日期列表和日期列表的函数。这导致HasOpportunity
还负责确定日期(DateTime.Now
和前 30 天)。也许我可以把它分开:
static bool HasOpportunity(double probability1, double probability2)
{
var threshold = 0.05D;
return probability2 >= threshold && probability1 < threshold;
}
所以这显然是可测试的。我什至可以提高门槛:
static bool HasOpportunity(double threshold, double probability1, double probability2)
{
return probability2 >= threshold && probability1 < threshold;
}
所以这更通用。
我这样做时遇到的问题是我刚刚将事情转移到Opportunities
:
static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
{
return dates.Where(ds => {
var current = OpportunityProbability(ds, DateTime.Now);
var previous = OpportunityProbability(ds, DateTime.Now.Subtract(TimeSpan.FromDays(30)));
return HasOpportunity(0.05D, current, previous);
}).ToList();
}
这是我不知道下一步要采取的地方。
功能霸主有什么想法吗?帮我用 C# 编写 F#,在此先感谢!
更新
所以再迈出一步,我可以得到:
static List<List<DateTime>> Opportunities(double threshold, DateTime currentDate, DateTime previousDate, List<List<DateTime>> dates)
{
return dates.Where(ds => {
var current = OpportunityProbability(ds, currentDate);
var previous = OpportunityProbability(ds, previousDate);
return HasOpportunity(threshold, current, previous);
}).ToList();
}
所以我仍然不知道如何测试这个,但很好的是这个函数的参数最终定义了机会是什么:
- 临界点
- 第一次约会
- 第二次约会
然后给出一个日期列表,它可以给你机会。