5

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

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

例如下面的 linq-query vs 扩展方法:

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 查询以提高可维护性索引有不同的想法?

4

5 回答 5

5

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

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

仅当这种重构为代码增加价值时才进行重构。 这样的价值是一个复杂的人类指标,无法用数字表达,而估计它正是编程经验的意义所在——在优化、可读性干净的 API 酷代码简单代码快速交付、泛化规范等之间找到平衡。

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

至于您的示例,如果一遍又一遍地使用相同的 LINQ 查询,那么创建一个EnumerableExtensionsinExtensions文件夹并将其提取到那里是非常有意义的。但是,如果它使用一次或两次,或者可能会发生变化,那么详细查询会好得多。

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

别忘了,工具是愚蠢的。我们人类也是。

于 2011-06-08T08:50:50.073 回答
4

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

您认为哪个适合您的应用?

于 2011-06-08T08:46:37.610 回答
2

我同意扩展方法策略。我已经在少数现实世界的应用程序中毫无问题地使用了它。

对我来说,这不仅关乎指标,还关乎那里代码的可重用性。请参阅以下伪示例:

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

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

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

此外,它们是可组合的:

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

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

我们面临的唯一问题是有一个 ReSharper 错误,有时扩展方法的 Intellisense 会发疯。你输入“.Whic”,它会让你选择你想要的扩展方法,但是当你在它上面“tab”时,它会将一些完全不同的东西放入代码中,而不是你选择的扩展方法。我已经考虑过为此从 ReSharper 切换,但是......不:)

于 2011-06-08T12:29:48.043 回答
1

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

问问自己什么更能说明问题。这:

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

或这个:

List.FilterBy(selectionFrom, selectionTo);

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

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

于 2011-06-08T09:22:00.037 回答
0

我已经在一些地方使用了这种技术,例如一个类 Payment 有一个相应的类 PaymentLinqExtensions,它为 Payments 提供特定于域的扩展。

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

如果您的系统中有多个对象,这些对象的日期概念很常见,那么请考虑一个接口,也许是 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)
于 2011-06-08T14:14:14.460 回答