陈述应该采用内部方法还是外部方法?


17

这些设计中哪个更好?各自的优缺点是什么?您会使用哪一个?任何其他关于如何处理类似方法的建议都值得赞赏。

可以合理地假设Draw()是调用其他绘制方法的唯一位置。这需要扩展到更多的Draw *方法和Show *属性,而不仅仅是此处显示的三个。

public void Draw()
{
    if (ShowAxis)
    {
        DrawAxis();
    }

    if (ShowLegend)
    {
        DrawLegend();
    }

    if (ShowPoints && Points.Count > 0)
    {
        DrawPoints();
    }
}

private void DrawAxis()
{
    // Draw things.
}

private void DrawLegend()
{
    // Draw things.
}

private void DrawPoints()
{
    // Draw things.
}

要么

public void Draw()
{
    DrawAxis();
    DrawLegend();
    DrawPoints();
}

private void DrawAxis()
{
    if (!ShowAxis)
    {
        return;
    }

    // Draw things.
}

private void DrawLegend()
{
    if (!ShowLegend)
    {
        return;
    }

    // Draw things.
}

private void DrawPoints()
{
    if (!ShowPoints ||  Points.Count <= 0))
    {
        return;
    }

    // Draw things.
}

欲了解更多的参考,我问这对SO - stackoverflow.com/questions/2966216/...
Tesserex

1
每当我看到诸如“这需要扩展到更多……”之类的短语时,我都会立即想到“这应该是一个循环”。
Paul Butcher

Answers:


28

我认为您无法针对此类情况制定总括规则,这取决于具体情况。

在这种情况下,我建议在方法外使用if子句,因为Draw方法的名称暗示它们只是在没有任何特殊条件的情况下绘制东西。

如果发现必须在许多地方调用方法之前进行检查,则可能需要将检查放入方法中并重命名它们以澄清这种情况


7

我说第二个。

方法应具有相同的抽象级别。

在第二个示例中,该Draw()方法的职责是像控制器一样操作,调用每个单独的绘制方法。此Draw()方法中的所有代码都处于相同的抽象级别。该准则在重用场景中起作用。例如,如果您想DrawPoints()在另一个公共方法中重用该方法(我们称它为Sketch()),则无需重复guard子句(if语句决定是否绘制点)。

但是,在第一个示例中,该Draw()方法负责确定是否调用各个方法,然后再调用这些方法。Draw()有一些有效的低级方法,但是它会将其他低级的工作委托给其他方法,因此Draw()具有处于不同抽象级别的代码。为了重复使用DrawPoints()Sketch(),您将需要复制保护条款中Sketch()也是如此。

我强烈建议在Robert C. Martin的书“ Clean Code”中讨论这个想法。


1
好东西。为了解决另一个答案中的观点,我建议更改DrawAxis()et。的名称。等 表示他们的执行是有条件的,也许TryDrawAxis()。否则,您需要从Draw()方法导航到每个单独的子方法以确认其行为。
乔什·伯尔

6

我对这个问题的看法颇具争议,但请忍受,因为我相信几乎每个人都对最终结果表示同意。我只是有另一种到达那里的方法。

在我的文章Function Hell中,我解释了为什么我不喜欢为创建更小的方法而拆分方法。我只在知道的情况下拆分它们,否则它们将被重用,或者,当然,当我可以重用它们时。

OP指出:

可以合理地假设Draw()是调用其他绘制方法的唯一位置。

这导致我提到了一个(中级)第三个选项。我创建了“代码块”或“代码段”,其他人将为其创建函数。

public void Draw()
{
    // Draw axis.
    if (ShowAxis)
    {
        // Drawing code ...
    }

    // Draw legend.
    if (ShowLegend)
    {
        // Drawing code ...
    }

    // Draw points.
    if (ShowPoints && Points.Count > 0)
    {
        // Drawing code ...
    }
}

OP还指出:

这需要扩展到更多的Draw *方法和Show *属性,而不仅仅是此处显示的三个。

...因此该方法将迅速变得非常大。几乎所有人都同意这会降低可读性。在我看来,正确的解决方案不仅是分成几种方法,而且还要将代码分成可重用的类。我的解决方案可能看起来像这样。

private void Initialize()
{               
    // Create axis.
    Axe xAxe = new Axe();
    Axe yAxe = new Axe();

    _drawObjects = new List<Drawable>
    {
        xAxe,
        yAxe,
        new Legend(),
        ...
    }
}

public void Draw()
{
    foreach ( Drawable d in _drawObjects )
    {
        d.Draw();
    }
}

当然,论点仍然需要传递等等。


尽管我不同意“功能地狱”,但您的解决方案是(IMHO)正确的解决方案,而该解决方案是正确使用Clean Code&SRP产生的。
Paul Butcher

4

对于要检查控制是否绘制的字段的情况,将其包含在内部是很有意义的Draw()。当该决定变得更加复杂时,我倾向于选择后者(或将其拆分)。如果最终需要更大的灵活性,则可以随时对其进行扩展。

private void DrawPoints()
{
    if (ShouldDrawPoints())
    {
        DoDrawPoints();
    }
}

protected void ShouldDrawPoints()
{
    return ShowPoints && Points.Count > 0;
}

protected void DoDrawPoints()
{
    // Draw things.
}

注意,其他方法受到保护,允许子类扩展其所需的内容。这也使您可以强制进行绘图以进行测试或任何其他可能的原因。


很好:重复的所有内容都使用自己的方法。现在,您甚至可以添加一种DrawPointsIfItShould()捷径if(should){do}:) 的方法
Konerak 2011年

4

在这两种选择中,我更喜欢第一个版本。我的理由是我希望一种方法能够执行其名称所暗示的功能,而没有任何隐藏的依赖关系。DrawLegend应该画一个传说,不是也许画一个传奇。

不过,史蒂文·杰里斯(Steven Jeuris)的回答比问题中的两个版本要好。


3

Show___我可能没有定义每个部分的单独属性,而是定义了一个位字段。这样可以将其简化为:

[Flags]
public enum DrawParts
{
    Axis = 1,
    Legend = 2,
    Points = 4,
    // More...
}

public class MyClass
{
    public void Draw() {
        if (VisibleParts.HasFlag(DrawPart.Axis))   DrawAxis();
        if (VisibleParts.HasFlag(DrawPart.Legend)) DrawLegend();
        if (VisibleParts.HasFlag(DrawPart.Points)) DrawPoints();
        // More...
    }

    public DrawParts VisibleParts { get; set; }
}

除此之外,我倾向于检查外部方法而不是内部方法的可见性。毕竟DrawLegend不是,而是DrawLegendIfShown。但这取决于课程的其余部分。如果还有其他地方调用该DrawLegend方法,并且它们也需要检查ShowLegend,那么我可能只是将检查移到DrawLegend


2

我会选择第一个选项-方法外使用“ if”。它更好地描述了要遵循的逻辑,此外,还为您提供了实际绘制轴的选项,例如,在无论设置如何都想要绘制轴的情况下。另外,它消除了额外的函数调用的开销(假设其未内联),如果您追求速度的话,该函数会累积(您的示例看起来可能只是在绘制图形,而它可能不是一个因素,而是在动画中)或游戏)。


1

总的来说,我更喜欢让较低级别的代码假设前提条件得到满足,并开始做他们应该做的任何工作,并在调用堆栈中进行更高级别的检查。这样做的好处是不进行冗余检查可以节省周期,但这也意味着您可以编写类似以下代码:

int do_something()
{
    sometype* x;
    if(!(x = sometype_new())) return -1;
    foo(x);
    bar(x);
    baz(x);
    return 0;
}

代替:

int do_something()
{
    sometype x* = sometype_new();
    if(ERROR == foo(x)) return -1;
    if(ERROR == bar(x)) return -1;
    if(ERROR == baz(x)) return -1;
    return 0;
}

当然,如果您强制执行一次退出,拥有需要释放的内存等,这甚至会变得更糟。


0

这完全取决于上下文:

void someThing(var1, var2)
{
    // If input fails validation then return quickly.
    if (!isValid(var1) || !isValid(var2))
    {   return;
    }


    // Otherwise do what is logical and makes the code easy to read.
    if (doTaskConditionOK())
    {   doTask();
    }

    // Return early if it is logical
    // This is OK in C++ but not C like languages.
    // You have to be careful of cleanup in C like languages while RIAA will do
    // that auto-magically in C++. 
    if (allFinished)
    {   return;
    }

    doAlternativeIfNotFinished();

    // -- Alternative to the above for C
    if (!allFinished)
    {   
        doAlternativeIfNotFinished();
    }

} 
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.