불법 코드 복제


56

일반적인 본능은 코드에 표시되는 코드 중복을 제거하는 것입니다. 그러나 나는 복제가 환상적 인 상황에서 나 자신을 발견했다 .

상황을 더 자세히 설명하려면 웹 응용 프로그램을 개발 중이며 대부분의보기는 기본적으로 동일합니다. 사용자가 스크롤하고 선택할 수있는 항목 목록, 선택한 항목이 포함 된 두 번째 목록 및 "저장" "버튼을 눌러 새 목록을 저장하십시오.

문제가 쉬운 것 같았습니다. 그러나 각각의 모든보기에는 고유 한 단점이 있습니다. 때로는 무언가를 다시 계산해야하고 때로는 추가 데이터를 저장해야합니다. 이는 주 논리 코드에 콜백 후크를 삽입하여 해결했습니다.

있다 이렇게 많은 나는 기본적으로 모든 기능을 콜백을 제공 할 필요가 있기 때문에, 적게 유지 관리되고 뷰 사이의 분 차이, 그리고 주요 논리는 콜백 호출의 거대한 순서처럼 보이기 시작한다. 결국 모든보기에는 자체 콜백이 실행되는 자체 코드가 있기 때문에 시간이나 코드를 저장하지 않습니다.

문제는 다음과 같습니다.

  • 차이점은 아주 미세 하여 모든 뷰에서 코드가 거의 똑같이 보입니다 .
  • 거기에 너무 많은 당신이 코드에, 세부 사항을 보면 그 차이입니다 조금 비슷하지

이 상황을 어떻게 처리해야합니까?
콜백 콜로 구성된 핵심 로직이 좋은 솔루션입니까?
아니면 코드를 복제하고 콜백 기반 코드의 복잡성을 제거해야합니까?


38
나는 보통 처음에 복제를하는 것이 도움이된다고 생각합니다. 몇 가지 예를 살펴보면 일반적인 내용과 그렇지 않은 부분을 훨씬 쉽게 확인하고 공통 부분을 공유하는 방법을 생각해 냈습니다.
Winston Ewert

7
아주 좋은 질문-고려해야 할 한 가지는 코드의 물리적 복제뿐만 아니라 의미 론적 복제입니다. 코드의 한 부분에서 변경이 반드시 동일한 변경이 다른 부분에서 중복 될 것이라는 것을 의미한다면, 그 부분은 아마도 리팩토링 또는 추상화의 후보 일 것입니다. 때로는 실제로 자신을 잡는 지점까지 정규화 할 수 있으므로 중복을 의미 론적으로 구별하는 실제적인 의미를 고려할 것입니다.
Ant P

동일한 작업을 수행하는 코드 만 재사용 할 수 있습니다. 앱이 다른 화면에서 다른 작업을 수행하는 경우 다른 콜백이 필요합니다. 그것에 대한 경우, 또는, 또는 엉덩이가 없습니다.
corsiKa

13
이것에 대한 나의 개인적인 경험 규칙은 다음과 같습니다. 한곳에서 코드를 변경하면 다른 곳에서 똑같이 변경하지 않으면 버그 입니까? 그렇다면, 그것은 잘못된 종류의 복제입니다. 확실하지 않으면 지금 더 읽기 쉬운 것을 사용하십시오. 귀하의 예에서 행동의 차이는 의도적이며 버그로 간주되지 않으므로 일부 복제가 좋습니다.
Ixrec

Aspect Oriented Programming에 대해 읽을 수 있습니다.
벤 잭슨

답변:


53

궁극적으로 중복을 제거하기 위해 유사한 코드를 결합할지 여부를 판단 해야합니다 .

"반복하지 마십시오"와 같은 원칙을 항상 규칙대로 따라야하는 규칙으로 취하는 불행한 경향이있는 것 같습니다. 실제로 이러한 규칙은 일반적인 규칙이 아니라 훌륭한 디자인을 생각하고 개발하는 데 도움이되는 지침입니다.

인생의 모든 것과 마찬가지로, 혜택과 비용을 고려해야합니다. 얼마나 많은 중복 코드가 제거됩니까? 코드가 몇 번 반복됩니까? 보다 일반적인 디자인을 작성하는 데 얼마나 많은 노력이 필요합니까? 앞으로 코드를 얼마나 개발할 것입니까? 등등.

특정 코드를 모르면 명확하지 않습니다. 아마도 중복을 제거하는 더 우아한 방법이있을 것입니다 (LindaJeanne이 제안한 것과 같은). 또는 아마도 추상화를 보증하기에 충분한 반복이 충분하지 않을 수도 있습니다.

디자인에 대한주의가 충분하지 않은 것은 함정이지만 과도한 디자인에도주의하십시오.


"불행한 경향"에 대한 귀하의 의견과 맹목적으로 따르는 지침이 제 자리에 있다고 생각합니다.
Mael

1
@Mael 당신은 건가요 경우 나중에이 코드를 유지하지 것입니다, 당신이 바로 그 디자인을 얻을 수있는 좋은 이유가없는 것? (범죄 없음, 당신이 그것에 대해 어떻게 생각하는지 알고 싶어합니다)
발견

2
@Mael 물론 우리는 불행한 구절로 생각할 수 있습니다! : D 그러나, 나는 (내가 내 자신을 고려 코드를 작성할 때 우리는 다른 사람을 위해있는 우리가 우리 자신에게 엄격해야한다고 생각 I 2 주를 작성 후 내 자신의 코드를 읽을 때).
발견

2
@ user61852 그러면 The Codeless Code를 매우 싫어 합니다.
RubberDuck

1
@ user61852, 하하 -하지만이 경우 어떻게 않는 모든 의존 (정보 문제에 주어진되지 않음)? 과도한 확신보다 도움이 덜되는 것은 거의 없습니다.

43

DRY는 지식에 관한 것임을 기억하십시오 . 두 코드 조각이 비슷하거나 동일하거나 완전히 다르게 보이는 것은 중요하지 않습니다. 시스템 에 대한 동일한 지식 이 두 코드에서 모두 발견 될 수 있다면 문제가됩니다 .

지식은 사실이거나 ( "의도 된 값과의 최대 허용 편차는 0.1 %입니다") 프로세스의 일부 측면 일 수 있습니다 ( "이 대기열에는 세 개 이상의 항목이 포함되지 않습니다"). 기본적으로 소스 코드로 인코딩 된 단일 정보입니다.

따라서 어떤 것이 중복 제거되어야하는지 결정할 때는 지식의 중복인지 물어보십시오. 그렇지 않은 경우에는 부수적 인 중복 일 수 있으며 일반적인 위치로 추출하면 나중에 복제 된 부분이 다른 유사한 구성 요소를 만들 때 문제가 발생할 수 있습니다.


12
이! DRY의 초점은 중복 변경을 피하는 것 입니다.
Matthieu M.

이것은 매우 도움이됩니다.

1
DRY의 초점은 동일하게 동작 해야 하지만 그렇지 않은 코드의 두 비트가 없도록하는 것이라고 생각했습니다 . 코드 변경은 두 번 적용되어야하기 때문에 문제는 두 배가되지 않습니다. 실제 문제는 코드 변경을 두 번 적용해야하지만 그렇지 않은 경우입니다.
gnasher729

3
@ gnasher729 네, 그게 요점입니다. 두 개의 코드가 지식의 중복을 가지고 있다면 , 하나를 변경해야 할 때 다른 하나도 변경해야하기 때문에 설명하는 문제가 발생할 수 있습니다. 부수적 인 중복 이있는 경우 , 하나를 변경해야 할 때 다른 하나도 동일하게 유지해야 할 수도 있습니다. 이 경우 일반적인 방법 (또는 그 밖의 방법)을 추출한 경우 다른 문제를 해결해야합니다.
Ben Aaronson

1
또한 필수 중복실수로 중복 참조 루비에서 우발적 인 도플 갱어를 하고 난 건조 에드 내 코드를하고 지금은 함께 작동하기 어렵다. 어떻게 된 거예요? . 우연한 중복은 컨텍스트 경계의 양쪽에서도 발생합니다 . 요약 : 이러한 종속성을 동시에 수정 하기 위해 클라이언트에 적합한 경우에만 병합을 복제합니다 .
Eric

27

전략 패턴 사용을 고려 했습니까 ? 여러 뷰에서 호출하는 공통 코드 및 루틴을 포함하는 하나의 View 클래스가 있습니다. View 클래스의 자식에는 해당 인스턴스에 특정한 코드가 포함됩니다. 그들은 모두 View에 대해 만든 공통 인터페이스를 사용하므로 차이점이 캡슐화되고 일관됩니다.


5
아니요, 나는 그것을 고려하지 않았습니다. 제안 해 주셔서 감사합니다. 전략 패턴에 대한 빠른 읽기에서 그것은 내가 찾고있는 것 같습니다. 확실히 더 조사하겠습니다.
Mael

3
템플릿 메소드 패턴 . 당신은 또한 그것을 고려할 수 있습니다
Shakil

5

변화의 가능성은 무엇입니까? 예를 들어, 우리의 응용 프로그램에는 각 영역마다 4 가지 이상의 사용자 유형이있는 8 개의 서로 다른 비즈니스 영역이 있습니다. 보기는 사용자 유형과 영역에 따라 사용자 정의됩니다.

처음에는 여러 가지 사항을 표시 해야하는지 여부를 결정하기 위해 여기와 몇 가지 검사를 통해 동일한보기를 사용하여 수행되었습니다. 시간이 지남에 따라 일부 사업 분야는 크게 다른 일을하기로 결정했습니다. 결국 우리는 기본적으로 비즈니스 영역 당 기능별로 하나의보기 (ASP.NET MVC의 경우 부분보기)로 마이그레이션했습니다. 모든 비즈니스 영역에 동일한 기능이있는 것은 아니지만 다른 비즈니스 기능이 필요한 경우 해당 영역에 대한 고유 한 관점을 갖습니다. 코드 이해력과 테스트 가능성이 훨씬 덜 번거 롭습니다. 예를 들어, 한 영역을 변경해도 다른 영역에 원치 않는 변경이 발생하지 않습니다.

@ dan1111이 언급했듯이 판단 요청으로 이어질 수 있습니다. 시간이 지나면 작동하는지 확인할 수 있습니다.


2

한 가지 문제는 단일 수준의 기능에만 인터페이스 (언어 기능이 아닌 이론적 인터페이스)를 제공한다는 것입니다.

A(a,b,c) //a,b,c are your callbacks or other dependencies

얼마나 많은 제어가 필요한지에 따라 여러 레벨 대신 :

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

내가 이해하는 한, 구현 세부 정보 (기타 사항)를 숨기고 고급 인터페이스 (A) 만 노출하십시오.

구현 세부 사항을 숨기는 데는 장점이 있으며, 저수준 인터페이스를 직접 사용할 때 가능한 모든 단일 기능을 명시 적으로 추가하지 않는 한 제어가 제한되는 단점이 있습니다.

따라서 두 가지 옵션이 있습니다. 하위 레벨 인터페이스 만 사용하거나 상위 레벨 인터페이스가 유지 보수하기에 너무 많은 작업을 수행했거나 상위 및 하위 레벨 인터페이스를 모두 노출하므로 하위 레벨 인터페이스를 사용하십시오. 합리적인 옵션은 중복 코드를 피하고 싶다는 가정하에 높은 수준의 인터페이스와 낮은 수준의 인터페이스 (및 그 사이의 모든 것)를 제공하는 것입니다.

그런 다음 다른 것 중 하나를 작성할 때, 지금까지 작성한 모든 사용 가능한 기능을보고 (다양한 가능성, 재사용 할 수있는 기능을 결정하는 데 달려 있음)이를 함께 조각하십시오.

제어가 거의 필요없는 단일 개체를 사용하십시오.

약간의 이상이 필요할 때 가장 낮은 수준의 기능을 사용하십시오.

또한 흑백이 아닙니다. 어쩌면 큰 고급 클래스 CAN이 가능한 모든 사용 사례를 합리적으로 다룰 수 있습니다. 사용 사례가 너무 다양하여 가장 낮은 수준의 기본 기능만으로 충분할 수 있습니다. 균형을 찾기 위해 당신에게 달려 있습니다.


1

다른 유용한 답변이 이미 있습니다. 나는 내 것을 추가 할 것입니다.

복제가 나쁘기 때문에

  1. 코드를 어지럽히 다
  2. 코드에 대한 이해를 어지럽히지만 가장 중요한 것은
  3. 여기에서 무언가 를 바꾸고 거기 에서도 무언가를 바꾸어야 한다면 , 버그를 잊어 버리거나 소개 할 수 있기 때문에 결코 잊을 수 없습니다.

요점은, 당신은 그것을 위해 중복을 제거하지 않거나 누군가가 중요하다고 말했기 때문입니다. 버그 / 문제를 줄이고 싶기 때문에 수행하고 있습니다. 귀하의 경우,보기에서 무언가 를 변경 하면 다른 모든보기에서 정확히 동일한 라인을 변경하지 않아도됩니다 . 따라서 실제 복제가 아닌 명백한 복제 가 있습니다 .

또 다른 중요한 점은 Joel이 말한 것처럼 원칙의 문제에 의해서만 작동하는 것을 처음부터 다시 쓰지 않는 것입니다. 따라서 귀하의 견해가 효과가 있다면 단계적으로 개선하고 "모든 소프트웨어 회사가 만들 수있는 최악의 전략적 실수"에 빠지지 마십시오.

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