调用层次结构的最大深度

时间:2013-12-13 08:36:39

标签: language-agnostic coding-style

我一直想知道呼叫层次结构的最大深度是什么。如果我有一个很大的方法,在重构之后,我经常会看到这样的代码:

void A()
{
    //...
    B();
}

void B()
{
    //...
    C();
}

void C()
{
    //...
}

所以我调用的方法调用一个调用方法的方法(依此类推) - 所有这些都在同一个类中。 有经验法则有多少级别太多了? (并且是术语"调用层次结构"对此问题是否正确?)

如果我重构以便A以某种方式同时调用B和C会不会更好?

4 个答案:

答案 0 :(得分:1)

我经常看到初级开发人员抱怨代码重要因为调用堆栈太深而无法跟踪。他们特别在调试期间抱怨它。有时候第一次学习代码时也是如此。我经常回答一个问题:

  

如果使用12个函数在内部实现“printf”,这有关系吗? (我已经看到过这种情况的实现,有4个函数不是12,但重点是这样)

事实是,如果在代码中的任何一点,您需要深入了解两个级别以了解正在发生的事情,那么您没有正确命名您的函数或您的函数原型/ API令人困惑。无论哪个都是设计糟糕的标志。

我个人认为实际的呼叫深度本身并不是问题所在。只是如果它表现为一个问题,那么这就是代号命名或设计错误的症状。

  • 如果你需要通过多个函数层传递一个参数,那么该参数应该是该类的私有变量。
  • 有时,单个类中的深度调用堆栈表示函数链实际上是过早耦合的简单函数。编写接受参数并返回一些内容然后显式调用它们的简单函数通常更好:C(B(A()))。换句话说,保持代码正交。
  • 如果在阅读代码时你被迫挖掘功能层,那么这些功能就没有被正确命名。
  • 如果功能名称很好,但您仍然需要挖掘图层,那么它可能表示您的班级中隐藏了另一个类。重构代码以将最深层函数的功能提取到它自己的类中,因为它似乎正在做其他与该类应该做的事情没有直接关系的事情。

答案 1 :(得分:1)

简短回答:这完全取决于您的编程语言,以及如何配置运行程序的机器。

长答案

虽然可能存在机械限制,但没有理论限制。这与没有最大数量的意义相同,但是你可以实际记下或存储在计算机内存中的数字总是有限制。

许多编程语言实现都使用call stack,运行它们的计算机通常在其程序最终转换为的指令集中明确支持这样的堆栈。堆栈是静态分配的内存区域,通常具有固定大小。每当程序进行方法调用时,机器都会将对程序中该位置的引用推送到堆栈中。当方法调用完成后,机器可以继续执行它停止的方法,并从堆栈中删除该“框架”。如果您的程序进行了大量嵌套调用,则可能需要将更多帧推送到堆栈而不是有空间。这将导致程序因堆栈溢出错误而崩溃。假设这是整个故事,“调用层次结构”深度的实际限制是程序调用堆栈的大小。

但这不是全部。如果调用另一个方法是方法所做的最后的事情(即它在尾部位置),那么就没有理由将该调用推送到堆栈上,因为程序不需要再回到那个地方。这种电话是tail call。将编写正确的编程语言实现,以便通过不将它们推入堆栈来尊重尾调用。这称为尾部呼叫消除。在这种情况下,只要程序处于尾部位置,程序就可以根据需要进行任意数量的嵌套调用。你甚至可以编写无限递归的程序。

某些编程语言实现是 stackless ,因此它们的执行模型根本不使用堆栈。然后他们将有一些中央执行机制来注册方法调用,例如trampoline或可以由thread pool服务的执行队列。即使是传统上使用堆栈的语言,您也可以自己使用这些技术来使您的程序无堆叠。

了解您的语言是否使用堆栈,如何配置其大小,以及是否能够消除尾部调用。

答案 2 :(得分:0)

当使用recursion的概念时,这些调用的深度可能非常大,仅受机器内存/堆栈的限制。因此理论上没有限制,实际上这些内存限制仅与嵌入式软件的开发人员相关。

从具有非常深的调用层次结构的设计pov可能会让程序员感到困惑,但只要您的代码清晰,深层次结构就不是坏事。此外,它还可以防止方法暴露在应用程序的其他部分。

答案 3 :(得分:-1)

我没有听说过编程语言强加的最大深度。递归深度通常受到堆栈上程序分配的内存量的限制。在您实际使用方法之前,在Web框架的堆栈跟踪中看到十几个条目并不罕见,所以我不会太担心它。