중복 코드를 제거하기위한 복잡성 추가


24

일반 기본 클래스에서 모두 상속되는 여러 클래스가 있습니다. 기본 클래스에는 여러 유형의 객체 모음이 포함되어 있습니다 T.

각 자식 클래스는 개체 컬렉션에서 보간 된 값을 계산할 수 있어야하지만 자식 클래스는 다른 유형을 사용하므로 계산은 클래스마다 조금씩 다릅니다.

지금까지 코드를 클래스에서 클래스로 복사 / 붙여 넣기하고 각각을 약간 수정했습니다. 그러나 이제 중복 된 코드를 제거하고 기본 클래스에서 하나의 일반 보간 방법으로 대체하려고합니다. 그러나 그것은 매우 어렵다는 것을 입증하고 있으며, 내가 생각한 모든 솔루션은 너무 복잡해 보입니다.

나는 DRY 원칙이 이런 상황에 많이 적용되지는 않지만 신성 모독처럼 들리 리라고 생각하기 시작했다. 코드 중복을 제거하려고 할 때 얼마나 복잡합니까?

편집하다:

내가 생각해 낼 수있는 가장 좋은 해결책은 다음과 같습니다.

기본 클래스 :

protected T GetInterpolated(int frame)
{
    var index = SortedFrames.BinarySearch(frame);
    if (index >= 0)
        return Data[index];

    index = ~index;

    if (index == 0)
        return Data[index];
    if (index >= Data.Count)
        return Data[Data.Count - 1];

    return GetInterpolatedItem(frame, Data[index - 1], Data[index]);
}

protected abstract T GetInterpolatedItem(int frame, T lower, T upper);

어린이 클래스 A :

public IGpsCoordinate GetInterpolatedCoord(int frame)
{
    ReadData();
    return GetInterpolated(frame);
}

protected override IGpsCoordinate GetInterpolatedItem(int frame, IGpsCoordinate lower, IGpsCoordinate upper)
{
    double ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);

    var x = GetInterpolatedValue(lower.X, upper.X, ratio);
    var y = GetInterpolatedValue(lower.Y, upper.Y, ratio);
    var z = GetInterpolatedValue(lower.Z, upper.Z, ratio);

    return new GpsCoordinate(frame, x, y, z);
}

어린이 클래스 B :

public double GetMph(int frame)
{
    ReadData();
    return GetInterpolated(frame).MilesPerHour;
}

protected override ISpeed GetInterpolatedItem(int frame, ISpeed lower, ISpeed upper)
{
    var ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);
    var mph = GetInterpolatedValue(lower.MilesPerHour, upper.MilesPerHour, ratio);
    return new Speed(frame, mph);
}

9
DRY 및 코드 재사용과 같은 개념을 지나치게 미세하게 적용하면 훨씬 더 큰 죄가 생깁니다.
Affe

1
좋은 일반적인 답변을 얻고 있습니다. 예제 함수를 포함하도록 편집하면이 특정 인스턴스에서 함수를 너무 많이 사용하고 있는지 판단하는 데 도움이 될 수 있습니다.
Karl Bielefeldt

이것은 실제로 해답이 아니며 관찰에 대한 더 많은 것입니다. 팩토 아웃 된 기본 클래스 무엇을하는지 쉽게 설명 할 수 없다면 , 그것을 가지고 있지 않는 것이 가장 좋습니다. 그것을 보는 또 다른 방법은 (나는 당신이 SOLID에 익숙하다고 생각합니까?) '이 기능의 소비자가 Liskov 대체를 요구할 가능성이 있습니까?'입니다. 보간 기능의 일반 소비자에 대한 비즈니스 사례가 없을 경우 기본 클래스는 가치가 없습니다.
Tom W

1
우선 삼중 항 X, Y, Z를 위치 유형으로 수집하고 그 유형에 보간을 멤버 또는 정적 방법으로 추가하는 것입니다. 위치 보간 (기타 위치, 비율).
kevin cline

답변:


30

어떤 방식으로, 당신은 마지막 단락에서 그 말로 자신의 질문에 대답했습니다.

나는 DRY 원칙이 이런 상황에서는 많이 적용되지 않는다고 생각하기 시작하지만 그것은 신성 모독 처럼 들린다 .

문제를 해결하는 데 실제로 실용적이지 않은 연습을 찾을 때마다 그 연습을 종교적으로 사용하지 마십시오 (단어 신성 모독 은 이에 대한 경고입니다). 대부분의 사례는이 whens이유들을 그들은 가능한 모든 사례의 99 %를 커버하는 경우에도, 당신은 다른 접근을해야 할 수도 1 % 것이 여전히있다.

특히, DRY 와 관련하여 , 때로는 볼 때 몸이 아프게 만드는 하나의 거대한 괴물보다 여러 개의 복제되었지만 간단한 코드를 갖는 것이 실제로 더 낫다는 것을 알았 습니다.

즉, 이러한 우연한 사례가 존재하면 부주의 한 복사 및 붙여 넣기 코딩에 대한 변명이나 재사용 가능한 모듈의 완전한 부족으로 사용되어서는 안됩니다. 간단히 말해, 어떤 언어에서 어떤 문제에 대해 일반적인 코드와 읽을 수있는 코드를 작성하는 방법을 모른다면 중복성을 갖는 것이 나쁘지 않을 것입니다. 코드를 유지해야하는 사람을 생각하십시오. 이중화 또는 난독 화로 더 쉽게 살 수 있습니까?

특정 예에 대한보다 구체적인 조언. 이러한 계산은 비슷 하지만 약간 다릅니다 . 계산 공식을 더 작은 하위 공식으로 나눈 다음 약간 다른 계산에서이 도우미 함수를 호출하여 하위 계산을 수행 할 수 있습니다. 모든 계산이 지나치게 많이 생성 된 코드에 의존하고 일부 재사용 수준이 유지되는 상황을 피할 수 있습니다.


10
유사하지만 약간 다른 점은 코드에서 비슷해 보이지만 "비즈니스"에서 유사해야한다는 의미는 아닙니다. 물론 그것이 무엇인지에 따라 다르지만, 똑같아 보이더라도 서로 다른 비즈니스 의사 결정 / 요구 사항을 기반으로 할 수 있기 때문에 사물을 분리하는 것이 좋습니다. 따라서 현명한 코드가 비슷해 보일지라도 크게 다른 계산으로 볼 수 있습니다. (규칙이나 어떤 것이 아니라, 사물을 결합하거나 리팩터링해야
할지를

@Svish 흥미로운 점. 그런 식으로 생각하지 마십시오.
Phil

17

자식 클래스는 다른 유형을 사용하므로 계산은 클래스마다 조금씩 다릅니다.

가장 먼저해야 할 일은 다음과 같습니다. 먼저 클래스간에 변경되는 부분과 변경되지 않는 부분을 명확하게 식별합니다. 이를 확인하면 문제가 해결됩니다.

리팩토링을 시작하기 전에 첫 번째 연습으로 수행하십시오. 그 후 다른 모든 것이 자동으로 제자리에 놓입니다.


2
잘 넣어 반복되는 기능이 너무 큰 문제 일 수 있습니다.
Karl Bielefeldt

8

나는 두 줄 이상의 코드를 거의 모든 반복이 어떤 식 으로든 인수 분해 될 수 있다고 생각하며 거의 항상해야합니다.

그러나 일부 언어에서는이 리팩토링이 다른 언어보다 쉽습니다. LISP, Ruby, Python, Groovy, Javascript, Lua 등과 같은 언어에서는 매우 쉽습니다. 일반적으로 템플릿을 사용하는 C ++에서는 그리 어렵지 않습니다. 유일한 툴은 전 처리기 매크로 일 수있는 C에서 더욱 고통 스럽다. Java에서 종종 고통스럽고 때로는 불가능합니다. 예를 들어 여러 내장 숫자 유형을 처리하기 위해 일반 코드를 작성하려고합니다.

좀 더 표현적인 언어에서는 의문의 여지가 없습니다. 두 줄 이상의 코드를 리팩터링하십시오. 표현력이 낮은 언어를 사용하면 리팩토링의 고통과 반복되는 코드의 길이 및 안정성의 균형을 유지해야합니다. 반복되는 코드가 길거나 자주 변경되기 쉬운 경우 결과 코드를 읽기가 어려운 경우에도 리팩터링하는 경향이 있습니다.

짧고 안정적이며 리팩토링이 너무 추한 경우에만 반복 코드를 수락합니다. 기본적으로 Java를 작성하지 않는 한 거의 모든 복제를 제외시킵니다.

코드를 게시하지 않았거나 사용중인 언어를 표시 했으므로 사례에 대한 특정 권장 사항을 제시하는 것은 불가능합니다.


루아는 약어가 아닙니다.
DeadMG

@DeadMG : 주목; 자유롭게 편집하십시오. 그래서 우리는 모든 명성을 얻었습니다.
케빈 클라인

5

기본 클래스가 알고리즘을 수행해야하지만 알고리즘이 각 하위 클래스마다 다르면 The Template Pattern에 대한 완벽한 후보처럼 들립니다 .

이를 통해 기본 클래스는 알고리즘을 수행하고 각 하위 클래스의 변형이 발생하면 구현할 하위 클래스의 책임 인 추상 메서드를 사용합니다. ASP.NET 페이지는 예를 들어 Page_Load를 구현하기 위해 코드를 연기합니다.


4

각 클래스에서 "하나의 일반 보간 방법"과 같은 소리가 너무 많이 발생하므로 더 작은 방법으로 만들어야합니다.

계산이 얼마나 복잡한 지에 따라 계산의 각 "조각"이 가상 방법이 될 수없는 이유

Public Class Fraction
{
     public virtual Decimal GetNumerator(params?)
     public virtual Decimal GetDenominator(params?)
     //Some concrete method to actually compute GetNumerator / GetDenominator
}

계산 논리에서 "약간 변동"을 만들어야하는 경우 개별 부분을 재정의하십시오.

(이것은 작은 방법을 재정의하면서 많은 기능을 추가하는 방법에 대한 매우 작고 쓸모없는 예입니다)


3

내 의견으로는, 당신은 어떤 의미에서 DRY가 너무 멀리 걸릴 수 있다는 것이 맞습니다. 두 개의 유사한 코드가 매우 다른 방향으로 발전 할 가능성이 높으면 처음에 반복하지 않으면 서 문제를 일으킬 수 있습니다.

그러나, 당신은 또한 그러한 신성 모독에주의해야합니다. 당신이 그것을 떠나기로 결정하기 전에 당신의 선택권을 생각하기 위해 매우 열심히 노력하십시오.

예를 들어 반복되는 코드를 기본 클래스가 아닌 유틸리티 클래스 / 메소드에 넣는 것이 더 좋을까요? 상속보다 컴포지션 선호를 참조하십시오 .


2

DRY는 따라야 할 지침이며 깨지지 않는 규칙이 아닙니다. 어느 시점에서,이 코드에는 반복이 없다고 말하는 데 사용하는 모든 클래스에서 X 레벨의 상속 및 Y 템플릿을 가질 가치가 없다고 결정해야합니다. 몇 가지 좋은 질문은 이러한 유사한 방법을 추출하고 하나의 방법으로 구현하는 데 시간이 더 걸리는 것입니다. 그러면 변경해야 할 경우 또는 변경이 발생할 가능성이있는 경우 모든 방법을 검색해야합니다. 처음에 이러한 방법을 추출하는 작업을 취소 하시겠습니까? 이 코드가 어디에서 무엇을하는지 이해하기 위해 추가 추상화가 시작되는 시점에 있습니까?

이러한 질문 중 하나에 예라고 대답 할 수 있다면 잠재적으로 중복 된 코드를 남겨 두는 강력한 사례가 있습니다


0

"왜 리팩터링해야합니까?"라는 질문을 스스로에게해야합니까? 하나의 알고리즘을 변경하는 경우 "유사하지만 다른"코드가있는 경우 다른 지점에서도 해당 변경 사항을 반영해야합니다. 이것은 일반적으로 재난에 대한 레시피이며, 다른 사람은 항상 한 지점을 놓치고 다른 버그를 소개합니다.

이 경우 알고리즘을 하나의 거대한 알고리즘으로 리팩토링하면 알고리즘이 너무 복잡해져 향후 유지 관리가 어려워집니다. 따라서 일반적인 것들을 현명하게 고려할 수 없다면 간단합니다.

// this code is similar to class x function b

댓글로 충분합니다. 문제 해결됨.


0

겹치는 기능을 가진 하나의 더 큰 방법 또는 두 개의 더 작은 방법을 갖는 것이 더 나은지 여부를 결정할 때, 첫 번째 $ 50,000 질문은 동작의 겹치는 부분이 변경 될 수 있는지 여부와 변경 사항이 더 작은 방법에 동일하게 적용되어야하는지 여부입니다. 첫 번째 질문에 대한 대답은 '예'이지만 두 번째 질문에 대한 대답은 '아니오' 이면 방법은 별도로 유지해야합니다 . 두 질문 모두에 대한 대답이 예라면, 모든 버전의 코드가 동기화 상태를 유지하도록 무언가를 수행해야합니다. 대부분의 경우 가장 쉬운 방법은 하나의 버전 만있는 것입니다.

Microsoft가 DRY 원칙을 위반 한 것으로 보이는 곳이 몇 군데 있습니다. 예를 들어, Microsoft는 실패가 예외를 발생시켜야하는지 여부를 나타내는 매개 변수를 허용하는 메서드를 갖는 것을 명시 적으로 권장하지 않았습니다. "failures throw exceptions"매개 변수가 메소드의 "일반 사용법"API에서 추악한 것은 사실이지만 이러한 매개 변수는 Try / Do 메소드를 다른 Try / Do 메소드로 구성해야하는 경우에 매우 유용합니다. 실패가 발생했을 때 외부 메소드가 예외를 처리해야하는 경우 실패한 내부 메소드 호출은 외부 메소드가 전파 할 수있는 예외를 처리해야합니다. 외부 메소드가 예외를 발생시키지 않으면 내부 메소드도 예외가 아닙니다. try / do를 구별하기 위해 매개 변수를 사용하는 경우, 외부 메소드는이를 내부 메소드로 전달할 수 있습니다. 그렇지 않으면 외부 메소드가 "try"로 작동해야 할 경우 "try"메소드를 호출하고 "do"로 작동해야하는 "do"메소드를 호출해야합니다.

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