2010-04-12 36 views
8

Manypeople已经讨论过函数大小。他们说一般的功能应该很短。观点从15行到“大约一个屏幕”之间有所不同,今天大概有40-80行。
此外,功能应该只能完成一项任务。有一个很长的初始化方法是不好的做法吗?

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

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

带有init函数的事情是它们通常只被调用一次,所以不需要重新使用任何组件。你还会把它们分解成几个更小的函数,你会认为大的初始化函数是可以的吗?

回答

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行函数是非常困难的。

+1

+1'断言()'孤单。 – ndim 2010-04-12 13:32:15

+0

+1:“不需要重新使用任何组件。”重复使用不是问题。写一些可以被别人理解和维护的东西远远更重要。 – 2010-04-12 13:33:17

+0

我记得关于将标识符以下划线开头的建议提供给C编译器的内部使用,并避免它们在程序中使用。另外,你应该将这三个一次性的init函数标记为'static'。一次,它们不会在当前源文件之外使用。作为一个额外的好处,一个聪明的编译器会看到他们只被调用一次,并且只是内联代码(以防万一你担心这种调用开销)。 – ndim 2010-04-12 13:35:06

5

如果分成较小的部分可以使代码更好地结构化和/或更具可读性 - 无论函数做什么,都要做到这一点。它不涉及代码质量的行数。

2

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

1

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

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

我觉得很有道理,设计你的代码的主体,使其可以注入 - 但是,注射是通过一个框架或只是初始化调用的一个硬编码(及潜在的漫长)名单对于不同的项目可能会有所改变。在这两种情况下,结果都很难通过运行应用程序来进行测试。

3

我仍然试图将功能分解为逻辑单元。它们应该尽可能长或短而有意义。例如:

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

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

1

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

但是,请注意不要抽象得太早。显然你已经有两个问题了:1)音频初始化和2)使用该音频。例如,在你抽象初始化音频设备,或者在初始化期间可以配置给定设备的方式之前,你的工厂方法(audioObjectFactory.Create()或其他)应该只是一个很好的方法。早期抽象仅用于混淆设计。

请注意audioObjectFactory.Create()不是可以进行单元测试的东西。测试它是一个集成测试,并且直到可以抽象出它的一部分,它仍然是一个集成测试。稍后,您可能会发现,您拥有多个不同配置的工厂;此时,将硬件调用抽象为接口可能是有益的,因此您可以创建单元测试以确保各个工厂以适当的方式配置硬件。

+0

其实,这正是我正在做的。该类本身应该是一个音频播放器对象,用于抽象CoreAudio混乱的处理。真的,音频数据准备和音频硬件初始化连接非常紧密,因为不同的音频数据需要不同的配置,反之亦然。 – bastibe 2010-04-12 13:48:55

1

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

1

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

根据代码结构,可能是值得考虑的事情来抽象出大的初始化函数。尽管把功能分解为_init_X(), _init_Y()等等,但我仍然同意梅格的观点,这是一个很好的选择。即使您不打算在此代码中重复使用注释,在您的下一个项目中,当您对自己说“我是如何初始化该X组件?”时,回头选择它会容易得多的小的_init_X()函数比从更大的函数中选出它要小,尤其是如果X初始化分散在整个函数中。

1

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

这样说的话,只要不需要很长的重复代码或其他片段就可以抽象出来,只需要很长的初始化方法就可以了。

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

相关问题