重构内部循环,在级别之间存在大量依赖关系

时间:2015-02-12 11:57:13

标签: java c# for-loop nested convention

我有以下preudo-code

using (some web service/disposable object)
{
    list1 = service.get1();

    list2 = service.get2();

    for (item2 in list2)
    {
        list3 = service.get3(depending on item2);

        for (item3 in list3)
        {
            list4 = service.get4(depending on item3 and list1);

            for (item4 in list4)
            {
                ...
            }
        }
    }
}

整个代码所在的位置,让我们说for语句中包含大量逻辑的500行。问题是将其重构为可读和可维护的代码,并作为类似情况的最佳实践。以下是我到目前为止找到的可能的解决方案。

1:拆分成方法 考虑到我们将每个for提取到自己的方法中,为了提高可读性,我们最终得到了method2,method3和method4。每个方法都有自己的参数和依赖关系,除了method4之外,这很好。 Method4依赖于list1,这意味着list1也必须传递给方法2和3。从我的观点来看,这变得难以理解。任何看着method2的开发人员都会意识到list1里面没有任何意义,所以他必须向下看,直到method4实际实现依赖 - >效率低下。那么,如果list4或item4发生变化并且不再需要依赖于list1会发生什么?我必须删除method4的list1参数(应该自然地完成),但也需要删除方法2和3(超出更改范围) - >再次效率低下。另一个副作用是,在许多依赖关系和多个级别的情况下,传递的参数数量将迅速增加。想想如果list4还依赖于list11,list12和list13,会发生在list1级别创建的情况。

2:保持一个单一的长方法 优点是每个列表和项目都可以访问每个父列表和项目,这使得进一步的更改成为一个单行。如果list4不再依赖于list1,只需删除/替换代码,而不更改其他任何内容。显然,问题是该方法是几百行,我们都知道它不好。

第3。两全其美? 我们的想法是只将方法分成每个for的内部逻辑部分。这样,主要方法将减少,我们获得可读性和可能的​​可维护性。将for保留在main方法中,我们仍然可以将每个依赖项作为一行进行访问。我们最终会得到这样的结论:

for (item2 in list2)
{
    compute();

    list3 = service.get3(depending on item2);

    for (item3 in list3)
    {
        compute(item2, eventually item1)

        list4 = service.get4(depending on item3 and list1);

        for (item4 in list4)
        {
            ...
        }
    }
}

但还有另一个问题。为了便于阅读,您如何命名这些compute方法?让我们说我有一个局部变量instanceA或类型ClassA,我从item2填充。 A类包含一个名为LastItem4的属性,它需要按创建日期或其他顺序保留,比如说最后一个item4。如果我使用compute来创建instanceA,那么这个计算只是部分计算。因为需要在item4级别上填充LastItem4属性,所以在不同的计算方法中。那么如何调用这两种计算方法来表明我实际在做什么呢?在我看来,答案很难。

4。 #地区 保留一个长方法但使用区域。这是一些在某些编程语言中严格使用的功能,在我看来像是一个补丁,而不是最佳实践,但也许它只是我。

我想知道你将如何进行这种重构?请注意,它可能比这更复杂。事实上这是一项服务有点误导,我的想法是我不喜欢使用一次性对象作为方法参数,因为你不知道意图而不阅读实际的方法代码。但我在评论中期待这一点,因为其他人可能会感到不同。

为了举例,我们假设该服务是第三方服务,但无法改变其工作方式。

4 个答案:

答案 0 :(得分:1)

您似乎正在寻找一般性答案而非具体答案。一般答案的关键是分离责任,努力实现单一责任原则

让我先说明你的方法 toooooooo很多东西。理想情况下这一类很多东西也不应该这样做。它应该在一组课程的帮助下完成。

Robert C.Martin曾经说过“课程倾向于隐藏在方法中”,这就是这里的情况。如果你的方法很大,你可以将它重构为较小的方法,但如果整个方法需要一个局部变量,那么该方法可能应该放在它自己的类中。

如果你需要一些字段可以一起访问,它们必须一起生活(我的意思是同一类型)。所以你的List1,List2,List3,List4都应该存在于一个类中,而不是作为方法参数。

鉴于您无法修改Service类(假设它是第三方API)。没关系,你可以随时创建自己的类来包装前面提到的服务。

所以你基本上是一个类似下面的类:

public class ServiceWrapper
{
    private IList<string> list1;
    private IList<string> list2;
    private IList<string> list3;
    private IList<string> list4;

    private readonly Service1 service;
    public ServiceWrapper(Service1 service)
    {
        this.service = service;
    }

    public SomeClass GetSomething()
    {
        list1 = service.Get1();
        list2 = service.Get2();

        foreach (var item2 in list2)
        {
            PopulateMore(item2);
        }

        return result;//Return some computed result
    }

    private void PopulateMore(string item2)
    {
        list3 = service.Get3(item2);

        foreach (var item3 in list3)
        {
            PopulateEvenMore(item3);
        }
    }

    private void PopulateEvenMore(string item3)
    {
        list4 = service.Get4(item3);

        foreach (var item4 in list4)
        {
            //And so on
        }
    }
}

鉴于您的服务看起来像这样

public class Service1 : IDisposable
{
    public void Dispose()
    {
        throw new NotImplementedException();
    }

    public IList<string> Get1()
    {
        throw new NotImplementedException();
    }

    public IList<string> Get2()
    {
        throw new NotImplementedException();
    }

    public IList<string> Get3(string item2)
    {
        throw new NotImplementedException();
    }

    public IList<string> Get4(string item3)
    {
        throw new NotImplementedException();
    }
}

所以你现在很长的方法变得非常小。

public void YourVeryLongMethodBecomesVerySmall()
{
    using (Service1 service = new Service1())
    {
        SomeClass result = new ServiceWrapper(service).GetSomething();
    }
}

正如您在评论中所说,如果您在这些方法中也有其他责任,则需要将这些责任分开并将其移至自己的类中。例如,如果你想解析一些东西,那应该进入一些Parser类,而ServiceWrapper应该使用解析器来完成工作(理想情况下是一些抽象和松耦合)。

你应该尽可能多地提取方法和类,这样就不再有意义了。

答案 1 :(得分:1)

简短回答,这取决于您的问题域以及您可以在有意义的动词中拆分您的方法的程度,但通常选项3是根据我的经验相应的最佳选择。

关于#region,请勿使用它。它最适用于将生成的代码与代码分开。使用region to&#34;结构&#34;你的代码就像把垃圾放在地毯下,它仍然存在,你可以感受到它。

还有一点需要注意的是,可能您正面临与收藏相关的问题,而像Guava(google-collections)这样的好图书馆会对您有所帮助。使用Java 8 lambda功能或.Net LINQ也可以帮助您重写代码并使其更具表现力。

答案 2 :(得分:1)

我最终使用问题中描述的解决方案3,与DTO结合使用。这不一定是最好的选择,但它在术语或可读性,灵活性和扩展方面优于其他选择。

答案 3 :(得分:0)

不要使用区域。如果你打印,如果你使用CodeMap等,它们就无济于事。

制作list1,list2,..私有类变量。

为每个for()循环创建一个方法:

private void method3()
{
    for (item3 in list3)
    {
        list4 = service.get4(depending on item3 and list1);
        method4();
    }
}

对每种方法都提出一些好评......

这样可以为每个methodX()

提供良好的单元测试