您维护的任何函数的最高循环复杂度是多少?您将如何重构它?

发布于 2024-08-03 03:35:33 字数 558 浏览 2 评论 0原文

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

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

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

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

I was doing a little exploring of a legacy system I maintain, with NDepend (great tool check it out), the other day. My findings almost made me spray a mouthful of coffee all over my screen. The top 3 functions in this system ranked by descending cyclomatic complexity are:

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

I mean 171, wow!!! Shouldn't it be below 20 or something? So this made me wonder. What is the most complex function you maintain or have refactored? And how would you go about refactoring such a method?

Note: The CC I measured is over the code, not the IL.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

李不 2024-08-10 03:35:33

与我几年前开发的一些 20 世纪 70 年代的老式 COBOL 相比,这简直就是小孩子的东西。我们使用原始的 McCabe 工具以图形方式显示某些代码的 CC。打印输出是纯黑色的,因为显示功能路径的线条非常密集并且像意大利面条一样。我没有数字,但它必须远高于 171。

该怎么做

根据代码完成 (第一版):

如果分数是:

  • 0-5 - 例程可能没问题
  • 6-10 - 开始考虑简化例程的方法
  • 10+ - 将例程的一部分分解为第二个例程并从第一个例程调用它例程

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

This is kid stuff compared to some 1970s vintage COBOL I worked on some years ago. We used the original McCabe tool to graphically display the CC for some of the code. The print out was pure black because the lines showing the functional paths were so densely packed and spaghetti-like. I don't have a figure but it had to be way higher than 171.

What to do

Per Code Complete (first edition):

If the score is:

  • 0-5 - the routine is probably fine
  • 6-10 - start to think about ways to simplify the routine
  • 10+ - break part of the routine into a second routine and call it from the first routine

Might be a good idea to write unit tests as you break up the original routine.

清醇 2024-08-10 03:35:33

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

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

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

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

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

如何更改它:

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

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

。主要方法是识别意大利面条的有凝聚力的部分(拉动一根线:))并将冗长的功能分解为较短的功能。通常,可以将映射或转换提取到函数或辅助类/对象中。改用 STL 而不是手工构建的容器和迭代器也可以减少大量代码。使用 std::string 代替 C 字符串有很大帮助。

This is for C/C++ code currently shipping in a product:

the highest CC value that I could reliably identify (i.e. I don't suspect the tool is erroneously adding complexity values for unrelated instances of main(...) ):

  • an image processing function: 184
  • a database item loader with verification: 159

There is also a test subroutine with CC = 339 but that is not strictly part of the shipping product. Makes me wonder though how one could actually verify the test case(s) implemented in there...

and yes, the function names have been suppressed to protect the guilty :)

How to change it:

There is already an effort in place to remedy this problem. The problems are mostly caused by two root causes:

  1. spaghetti code (no encapsulation, lots of copy-paste)
  2. code provided to the product group by some scientists with no real software construction/engineering/carpentry training.

The main method is identifying cohesive pieces of the spaghetti (pull on a thread:) ) and break up the looooong functions into shorter ones. Often there are mappings or transformations that can be extracted into a function or a helper class/object. Switching to using STL instead of hand-built containers and iterators can cut a lot of code too. Using std::string instead of C-strings helps a lot.

初见 2024-08-10 03:35:33

我从 这篇博客文章 与各种代码库进行比较时,它似乎很有意义并且对我有用。我知道这是一个非常固执己见的话题,所以 YMMV。

  • 1-10 - 简单,风险不大
  • 11-20 - 复杂,低风险
  • 21-50 - 太复杂,中等风险,关注
  • 超过 50 - 太复杂,无法测试,高风险

I have found another opinion on this from this blog entry which seems to make good sense and work for me when comparing it against various code bases. I know it's a highly opinionated topic, so YMMV.

  • 1-10 - simple, not much risk
  • 11-20 - complex, low risk
  • 21-50 - too complex, medium risk, attention
  • More than 50 - too complex, can't test , high risk
独木成林 2024-08-10 03:35:33

以下是 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 循环组成,并不难理解。令我困惑的是,在我用 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

bezitest 中的 main 只是一长串 if 语句:如果我应该测试三角形,然后运行testtriangle。如果我应该测试测量单位,请运行 testmeasure。等等。

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

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

These are the six most complex functions in PerfectTIN, which will hopefully go into production in a few weeks:

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

Where the two numbers are different, it's because of switch statements.

testmatrix consists of several order-2 and order-1 for-loops in a row and is not hard to understand. The thing that puzzled me, looking at it years after I wrote it in Bezitopo, is why it mods something by 83.

The two tick methods are run 20 times a second and check several conditions. I've had a bit of trouble with the complexity, but the bugs are nothing worse than menu items being grayed out when they shouldn't, or the TIN display looking wonky.

The TIN is stored as a variant winged-edge structure consisting of points, edges, and triangles all pointing to each other. checkTinConsistency has to be as complex as it is because the structure is complex and there are several ways it could be wrong.

The hardest bugs to find in PerfectTIN have been concurrency bugs, not cyclomatic bugs.

The most complex functions in Bezitopo (I started PerfectTIN by copying code from Bezitopo):

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

main in bezitest is just a long sequence of if-statements: If I should test triangles, then run testtriangle. If I should test measuring units, then run testmeasure. And so on.

The complexity in subdivide is partly because roundoff errors very rarely produce some wrong-looking conditions that the function has to check for.

What is now tryStartPoint used to be part of maketin (which now has a complexity of only 11) with even more complexity. I broke out the inside of the loop into a separate function because I had to call it from the GUI and update the screen in between.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文