有一个很长的初始化方法是不好的做法?

时间:2010-04-12 13:20:16

标签: function initialization code-size

Many people讨论了功能大小问题。他们说一般来说功能应该很短。意见从15行到“大约一个屏幕”不等,现在可能大约有40-80行 此外,功能应始终只执行一项任务。

但是,我的代码中有两种标准经常失败的函数:初始化函数。

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

具有init函数的东西通常只调用一次,因此不需要重用任何组件。如果你认为大的初始化函数没问题,你还会将它们分解成几个较小的函数吗?

9 个答案:

答案 0 :(得分:12)

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

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

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

编写简洁的函数既可以将错误与变更隔离,也可以保持可读性。如果您知道失败在_convert_format(),那么您可以更快地追踪导致错误的~40行。如果您提交仅触及一个函数的更改,则同样适用。

最后一点,我经常使用assert()因此我可以“经常失败并提前失败”,并且函数的开头是一些完整性检查断言的最佳位置。保持功能简短允许您根据更狭窄的职责更彻底地测试功能。很难对400行函数进行单元测试,它可以完成10种不同的操作。

答案 1 :(得分:5)

如果分成更小的部分可以使代码更好地结构化和/或更具可读性 - 无论函数做什么都可以。它不是关于代码质量的行数。

答案 2 :(得分:3)

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

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

为它们指定清晰的名称会使所有内容更直观,更易读。此外,将它们分开使得将来的更改和/或其他程序更容易重用它们。

答案 3 :(得分:2)

在这种情况下,我认为这取决于个人偏好。我更喜欢让函数只做一件事,所以我将初始化分成单独的函数,即使它们只被调用一次。但是,如果有人想在一个函数中完成所有操作,我就不会太担心(只要代码清晰)。有更重要的事情需要争论(比如花括号是否属于他们自己的单独行)。

答案 4 :(得分:1)

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

另一种替代方法是使用依赖注入框架(例如Spring,Castle Windsor,Guice等)。这有一定的优点和缺点......虽然通过一个大方法工作可能会非常痛苦,你至少可以很好地了解一切都被初始化的地方,并且没有必要担心可能会发生什么“魔术” 。然后,部署后不能更改初始化(例如,可以使用Spring的XML文件)。

我认为设计代码的主体以便可以注入是有意义的 - 但是注入是通过框架还是仅通过硬编码(并且可能很长)列表初始化调用是一个可能会改变不同项目的选择。在这两种情况下,除了运行应用程序之外,结果很难测试。

答案 5 :(得分:1)

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

但是,要小心也不要过早抽象。显然,你确实有两个问题:1)音频初始化和2)使用该音频。例如,在您初始化要抽象的音频设备或初始化期间可以配置给定设备的方式之前,您的工厂方法(audioObjectFactory.Create()或其他)应该只保留一个大方法。早期抽象只是为了模糊设计。

请注意,audioObjectFactory.Create()不是可以进行单元测试的。测试它是一个集成测试,直到它的一部分可以被抽象化,它仍将是一个集成测试。稍后,您可能会发现您有多个不同的工厂用于不同的配置;在这一点上,将硬件调用抽象到接口中可能是有益的,因此您可以创建单元测试以确保各个工厂以适当的方式配置硬件。

答案 6 :(得分:1)

我认为尝试计算行数并根据它确定函数是错误的方法。对于像初始化代码这样的东西,我经常有一个单独的函数,但主要是因为Load或Init或New函数不会混乱和混乱。如果您可以将其分成一些其他人建议的任务,那么您可以将其命名为有用的并帮助组织。即使你只召唤一次,这也不是一个坏习惯,通常你会发现有时你可能想重新启动它并且可以再次使用该功能。

答案 7 :(得分:1)

以为我会把它扔到那里,因为它还没有被提及 - Facade Pattern有时被引用作为复杂子系统的接口。我自己并没有做太多,但隐喻通常类似于打开电脑(需要几个步骤),或打开家庭影院系统(打开电视,打开接收器,关灯等)。 。)

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

答案 8 :(得分:1)

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

有了这个说法,有一个很长的初始化方法并不坏,只要它不长,因为有很多重复的代码或其他片段可以被抽象出来。

希望有所帮助,
Carlos Nunez