技术开发 频道

如何避免在.NET代码中出现不恰当依赖?

  让我们来看看CQLinq的代码规范体 避免命名空间依赖环。我们可以看到开头有很多描述如何使用的注释。这是通过注释和C#代码和读者交流的好机会,感谢即将发布的Roslyn compiler as services,我相信所提倡的简短C#代码摘录(excerpt)而不是DLL或者VS项目,将会越来越受欢迎。

// <Name>避免命名空间依赖环</Name>
warnif count
> 0
// 这个查询列出了应用程序的所有命名空间依赖环。
// 每一行显示一个不同的环,并以缠在环中的命名空间作为前缀。
//
// 想要在依赖图或依赖矩阵中查看某个环,右键点击
// 该环然后将相应的命名空间导出为依赖图或依赖矩阵即可!
//
// 在矩阵中,依赖环以红色方块或黑色单元格表示。
// 为了能够方便地浏览依赖环,依赖矩阵需有该选项:
// --> 显示直接和间接依赖
//
// 请阅读我们关于分解代码的白皮书,
// 以更深入地了解命名空间依赖环,以及弄明白为什么
// 避免出现依赖环是组织代码结构的简单而有效的解决方案。
// http://www.ndepend.com/WhiteBooks.aspx


// 优化:限定程序集范围
// 如果命名空间是相互依赖的
// - 则它们必定在同一个程序集中被声明
// - 父程序集必定ContainsNamespaceDependencyCycle
from assembly in Application.Assemblies
                 .Where(a
=> a.ContainsNamespaceDependencyCycle != null &&
                             a.ContainsNamespaceDependencyCycle.Value)

// 优化:限定命名空间范围
// 依赖环中命名空间的Level值必须为null。
let namespacesSuspect = assembly.ChildNamespaces.Where(n => n.Level == null)

// hashset用来避免再次遍历环中已经被捕获的命名空间。
let hashset = new HashSet<INamespace>()


from suspect in namespacesSuspect
  
// 若注释掉这一行,则将查询环中的所有命名空间。
  where !hashset.Contains(suspect)

  
// 定义2个代码矩阵
  
// - 非直接使用嫌疑命名空间的命名空间的深度。
  
// - 被嫌疑命名空间非直接使用的命名空间的深度。
  
// 注意:直接使用的深度等于1。
  
let namespacesUserDepth = namespacesSuspect.DepthOfIsUsing(suspect)
  
let namespacesUsedDepth = namespacesSuspect.DepthOfIsUsedBy(suspect)

  
// 选择使用namespaceSuspect或者被namespaceSuspect使用的命名空间
  
let usersAndUsed = from n in namespacesSuspect where
                       namespacesUserDepth[n]
> 0 &&
                       namespacesUsedDepth[n]
> 0
                    
select n

  where usersAndUsed.Count()
> 0

  
// 这里我们找到了使用嫌疑命名空间或者被嫌疑命名空间使用的命名空间。
  
// 找到了包含嫌疑命名空间的环!
  
let cycle = usersAndUsed.Append(suspect)

  
// 将环中的命名空间填充到hashset。
  
// 需要使用.ToArray() 来推进迭代过程。
  
let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray()

select new { suspect, cycle }

  代码规范体包括若干区域:

  • 首先,利用属性IAssembly.ContainsNamespaceDependencyCycle以及属性IUser.Level,我们可以尽可能地消除掉多余的程序集和命名空间。因此,对于每个包含命名空间依赖环的程序集,我们只保留了被称为嫌疑命名空间(suspect namespaces)的集合。

  • 定义的范围变量(range variable)hashset被用来避免由N个命名空间构成的环被显示N次。注释掉这行代码where !hashset.Contains(suspect)则会将依赖环显示N次。

  • 该查询的核心是对两个扩展方法 DepthOfIsUsing() 和DepthOfIsUsedBy()的调用。这两个方法非常强大,因为他们各自创建了 ICodeMetric对象。通常,如果A依赖于B,B依赖于C,则DepthOfIsUsing(C)[A]的值等于2,DepthdOfIsUsedBy(A)[C]的值也等于2。基本上,如果存在一个或多个嫌疑命名空间B使得DepthOfIsUsing(A)[B] 和DepthOfIsUsedBy(A)[B] 的值同时非null且为正数,则包含嫌疑命名空间A的依赖环就会被检测到。

  • 接着我们只需构建命名空间B的集合,然后将它附加上命名空间A,从而使整个环包含A。

0
相关文章