仅调用其他功能的功能。这是一个好习惯吗?


13

我目前正在处理一组报告,这些报告包含许多不同的部分(均需要不同的格式),并且我正在尝试找出构造代码的最佳方法。我们过去完成的类似报告最终都具有非常大的功能(超过200行),这些功能可以对报告进行所有数据处理和格式化,从而使工作流看起来像这样:

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

我希望能够将这些大功能分解为较小的块,但恐怕最终我将拥有数十个不可重用的功能,而类似的“在这里做所有事情”功能的唯一作用是调用所有这些较小的函数,如下所示:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

或者,如果我们更进一步:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

这真的是更好的解决方案吗?从组织的角度来看,我认为是这样(即,一切都比可能的组织得要好得多),但是就代码的可读性而言,我不确定(可能只调用其他函数的功能链很大)。

有什么想法吗?


归根结底...更具可读性吗?是的,所以这是一个更好的解决方案。
sylvanaar 2012年

Answers:


18

这通常称为功能分解,如果正确完成,通常是一件好事。

同样,函数的实现应在单个抽象级别内。如果您采用largeReportProcessingFunction,那么它的作用是定义按照哪个顺序执行哪些不同的处理步骤。所有这些步骤的实现都在下面的抽象层上,largeReportProcessingFunction不应直接依赖于此。

请注意,这是一个不好的命名选择:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

您会看到callAllSection1Functions一个实际上并没有提供抽象的名称,因为它实际上并没有说出它是做什么的,而是说它如何实现的。应该调用它,processSection1或者在上下文中实际上有意义的任何东西。


6
我基本上同意这个答案,除了我看不到“ processSection1”是一个有意义的名字。它实际上等效于“ callAllSection1Functions”。
埃里克·金

2
@EricKing:callAllSection1Functions泄漏有关实现的详细信息。从外部角度看,是否processSection1将其工作推迟到“所有section1功能”,或者是否直接执行它,还是是否使用pixie-dust召集将完成实际工作的独角兽都没有关系。真正重要的是,在调用之后processSection1section1可以被视为已处理。我同意处理是一个通用名称,但是如果您具有很高的凝聚力(您应该一直为之奋斗),那么您的上下文通常会狭窄到足以消除歧义。
back2dos 2012年

2
我的意思是,称为“ processXXX”的方法几乎总是可怕的,无用的名称。所有方法都“处理”事物,因此将其命名为“ processXXX”与将其命名为“ doSomethingWithXXX”或“ manipulateXXX”以及类似名称一样有用。对于名为“ processXXX”的方法,几乎​​总是有一个更好的名称。从实用的角度来看,它与“ callAllSection1Functions”一样有用(无用)。
埃里克·金

4

您说您正在使用C#,所以我想知道您是否可以利用您使用的是面向对象的语言来构建结构化的类层次结构?例如,如果有必要将报告分为几个部分,请创建一个Section类并将其扩展以满足客户的特定要求。

考虑一下它就像创建非交互式GUI一样有意义。考虑一下您可能创建的对象,就像它们是不同的GUI组件一样。问自己,基本报告是什么样的?复杂的怎么样?扩展点应该在哪里?


3

这取决于。如果您创建的所有功能确实是一个单一的工作单元,那么很好,如果它们只是调用功能的第1-15、16-30、31-45行是不好的。长函数不是邪恶的,如果它们做一件事情,如果您的报表有20个过滤器,并且具有一个验证函数,该函数将检查所有20个过滤器将变长。很好,按过滤器或过滤器组将其分解只是增加了额外的复杂性而没有真正的好处。

拥有大量私有功能也使自动单元测试变得更加困难,因此一些人会尝试避免这种原因,但是我认为这是一个很差的理由,因为它倾向于创建更大,更复杂的设计来适应测试。


3
以我的经验,长功能邪恶的。
伯纳德

如果您的示例是面向对象的语言,那么我将创建一个基本的过滤器类,而这20个过滤器中的每一个将在不同的派生类中。将使用创建调用来替换Switch语句以获取正确的过滤器。每个功能都做一件事,而所有功能都很小。
DXM 2012年

3

由于您正在使用C#,因此请尝试在对象中进行更多思考。看起来您仍在通过后续函数依赖的“全局”类变量来进行程序编码。

在单独的功能中分解逻辑绝对比在单个功能中包含所有逻辑更好。我敢打赌,通过将功能分解为几个对象,还可以分离出函数要处理的数据。

考虑使用一个ReportBuilder类,您可以在其中传递报告数据。如果您可以将ReportBuilder分成单独的类,则也可以这样做。


2
程序具有完美的功能。
DeadMG 2012年

1

是。

如果您有需要在多个地方使用的代码段,请使用自己的函数。否则,如果必须更改功能,则需要在多个位置进行更改。这不仅增加了工作量,而且还增加了错误发生的风险,因为代码在一个地方而不是在另一个地方或在一个地方被引入但在另一个地方而不是另一个地方被更改。

如果使用描述性方法名称,它也有助于使该方法更具可读性。


1

您使用编号来区分功能的事实表明,重复或类执行过多操作存在潜在的问题。将大型功能分解为许多较小的功能可能是重构重复项的有用步骤,但它不应该是最后一个。例如,您创建两个函数:

processSection1HeaderData();
processSection2HeaderData();

用于处理节标题的代码中没有相似之处吗?您可以通过参数化差异来为它们提取通用功能吗?


这实际上不是生产代码,因此函数名称仅是示例(在生产中,函数名称更具描述性)。通常,不存在,不同部分之间没有相似之处(尽管有相似之处时,我会尽力重用代码)。
Eric C.

1

即使不重用子功能,将功能分解为逻辑子功能也是有帮助的–将其分解为简短易懂的块。但这必须由逻辑单元完成,而不仅仅是n#行。您应该如何分解它取决于您的代码,常见的主题是循环,条件,对同一对象的操作。基本上任何您可以描述为离散任务的内容。

因此,是的,这是一种很好的样式-听起来很奇怪,但是鉴于您描述的重用有限,我建议您仔细考虑ATTEMPTING重用。确保用法是完全相同的,而不仅仅是偶然的相同。尝试处理同一块中的所有情况通常是您最终在第一时间获得大量笨拙功能的原因。


0

我认为这主要是个人喜好。如果您的函数将被重用(并且您确定不能使它们更通用和可重用?)我肯定会说拆分它们。我还听说过,这是一个很好的原则,即任何函数或方法中都不应包含超过2-3个屏幕的代码。因此,如果您的大型函数不断运行,则可能会使您的代码更具可读性以将其拆分。

您不会提及正在使用哪些技术来开发这些报告。看来您可能正在使用C#。那是对的吗?如果是这样,如果您不想将函数拆分为较小的函数,则也可以考虑使用#region指令作为替代。


是的,我正在使用C#。我可能可以重用某些功能,但是对于许多此类报告,不同部分的数据和布局(客户要求)截然不同。
Eric C.

1
+1(建议区域除外)。我不会使用区域。
伯纳德2012年

@Bernard-有什么特殊原因吗?我假设询问者仅在他已经排除了拆分功能的情况下才考虑使用#regions。
约书亚·卡莫迪

2
区域往往会隐藏代码并可能被滥用(即嵌套区域)。我喜欢在代码文件中一次看到所有代码。如果需要在视觉上分隔文件中的代码,请使用一行正斜杠。
伯纳德

2
区域通常是代码需要重构的标志
编码器
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.