我们如何以编程方式识别对C#方法的引用数量

时间:2020-03-06 14:28:12  来源:igfitidea点击:

我最近继承了需要一些修剪和清理的Cconsole应用程序。长话短说,该应用程序由一个包含超过110,000行代码的类组成。是的,单节课超过110,000行。而且,当然,该应用程序是我们业务的核心,它全天候运行更新动态网站上使用的数据。尽管我被告知我的前任是"一个非常好的程序员",但很明显他根本没有加入OOP(或者版本控制)。

无论如何……在使自己熟悉代码的同时,我发现了很多已声明但从未引用的方法。看起来好像是使用复制/粘贴来对代码进行版本控制,例如说我有一个名为getSomethingImportant()的方法,很可能还有另一个名为getSomethingImortant_July2007()的方法(在大多数情况下,模式为functionName_ [datestamp])。看起来当要求程序员对getSomethingImportant()进行更改时,他会复制/粘贴然后重命名为getSomethingImortant_Date,对getSomethingImortant_Date进行更改,然后将代码中的任何方法调用更改为新方法名称,而将旧方法保留在代码,但从未引用过。

我想编写一个简单的控制台应用程序,该应用程序爬过一个巨大的类并返回所有方法的列表以及每个方法被引用的次数。据我估计,目前有1000多种方法,因此手工完成将花费一些时间。

.NET框架中是否有可用于检查此代码的类?还是任何其他有用的工具,可能有助于识别已声明但从未引用的方法?

(旁问:有没有其他人见过像这样的Capp,一个让人毛骨悚然的大类?这或者多或者少是一个巨大的程序过程,我知道这是我见过的第一个,至少是这种规模的。)

解决方案

我不知道为处理此特定情况而构建的任何内容,但是我们可以使用Mono.Cecil。反映程序集,然后计算IL中的引用。不应该太强硬。

如果我们只需要提取有关类的某些统计信息,则可以尝试使用NDepend。请注意,此工具内部依赖于Mono.Cecil来检查装配。

我不认为我们想自己写这个,只是购买NDepend并使用其代码查询语言

Reflector中的"分析器"窗口可以向我们显示调用方法的位置(使用者)。
听起来要以这种方式获取信息将花费很长时间。
我们可能会查看Reflector提供的用于编写插件的API,并查看我们是否可以通过这种方式完成分析工作。我希望代码指标加载项的源代码可以告诉我们一些有关如何从反射器API获取有关方法的信息的信息。

编辑:同样,Reflector的代码模型查看器加载项也可以提供帮助。这是探索Reflector API的好方法。

.NET框架本身中没有简单的工具可以做到这一点。但是我不认为我们真的需要一次使用未使用方法的列表。正如我所看到的,我们将仅遍历代码,并针对每种方法检查其是否未使用,然后将其删除。我将使用Visual Studio"查找引用"命令来执行此操作。或者,我们可以在其" Analize"窗口中使用Resharper。或者,我们可以只使用Visual Studio代码分析工具来查找所有未使用的私有方法。

下载Resharper的免费试用版。使用Resharper->搜索->在文件中查找用法(Ctrl-Shift-F7)以突出显示所有用法。同样,计数将显示在状态栏中。如果要搜索多个文件,也可以使用Ctrl-Alt-F7来执行。

如果我们不喜欢这样做,请在Visual Studio中按文本搜索功能名称(Ctrl-Shift-F),这应该告诉我们在解决方案中找到了多少个,以及它们在哪里。

尝试让编译器发出x86指令中的汇编程序文件,而不是.NET程序集。

为什么?因为解析汇编代码比Ccode或者.NET汇编要容易得多。

例如,函数/方法声明看起来像这样:

.string "w+"
    .text
    .type   create_secure_tmpfile, @function
create_secure_tmpfile:
    pushl   %ebp
    movl    %esp, %ebp
    subl    , %esp
    movl    $-1, -8(%ebp)
    subl    , %esp

函数/方法引用将如下所示:

subl    , %esp
    pushl   24(%ebp)
    call    create_secure_tmpfile
    addl    , %esp
    movl    20(%ebp), %edx
    movl    %eax, (%edx)

当我们看到" create_secure_tmpfile:"时,我们知道我们有一个函数/方法声明,当我们看到"调用create_secure_tmpfile"时,便知道我们有一个函数/方法引用。对于目的而言,这可能已经足够好了,但是如果不是这样,那么我们仅需执行几个步骤,便可以为整个应用程序生成非常可爱的调用树。

FXCop的规则将识别未使用的私有方法。因此,我们可以将所有方法标记为私有,并使其生成一个列表。

如果我们想成为更高级的人,FXCop也提供一种语言
http://www.binarycoder.net/fxcop/

如果我们不想为NDepend掏腰包,因为听起来好像在单个程序集中只有一个类,请注释掉方法并进行编译。如果可以编译,请将其删除,我们将不会遇到任何继承问题,虚拟方法或者类似问题。我知道这听起来很原始,但有时重构只是像这样艰苦的工作。这是一种假设,我们需要在每次构建后运行单元测试,直到清理完代码(红色/绿色/重构)为止。

为了完成Romain Verdier的答案,让我们在这里深入探讨NDepend可以为我们带来的好处。 (免责声明:我是NDepend团队的开发人员)

NDepend使我们可以使用一些LINQ查询来查询.NET代码。知道哪些方法可以调用,哪些方法可以被其他方法调用,就像编写以下LINQ查询一样简单:

from m in Application.Methods
select new { m, m.MethodsCalled, m.MethodsCallingMe }

该查询的结果以易于浏览的呼叫者和被呼叫者的方式呈现(并将其100%集成到Visual Studio中)。

还有许多其他的NDepend功能可以为我们提供帮助。例如,我们可以右键单击Visual Studio> NDepend>选择方法...>正在使用我的方法(直接或者间接)...

生成以下代码查询...

from m in Methods 
let depth0 = m.DepthOfIsUsing("NUnit.Framework.Constraints.ConstraintExpression.Property(String)")
where depth0  >= 0 orderby depth0
select new { m, depth0 }

...匹配直接和间接呼叫者,并带有呼叫深度(1表示直接呼叫者,2表示直接呼叫者的呼叫者,依此类推)。

然后通过单击导出到图形按钮,我们将获得数据透视方法的调用图(当然也可以是相反的方式,即方法由特定数据透视方法直接或者间接调用)。