Dog foot print

[Architecture] S.O.L.I.D [S,O] 본문

Architecture

[Architecture] S.O.L.I.D [S,O]

개 발자국 2021. 4. 30. 23:00

서론 

 

로버트 C 마틴의 Clean Architecture 의 책을 읽는 중, SOLID원칙이 등장하여, 이를 정리하고자 글을 써본다. 

 

재미있는 부분이라 함은 SOLID 원칙의 SOLID는 기존에 존재하던 5가지의 원칙의 이름을 2000년대 초반 마이클 페더스(레거시 코드 활용전략 저자)가 로버트 C마틴에게 앞 글자만 따서 재배열하여 SOLID라는 이름이 탄생한 것이다. 

SOLID

SOLID 원칙은 함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명해준다.  (절대 객체지향프로그래밍 패러다임에 국한되지 않는다.)

 

SOLID원칙의 목적은 다음과 같다. 

 

  • (클래스의 역할이 아닌 삽입과 삭제)변경에 유연하다.
  • (프로그래머가) 이해하기 쉽다.
  • 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트 기반이 된다. 

SOLID원칙의 5가지는 다음과 같다. 

 

  • SRP(Single Responsibility Principle) : 단일책임원칙 
  • OCP(Open-Cosed Principle) : 개방 폐쇄 원칙
  • LSP(Liskov Substitution Principle) : 리스코프 치환 법칙
  • ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
  • DIP(Dependency Inversion Principle) : 의존성 역전 원칙 

SRP(Single Responsibility Principle) : 단일책임원칙

단일 책임 원칙은 한개의 함수는 반듯이 한개의 일만 하는 것을 의미한다. 이 포스팅을 읽고 있는 사람은 "Side Effect" 라는 단어를 알고 있을 것이다. 모르는 사람을 위해서 간단하게 설명하면, 함수가 지역변수외에 전역변수를 변경시키는 것을 의미한다. 물론 SIDE EFFECT가 부정적인 의미가 아니지만, 여기서는 함수의 역할과 별개의 SIDE EFFECT를 부정적으로 보아야 한다. 

 

많은 학부생들이 컴퓨터 공학부의 과제를 진행 하는 것을 보면, 한개의 클래스에서 모든 Actor의 메서드를 정의한다. 예를 들어 교수, 교직원, 학생 의 Actor가 존재할 때 모자른 컴공학생은 SchoolOffiial 이라는 클래스에서 모든 메서드와 멤버 변수를 정의하고 메서드는 각기 다른 메서드에서 사용되는 멤버변수를 공유할 것이다. 물론 이렇게 해도 동작은 할 것이다. 그러나 추가 및 변경이 존재하는 경우, 너무 높은 결합이 존재하여 클래스 전부를 뜯어 고쳐야 한다. 

 

그래서 결국 하고 싶은 이야기가 무엇인가 ? 각 모듈(소스 코드 | 함수 | 클래스)는 한개의 액터에 대해서 책임을 져야 하며, 함수의 역할과 별개인 SIDE EFFECT를 없애야 한다. 

 

interface User {
  report: () => void;
}

class App {
  user: User;
  constructor(type: string) {
    this.user = makeUser(type);
  }
}
function makeUser(type: string): User {
  switch (type) {
    case "Stu":
      return new Student();
    case "Pro":
      return new Professor();
    case "SchStf":
      return new SchoolStaff();
    default:
      return new Guest();
  }
}
class Guest implements User {
  report() {}
}

class Student implements User {
  report() {}
}

class Professor implements User {
  report() {}
}

class SchoolStaff implements User {
  report() {}
}

 

OCP(Open-Closed Principle) : 개방 폐쇄 원칙

"확장에는 열려 있어야하고, 변경에는 닫혀있어야 한다." 라는 것이 OCP원칙이다. 단번에 이해 하기 어려운 내용임이 틀림없다. 그러니 천천히 예시를 보면서 적용해보도록 하자. 

 

우리의 모자른 대학생 A는 간단한 정렬 알고리즘을 통해 숫자의 배열을 오름차순으로 정렬하는 과제를 받았다. A는 정렬 알고리즘 내부에 if문을 써서 a > b 인 경우 앞 뒤 숫자를 변경하게 끔 하였다. 이후 교수는 내림차순으로 정렬하라는 과제를 전달했고, A는 단순히 a < b 로 변경하여, 제출하였다. 이 경우에도 문제는 없었다. 이제 교수는 사용자의 입력에 따라, 숫자 배열을 정렬하라고 하였다. a는 정렬 함수의 매개변수에, string type인 type을 새로 만들었고, 이 type에 따라 중첩 if문을 만들어 어떻게든 되게 만들었다. 

 

let type = "asc";
if(type === "asc"){
    if(a > b){
        return true;
    }else{
        return false;
    }
}else{
    if(b < a){
        return true;
    }else{
        return false
    }
}

 

이제 지친 A는 " 더 이상의 추가 요구는 없겠지 ??? " 라는 생각을 하고 있었지만, 교수는 이제 문자열 데이터 또한 정렬 하라고 한다. A학생은 더 이상 이 함수를 고쳤다가는 추가 수정이 불가능할 것임을 예상하고 아예 새롭게 문자열만을 위한 동일한 정렬 함수를 만들어버렸다. 

 

대학생 A는 무엇을 놓쳤을까 ? 바로 확장을 염두해두지 않았다

 

문자배열을 정렬하거나, 숫자배열을 정렬하는 기본적인 로직은 동일하다. 그러나, 가엾은 A는 처음부터, 확장을 염두해두지 않고 무작정 코드를 짜버렸기 때문에, 추가적인 요구를 받아 들일 수 없는 지경에 이른 것이다. 

 

만약 똑똑한 B학생이 존재한다면 이 상황에서 코드를 어떻게 작성했을까 ? 이 상황에서는 전달 받는 데이터와 select되는 정렬 방식에 따라 콜백을 전달하여 결과를 보여주는 것이 이상적이다. 

 

function sort<T>(list: Array<T>, callBack: (a: T, b: T) => boolean) {
    ...
}

 

이처럼 확장을 염두해두지 않은 상황은 코드를 작성하다보면 정말 많이 발생한다. 그 이유는 개발자는 정말 바쁘기 때문에, 바로 결과를 보여주기에는 A와 같은 방법이 빠르기 때문이다. 그러나 시간이 지날수록 A의 코드처럼 수정이 힘들어 오히려 B에 비해 시간이 더 많이 발생한다. 

 

그럼 변경이 어려운 것은 무엇을 의미할까 ? 위의 B의 코드에서 알 수 있듯이, 새로운 기능을 확장 할 때, 중요한 sort 로직은 변경 되지 않는다. 이 변경은 함수의 기능상 문제가 존재할 때 만 변경하며, 최적의 상태로 작성되었기 때문에, 새로운 기능의 추가에도 로직은 변경 되지 않는다. 이렇게 확장에 있어서는 열려있고, 확장이 존재 할 때 코드의 변경은 이루어지지 않는 원칙이 OCP원칙이다. 

 

마무리 

다음 포스팅에서는 나머지 L.I.D 원칙에 대해서 다뤄 보겠습니다. 

 

요즘 쫌 포스팅이 뜸한데, 이직으로 인하여 생각보다 바쁘네요 ㅜ.ㅜ 

 

반응형

'Architecture' 카테고리의 다른 글

[GOF] 추상 팩토리 메서드 패턴  (0) 2021.10.23
[GOF] 팩토리 메서드 패턴  (0) 2021.10.17
[Architecture] S.O.L.I.D [D]  (0) 2021.05.13
[Architecture] S.O.L.I.D [I]  (0) 2021.05.11
[Architecture] S.O.L.I.D [L]  (0) 2021.05.06
Comments