나쁜 설계의 경우 다음과 같은 요소들을 가지고 있다.
- 경직성 : 무엇인듯 하나를 바꿀 때마다 반드시 다른 것도 바꿔야하고 변화의 사슬이 끊이지 않아 시스템 변경이 힘듦.
- 부서지기 쉬움 : 시스템 한 부분을 변경하면 전혀 상관없는 다른 부분에 이상이 생김.
- 부동성 : 시스템이 여러 컴포넌트로 분해되어 있어 다른 시스템에서 재사용하기 힘듦.
- 끈끈함 : 편집-컴파일-테스트 순환을 한 번 도는 시간이 엄청나게 길다.
- 쓸데없이 복잡함 : 괜히 머리를 굴려서 짠 코드 구조가 많다.
- 필요 없는 반복 : 복사/붙여넣기, 중복코드가 많음.
- 불투명함 : 코드 작성 의도가 불분명하다, 코드와 그에 대한 설명에 괴리가 있다.
잘못 관리한 의존 관계가 위 요소들의 원인이다.
위와 같은 요소들을 발견하면 다음 원칙들을 이용하여 개선을 해보자.
1. 단일 책임 원칙(The Single Responsibility Principle, SRP)
어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
예를 들어, Employee 클래스가 DISK에 쓰는 메소드, XML 형태로의 변환 메소드 등의 기능까지 가지고 있으면 Employee의 데이터 변경 뿐아니라 다른 이유로 인해서도 변경이 발생해야한다.
2. 개방 - 폐쇄 원칙(The Open - Closed Principle, OCP)
소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 개방되어야 하지만, 변경에 대해서는 폐쇄되어야 한다.
MVC패턴이 좋은 예이다. 보여줄 데이터를 관리하고 다루는 코드와 GUI API를 다루는 코드는 분리해야 한다.
=> 클래스를 변경하지 않고도 그 클래스의 환경을 바꿀 수 있어야 한다.
3. 리스코프 교체 원칙(Liskov Substitution Principle, LSP)
서브타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다.
기반 클래스(base class) 사용자는 서브 클래스를 기반 클래스로써 사용할 때 원래 기반 클래스 사용하는 양 그대로 사용할 수 있어야 한다.
(instanceof, 다운캐스트를 할 필요가 없어야 한다.)
서브 타입에 기반 클래스를 사용하는 입장에서 예외적으로 처리해야 하는 부분이 생긴다면 코드가 복잡해진다.
4. 의존 관계 역전 원칙(Dependency Inversion Principle, DIP)
A. 고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
B. 추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.
쉽게 말하자면, "자주 변경되는 컨크리트 클래스에 의존하지 마라".
어떤 클래스에서 상속받아야 한다면, 추상 클래스를 만들어 상속받아라.
어떤 클래스의 참조를 가져야 한다면, 그 클래스를 추상 클래스로 만들어라.
어떤 함수를 호출해야 한다면 호출되는 함수를 추상 함수로 만들어라.
추상 클래스와 인터페이스는 변경이 적기 때문에 여기에 의존하게 하는게 변화에 대한 영향을 줄일 수 있다.
=> 자주 변경하는 컨크리트 클래스 대신 인터페이스나 추상 클래스에 의존하라.
5. 인터페이스 격리 원칙(Interface Segregation Principle, ISP)
클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.
사용하지 않는 메서드에 의존 관계를 가지게 되면, 자신과 상관없는 변경에 의해서도 자신도 변경해야 하기 때문이다.
사용하는 메서드들만 추출하여 별도의 인터페이스를 만들어 그 인터페이스에 의존하게 해야 한다.
=> 어떤 객체의 사용자에게 그 사용자한테 필요한 메서드만 있는 인터페이스를 제공하라.
전체 시스템이 언제나 모든 원칙을 따르게끔 노력하는 것은 현명하지 못하다.
문제가 생겼을 때 적용하는 것이 가장 좋은 방법이다. 단위 테스트를 엄청나게 작성해 보는 것이 문제를 찾는데 가장 좋은 방법 중 하나다.
- 참조 : UML 실전에서는 이것만 쓴다