解开 Knuth 的结:如何重构意大利面条式代码?

2024-05-18

这个问题的灵感来自如何将流程图转化为实施? https://stackoverflow.com/questions/36647765它询问如何通过算法消除goto代码中的语句。这answer https://stackoverflow.com/a/36661381/4068338一般问题描述于this http://dl.acm.org/citation.cfm?id=512979科学论文。

我已经实现了一些代码如下高级草图来自 Knuth 的算法 X计算机编程的艺术描述了产生具有受限前缀的词典排列(见本期第 16 页draft http://www.cs.utsa.edu/~wagner/knuth/fasc2b.pdf).

这是对应的流程图 https://i.stack.imgur.com/v99AG.png上述算法。

这可能是一个very聪明和very高效的算法,但是结构代码似乎很难遵循。我最终使用了旧的goto-风格的实现:

//Algorithm X;
1:
initialize();
2:
enter_level(k);
3:
set(a[k],q);
if(test() == ok) {
  if (k == n) {
    visit();
    goto 6;
  }
  goto 4;
}
goto 5;
4:
increase(k);
goto 2;
5:
increasev2(a[k]);
if (q != 0) {
  goto 3;
}
6:
decrease(k);
if (k==0) {
  goto 7;
}
set(p,u_k);
goto 5;
7:
return;

问题是:如何重构这段代码以消除all the goto calls?

一个(虚假的)答案是建议“查找引用的科学论文,并逐行跟踪”——确实,这当然是有可能的。但这个问题是关于有经验的程序员一旦看到这个就会立即看到什么意大利面条代码 https://stackoverflow.com/questions/983574/unravelling-assembly-language-spaghetti-code.

我感兴趣的是how一步一步的重构,不仅仅是代码。


Note:

  1. 根据算法 X 的高级规范和goto跳跃。实现黑盒功能initialize()等只需要一些额外的说明,但这些与结构的代码。函数调用期间发生的事情并不重要,因为现在的重点是程序的流程。
  2. 通常争论的“是GOTO 仍然被视为有害吗? https://stackoverflow.com/questions/46586/goto-still-considered-harmful“与这个问题完全无关,应该not在答案和评论中都得到解决。

有关的:如何使用或完成意大利面条代码? https://stackoverflow.com/questions/4694053/how-to-work-with-or-complete-the-spaghetti-code


无需太多努力(也没有太多风险),您就可以快速减少 goto 和标签的数量。

1) 删除未在任何地方引用的标签(这将是标签 1:)

2)查找除了 goto 之外无法输入的代码块,这些代码在少数地方被调用。这些通常可以简单地分解出来。 4:可以通过将代码移动到调用它的地方来处理,并且可以安全地完成,因为它唯一的出口是 goto。这也允许我们删除上面的 goto 5,因为该代码将简单地跳转到 5:。 7:可以通过修改if语句来处理。此时我们有

initialize();
2:
enter_level(k);
3:
set(a[k],q);
if(test() == ok) {
  if (k == n) {
    visit();
    goto 6;
  }
  increase(k);
  goto 2;
}
5:
increasev2(a[k]);
if (q != 0) {
  goto 3;
}
6:
decrease(k);
if (k!=0) {
  set(p,u_k);
  goto 5;
}
return;

我倾向于在这里停下来。但如果继续下去,问题就变成了识别循环并用循环结构替换 goto。然而,由于代码的构造方式,进行这些更改的风险似乎要大得多。另外,你可能最终会遇到中断和继续,无论如何,这都是某种 goto。我最终得到的是这个(如果没有一些我不会保证它的正确性very严格测试):

initialize();
enter_level(k);
while (true) {
  set(a[k],q);
  if(test() == ok) {
    if (k == n) {
      visit();
    } else {
      increase(k);
      enter_level(k);
      continue;
    }
  } else {
    increasev2(a[k]);
    if (q != 0) {
      continue; 
    }
  }
  while (true) {
    decrease(k);
    if (k!=0) {
      set(p,u_k);
      increasev2(a[k]);
      if (q != 0) {
        break; 
      }
    } else {
      return;
    }
  }
}

我做了3:一个循环,6:一个内循环。我通过复制 5: 代码来代替 goto,并用中断替换 goto 3,从而摆脱了 goto 5。这使得制作更干净的循环变得更容易。 goto 6 通过使用 else 来修复。 goto 3 继续。

之后(如果你还有精力)你可以尝试将循环从 while(true) 与 continue 更改为 whiles 与实际条件。

最好先开发测试,然后进行一两个更改并进行测试。进行另一次更改,然后再次测试。如果你不这样做,很容易在早期犯下结构性错误,然后使后续步骤无效并迫使你重新开始。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解开 Knuth 的结:如何重构意大利面条式代码? 的相关文章

  • boost::multi_index_container 复合键中的 equal_range 与比较运算符

    我正在尝试从多索引容器查询结果 其中值类型是三个元素的结构 第一个值已给出 但第二个和第三个值必须大于或小于查询参数 经过搜索后 我发现必须实现自定义密钥提取器 并且这里的一些链接建议相同 但我无法实现它 boost multi index
  • 在 LINQ 查询中返回不带时间的日期

    我正在编写一个查询 我想计算按日期联系我们的呼叫中心的次数 看起来很简单 但由于联系日期字段是日期时间字段 我得到了时间 因此当我按联系日期 时间 分组时 每个联系日期实例的计数为 1 所以 我想只按日期分组 而不按时间分组 下面是我用来查
  • Signalr 在生产服务器中总是陷入长轮询

    当我在服务器中托管应用程序时 它会检查服务器端事件并始终回退到长轮询 服务器托管环境为Windows Server 2012 R1和IIS 7 5 无论如何 我们是否可以解决这个问题 https cloud githubuserconten
  • 如何在C++中实现模板类协变?

    是否可以以这样一种方式实现类模板 如果模板参数相关 一个对象可以转换为另一个对象 这是一个展示这个想法的例子 当然它不会编译 struct Base struct Derived Base template
  • SSH 主机密钥指纹与模式 C# WinSCP 不匹配

    我尝试通过 WinSCP 使用 C 连接到 FTPS 服务器 但收到此错误 SSH 主机密钥指纹 与模式不匹配 经过大量研究 我相信这与密钥的长度有关 当使用 服务器和协议信息 下的界面进行连接时 我从 WinSCP 获得的密钥是xx xx
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J
  • 按字典顺序对整数数组进行排序 C++

    我想按字典顺序对一个大整数数组 例如 100 万个元素 进行排序 Example input 100 21 22 99 1 927 sorted 1 100 21 22 927 99 我用最简单的方法做到了 将所有数字转换为字符串 非常昂贵
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • 编译的表达式树会泄漏吗?

    根据我的理解 JIT 代码在程序运行时永远不会从内存中释放 这是否意味着重复调用 Compile 表达式树上会泄漏内存吗 这意味着仅在静态构造函数中编译表达式树或以其他方式缓存它们 这可能不那么简单 正确的 他们可能是GCed Lambda
  • 像“1$”这样的位置参数如何与 printf() 一起使用?

    By man I find printf d width num and printf 2 1 d width num 是等价的 但在我看来 第二种风格应该与以下相同 printf d num width 然而通过测试似乎man是对的 为什
  • 更改窗口的内容 (WPF)

    我创建了一个简单的 WPF 应用程序 它有两个 Windows 用户在第一个窗口中填写一些信息 然后单击 确定 这会将他们带到第二个窗口 这工作正常 但我试图将两个窗口合并到一个窗口中 这样只是内容发生了变化 我设法找到了这个更改窗口内容时
  • 网络参考共享类

    我用 Java 编写了一些 SOAP Web 服务 在 JBoss 5 1 上运行 其中两个共享一个类 AddressTO Web 服务在我的 ApplycationServer 上正确部署 一切都很顺利 直到我尝试在我的 C 客户端中使用
  • 在 URL 中发送之前对特殊字符进行百分比编码

    我需要传递特殊字符 如 等 Facebook Twitter 和此类社交网站的 URL 为此 我将这些字符替换为 URL 转义码 return valToEncode Replace 21 Replace 23 Replace 24 Rep
  • Java中获取集合的幂集

    的幂集为 1 2 3 is 2 3 2 3 1 2 1 3 1 2 3 1 假设我有一个Set在爪哇中 Set
  • 方法参数内的变量赋值

    我刚刚发现 通过发现错误 你可以这样做 string s 3 int i int TryParse s hello out i returns false 使用赋值的返回值是否合法 Obviously i is but is this th
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 如何将字符串“07:35”(HH:MM) 转换为 TimeSpan

    我想知道是否有办法将 24 小时时间格式的字符串转换为 TimeSpan 现在我有一种 旧时尚风格 string stringTime 07 35 string values stringTime Split TimeSpan ts new
  • 期望最大化算法的数值示例[重复]

    这个问题在这里已经有答案了 由于我不确定给出的公式 有人可以提供 EM 算法的简单数字示例吗 一个非常简单的具有 4 或 5 个笛卡尔坐标的坐标就可以了 那这个呢 http en wikibooks org wiki Data Mining
  • 为什么 strtok 会导致分段错误?

    为什么下面的代码给出了Seg 最后一行有问题吗 char m ReadName printf nRead String s n m Writes OK char token token strtok m 如前所述 读取字符串打印没有问题 但

随机推荐