내부 또는 외부 방법으로 진술해야합니까?


17

이 중 어느 것이 더 낫습니까? 각각의 장단점은 무엇입니까? 어느 것을 사용 하시겠습니까? 같은 방법을 다루는 방법에 대한 다른 제안은 높이 평가됩니다.

Draw ()가 다른 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

답변:


28

나는 당신이 이런 종류의 담요 규칙을 가질 수 있다고 생각하지 않습니다. 그것은 상황에 달려 있습니다.

이 경우 Draw 메소드의 이름은 특별한 조건없이 물건을 그리는 것을 암시하기 때문에 메소드 외부에 if 절을 사용하는 것이 좋습니다.

여러 곳에서 메소드를 호출하기 전에 점검해야하는 것을 발견 한 경우, 메소드 내부에 점검을 넣고 이름을 바꾸어 이것이 일어나고 있음을 명확히 할 수 있습니다.


7

나는 두 번째를 말한다.

메소드는 동일한 추상화 레벨을 가져야합니다.

두 번째 예에서, Draw()메소드 의 책임은 각각의 개별 드로잉 메소드를 호출하는 컨트롤러처럼 작동하는 것입니다. 이 Draw()메소드 의 모든 코드 는 동일한 추상화 레벨에 있습니다. 이 지침은 재사용 시나리오에서 사용됩니다. 예를 들어, DrawPoints()다른 공용 메소드에서 메소드 를 재사용하려는 유혹을 가졌다면 (그것을 호출하자 Sketch()) 가드 절 (점을 그릴 지 여부를 결정하는 if 문)을 반복 할 필요가 없습니다.

그러나 첫 번째 예에서이 Draw()메소드는 각 개별 메소드를 호출할지 여부를 결정한 다음 해당 메소드를 호출합니다. Draw()작동하는 저급 방법이 있지만 다른 저수준 작업을 다른 방법으로 위임하므로 Draw()다른 추상화 수준의 코드가 있습니다. 다시 사용하기 위해 DrawPoints()Sketch(), 당신의 가드 조항을 복제해야합니다 Sketch()뿐만 아니라.

이 아이디어는 Robert C. Martin의 저서 인 "Clean Code"에서 논의 할 것입니다.


1
좋은 물건. 다른 답변에서 한 요점을 다루기 위해 DrawAxis()et. 알. 실행이 조건부임을 나타냅니다 TryDrawAxis(). 그렇지 않으면 Draw()메소드에서 각 개별 하위 메소드 로 이동 하여 동작을 확인해야합니다.
Josh Earl

6

이 주제에 대한 나의 의견은 논란의 여지가 있지만, 거의 모든 사람이 최종 결과에 동의한다고 믿기 때문에 나와 함께 견뎌야합니다. 나는 거기에 도착하는 다른 접근법을 가지고 있습니다.

내 기사에서 함수 지옥 에서 작은 메소드를 만들기 위해 메소드를 나누는 것을 좋아하지 않는 이유를 설명합니다. 내가 아는 경우에만 나누면 재사용 할 수있을 때 재사용되거나 물론 재사용됩니다.

OP는 다음과 같이 말했습니다.

Draw ()가 다른 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();
    }
}

물론 논쟁은 여전히 ​​통과되어야 할 것입니다.


Function hell에 대해서는 동의하지 않지만 솔루션은 (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

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

} 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.