9

许多 就函数大小争论不休。他们说一般来说功能应该很短。意见从 15 行到“大约一屏”不等,今天大概是 40-80 行。
此外,函数应该始终只完成一项任务。

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

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

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

4

9 回答 9

12

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

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

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

编写简洁的函数既是为了隔离错误和变化,也是为了保持可读性。如果您知道故障出现在 中_convert_format(),您可以更快地找到导致错误的大约 40 行。如果您提交仅涉及一个功能的更改,则同样适用。

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

于 2010-04-12T13:24:52.690 回答
5

如果分解成更小的部分可以使代码结构更好和/或更具可读性 - 无论函数做什么,都要这样做。它与代码质量无关,与行数无关。

于 2010-04-12T13:22:32.823 回答
3

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

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

为它们分配清晰的名称会使一切变得更加直观和易读。此外,将它们分开可以使将来的更改和/或其他程序更容易重用它们。

于 2010-04-12T13:25:31.060 回答
2

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

于 2010-04-12T13:24:30.433 回答
1

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

一种替代方法是使用依赖注入框架(例如 Spring、Castle Windsor、Guice 等)。这有明确的优点和缺点......虽然通过一种大方法工作可能会很痛苦,但您至少对所有内容的初始化位置有一个很好的了解,并且无需担心可能会发生什么“魔法” . 再说一次,在部署之后不能更改初始化(例如,它可以使用 Spring 的 XML 文件)。

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

于 2010-04-12T13:24:39.240 回答
1

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

但是,也要注意不要过早抽象。显然,您确实已经有两个问题:1)音频初始化和 2)使用该音频。直到,例如,您抽象了要初始化的音频设备,或者在初始化期间可以配置给定设备的方式,您的工厂方法(audioObjectFactory.Create()或其他)实际上应该只保留一个大方法。早期的抽象只会混淆设计。

请注意,这audioObjectFactory.Create()不是可以进行单元测试的东西。测试它是一个集成测试,直到它的部分可以被抽象出来,它仍然是一个集成测试。稍后,您可能会发现您有多个不同的工厂用于不同的配置;那时,将硬件调用抽象到接口中可能是有益的,这样您就可以创建单元测试以确保各种工厂以正确的方式配置硬件。

于 2010-04-12T13:36:32.363 回答
1

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

于 2010-04-12T13:47:50.730 回答
1

只是想我会把它扔在那里,因为它还没有被提及——外观模式有时被引用为复杂子系统的接口。我自己并没有做太多,但比喻通常是像打开电脑(需要几个步骤),或者打开家庭影院系统(打开电视,打开接收器,关灯等)。 .)

Depending on the code structure, might be something worth considering to abstract away your large initialization functions. I still agree with meagar's point though that breaking down functions into _init_X(), _init_Y(), etc. is a good way to go. Even if you aren't going to reuse comments in this code, on your next project, when you say to yourself, "How did I initialize that X-component?", it'll be much easier to go back and pick it out of the smaller _init_X() function than it would be to pick it out of a larger function, especially if the X-initialization is scattered throughout it.

于 2010-04-12T13:53:23.027 回答
1

Function length is, as you tagged, a very subjective matter. However, a standard best-practice is to isolate code that is often repeated and/or can function as its own entity. For instance, if your initialization function is loading library files or objects that will be used by a specific library, that block of code should be modularized.

With that said, it's not bad to have an initialization method that's long, as long as it's not long because of lots of repeated code or other snippets that can be abstracted away.

Hope that helps,
Carlos Nunez

于 2010-04-12T14:22:27.337 回答