統計が内側の方法か外側の方法か


17

これらのデザインのどれが優れていますか?それぞれの長所と短所は何ですか?どちらを使用しますか?のようなメソッドを処理する方法に関する他の提案を歓迎します。

Draw()が他の描画メソッドが呼び出される唯一の場所であると仮定するのは合理的です。これは、ここに示した3つだけでなく、さらに多くの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
「これはもっともっと拡張する必要があります...」のようなフレーズを見るたびに、私はすぐに「これはループであるべきだ」と思います。
ポール・ブッチャー

回答:


28

私はあなたがこの種のことに対して包括的なルールを持つことができるとは思わない、それは状況に依存する。

この場合、Drawメソッドの名前は、特別な条件なしで物事を描画することを暗示しているため、メソッドの外側にif句を含めることをお勧めします。

多くの場所でメソッドを呼び出す前にチェックを行う必要があることがわかった場合は、メソッド内にチェックを入れて名前を変更して、これが起こっていることを明確にすることができます


7

二番目と言います。

メソッドは同じレベルの抽象化を持つ必要があります。

2番目の例では、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()が他の描画メソッドが呼び出される唯一の場所であると仮定するのは合理的です。

これにより、言及されていない(中間の)3番目のオプションが表示されます。他の人が関数を作成する「コードブロック」または「コードパラグラフ」を作成します。

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は次のよ​​うにも述べました。

これは、ここに示した3つだけでなく、さらに多くの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();
    }
}

もちろん、引数などを渡す必要があります。


私はFunction hellについてはあなたに同意しませんが、あなたの解決策は(IMHO)正しいものであり、Clean Code&SRPの適切な適用から生じるものです。
ポール・ブッチャー

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

4

これら2つの選択肢のうち、最初のバージョンを好みます。私の理由は、隠された依存関係なしに、名前が示すとおりにメソッドを実行したいからです。DrawLegendはない、凡例を描画する必要があり、おそらく伝説を描きます。

ただし、Steven Jeurisの回答は、質問の2つのバージョンよりも優れています。


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();
    }

} 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.