您会将 LINQ 查询抽象为扩展方法吗
在我当前的项目中,我们为代码指标“可维护性指数”和“循环复杂度”设定了一些目标。可维护性指数应为 60 或更高,圈复杂度应为 25 或更低。我们知道,60 及以上的可维护性指数是相当高的。
我们还使用大量 linq 来过滤/分组/选择实体。我发现这些 linq 查询在可维护性指数上的得分并不高。 将这些查询抽象为扩展方法给了我更高的可维护性指数,这很好。但在大多数情况下,扩展方法不再是通用的,因为我将它们与我的类型而不是通用类型一起使用。
例如,以下 linq-query 与扩展方法:
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 查询以提高可维护性指数有不同的想法?
On my current project we set ourselves some goals for the code metrics "Maintainability Index" and "Cyclometic Complexity". Maintainability Index should be 60 or higher and Cyclometic Complexity 25 or less. We know that the Maintainability Index of 60 and higher is a pretty high one.
We also use a lot of linq to filter/group/select entities. I found out that these linq queries aren't scoring that high on Maintainability Index.
Abstracting this queries into extension methods is giving me a higher Maintainability Index, which is good. But in most of the cases the extension methods are not generic anymore because I use them with my Types instead of generic types.
For example the following linq-query vs extension method:
Linq query
List.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo)
Extension method:
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);
The extension method gives me a Maintainability Index improvement of 6 points, and gives a nice fluent syntax.
On the other hand I have to add a static class, it's not generic.
Any ideas on what approach would have your favor? Or maybe have different ideas about how to refactor the linq queries to improve Maintainability Index?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您不应该为了指标而添加类。任何指标都是为了让您的代码更好,但盲目遵循规则,即使是最好的规则,实际上可能会损害您的代码。
我认为坚持某些可维护性和复杂性指标不是一个好主意。我相信它们对于评估旧代码很有用,即当您继承一个项目并需要估计其复杂性时。然而,因为你没有获得足够的分数而提取方法是荒谬的。
只有在重构为代码增加价值的情况下才进行重构。这种价值是一种无法用数字表达的复杂的人类指标,而对其进行估计正是编程体验的意义所在——在两者之间找到平衡优化vs可读性vs干净的APIvs酷代码vs简单代码vs快速发货对比泛化与规范等。
这是您应该遵循的唯一指标,但它并不总是每个人都同意的指标...
至于您的示例,如果一遍又一遍地使用相同的 LINQ 查询,它在
Extensions
文件夹中创建一个EnumerableExtensions
并将其提取到那里是非常有意义的。但是,如果它使用一次或两次,或者可能会发生变化,则详细查询要好得多。我也不明白为什么你说它们不是通用的,带有一些负面含义。你不需要到处都是泛型!事实上,在编写扩展方法时,您应该考虑可以选择的最具体的类型,以免污染其他类的方法集。如果您希望帮助程序仅使用
IEnumerable
,那么为this IEnumerable
声明一个扩展方法绝对没有什么可耻的。顺便说一下,您的示例中有多余的转换。摆脱它。不要忘记,工具是愚蠢的。我们人类也是如此。
You shouldn't add classes for the sake of metrics. Any metrics are meant to make your code better but following rules blindly, even the best rules, may in fact harm your code.
I don't think it's a good idea to stick to certain Maintainability and Complexity indexes. I believe they are useful for evaluating old code, i.e. when you inherited a project and need to estimate its complexity. However, it's absurd to extract a method because you haven't scored enough points.
Only refactor if such refactoring adds value to the code. Such value is a complex human metric inexpressible in numbers, and estimating it is exactly what programming experience is about—finding balance between optimization vs readability vs clean API vs cool code vs simple code vs fast shipping vs generalization vs specification, etc.
This is the only metric you should follow but it's not always the metric everyone agrees upon...
As for your example, if the same LINQ query is used over and over, it makes perfect sense to create an
EnumerableExtensions
inExtensions
folder and extract it there. However, if it used once or twice, or is subject to change, verbose query is so much better.I also don't understand why you say they are not generic with somewhat negative connotations. You don't need generics everywhere! In fact, when writing extension methods, you should consider the most specific types you can choose as to not pollute other classes' method set. If you want your helper to only work with
IEnumerable<MyType>
, there is absolutely no shame in declaring an extension method exactly forthis IEnumerable<MyType>
. By the way, there's redundant casting in your example. Get rid of it.And don't forget, tools are stupid. So are we, humans.
我给您的建议是...不要成为指标的奴隶!它们是机器生成的,仅用作指导。他们永远无法取代熟练且经验丰富的程序员。
您认为哪一个最适合您的应用程序?
My advice to you would be ... don't be a slave to your metrics! They are machine generated and only intended to be used as guidance. They are never going to be a replacement for a skilled experienced programmer.
Which do you think is right for your application?
我个人同意扩展方法策略。我在一些实际应用程序中使用它没有出现任何问题。
对我来说,这不仅与指标有关,还与代码的可重用性有关。请参阅以下伪示例:
使用这两种扩展方法可以实现您的指标目标,并且它还提供“一个定义黄金客户的地方”。当不同的开发人员需要与“黄金客户”合作时,您不会在不同的地方创建不同的查询。
此外,它们是可组合的:
恕我直言,这是一种成功的方法。
我们面临的唯一问题是 ReSharper 存在一个错误,有时扩展方法的智能感知会变得疯狂。您键入“.Whic”,它可以让您选择所需的扩展方法,但是当您在其上“选项卡”时,它会在代码中放入完全不同的内容,而不是您选择的扩展方法。为此,我考虑过从 ReSharper 切换过来,但是……不:)
I for one agree with the extension method strategy. I've used it without a problem in a handful of real-world apps.
To me, it is not only about the metrics, but also the re-usability of the code there. See the following psuedo-examples:
Having those two extension methods accomplishes your goal for metrics, and it also provides "one place for the definition of what it is to be a gold customer." You don't have different queries being created in different places by different developers when they need to work with "gold customers."
Additionally, they are composable:
IMHO this is a winning approach.
The only problem we've faced is that there is a ReSharper bug that sometimes the Intellisense for the extension methods goes crazy. You type ".Whic" and it lets you pick the extension method you want, but when you "tab" on it, it puts something completely different into the code, not the extension method that you selected. I've considered switching from ReSharper for this, but... nah :)
不:在这种情况下,我会忽略圈复杂度 - 你最初拥有的更好。
问问自己什么更能解释。这个:
或者这个:
第一个清楚地表达了你想要的东西,而第二个则没有。了解“FilterBy”含义的唯一方法是进入源代码并查看其实现。
将查询片段抽象为扩展方法对于更复杂的场景来说是有意义的,在这些场景中,很难一眼判断查询片段正在做什么。
NO: in this case I would ignore the cyclomatic complexity - what you had originally was better.
Ask yourself what is more explanatory. This:
or this:
The first clearly expresses what you want, whereas the second does not. The only way to know what "FilterBy" means is to go into the source code and look at its implementation.
Abstracting query fragments into extension methods makes sense with more complex scenarios, where it's not easy to judge at a glance what the query fragment is doing.
我已经在一些地方使用了这种技术,例如,一个 Payment 类有一个相应的 PaymentLinqExtensions 类,它为 Payments 提供了特定于域的扩展。
在您给出的示例中,我将选择一个更具描述性的方法名称。还有一个问题是范围是包含还是排除,否则看起来还可以。
如果您的系统中有多个对象,其中日期的概念很常见,那么请考虑一个接口,也许是 IHaveADate (或更好的东西:-)
(IQueryable 很有趣。我不认为 IEnumerable 可以转换为它,这是一个遗憾的是,如果您正在使用数据库查询,那么它可以让您的逻辑出现在发送到服务器的最终 SQL 中,这很好,所有 LINQ 都存在一个潜在问题,即您的代码不会在您期望的时候执行。是)
如果日期范围是应用程序中的一个重要概念,并且您需要在该范围是从“EndDate”结束时的午夜开始还是在其开始时的午夜开始方面保持一致,那么 DateRange 类可能会很有用。然后
,如果您愿意,您也可以提供,
但这对我来说感觉更多与 DateRange 有关。我不知道它会使用多少,尽管您的情况可能会有所不同。我发现过于通用会使事情变得难以理解,并且 LINQ 也很难调试。
I have used this technique in places, for example a class Payment has a corresponding class PaymentLinqExtensions which provides domain specific extensions for Payments.
In the example you give I'd choose a more descriptive method name. There is also the question of whether the range is inclusive or exclusive, Otherwise it looks OK.
If you have multiple objects in your system for which the concept of having a date is common then consider an interface, maybe IHaveADate (or something better :-)
(IQueryable is interesting. I don't think IEnumerable can cast to it which is a shame. If you're working with database queries then it can allow your logic to appear in the final SQL that is sent to the server which is good. There is the potential gotcha with all LINQ that your code is not executed when you expect it to be)
If date ranges are an important concept in your application, and you need to be consistent about whether the range starts at midnight on the end of "EndDate" or midnight at the start of it, then a DateRange class may be useful. Then
You could also, if you feel like it, provide
but this to me feels more something to do with DateRange. I don't know how much it would be used, though your situation may vary. I've found that getting too generic can make things hard to understand, and LINQ can be hard to debug.