17

前几天,我正在使用NDepend(很棒的工具检查一下)对我维护的遗留系统进行一些探索。我的发现几乎让我在屏幕上喷了一口咖啡。该系统中按圈复杂度降序排列的前 3 个函数是:

  1. SomeAspNetGridControl.CreateChildControls (CC of 171!!!)
  2. SomeFormControl.AddForm (CC of 94)
  3. SomeSearchControl.SplitCriteria (CC of 85)

我的意思是171,哇!它不应该低于20或什么的吗?所以这让我很奇怪。您维护或重构的最复杂的功能是什么?您将如何重构这样的方法?

注意:我测量的 CC 是代码,而不是 IL。

4

4 回答 4

18

与我几年前工作的一些 1970 年代复古 COBOL 相比,这是孩子们的东西。我们使用原始McCabe工具以图形方式显示部分代码的 CC。打印出来的东西是纯黑色的,因为显示功能路径的线条非常密集,像意大利面条一样。我没有数字,但它必须比 171 高得多。

该怎么办

每个代码完整(第一版):

如果分数是:

  • 0-5 - 例行程序可能很好
  • 6-10 - 开始思考简化例程的方法
  • 10+ - 将例程的一部分分解为第二个例程并从第一个例程中调用它

在分解原始例程时编写单元测试可能是个好主意。

于 2009-09-01T23:40:43.590 回答
3

这是针对当前在产品中发布的 C/C++ 代码:

我可以可靠识别的最高 CC 值(即我不怀疑该工具错误地为不相关的 main(...) 实例添加复杂度值):

  • 图像处理功能:184
  • 带有验证的数据库项目加载器:159

还有一个 CC = 339 的测试子程序,但严格来说,这不是发货产品的一部分。让我想知道如何才能真正验证在那里实现的测试用例......

是的,函数名称已被禁止以保护有罪者:)

如何改变它:

已经在努力解决这个问题。这些问题主要是由两个根本原因引起的:

  1. 意大利面条代码(无封装,大量复制粘贴)
  2. 一些没有真正接受过软件构建/工程/木工培训的科学家提供给产品组的代码。

主要方法是识别意大利面条的粘性部分(拉线:))并将 looooong 函数分解为较短的函数。通常可以将映射或转换提取到函数或辅助类/对象中。切换到使用 STL 而不是手工构建的容器和迭代器也可以减少大量代码。使用 std::string 而不是 C-strings 有很大帮助。

于 2009-09-07T12:14:39.133 回答
0

以下是 PerfectTIN 中最复杂的六个功能,有望在几周内投入生产:

32      32      testptin.cpp(319): testmatrix
36      39      tincanvas.cpp(90): TinCanvas::tick
53      53      mainwindow.cpp(126): MainWindow::tick
56      60      perfecttin.cpp(185): main
58      58      fileio.cpp(457): readPtin
62      62      pointlist.cpp(61): pointlist::checkTinConsistency

两个数字不同的地方,是因为switch陈述。

testmatrix由连续的几个 order-2 和 order-1 for-loops 组成,不难理解。让我感到困惑的是,在我在 Bezitopo 写它多年之后再看它,就是为什么它在 83 之前修改了一些东西。

这两种tick方法每秒运行 20 次并检查多个条件。我在复杂性方面遇到了一些麻烦,但是这些错误并不比菜单项在不应该变灰时变灰更糟糕,或者 TIN 显示看起来很不稳定。

TIN 存储为一种变体翼边结构,由点、边和三角形组成,这些点、边和三角形都相互指向。checkTinConsistency必须尽可能复杂,因为结构很复杂,并且有几种方法可能是错误的。

PerfectTIN 中最难发现的错误是并发错误,而不是圈错误。

Bezitopo 中最复杂的函数(我通过从 Bezitopo 复制代码来启动 PerfectTIN):

49      49      tin.cpp(537): pointlist::tryStartPoint
50      50      ptin.cpp(237): readPtin
51      51      convertgeoid.cpp(596): main
54      54      pointlist.cpp(117): pointlist::checkTinConsistency
73      80      bezier.cpp(1070): triangle::subdivide
92      92      bezitest.cpp(7963): main

maininbezitest只是一长串 if 语句:如果我应该测试三角形,则运行testtriangle. 如果我应该测试测量单位,然后运行testmeasure​​. 等等。

复杂性subdivide部分是因为舍入误差很少会产生函数必须检查的一些错误的条件。

现在tryStartPoint曾经是其中的一部分maketin(现在只有 11 的复杂度),而且复杂度更高。我将循环的内部分解为一个单独的函数,因为我必须从 GUI 调用它并在其间更新屏幕。

于 2020-05-15T09:37:32.403 回答
0

我从这个博客条目中找到了另一种观点,当将它与各种代码库进行比较时,这似乎很有意义并且对我有用。我知道这是一个高度自以为是的话题,所以 YMMV。

  • 1-10 - 简单,风险不大
  • 11-20 - 复杂,低风险
  • 21-50 - 太复杂,中等风险,注意
  • 超过 50 - 太复杂,无法测试,高风险
于 2019-02-06T12:07:07.557 回答