提问人:user3021905 提问时间:12/13/2013 更新时间:12/16/2013 访问量:673
呼叫层次结构的最大深度
Maximum Depth of a Call Hierarchy
问:
一段时间以来,我一直在想呼叫层次结构的最大深度是多少。如果我有一个大方法,在重构之后,我经常会来编写这样的代码:
void A()
{
//...
B();
}
void B()
{
//...
C();
}
void C()
{
//...
}
所以我调用了一个方法,该方法调用了一个方法,该方法调用了一个方法(等等)——所有这些都在同一个类中。 有没有经验法则,多少个级别太多了?(对于这个问题,术语“呼叫层次结构”是否正确?
如果我重构,让 A 以某种方式同时调用 B 和 C 会更好吗?
答:
我还没有听说过编程语言施加的最大深度。递归深度通常受堆栈上程序分配的内存量的限制。在你实际使用你的方法之前,在 Web 框架的堆栈跟踪中看到十几个条目的情况并不少见,所以我不会太担心它。
评论
当使用递归的概念时,这些调用的深度可能非常大,仅受机器内存/堆栈的限制。因此,从理论上讲,没有限制,实际上,这些内存限制仅与嵌入式软件的开发人员有关。
从设计的角度来看,具有非常深的调用层次结构可能会让程序员感到困惑,但只要您的代码清晰,深层次结构并不是一件坏事。此外,它还可以防止方法暴露到应用程序的其他部分。
我经常看到初级开发人员抱怨调用堆栈太深而无法跟踪的大量代码。他们在调试过程中尤其抱怨它。有时在第一次学习代码时也是如此。我通常会回答一个问题:
如果“printf”是使用 12 个函数在内部实现的,这有什么关系吗?(我已经看到过这种实现,它是 4 个函数而不是 12 个,但这一点成立)
事实是,如果在代码中的任何时候,您需要挖掘两个以上的级别来了解发生了什么,那么您没有正确命名您的函数,或者您的函数原型/API 令人困惑。不管是哪一种,都是设计不好的标志。
就我个人而言,我不认为实际通话深度本身是一个问题。只是,如果它表现为一个问题,那么它就是命名或设计错误的代码的症状。
- 如果需要通过多个函数层不变地传递参数,则该参数应该是类的私有变量。
- 有时,单个类中的深度调用堆栈表明函数链实际上是过早耦合的简单函数。通常最好编写简单的函数来接受参数并返回某些内容,然后显式调用它们,例如:.换句话说,保持代码正交。
C(B(A()))
- 如果在阅读代码时被迫挖掘函数层,则函数未正确命名。
- 如果函数命名良好,但您仍然需要挖掘层,那么这可能表明您的类中隐藏了另一个类。重构代码以将最深层函数的功能提取到自己的类中,因为它似乎正在执行与类应该执行的操作没有直接关系的其他操作。
简短的回答:这完全取决于您的编程语言,以及运行程序的机器的配置方式。
长答案:
没有理论限制,尽管可能有机械限制。这与没有最大数字本身的意义相同,但是您实际可以在计算机内存中写下或存储多大的数字总是有限制的。
许多编程语言实现都使用调用堆栈,运行它们的机器通常在其指令集中明确支持此类堆栈,您的程序最终会转换为该堆栈。堆栈是静态分配的内存区域,通常具有固定大小。每当程序进行方法调用时,计算机都会将对程序中该位置的引用推送到堆栈上。方法调用完成后,计算机可以继续从中断的位置执行方法,并从堆栈中删除该“帧”。如果程序进行大量嵌套调用,则可能需要将更多的帧推送到堆栈上,而不是空间。这将导致您的程序崩溃并出现堆栈溢出错误。假设这是整个故事,那么“调用层次结构”深度的实际限制是程序调用堆栈的大小。
但这还不是全部。如果调用另一个方法是方法做的最后一件事(即它处于尾部位置),则没有理由将该调用推送到堆栈上,因为程序不需要再次返回到该位置。这种调用是尾部调用。将编写正确的编程语言实现,以便它通过不将尾调用推送到堆栈上来尊重尾部调用。这称为尾部呼叫消除。在这种情况下,程序可以根据需要进行任意数量的嵌套调用,前提是它们处于尾部位置。您甚至可以编写无限递归的程序。
一些编程语言实现是无堆栈的,因此它们的执行模型根本不使用堆栈。然后,他们将拥有一些注册方法调用的中央执行机制,例如蹦床或可能由线程池提供服务的执行队列。即使在传统上使用堆栈的语言中,您也可以自己使用这些技术来使程序无堆栈。
了解您的语言是否使用堆栈,如何配置其大小,以及它是否能够消除尾调用。
评论