[디자인패턴의 아름다움] 단일 책임 원칙과 개방 폐쇄 원칙

설계 원칙에 대해 이해하는 것도 중요하지만, 그보다는 실제 프로젝트에서 사용하는 방법을 정확하게 알고 있어야한다. 설계 원칙에 대한 이해가 충분하지 않으면, 불필요하게 독단적이고 엄격한 사용으로 이어져 결국 역효과를 낳을 것이다.

단일 책임 원칙

단일 책임 원칙이란 클래스와 모듈은 하나의 책임 또는 기능만을 가지고 있어야 한다는 설계 원칙이다.

모듈(module)이란 프로그램을 구성하는 구성 요소로, 관련된 데이터와 함수를 하나로 묶은 단위이다. 클래스란 동일한 속성과 행위를 수행하는 객체의 집합이다.

클래스와 모듈이라는 두 가지 개념에 대해 두 가지 이해 방식이 있다.

  1. 모듈을 클래스의 추상적인 개념으로 간주하고 클래스를 일종의 모듈로 간주
  2. 모듈을 포괄적인 범위의 대상으로 놓고, 여러 클래스가 하나의 모듈을 구성한다고 간주

단일 책임 원칙은 클래스가 하나의 책임이나 기능만을 담당한다는 것을 의미한다. 즉, 포괄적인 클래스를 설계하는 대신, 작은 단위와 단일 기능을 가진 클래스를 설계해야 한다. 클래스에 비즈니스와 관련 없는 기능이 두 개 이상 포함되어 있으면 책임이 단일하지 ㅇ낳으므로, 단일 기능을 가진 여러 개의 작은 클래스로 분할되어야 한다.

클래스에 단일 책임이 있는지 판단하는 것은 쉽지 않다. 동일한 클래스라 할지라도 요구 사항에 따라 책임이 단일한지 아닌지를 판단하는 것이 다를 수 있다. 응용 시나리오나 요구 사항이 변경되면 단일 책임을 만족하던 클래스도 단일 책임 원칙을 충족하지 못할 수 있다. 그럴때는 계속해서 더 작은 클래스로 분할해야 한다.

단일 책임이 있는지 평가하기 위한 명확한 기준이 없기 때문에, 실제 개발할 때는 과도하게 너무 세분화하여 설계할 필요는 없다. 처음에는 현재 요구 사항에 충족하기 위해 클래스를 작헝하고, 요구사항이 늘어가면서 코드가 점점 복잡해지면 세분화된 클래스로 나눌 수 있을 것이다.

단일 책임 여부를 결정하기 위한 원칙

  1. 클래스에 코드, 함수 또는 속성이 너무 많아 코드의 가독성과 유지 보수에 영향을 미치는 경우 클래스 분할을 고려
  2. 클래스가 너무 과하게 다른 클래스에 의존한다면, 높은 응집도와 낮은 결합도의 코드 설계상에 부합하지 않으므로 클래스 분할 고려
  3. 클래스에 private 메서드가 너무 많은 경우 이 private 메서드를 새로운 클래스로 분리하여 더 많은 클래스에서 사용할 수 있도록 public 메서드로 설정하여 코드의 재상용성을 햘상시켜야 한다. (이 부분은 이해가 살짝 안됨. public이면…패쇄성이..?)
  4. 클래스의 이름을 일반적인 단어가 아니면 정의하기 어려울 경우, 책임 저으이가 충분히 명확하지 않음을 의미
  5. 클래스의 많은 메서드가 여러 속성 중 일부에서만 작동하는 경우 이러한 속성과 해당 메서드를 분할하는 것을 고려

클래스 분할로 인해 코드의 유지 보수성이 극히 낮아지는 결과를 초래할 수도 있다. 목표는 코드의 가독성, 확장성, 재사용성, 유지 보수성을 향상시키는 것이다. 어떤 설계 원칙을 적용하는 것이 타당한지를 판단할 때 이를 최종 평가 기준으로 삼을 수 있다.

개방 폐쇄 원칙

개방 패쇄 원칙은 확장할 때는 개방, 수정할 때는 패쇄 원칙으로 불린다. 개방 패쇄 원칙은 코드를 변경할 때, 그 결과를 확장으로 봐야하는지, 수정으로 보아야 하는지 명확하게 구분하기 어렵다. 개방 패쇄 원칙은 확장성이 코드 품질의 중요한 척도이다.

모듈, 클래스, 함수와 같은 소프트웨어의 단위들을 확장을 위해 개방되어야 하지만 수정을 위해서는 패쇄되어야 한다. 즉, 새로운 기능을 추가할 때 기존의 모듈, 클래스, 함수를 수정하기보다는 기존 코드를 기반으로 모듈, 클래스, 함수를 추가하는 방식으로 코드를 확장해야 한다는 뜻이다.

기존 코드를 수정하지 않고 추가하는 방식으로 코드를 확장하면 기존 클래스에 대한 단위 테스트를 매번 수정할 필요 없이 새로 추가된 핸들러 클래스에 대한 테스트를 추가하는 것으로 충분하다.

그렇다면, 코드를 수정하는 것은 개방 패쇄 원칙을 위반하는 것인가? 수정없이 추가를 한다는 것은 실상 매우 어렵고 불가능하다고 볼 수도 있다. 클래스에 새 속성과 메서드를 추가하는 것은 수정에 해당하는가 아니면 확장에 해당하는가? 이는 관점에 따라 다르다. 코드 단위를 크게 잡으면 수정으로 간주되지만, 세세하게 보면 확장(추가)에 해당한다. 개방 패쇄 원칙의 기본적인 목적을 다시 떠올려보면, 코드의 수정이 기존에 작성되었던 코드와 단위 테스트를 깨뜨리지 않는 한, 이는 개방 패쇄 원칙을 위반하지 않는다고 판단해도 무방하다. 수정을 아예 안하는 것이 아니라 수저을 가능한 한 상위 수준의 코드에서 진행하고, 코드의 핵심 부분이나 복잡한 부분, 공통 코드나 기반 코드가 개방 패쇄 원칙을 충족하는 방향으로 노력해야 한다.

변경 가능한 부분과 변경할 수 없는 부분을 잘 식별해야 한다. 변경되는 사항을 기존 코드와 분리할 수 있도록 변수 부분을 캡슐화하고, 상위 시스템에서 사용되는 변경되지 않을 추상인터페이스를 제공해야 한다. 특정 구현이 변경되어도 추상 인터페이스를 기반으로 새로운 구현을 확장하여 기존 구현을 대체할 수 있으며, 상위 시스템의 코드를 수정할 필요가 없다.

이런 설계 사상을 떠올리려면 천천히 학습하고 그 결과로 축적되는 탄탄한 이론 지식과 풍부한 실무 경험이 필요하다. 확장 가능하도록 코드를 설계하려면 비즈니스에 대한 이해가 충분해야 한다. 어떻게 사용될지, 어떤 요구사항이 있을지 이해하는 것은 매우 중요하다. 이해가 충분하지 않으면 확장에 닫혀있는 설계 또는 과도한 설계가 될 수 있다. 단기간 내에 진행할 수 있는 확장, 코드 구조 변경에 미치는 영향이 비교적 큰 확장, 구현 비용이 많이 들지 ㅇ낳는 확장에 대해 확장 포인트를 미리 준비해야 한다.

코드의 확장성은 가독성을 떨어뜨리기도 한다. 코드의 확장성과 가독성 사이에서 적절한 균형을 유지해야 한다.

결론 ( 느낀점 )

이 설계 원칙들을 무작정 학습하고 써보려는 것이 아니라 목적을 이해해야한다. 실무에서의 비지니스적 상황을 고려하여 적절한 균형을 유지해야한다. 학습은 탄탄히 해야하지만, 결국 실무에서는 어떻게(How)에 집중하는 것이 아니라 왜(Why)에 집중해야 한다.

results matching ""

    No results matching ""