长方法重构:保持原样vs分离方法vs使用局部函数


9

假设我有这样长的方法:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

此方法没有任何重复的部分,应将其移至单独的方法或局部函数。

有很多人(包括我在内)认为长方法就是代码的味道。我也不喜欢#region在这里使用(s)的想法,并且有一个非常受欢迎的答案来解释为什么这很糟糕


但是如果我将此代码分成方法

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

我看到以下问题:

  • 用一个方法内部使用的定义来污染类定义范围,这意味着我应该在某个地方进行文档记录,Task1并且Task2只供内部使用SomeLongMethod(否则,每个阅读我的代码的人都必须推断出这个想法)。

  • 污染将仅在单个SomeLongMethod方法中使用一次的方法的IDE自动完成功能(例如Intellisense)。


因此,如果我将此方法代码分成本地函数

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

那么这没有单独方法的弊端,但这看起来并不比原始方法更好(至少对我而言)。


哪个版本SomeLongMethod更易于维护和阅读,为什么?



@gnat我的问题是特定于c#的,而不是java的。我主要关心的是经典方法与局部函数的关系,而不仅仅是是否分离为方法。
Vadim Ovchinnikov

@gnat而且AFAIK Java(与C#不同)不支持嵌套函数。
Vadim Ovchinnikov

1
private关键字肯定可以保护您的“类定义范围”吗?
伊万

1
等等...你的课程有多大?
伊万

Answers:


13

自包含任务应移至自包含方法。没有足够大的缺点可以超越此目标。

您说它们污染了类的名称空间,但这并不是在考虑代码时必须要考虑的折衷。将来该课程的读者(例如您)很可能会问:“ 此特定方法会做什么,我该如何更改它以便它满足更改后的要求?” 比“ 所有方法的目的和意义是什么? ”因此,使每个方法更易于理解(通过减少元素数量)比使整个类更易于理解更为重要。 (通过减少方法)。

当然,如果任务不是真正独立的,而是需要一些状态变量来完成,那么方法对象,帮助对象或类似的构造可能是正确的选择。而且,如果任务是完全独立的并且根本不与应用程序域捆绑在一起(例如,仅是字符串格式),那么可以想象它们应该进入一个完全不同的(实用程序)类。但是,所有这些都不会改变“保持每个班级的方法最多”是至高无上的目标。


所以,您只使用方法,而不是局部函数,对吗?
Vadim Ovchinnikov

2
@VadimOvchinnikov使用现代代码折叠IDE几乎没有什么区别。局部函数的最大优点是它可以访问原本必须作为参数传递的变量,但是如果有很多变量,那么这些任务并不是真正独立的。
Kilian Foth,

6

我不喜欢任何一种方法。您没有提供任何更广泛的上下文,但是在大多数情况下看起来是这样的:

您有一个可以通过将多个服务的功能组合到单个结果中来完成某些工作的类。

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

您的每个服务仅实现逻辑的一部分。特殊的东西,只有这个服务可以做到。如果除了支持主API的方法以外,它还有其他私有方法,那么您可以轻松地对它们进行排序,而且它们也不应该太多。

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

底线是:如果您开始在代码中创建区域以使用方法,那么该开始考虑使用新服务封装其逻辑了。


2

在方法内创建函数的问题是它不会减少方法的长度。

如果您想要额外的保护级别“专用于方法”,则可以创建一个新类

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

但是,课堂上的课堂很难看:/


2

尽量减少诸如变量和方法之类的语言结构的范围是一种好习惯。例如,避免使用全局变量。这使代码更易于理解,并有助于避免滥用,因为可以在较少的地方访问该构造。

这表明使用局部功能。


1
嗯,肯定会重用代码==在许多调用者之间共享一种方法。即最大化其范围
Ewan

1

我同意长方法通常会产生代码异味,但是我不认为这是全部,而是这就是为什么长方法指示问题的原因。我的一般规则是,如果代码执行多个不同的任务,那么这些任务中的每一个都应该是自己的方法。对我而言,代码的这些部分是否重复并不重要。除非您真的绝对确定将来没有人愿意单独调用它们(这是很难确定的!)将它们放在另一种方法中(并将其设为私有-这应该是一个非常有力的指标)您不希望它在课程之外使用)。

我必须承认我还没有使用局部函数,因此我的理解还远远不够,但是您提供的链接建议它们通常会以与lambda表达式类似的方式使用(尽管这里有许多用例)它们可能比lambda更合适的地方,或者您不能使用lambda的地方,请参见与lambda表达式进行比较的局部函数)。然后,我认为这可以归结为“其他”任务的粒度。如果它们相当琐碎,那么本地函数可能还可以吗?另一方面,我看到了庞然大物lambda表达式的示例(可能是开发人员最近发现LINQ的示例),这些示例使得代码比从其他方法简单地调用时难以理解和可维护。

本地函数页指出:

'本地函数使您的代码意图清晰。任何阅读您的代码的人都可以看到,该方法只能由containing方法调用,而不能调用。对于团队项目,它们还使其他开发人员无法从类或结构的其他地方错误地直接调用该方法。

我不确定我是否真的会使用MS作为使用理由。你的方法(连同其名称和注释)的范围应当明确你的目的是什么,在那个时候。有了Local Function,我可以看到,其他开发人员可能认为它更加神圣不可侵犯,但如果将来发生更改,要求重新使用该功能,他们可能会编写自己的过程,并将Local Function保留在原处; 从而造成代码重复问题。如果您不信任团队成员在调用某个私有方法之前先了解它,因此以不恰当的方式调用它,那么上面引用中的后一句话可能是有意义的(因此您的决定可能会受到影响,尽管在这里接受教育是一个更好的选择) 。

总而言之,总的来说,我倾向于将方法分开,但是MS出于某种原因编写了Local Functions,所以我当然不会说不使用它们,

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.