리팩토링 및 개방 / 폐쇄 원칙


12

최근에 깨끗한 코드 개발에 관한 웹 사이트를 읽고 있습니다 (영어가 아니기 때문에 여기에 링크를 두지 않습니다).

이 사이트에서 광고하는 원칙 중 하나는 공개 폐쇄 원칙입니다 . 각 소프트웨어 구성 요소는 확장을 위해 개방되고 수정을 위해 폐쇄되어야합니다. 예를 들어, 클래스를 구현하고 테스트 한 경우 버그를 수정하거나 새로운 기능 (예 : 기존 기능에 영향을 미치지 않는 새로운 메소드)을 추가하기 위해 클래스 만 수정해야합니다 . 기존 기능 및 구현을 변경해서는 안됩니다.

나는 보통 인터페이스 I와 해당 구현 클래스 를 정의하여이 원칙을 적용한다 A. 수업 A이 안정 되면 (구현되고 테스트 된), 나는 보통 너무 많이 수정하지 않을 것입니다.

  1. 새로운 요구 사항이 코드에 큰 변화를 요구 (예를 들어, 성능 또는 인터페이스의 완전히 새로운 구현을) 도착하면, 나는 새로운 구현을 작성 B하고, 계속 사용 A긴만큼 B성숙되지 않습니다. B성숙 되면 I인스턴스화 방법을 변경하기 만하면됩니다.
  2. 새로운 요구 사항이 인터페이스 변경을 제안하는 경우 새 인터페이스 I'와 새로운 구현을 정의합니다 A'. 그래서 I, A냉동하고 유지 구현 긴만큼 생산 시스템 I'A'이를 대체 할 안정적인 충분하지 않다.

따라서 이러한 관찰에 비추어 볼 때 웹 페이지가 복잡한 리팩토링 의 사용을 제안한 것에 약간 놀랐습니다 . "... 최종 형태로 코드를 직접 작성할 수 없기 때문입니다."

공개 / 폐쇄 원칙 시행과 복잡한 리팩터링 사용을 모범 사례로 제안하는 것 사이에 모순 / 충돌이 없는가? 또는 여기서 아이디어는 클래스를 개발하는 동안 복잡한 리팩토링을 사용할 수 A있지만 클래스가 성공적으로 테스트되면 동결되어야합니까?

답변:


9

Open-Closed 원칙을 디자인 목표 라고 생각합니다 . 당신이 그것을 위반해야한다면, 그것은 초기 설계가 실패했음을 의미하며, 이는 아마도 가능하고 가능할 수도 있습니다.

리팩토링은 기능을 변경하지 않고 디자인을 변경한다는 의미입니다. 문제가 있기 때문에 디자인을 변경하고있을 가능성이 있습니다. 아마도 문제는 기존 코드를 수정할 때 공개 폐쇄 원칙을 따르는 것이 어렵다는 것입니다.

다음 기능 을 수행 할 때 OCP 위반 하지 않고 다음 기능을 구현할 수 있도록 리팩토링을 수행하고있을 수 있습니다 .


당신은 확실히 디자인 목표 로 어떤 원칙으로 생각해서는 안됩니다 . 그것들은 도구입니다-당신은 소프트웨어를 내부적으로 예쁘고 이론적으로 올바르게 만들지 않고 고객을 위해 가치를 창출하려고합니다. 더 이상 은 지침 이 아닙니다.
T. Sar

@ T.Sar 원칙은 지침이며, 사용자가 노력하는 것은 유지 관리 성과 확장성에 중점을 둡니다. 그것은 저에게 디자인 목표처럼 보입니다. 디자인 패턴이나 프레임 워크를 도구로 보는 방식에서 도구로 원리를 볼 수 없습니다.
Tulains Córdova

@ TulainsCórdova 유지 보수성, 성능, 정확성, 확장 성-이것이 목표입니다. 공개 폐쇄 원칙은 많은 사람들 중 하나에 대한 수단 입니다. 적용 할 수 없거나 프로젝트의 실제 목표를 방해 할 경우 개방형 원칙을 향해 무언가를 밀 필요가 없습니다. 고객 에게 "개방성"을 판매 하지 않습니다 . 단순한 지침 으로서, 더 읽기 쉽고 명확한 방법으로 일을하는 방법을 찾는다면 버릴 수있는 경험 법보다 낫지 않습니다. 가이드 라인은 결국 도구 일뿐입니다.
T. Sar

@ T.Sar 고객에게 팔 수없는 것들이 너무 많습니다 ... 반면에, 나는 프로젝트의 목표에서 벗어나는 일을해서는 안된다는 점에 동의합니다.
Tulains Córdova

9

공개 폐쇄 원칙은 소프트웨어가 얼마나 잘 설계되었는지를 나타내는 지표입니다 . 말 그대로 따르는 원칙이 아닙니다. 또한 기존 인터페이스 (귀하가 호출하는 클래스 및 메소드 및 작동 방식)를 실수로 변경하지 않도록하는 원칙이기도합니다.

목표는 양질의 소프트웨어를 작성하는 것입니다. 이러한 특성 중 하나는 확장 성입니다. 즉, 기존 클래스의 수만큼 제한적인 경향이있는 코드를 쉽게 추가, 제거, 변경할 수 있습니다. 새로운 코드를 추가하는 것은 기존 코드를 변경하는 것보다 덜 위험 하므로 Open-Closed는 좋은 방법입니다. 그러나 우리는 정확히 어떤 코드를 이야기하고 있습니까? 기존 방식을 변경하지 않고 클래스에 새로운 메소드를 추가 할 수 있으면 OC를 위반하는 범죄는 훨씬 적습니다.

OC는 프랙탈 입니다. 디자인의 모든 깊이에 적용됩니다. 모든 사람들은 그것이 수업 수준에서만 적용된다고 가정합니다. 그러나 방법 수준이나 어셈블리 수준에서도 동일하게 적용됩니다.

적절한 수준에서 OC를 너무 자주 위반하면 리팩토링 할 시간이 될 수 있습니다. "적절한 수준"은 전반적인 디자인과 관련이있는 판단 요청입니다.

Open-Closed를 따르는 것은 말 그대로 클래스 수가 폭발한다는 것을 의미합니다. 불필요하게 (자본 "I") 인터페이스를 작성합니다. 클래스 전체에 약간의 기능이 퍼져서 모두 더 많은 코드를 작성해야합니다. 언젠가는 원래 수업을 바꾸는 것이 더 나았을 것입니다.


2
"기존의 방법을 변경하지 않고 클래스에 새로운 방법을 추가 할 때 OC를 위반하는 범죄는 훨씬 적습니다." . 문제는 잘 정의 된 인터페이스를 구현하는 기존 메소드를 변경하는 것이므로 이미 정의가 잘 정의되어 있습니다 (수정을 위해 닫힘). 원칙적으로 리팩토링은 의미를 변경하지 않으므로 내가 볼 수있는 유일한 위험은 이미 안정적이고 잘 테스트 된 코드에 버그를 도입하는 것입니다.
Giorgio

1
다음은 확장을 위해 열린 것을 보여주는 CodeReview 답변입니다 . 그 클래스 디자인은 확장 가능합니다. 반대로 메서드를 추가하면 클래스가 수정됩니다.
radarbob

새로운 방법을 추가하면 OCP가 아닌 LSP를 위반하게됩니다.
Tulains Córdova

1
새로운 방법을 추가 해도 LSP를 위반 하지 않습니다 . 메소드를 추가하면 새로운 인터페이스 @ TulainsCórdova
RubberDuck

6

공개 폐쇄 원칙은 TDD가 더 널리 보급되기 전에 등장한 원칙 인 것 같습니다. 아이디어는 코드를 리팩터링하는 것이 위험하므로 무언가를 깨뜨릴 수 있기 때문에 기존 코드를 그대로두고 추가하는 것이 더 안전합니다. 테스트가 없으면 이것이 의미가 있습니다. 이 접근법의 단점은 코드 위축입니다. 클래스를 리팩토링하지 않고 확장 할 때마다 추가 레이어가 생깁니다. 당신은 단순히 코드를 볼트로 고정하고 있습니다. 더 많은 코드를 사용할 때마다 중복 가능성이 높아집니다. 상상해보십시오. 내 코드베이스에 사용하려는 서비스가 있습니다. 원하는 서비스가 없기 때문에이를 확장하고 새로운 기능을 포함하는 새 클래스를 만듭니다. 다른 개발자가 나중에 와서 동일한 서비스를 사용하려고합니다. 불행히도, 그들은 내 확장 버전이 존재한다는 것을 알지 못합니다. 그들은 원래 구현에 대해 코딩하지만 내가 코딩 한 기능 중 하나가 필요합니다. 내 버전을 사용하는 대신 이제 구현을 확장하고 새로운 기능을 추가합니다. 이제 우리는 기능이 중복 된 원래 1 개와 2 개의 새 버전 인 3 개의 클래스가 있습니다. 개방 / 폐쇄 원칙을 따르십시오.이 복제는 프로젝트 수명 기간 동안 계속해서 쌓여 불필요하게 복잡한 코드 기반으로 이어질 것입니다.

잘 테스트 된 시스템을 사용하면이 코드 위축을 겪을 필요가 없으므로 코드를 안전하게 리팩터링하여 디자인이 새로운 코드를 지속적으로 사용하지 않고 새로운 요구 사항을 충족시킬 수 있습니다. 이러한 스타일의 개발을 응급 설계라고하며 점차적으로 크래프트를 수집하지 않고 전체 수명 동안 양호한 상태를 유지할 수있는 코드 기반으로 연결됩니다.


1
나는 공개 폐쇄 원칙이나 TDD를지지하는 사람이 아니다 (내가 그것들을 발명하지 않았다는 의미에서). 놀랍게도 누군가 공개 폐쇄 원칙과 리팩토링 및 TDD 사용을 동시에 제안했다는 것이 놀랍습니다. 이것은 나에게 모순되는 것처럼 보였으 므로이 모든 지침을 일관된 프로세스로 통합하는 방법을 알아 내려고했습니다.
Giorgio

"아이디어를 깨뜨릴 수 있기 때문에 코드를 리팩터링하는 것이 위험하다는 생각은 기존 코드를 그대로두고 단순히 추가하는 것이 더 안전합니다.": 실제로 이런 방식으로 코드를 볼 수는 없습니다. 아이디어는 교체하거나 확장 할 수있는 작은 독립형 장치를 갖기위한 것이지만 (따라서 소프트웨어가 진화 할 수 있도록) 철저한 테스트를 거친 후에는 각 장치를 만지지 않아야합니다.
조르지오

클래스는 코드베이스에서만 사용될 것이라고 생각해야합니다. 작성한 라이브러리는 다른 프로젝트에서 사용될 수 있습니다. 따라서 OCP가 중요합니다. 또한, 필요한 기능을 갖춘 확장 클래스를 모르는 새로운 프로그래머는 디자인 문제가 아니라 커뮤니케이션 / 문서 문제입니다.
Tulains Córdova

응용 프로그램 코드의 @ TulainsCórdova는 관련이 없습니다. 라이브러리 코드의 경우 시맨틱 버전 관리가 주요 변경 사항을 전달하는 데 더 적합하다고 주장했습니다.
opsb

1
라이브러리 코드 API 안정성을 갖춘 @ TulainsCórdova는 클라이언트 코드를 테스트 할 수 없으므로 훨씬 중요합니다. 응용 프로그램 코드를 사용하면 테스트 범위가 즉시 파손을 알려줍니다. 다시 말해, 애플리케이션 코드는 위험없이
주요한

6

평신도의 말로 :

A. O / C 원칙은 전문화 된 요구를 수용하기 위해 클래스를 수정하는 것이 아니라 확장하여 전문화 를 수행해야 함을 의미 합니다.

B. 누락 된 (특별하지 않은) 기능을 추가하면 설계가 완료되지 않았으며 계약을 위반하지 않고 기본 클래스에 추가해야합니다. 나는 이것이 원칙을 위반하지 않는다고 생각합니다.

C. 리팩토링은 원칙을 위반하지 않습니다.

예를 들어, 제작 후 일정 시간이 지나면 디자인이 완성 될 때 :

  • 그렇게 할 이유가 거의 없어야하고 (지점 B) 시간이 지남에 따라 0이되는 경향이 있습니다.
  • 더 드물기는하지만 항상 (포인트 C)가 가능합니다.
  • 모든 새로운 기능은 전문화되어야하며, 클래스를 확장 (상속)해야합니다 (A 지점).

개방 / 폐쇄 원칙은 매우 오해됩니다. 당신의 포인트 A와 B는 정확히 맞습니다.
gnasher729

1

나에게 개방 원칙은 엄격하고 빠른 규칙이 아닌 지침입니다.

원칙의 열린 부분과 관련하여 비공개로 선언 된 모든 생성자가있는 Java의 최종 클래스와 C ++의 클래스는 공개 된 원칙의 열린 부분을 위반합니다. 최종 클래스에 대한 견고한 사용 사례 (참고 : SOLID가 아닌 솔리드)가 있습니다. 확장 성을위한 디자인이 중요합니다. 그러나 이것은 많은 선견과 노력을 필요로하며, 항상 YAGNI를 위반하는 선을 긋고 (추천하지 않을 것입니다), 추론 적 일반성의 코드 냄새를 주입합니다. 확장을 위해 주요 소프트웨어 구성 요소를 열어야합니까? 예. 모두? 아닙니다. 그 자체는 투기적인 일반성입니다.

닫힌 부분과 관련하여 일부 제품의 버전 2.0에서 2.1에서 2.2에서 2.3으로 갈 때 동작을 수정하지 않는 것이 좋습니다. 모든 마이너 릴리스가 자체 코드를 손상시킬 때 사용자는 그것을 정말로 좋아하지 않습니다. 그러나 버전 2.0의 초기 구현이 근본적으로 손상되었거나 초기 설계를 제한 한 외부 제약 조건이 더 이상 적용되지 않는 경우가 종종 있습니다. 릴리스 3.0에서 디자인을 유지하고 유지하고 있습니까? 아니면 어떤 측면에서 3.0과 호환되지 않습니까? 이전 버전과의 호환성은 큰 제약이 될 수 있습니다. 주요 릴리스 경계는 이전 버전과의 호환성을 허용하는 장소입니다. 이렇게하면 사용자가 화를 낼 수 있다는 점에주의해야합니다. 과거와의이 단절이 필요한 이유에 대한 좋은 사례가 있어야합니다.


0

리팩토링은 정의에 따라 동작을 변경하지 않고 코드 구조를 변경합니다. 따라서 리팩토링 할 때 새로운 기능을 추가하지 않습니다.

Open Close 원칙에 대한 예제로 수행 한 작업은 정상입니다. 이 원칙은 새로운 기능으로 기존 코드를 확장하는 것입니다.

그러나이 대답을 잘못하지 마십시오. 나는 당신이 단지 기능을 수행하거나 대량의 데이터 덩어리에 대해서만 리팩토링을 수행해야 함을 의미하지는 않습니다. 프로그래밍의 가장 일반적인 방법은 약간의 리팩토링을 즉시 수행하는 것보다 약간의 기능을 수행하는 것입니다 (물론 동작을 변경하지 않도록 테스트와 결합 함). 복잡한 리팩토링은 "큰"리팩토링을 의미하는 것이 아니라, 복잡하고 잘 알려진 리팩토링 기술을 적용하는 것을 의미합니다.

SOLID 원리에 대해. 그것들은 소프트웨어 개발을위한 훌륭한 지침이지만 맹목적으로 따르는 종교적 규칙은 아닙니다. 때로는 두 번째 및 세 번째 및 n 번째 기능을 추가 한 후 초기 디자인이 Open-Close를 고려하더라도 다른 원칙이나 소프트웨어 요구 사항을 따르지 않는다는 것을 종종 알 수 있습니다. 더 복잡한 변경을 수행해야 할 때 디자인과 소프트웨어의 진화에 대한 요점이 있습니다. 요점은 이러한 문제를 가능한 빨리 찾아서 인식하고 리팩토링 기술을 적용하는 것입니다.

완벽한 디자인과 같은 것은 없습니다. 기존의 모든 원칙 또는 패턴을 존중할 수 있고 존중해야 할 디자인은 없습니다. 그것은 유토피아를 코딩하고 있습니다.

이 답변이 딜레마에 도움이 되었기를 바랍니다. 필요한 경우 설명을 요청하십시오.


1
"리팩토링 할 때 새로운 기능을 추가하지 않습니다.": 테스트 된 소프트웨어에 버그가 생길 수 있습니다.
Giorgio

"때로는 두 번째 및 세 번째 및 n 번째 기능을 추가 한 후 초기 디자인이 Open-Close를 존중하더라도 다른 원칙이나 소프트웨어 요구 사항을 존중하지 않는다는 것을 알고 있습니다." 새 구현 작성을 시작 B하고 준비가되면 이전 구현 A을 새 구현 B(인터페이스 사용)으로 바꿉니다. A의 코드는 코드의 기초가 B될 수 B있으며 개발 중에 코드에서 리팩토링을 사용할 수 있지만 이미 테스트 된 A코드는 고정되어 있어야 한다고 생각합니다 .
Giorgio

@Giorgio 리팩터링 할 때 버그가 생길 수 있으므로 테스트를 작성해야합니다 (또는 TDD를 사용하는 것이 더 좋습니다). 리팩터링하는 가장 안전한 방법은 작동중인 코드를 변경하는 것입니다. 통과하는 일련의 테스트를 통해이를 알 수 있습니다. 프로덕션 코드를 변경 한 후에도 테스트는 통과해야하므로 버그가 발생하지 않았다는 것을 알게됩니다. 또한 테스트는 프로덕션 코드만큼 중요하므로 프로덕션 코드와 동일한 규칙을 적용하고 정기적으로 자주 깨끗하게 리팩터링합니다.
Patkos Csaba

@Giorgio하면 코드는 B코드에 구축 A의 진화로 A, 때보다, B해제, A제거하고 다시 사용되지 않습니다. 인터페이스 가 변경되지 않았기 때문에 이전에 사용했던 고객 은 변경 사항을 몰라도 A사용할 것입니다 (여기서는 Liskov 대체 원칙이 약간 있습니까? ... SOLID 의 L )BI
Patkos Csaba

그렇습니다. 이것은 내가 생각한 것입니다. 유효한 (잘 테스트 된) 교체가 될 때까지 작업 코드를 버리지 마십시오.
조르지오

-1

내 이해에 따라 기존 클래스에 새 메소드를 추가하면 OCP가 중단되지 않습니다. 그러나 im 비트는 클래스에 새로운 변수를 추가하는 것과 혼동되었습니다. 그러나 기존 방법에서 기존 방법과 매개 변수를 변경하면 의도적으로 방법을 변경할 경우 코드가 이미 테스트되고 전달되므로 문제가 발생합니다.

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