代码之家  ›  专栏  ›  技术社区  ›  bastibe

使用长的初始化方法是不好的做法吗?

  •  8
  • bastibe  · 技术社区  · 14 年前

    Many people 对函数大小有过争论。他们说函数一般应该很短。从15行到“大约一个屏幕”,现在大概有40-80行。
    此外,函数应该总是只完成一个任务。

    但是,在我的代码中,有一种函数在这两个条件中经常失败:初始化函数。

    例如,在音频应用程序中,必须设置音频硬件/API,必须将音频数据转换为适当的格式,并且必须正确初始化对象状态。这显然是三个不同的任务,根据API的不同,可以轻松跨越50多行。

    init函数的特点是通常只调用一次,因此不需要重新使用任何组件。你还会把它们分成几个小的函数吗?你认为大的初始化函数可以吗?

    9 回复  |  直到 14 年前
        1
  •  12
  •   user229044    14 年前

    我仍然会按任务分解函数,然后从面向公共的初始化函数内部调用每个较低级别的函数:

    void _init_hardware() { }
    void _convert_format() { }
    void _setup_state() { }
    
    void initialize_audio() {
        _init_hardware();
        _convert_format();
        _setup_state();
    }
    

    写简洁的函数和隔离错误和变化一样重要,就像保持事物的可读性一样。如果你知道失败在 _convert_format() ,您可以更快地找到引起错误的~40行。如果提交只涉及一个函数的更改,则同样适用。

    最后一点,我利用 assert() 非常频繁,这样我就可以“经常失败并提早失败”,并且函数的开头是两个健全性检查断言的最佳位置。保持功能简短可以让您根据其更窄的职责集更彻底地测试功能。要对执行10种不同操作的400行函数进行单元测试是非常困难的。

        2
  •  5
  •   sharptooth    14 年前

    如果将代码分成更小的部分,会使代码更具结构化和/或可读性,那么不管函数做什么,都要这样做。它不是关于行数,而是关于代码质量。

        3
  •  3
  •   Paul Williams    14 年前

    我仍然会尝试将函数分解为逻辑单元。它们应该尽可能长或短。例如:

    SetupAudioHardware();
    ConvertAudioData();
    SetupState();
    

    为它们分配清晰的名称可以使所有内容更加直观和易读。此外,将它们分开使以后的更改和/或其他程序更容易重用它们。

        4
  •  2
  •   TLiebe    14 年前

    在这种情况下,我认为归根结底是个人偏好的问题。我宁愿让函数只做一件事,所以我会将初始化拆分为单独的函数,即使它们只被调用一次。但是,如果有人想在一个函数中完成所有的工作,我不会太担心(只要代码是清晰的)。还有更重要的事情要争论(比如大括号是否属于它们自己的独立行)。

        5
  •  1
  •   Jon Skeet    14 年前

    如果您有很多组件需要相互插入,那么拥有一个大型方法当然是很自然的——即使在可行的情况下,将每个组件的创建重构为一个单独的方法。

    另一种方法是使用依赖注入框架(例如Spring、Castle Windsor、Guice等)。有明确的利弊…虽然通过一个大的方法工作会非常痛苦,但至少您对所有东西的初始化位置有一个很好的了解,并且不需要担心“魔法”会发生什么。然后,在部署之后不能更改初始化(例如,它可以在Spring中使用XML文件)。

    我认为设计代码的主体以使其 可以 注入——但无论是通过框架还是仅仅是硬编码(可能很长)的初始化调用列表进行注入,对于不同的项目来说都是一个很好的选择。在这两种情况下,除了运行应用程序之外,结果很难测试。

        6
  •  1
  •   xofz    14 年前

    首先,应该使用工厂而不是初始化函数。也就是说,而不是 initialize_audio() ,你有一个 new AudioObjectFactory (你可以想一个更好的名字)。这保持了关注的分离。

    但是,也要注意不要过早地抽象。显然,您已经有两个问题:1)音频初始化和2)使用该音频。例如,在提取要初始化的音频设备或在初始化期间配置给定设备的方式之前,您的工厂方法( audioObjectFactory.Create() 不管怎样),应该只保留一个大方法。早期的抽象只会使设计变得模糊。

    注意 音频对象工厂。创建() 不是可以进行单元测试的东西。测试它是一个集成测试,在它的某些部分可以被抽象之前,它仍然是一个集成测试。稍后,您可能会发现对于不同的配置,您有多个不同的工厂;此时,将硬件调用抽象到一个接口中可能会有好处,这样您就可以创建单元测试,以确保各个工厂以正确的方式配置硬件。

        7
  •  1
  •   Thyamine    14 年前

    我认为尝试计算行数并以此为基础确定函数的方法是错误的。对于初始化代码之类的东西,我通常有一个单独的函数,但主要是为了使装入、初始化或新函数不会混乱和混乱。如果你能像其他人建议的那样把它分成几个任务,那么你就可以给它命名一些有用的东西并帮助组织起来。即使您只调用一次,这也不是一个坏习惯,而且通常您会发现,有时您可能需要重新初始化一些东西,并可以再次使用该函数。

        8
  •  1
  •   awshepard    14 年前

    我只是想把这个扔到外面去,因为还没有人提到它 Facade Pattern 有时被称为复杂子系统的接口。我自己也没怎么做过,但比喻通常是打开电脑(需要几个步骤)或打开家庭影院系统(打开电视、打开接收器、关灯等)。

    根据代码结构的不同,可能需要考虑抽象掉大型初始化函数。我仍然同意美加的观点,尽管它将函数分解为 _init_X(), _init_Y() 等是一个很好的方法。即使您不打算在下一个项目中重用此代码中的注释,当您对自己说,“我是如何初始化X组件的?”,回去把它从小一点的里面挑出来会容易得多。 _init_X() 函数,而不是从一个更大的函数中选择它,特别是当x初始化分散在函数中时。

        9
  •  1
  •   Carlos Nunez    14 年前

    函数长度,正如您所标记的,是一个非常主观的问题。然而,标准的最佳实践是隔离经常重复和/或可以作为其自身实体运行的代码。例如,如果初始化函数正在加载特定库将使用的库文件或对象,则该代码块应该模块化。

    尽管如此,拥有一个长的初始化方法并不坏,只要它不长,因为有很多重复的代码或其他可以被抽象掉的片段。

    希望有帮助,
    卡洛斯·努涅兹