9

目前,如果我想在我选择的解决方案中检查循环引用Architecture - Generate Dependency Graph - For Solution。然后从打开的新选项卡中选择Layout - Analyzers - Circular References Analyzer. 最后,如果我从各个程序集向下钻取并且有循环引用,我可以看到它们在图表上以红色突出显示,并且它们也在错误列表中显示为警告。

因为我打算在同一个类的方法之间发现循环引用,所以在中等大小的代码库上这很容易出错并且很耗时。

我想知道是否有一种方法可以一次获取所有警告,而无需扩展节点,或者可能打开父节点的突出显示,以便我只能深入了解肯定包含循环引用的组合。

NDepend 应该能够提供帮助,但我更喜欢让事情尽可能简单,所以我总是对采用额外的工具持谨慎态度。

4

1 回答 1

7

是的,NDepend 可以有效地查找循环引用让我解释一下如何,因为它可能比您想象的要容易(免责声明:我是 NDepend 的开发人员之一)。到目前为止,您可以找到开箱即用的命名空间依赖循环,但是,正如我在下面解释的那样,很容易找到命名空间中的类型或类型的方法之间的循环。

有一个默认的 C# LINQ 代码规则列出了命名空间依赖循环。然后可以将这样的循环导出到依赖图或依赖矩阵。这是 2012 年 6 月在 Roslyn 代码库 CTP 上执行的规则的屏幕截图(请注意,它只需要 16 毫秒即可运行)。它发现了 11 个不同的循环,如屏幕截图所示,您可以深入了解每个循环并将一个循环导出到图表中:

匹配命名空间依赖循环的规则

这是 7 个命名空间循环的依赖关系图。请注意,它看起来比经典的 O 形环循环更复杂。这里的关键是,您可以从这些命名空间中的任何一个访问所有其他命名空间。这是循环(纠缠)的广义概念。

匹配命名空间依赖循环图

列出命名空间依赖周期的默认 C# LINQ 代码规则的代码乍一看可能令人生畏。但是 C# 开发人员应该在几分钟内理解它,然后可以轻松地对其进行调整以找到任何类型的依赖循环。

例如,要查找相同类型的方法循环(而不是相同组装循环的命名空间),几乎就像用方法替换所有命名空间单词,用类型替换组装单词一样简单。

// <Name>Avoid methods of a type to be in cycles</Name>
warnif count > 0

from t in Application.Types
                 .Where(t => t.ContainsMethodDependencyCycle != null && 
                             t.ContainsMethodDependencyCycle.Value)

// Optimization: restreint methods set
// A method involved in a cycle necessarily have a null Level.
let methodsSuspect = t.Methods.Where(m => m.Level == null)

// hashset is used to avoid iterating again on methods already caught in a cycle.
let hashset = new HashSet<IMethod>()


from suspect in methodsSuspect
   // By commenting this line, the query matches all methods involved in a cycle.
   where !hashset.Contains(suspect)

   // Define 2 code metrics
   // - Methods depth of is using indirectly the suspect method.
   // - Methods depth of is used by the suspect method indirectly.
   // Note: for direct usage the depth is equal to 1.
   let methodsUserDepth = methodsSuspect.DepthOfIsUsing(suspect)
   let methodsUsedDepth = methodsSuspect.DepthOfIsUsedBy(suspect)

   // Select methods that are both using and used by methodSuspect
   let usersAndUsed = from n in methodsSuspect where 
                         methodsUserDepth[n] > 0 && 
                         methodsUsedDepth[n] > 0 
                      select n

   where usersAndUsed.Count() > 0

   // Here we've found method(s) both using and used by the suspect method.
   // A cycle involving the suspect method is found!
   // 8Feb2021: invoke extension method Append() explicitly to avoid ambiguous compiler error
   //           because of the new .NET BCL extension methods Append() method
   let cycle = NDepend.Helpers.ExtensionMethodsEnumerable.Append(usersAndUsed,suspect)

   // Fill hashset with methods in the cycle.
   // .ToArray() is needed to force the iterating process.
   let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray()

select new { suspect, cycle }

...这是该规则的结果的样子(仍然可以将方法循环导出到依赖图或矩阵)。请注意,由于方法和类型的数量远高于命名空间和程序集的数量,因此在 Roslyn 这样的大型代码库上运行此查询大约需要 10 秒(而不是命名空间周期的 16 毫秒),因此您可能需要调整CQLinq 查询执行超时(默认为 2 秒)。

类型依赖循环

完整地说,我注意到循环大部分时间是由一些双向引用引起的(即 A 使用 B,B 使用 A)。因此,删除双向引用是打破循环的第一件事。这就是为什么我们提供了默认的 CQLinq 规则避免命名空间相互依赖,它仍然可以适应类型或方法循环。

于 2013-02-28T08:47:56.673 回答