CHAPTER 3 설계 원칙
3.1 단일 책임 원칙
3.1.1 단일 책임 원칙의 정의 및 해석
단일 책임 원칙(single responsibility principle, SRP)은 클래스와 모듈은 하나의 책임 또는 기능만을 가지고 있어야 된다
주의할 점은 단일 책임 원칙이 설명하는 대상에는 클래스와 모듈이라는 두가지 종류가 있다는 점
- 모듈을 클래스보다 더 추상적인 개념으로 간주하고 클래스를 일종의 모듈로 간주하는것
- 모듈을 좀 더 포괄적인 범위의 대상으로 놓고 여러 클래스가 하나의 모듈을 구성한다고 간주하는 것
3.1.2 클래스에 단일 책임이 있는지 판단하는 방법
동일한 클래스라 할지라도 다른 응용 시나리오나 다른 단계의 요구 사항에 따라 클래스의 책임이 달라질 수 있다.
요구 사항이 달라지면 해당 클래스의 설계가 단일 책임 원칙을 충족하지 못할 수 있으므로 계속해서 더 작은 클래스로 분활해야 된다
단일 책임 여부를 결정하기 위해 사용되는 몇 가지 결정 원칙
- 클래스에 코드, 함수 또는 속성이 너무 많아 코드의 가독성과 유지보수성에 영향을 미치는 경우 클래스 분활을 고려해야 된다
- 클래스가 너무 과하게 다른 클래스에 의존한다면, 높은 응집도와 낮은 결합도의 코드 설계 사상에 부합하지 않으므로 클래스 분활을 고려해야한다
- 클래스에 private 메서드가 너무 많다면 이 private 메서드를 새로운 클래스로 분리하고 더 많은 클래스에서 사용할 수 있도록 public 메서드로 설정하여 코드 재사용성을 높혀야 한다
- 클래스의 이름을 비즈니스적으로 정확하게 지정하기 어렵거나 manager, context처럼 일반적인 단어가 아니면 클래스 이름을 정의하기 어려운 경우 클래스 책임 정의가 충분히 명확하지 않음을 의미할수 있다
- 클래스의 많은 메서드가 여러속성 중 일부에서만 작동하는 경우 이런 속성과 해당 메서드를 분활하는것을 고려할수 있다
3.2 개방 폐쇄 원칙
개방 폐쇄 원칙 (open-closed principle, OCP)
- 확장할때는 개방
- 수정할때는 폐쇄
개방 폐쇄 원칙은 확장성이 코드 품질의 중요한 척도이기 때문에 가장 유용하다
3.2.1 확장할 때는 개방, 수정할 때는 폐쇄
모듈, 클래스, 함수와 같은 소프트웨어의 단위들은 확장을 위해 개방되어야 하지만 수정을 위해서는 폐쇄되어야 한다.
다시 말해 새로운 기능을 추가할때는 기존의 모듈, 클래스, 함수를 수정하지 보다는
기존 코드를 기반으로 모듈, 클래스, 함수등을 추가하는 방식으로 코드를 확장해야 한다
3.2.2 코드를 수정하는 것은 개방 폐쇄 원칙을 위반하는 것일까?
클래스에 새 속성과 메서드를 추가하는 것은 수정에 해당하는가 아니면 확장에 해당하는가?
코드의 수정이 기존에 작성되었던 코드와 단위 테스트를 깨뜨리지 않는 한 이는 개방 폐쇄 원칙을 위반하지 않는다고 판단해도 무방
3.2.3 확장할 때는 개방, 수정할 때는 폐쇄를 달성하는 방법
확장 가능한 코드를 작성하려면 확장, 추상화, 캡슐화에 대해 인식하고 있는 것이 매우 중요하며
이는 개발 기술 자체보다 훨씬 더 중요할 수 있다
3.2.4 프로젝트에 개방 폐쇄 원칙을 유연하게 적용하는 방법
단기간 내에 진행할 수 있는 확장, 코드 구조 변경에 미치는 영향이 비교적 큰확장,
구현 비용이 많이 들지 않는 확장에 대해 확장 포인트를 미리 준비하는것
코드의 확장성이 더 중요한 시나리오에는 코드의 가독성을 일부 희생할 필요가 있다
3.3 리스코프 치환 원칙
3.3.1 리스코프 치환 원칙의 정의
1986년에 MIT의 바바라 리스코프 교수에 의해 제안된 원칙
만약 S가 T의 하위 타입이라면 프로그램을 중단하지 않고 T 타입의 객체를 S 타입의 객체로 대체할 수 있어야 한다
3.3.2 리스코프 치환 원칙과 다형성의 차이점
다형성은 코드를 구현하는 방식에 해당하지만, 리스코프 치환 원칙은 상속 관계에서 하위 클래스의 설계 방식을 설명하는 설계 원칙에 해당한다
3.3.3 리스코프 치환 원칙을 위반하는 안티 패턴
리스코프 원칙에는 좀더 이해하기 쉬운 설명 방식이 있는데 바로 계약에 따른 설계라는 표현이다
- 하위 클래스가 구현하려는 상위 클래스에서 선언한 기능을 위반하는 경우
- 하위 클래스가 입력, 출력 및 예외에 대한 상위 클래스의 계약을 위반하는 경우
- 하위 클래스가 상위 클래스의 주석에 나열된 특별 지침을 위반하는 경우
3.4 인터페이스 분리 원칙
클라이언트는 필요하지 않은 인터페이스를 사용하도록 강요되어서는 않된다
인터페이스
- API나 기능의 집합
- 단일 API나 기능
- 객체지향 프로그래밍에서의 인터페이스
3.4.1 API나 기능의 집합으로서의 인터페이스
인터페이스 또는 기능의 일부가 호출자 중 일부에만 사용되거나 전혀 사용되지 않는다면 불필요한 항복을 강요하는 대신
인터페이스나 기능에서 해당 부분을 분리하여 해당 호출자에게 별도로 제공해야하며 사용하지 않는 인터페이스나 기능에는 접근하지 못하게 해야 한다
3.4.2 단일 API나 기능으로서의 인터페이스
API나 기능은 가능한 단순해야 하며 하나의 기능은 가능한 단순해야 하며 하나의 기능에 여러 다른 기능 논리를 구현하지 않아야 한다
호출자가 인터페이스의 일부 또는 그 기능의 일부만 사용하는 경우 해당 인터페이스 설계는 단일 책임 원칙을 충족하지 않는다고 말할 수 있다
3.5 의존 역전 원칙
- 의존성 역전이 뜻하는 것은 어떤 대상 사이의 역전인가? 그리고 어떤 의존이 역전되는 것인가? 그리고 여기서 말하는 역전은 무엇을 의미하는가?
- 종종 제어 반전과 의존성 주입이라는 두가지 다른 개념을 접할수 있는데 이 개념은 의존 역전과 같은 개념에 속하는가? 만약 그렇지 않다면 그차이는 무엇인가?
- spring 프레임워크의 IoC는 앞에 언급한 세 가지 개념과 어떤 관련이 있는가?
3.5.1 제어 반전
실행 흐름은 프레임워크에 의해 제어되고 흐름의 제어는 프로그래머에서 프레임워크로 역전되는것
3.5.2 의존성 주입
클래스 내부에 종속되는 클래스의 객체를 생성하는 대신, 외부에서 종속 클래스의 객체를 생성한 후 생성자, 함수의 매개변수 등을 통해 클래스에 주입하는 것을 의미
3.5.3 의존성 주입 프레임워크
의존성 주입은 비즈니스 논리에 속하지 않기 때문에 프레임워크에 의해 자동으로 완성되는 코드 형태로 완전히 추상화 될수 있다
그리고 이러한 프레임워크를 의존성 주입 프레임워크라고 한다
spring 프레임워크를 제어 반전 컨테이너라고 부르고 또 어떤사람은 의존성 주입 프레임워크라고 부른다
사실 두개의 표현은 모두 틀리지 않으며 단지 제어 반전 컨테이너라는 표현이 더 광범위 하고, 의존성 주입 프레임워크라는 표현이 좀 더 구체적일 뿐이다
3.5.4 의존 역전 원칙
- 상위 모듈은 하위 모듈에 의존하지 않아야 하며, 추상화에 의존해야만 한다
- 추상화가 세부 사항에 의존하는 것이 아니라, 세부사항이 추상화에 의존해야 한다
3.6 KISS 원칙과 YAGNI 원칙
- KISS 원칙에서 단순한이라는 단어가 가지는 의미는 무엇일까?
- 어떤 종류의 코드를 단순한 코드라고 할 수 있을까?
- 복잡한 코드는 어떤 코드인가?
- 간단한 코드를 작성하려면 어떻게 하면 좋을까?
- YAGNI 원칙은 KISS 원칙과 어떤 차이가 있는가?
3.6.1 KISS 원칙의 정의와 해석
가능한 단순하게 유지하라 많은 상황에 적용될 수 있는 포괄적인 설계 원칙
KISS 원칙은 코드를 일고 유지 관리할 수 있도록 해주는 중요한 수단
3.6.2 적은 줄 수의 코드가 더 간단하지 않다
코드의 길이가 짧다고 해서 코드가 간단하다고 말할 수 없다
3.6.3 복잡한 코드가 반드시 KISS 원칙을 위반하는 것은 아니다
알고리즘 자체의 논리가 복잡하고 구현이 어렵고 가독성이 떨어지는 특성을 가지고 있지만 복잡한 알고리즘을 사용하여 복잡한 문제를 해결하는 것은 KISS 원칙을 위반하는 것이 아니다
3.6.4 KISS 원칙을 만족하는 코드 작성 방법
- 복잡한 정규표현식, 프로그래밍 언어에서 제공하는 지나치게 높은 레벨의 코드 등 지나치게 복잡한 기술을 사용하여 코드를 구현하지 않는다
- 바퀴를 다시 발명하는 대신 기존 라이브러리를 사용하는 것을 고려한다
- 과도하게 최적화하지 않는다
3.6.5 YAGNI 원칙과 KISS 원칙의 차이
YAGNI (You Aren’t Gonna Need It) 원칙은 불필요한 기능을 추가하지 않는 것을 의미하며, 과도하게 설계하지 말라는것
KISS 원칙은 방법에 관한 것인데 YAGNI 원칙은 금지에 관한 것이다
3.7 DRY 원칙
중복 코드를 작성하지 말라는 뜻으로 번역된다
3.7.1 코드 논리의 중복
코드의 논리가 중복되지만 의미가 틀리면 중복되는 함수라고 할수 없다
3.7.2 기능적(의미론적) 중복
코드의 논리가 중복되지 않아도 의미적인 중복은 DRY 원칙을 위반하는 것이다
3.7.3 코드 실행의 중복
코드 실행의 중복이 되도 DRY 원칙을 위반하는 것이다
3.7.4 코드 재사용성
- 코드의 결합도를 줄인다
- 단일 책임 원칙을 충족시켜야 한다
- 코드 모듈화는 필수다
- 비즈니스 논리와 비즈니스 논리가 아닌 부분을 분리할 필요가 있다
- 일반적인 코드는 하위 계층으로 내려보낸다
- 상속, 다형성, 추상화, 캡슐화를 활용한다
- 애플리케이션 템플릿과 같은 디자인 패턴을 활용하면 코드 재사용성을 향상 시킬수 있다
3의 법칙(rule of three)은 처음 코드를 작성할 때는 재사용성을 고려하지 않고, 나중에 재사용 시나리로를 만나면 다시 사용할 수 있도록 리팩터링하면 되다
3.8 LoD
데메테르의 법칙 (Law of Demeter, LoD)은 높은 응집도와 낮은 결합도를 달성하는 데 도움이 될수 있다
3.8.1 높은 응집도와 낮은 결합도에 대한 생각
코드의 가독성과 유지 보수성을 효과적으로 향상시키고 기능 변경으로 인한 코드 변경 범위를 줄일 수 있는 매우 중요한 설계 사상
많은 설계 원칙이 코드의 높은 응집도와 낮은 결합도를 달성하는 것을 목표로 하고 있다
- 높은 응집도 : 클래스 자체의 설계에 사용된다. 유사한 기능은 동일한 클래스에 배치되어야 하고, 유사하지 않은 기능은 다른 클래스로 분리해야한다
- 낮은 결합도 : 클래스 간의 의존성 설계에 사용되는데, 코드에서 클래스 간의 의존성이 단순하고 명확해야 함
3.8.2 LoD의 정의
최소 지식의 원칙
- 모든 유닛이 자신과 매우 밀접하게 관련된 유닛에 대해서 제한된 지식만 알아야 한다