좋은 아키텍처와 SOLID 설계원칙

JACKJACK·2023년 8월 3일
1
post-thumbnail

좋은 아키텍처란?

변경에 유연하고, 이해하기 쉽고, 많은 소프트웨어에서 사용될 수 있는 구조를 갖춰야한다.

이러한 좋은 아키텍처를 위한 원칙이 5가지로 정의되는데 그것이 바로 SOLID 원칙이다. 자 이제 차례대로 하나씩 살펴보자.

  • SRP(단일 책임 원칙)
  • OCP(개방-폐쇄 원칙
  • LSP(리스코프 치환 원칙)
  • ISP(인터페이스 분리 원칙)
  • DIP(의존성 역전 원칙)

- SRP 단일 책임 원칙

"단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야한다." 는 원칙

SRP가 지향하고자하는 바는 SRP의 위반사례를 통해 설명할 수 있다.

다음과같이 Employee 클래스를 안의 각각의 메소드를 CFO, COO, CTO 각 팀에서 사용할 때 CFO의 변경사항이 나머지 메소드에 영향을 줄 수 있다. 예를들어 CFO팀에서 임금계산 메소드를 수정하면 COO팀에서 임금계산이 뻥튀기가 되어 문제가 생기거나 CTO에서 의존하는 무언가에 영향을 줄 수 있다. 그래서 이러한 일이 일어나지 않게 SRP를 통해 의존하는 코드를 분리해야 한다.


- OCP 개방-폐쇄 원칙

"소프트웨어 개체는 확장에 열려있어야하고, 변경에는 닫혀있어야한다."는 원칙

이 원칙은 소프트웨어 아키텍처를 공부하는 가장 근본적인 이유이며 요구사항을 확장할 때, OCP를 위반해 소프트웨어를 많이 수정해야한다면 아키텍트는 실패에 맞닥뜨린것이다.

OCP의 예시로 재무제표를 보여주기만 하는 웹 페이지가 있고 음수 값을 빨간색으로 표시했는데, 이를 흑백프린터로 보고서 출력을 요청 받으면 음수 값을 괄호로 감싸고 내용이 많다면 프린트를 위해 페이지를 여러 장 수로 나누어 페이지 번호를 넣어줘야 하는 일이 발생한다.

앞서 설명한 재무제표의 컴포넌트 다이어그램이 다음과 같고 화살표의 방향은 의존의 방향이라고 가정하면. Interactor 컴포넌트의 경우 가장 많은 의존성을 띄므로 OCP를 지켜 컴포넌트를 변경이 없도록 보호해줘야한다.

프린팅 기능 외에도 다른기능이 필요할 때 OCP를 지켜 훌륭한 소프트웨어 아키텍처가 들어간다면, 코드를 매우 조금 수정하여 해결이 가능하다고 한다.


- LSP 리스코프 치환 원칙

"상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환 가능해야 한다는 계약을 반드시 지켜야 한다."

LSP는 위반사례를보면 빠르게 이해가 가능하다.

정사각형이 직사각형의 하위타입으로 들어가면 직사각형에서 높이와 너비를 따로 세팅하는 메소드를 정사각형에서 사용했을 때 정사각형의 특성이 깨지게 된다.

이런식으로 LSP는 상속을 가이드하는 방법 정도로 간주되었다가 점점 인터페이스와 구현체에 적용되며 더욱 광범위한 소프트웨어 원칙이 되었다.

그 예시로 카카오택시와 우버를 인수한다고 가정하고, 각각 다른 URI를 통해 시스템이 구성되어 있을 때. 카카오의 수락서비스의 파라미터가 "pickupAddress", "pickupTime" , "destination" 인데 반해 우버에서는 destination대신 축약해서 dest를 사용하는 경우 상위에서 정의한 객체와 치환이 되지않아 LSP 위반이 될 수있다. 또한 보안 및 온갖 문제를 발생할 여지를 만들어버린다.


- ISP 인터페이스 분리 원칙

클라이언트가 사용하지 않는 인터페이스에 의존하지 않도록하여, 인터페이스를 작은 단위로 분리하는 원칙

ISP는 다음 그림을 보면 이해가 빠르다.

그림에서는 User1,2,3에 따라 각각 op1,2,3 메서드를 사용중인데, 만약 op1이 변경된다면 OPS를 재정의하기 위해 새로 컴파일 해야하는 번거로움이 생기게 되며, 예상치 못한 문제에 빠질 수 있다. 따라서 다음 그림과 같이 인터페이스를 분리해서 사용하자는것이 ISP이다.

결국 ISP의 근본적인 동기는 필요 이상으로 많은것을 포함하는 모듈에 의존하는것은 해로운 일이니 지양하자는 이야기이다.


- DIP 의존성 역전 원칙

DIP는 상위 수준의 코드가 하위 수준의 코드에 의존해서는 안되며, 추상화된 인터페이스에 의존해야한다는 원칙이다.

조금 더 쉽게 풀어 설명하자면 DIP는 변동성이 큰 구체적인 요소를 피하고 안정적인 요소에 의존함으로써 유연하고 변경에 강한 소프트웨어 시스템을 구축하기 위해 존재한다.
(String Class는 예외 -구체클래스이지만 추상화시키기에는 세부사항에 엄격해야하며, 엄격한 통제를 받아 변경될 일이 없기 때문)

즉 변동성이 큰 것을 사용하지말고 "인터페이스를 사용하자"이다. 그 예시로 상위 수준의 클래스(Animal)에서 "소리를 낸다"를 정의하고 하위 수준은 클래스(Dog, Cat)에서는 상위 클래스에서 정의한 범위 내에서 소리를 구체적으로 구현해 하위가 상위를 의존하게끔 하는것이다.

구체적인 코딩실천법으로는 다음과 같은것들이 있다.

  1. 변동성이 큰 구체 클래스를 참조하지 말라. 추상 인터페이스를 참조하라.
  2. 변동성이 큰 구체 클래스로부터 파생하지 말라. 특히 상속과 같은 관계는 모든 관계 중 가장 강력하며 뻣뻣하여 변경이 어렵다. 따라서 상속은 신중해야 한다.(직사각형-x->정사각형)
  3. 구체함수를 오버라이드 하지 말아라. 구체함수는 소스코드의 의존성을 필요로 하기 때문에 오버라이드하면 의존성을 상속하게 된다. 따라서 추상함수로 선언하고 각자 용도에 맞게 구현해야한다.



결론

- 좋은 소프트웨어 시스템은 좋은 코드와 함께 "좋은 아키텍처"를 정의하는 원칙이 필요하다.

- 좋은 아키텍처는 SOLID를 지켜 변경에 유연하고, 이해하기 쉽고, 많은 소프트웨어 시스템에서 사용될 수 있는 구조를 갖춰야 한다.

profile
러닝커브를 빠르게 높이자🎢

2개의 댓글

comment-user-thumbnail
2023년 8월 3일

많은 것을 배웠습니다, 감사합니다.

1개의 답글