您会将LINQ查询抽象为扩展方法吗?

时间:2011-06-08 08:41:06

标签: linq extension-methods abstraction maintainability cyclomatic-complexity

在我目前的项目中,我们为代码指标“可维护性指数”和“Cyclometic Complexity”设定了一些目标。可维护性指数应为60或更高,Cyclometic Complexity为25或更低。我们知道60和更高的可维护性指数相当高。

我们还使用了很多linq来过滤/分组/选择实体。我发现这些linq查询在维护性指数上得分并不高。 将这些查询提取到扩展方法中,可以给我一个更高的可维护性指数,这很好。但是在大多数情况下,扩展方法不再是通用的,因为我将它们用于我的类型而不是泛型类型。

例如以下linq-query vs extension方法:

Linq查询

List.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo)

扩展方法:

public static IEnumerable<MyType> FilterBy(this IEnumerable<MyType> source, DateTime selectionFrom, DateTime selectionTo)
{
    return (IEnumerable<MyType>)source.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo);
}

List.FilterBy(selectionFrom, selectionTo);

扩展方法为我提供了6点的可维护性指数改进,并提供了一个很好的流利语法。 另一方面,我必须添加一个静态类,它不是通用的。

关于什么方法的任何想法会对你有利吗?或者可能对如何重构linq查询以改进维护性指数有不同的想法?

5 个答案:

答案 0 :(得分:5)

您不应为了指标而添加类。任何指标都旨在使您的代码更好,但盲目遵循规则,即使是最好的规则,实际上可能会损害您的代码

我认为坚持某些可维护性和复杂性指数并不是一个好主意。我相信它们对于评估旧代码非常有用,即当您继承项目并需要估计其复杂性时。但是,提取方法是荒谬的,因为你没有得到足够的分数。

只有重构才能为代码增加价值。这样的是一个复杂的人类指标,无法用数字表达,估计它正是编程经验的关键 - 在优化 vs 可读性之间寻找平衡 vs 清洁API vs 酷代码 vs 简单代码 vs 快速发货 vs 概括 vs 规范等

这是您应该遵循的唯一指标,但并不总是每个人都同意的指标......

至于您的示例,如果反复使用相同的LINQ查询,则在EnumerableExtensions文件夹中创建Extensions并在其中提取它是完全合理的。但是,如果它使用了一次或两次,或者可能会发生变化,那么详细查询会更好。

我也不明白为什么你说他们不是通用有一些负面的含义。你到处都不需要泛型!实际上,在编写扩展方法时,您应该考虑可以选择的最具体的类型,以免污染其他类的方法集。如果您希望助手只能使用IEnumerable<MyType>,那么在为this IEnumerable<MyType>声明扩展方法时绝对不会感到羞耻。顺便说一句,您的示例中有多余的强制转换。摆脱它。

不要忘记,工具是愚蠢的。我们人类也是如此。

答案 1 :(得分:4)

我对你的建议是...... 不要成为指标的奴隶!它们是机器生成的,仅用作指导。它们永远不会成为熟练的有经验的程序员的替代品。

您认为您的应用程序适合您?

答案 2 :(得分:2)

我同意扩展方法策略。我在少数真实世界的应用程序中使用它没有问题。

对我来说,不仅仅是指标,还有代码的可重用性。请参阅以下psuedo-examples:

var x = _repository.Customers().WhichAreGoldCustomers();

var y = _repository.Customers().WhichAreBehindInPayments();

使用这两种扩展方法可以实现指标的目标,并且它还提供了“定义黄金客户的一个地方”。当不同的开发人员需要与“黄金客户”合作时,您不会在不同的地方创建不同的查询。

此外,它们是可组合的:

var z = _repository.Customers().WhichAreGoldCustomers().WhichAreBehindInPayments();

恕我直言,这是一种成功的方法。

我们遇到的唯一问题是有一个ReSharper错误,有时扩展方法的Intellisense会变得疯狂。您键入“.Whic”,它可以让您选择所需的扩展方法,但是当您“选项卡”时,它会将完全不同的内容放入代码中,而不是您选择的扩展方法。我考虑过从ReSharper切换,但是......不::)

答案 3 :(得分:1)

否:在这种情况下,我会忽略圈复杂性 - 你原来的更好。

问问自己什么更具解释性。这样:

List.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo)

或者这个:

List.FilterBy(selectionFrom, selectionTo);

第一个清楚地表达了你想要的东西,而第二个则没有。了解“FilterBy”意味着什么的唯一方法是进入源代码并查看其实现。

将查询片段抽象为扩展方法对于更复杂的场景是有意义的,在这些场景中,一目了然地判断查询片段正在做什么并不容易。

答案 4 :(得分:0)

我在某些地方使用过这种技术,例如,一个班级付款有一个相应的类PaymentLinqExtensions,它为付款提供特定于域的扩展。

在您给出的示例中,我将选择更具描述性的方法名称。还有一个问题是范围是包容性的还是排他性的,否则看起来没问题。

如果您的系统中有多个具有日期概念的对象,那么请考虑一个界面,也许是IHaveADate(或更好的东西: - )

public static IQueryable<T> WithinDateRange(this IQueryable<T> source, DateTime from, DateTime to) where T:IHaveADate

(IQueryable很有趣。我不认为IEnumerable可以转向它,这是一种耻辱。如果你正在使用数据库查询,那么它可以允许你的逻辑出现在发送到服务器的最终SQL中很好。所有LINQ都有潜在的问题,你的代码没有按预期执行)

如果日期范围是您应用程序中的一个重要概念,并且您需要确定范围是在“EndDate”结束时的午夜还是在其开始时的午夜开始,那么DateRange类可能很有用。然后

public static IQueryable<T> WithinDateRange(this IQueryable<T> source, DateRange range) where T:IHaveADate

如果您愿意,也可以提供

public static IEnumerable<T> WithinDateRange(this IEnumerable<T> source, DateRange range, Func<DateTime,T> getDate)

但这对我来说更像是与DateRange有关。虽然您的情况可能会有所不同,但我不知道会使用多少。我发现过于通用会让事情难以理解,而LINQ很难调试。

var filtered = myThingCollection.WithinDateRange(myDateRange, x => x.Date)