我喜欢编写纯函数的想法,但我无法理解将它们组合在一起的方法,从而导致可测试的代码。我习惯于提取类,然后适当地进行存根,并且觉得我缺少对函数式编程的一些关键洞察力。
这是一个我从目前面临的问题中瘦下来的例子。
我想列出一份日期清单,并根据“机会”标准对其进行过滤。
在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
)的上方。{/ p>
我知道如何测试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();
}
所以我仍然不知道如何测试它,但很好的是,这个函数的参数最终定义了机会是什么:
然后给出一份日期列表,它可以为您提供机会。
答案 0 :(得分:2)
您是否考虑过使用高阶函数?将OpportunityProbability函数传递给HasOpportunity。
static List<List<DateTime>> Opportunities(List<List<DateTime>> dates)
{
return dates.Where(ds => HasOpportunity(ds, OpportunityProbability, OpportunityProbability)).ToList();
}
static bool HasOpportunity(List<DateTime> dates, Func<List<DateTime>, DateTime, double> currentProb, Func<List<DateTime>, DateTime, double> prevProb)
{
var threshold = 0.05D;
var current = currentProb(dates, DateTime.Now);
var previous = prevProb(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(在HasOpportunity的情况下,您可以“存根”第二个和最后一个参数。如果您想要更多分离,您也可以将OpportunityProbability传递给商机。
答案 1 :(得分:0)
我认为你应该投入一些古老的面向对象,并尊重单一责任模式。一种可能的方法是创建类:
OpportunityCalculator
double OpportunityProbability(List<DateTime> dates, DateTime endDate)
使用方法OpportunityFilter
bool HasOpportunity(double threshold, double probability1, double probability2)
这些类可以独立测试:
OpportunityCalculator
抽象出复杂的数学。 OpportunityFilter
时,您可以删除OpportunityCalculator
。您的测试将围绕这样一个事实,即计算器将使用正确的参数进行两次咨询。