객체(함수)는 오직 하나의 책임을 가져야 한다.(객체(함수)는 오직 하나의 변경의 이유만을 가져야 한다.)
같은 이유로 변경될 코드들은 모으고, 다른 이유로 변경될 코드들은 흩어라
함수는 한가지 작업을 수행해야 합니다. 이것은 소프트웨어 엔지니어링에서 단연코 가장 중요한 규칙입니다. 함수가 한가지 이상을 수행하면 구성, 테스트 및 추론하기가 더 어렵습니다. 함수를 하나의 작업을 분리할 수 있으며 쉽게 리팩토링할 수 있습니다.
클래스를 쓰지 않고 함수만 사용한다고 함수형 프로그래밍이라고 할 수는 없다. 함수형 프로그래밍이 되기 위해서는 순수함수와 부수효과를 분리하는 구조가 되어야 한다.
순수함수란?
- 1개의 반환값이 반드시 존재한다
- 같은 인자를 넣었을대에는 항상 같은 값을 반환한다.
- 함수 외부의 어떤한 값을 변화시켜서는 안된다.
순수함수는 너무나도 SRP이 원칙에 들어맞는 모양이 되게 됩니다. 그러니 함수형 프로그래밍의 핵심인 가급적 순수함수로 작성하는 원칙은 SOLID의 첫 번째 원칙인 SPR와 함께 엮어서 생각을 해주시기 바랍니다.
"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다."
🤔함수형 프로그래밍에서 이 OCP를 가장 잘 느낄 수 있는 것은 바로 map, filter, reduce와 같은 것들이 있다.
"프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다." 계약에 의한 설계를 참고하라.
일단 예시를 통해 일단 한번 직관적으로 이해를 하는 것이 좋기 때문에 새와 앵무새와 펭귄의 이야기로 해보겠습니다.
새라는 객체를 만들고 "앵무새는 새다." "펭귄은 새다" 라는 정의에 따라 "새"를 상속을 받아서 앵무새와 펭귄을 만들었습니다. 객체지향에서 상속은 is-a 관계이므로 언뜻보면 맞는 것 같습니다. 😢하지만 이 경우 우리는 새를 정의하기 위해서 fly()라는 메서드가 동작하는 것을 만들었기 때문에 앵무새는 fly()하기에 새가 될 수 있지만 펭귄은 fly()하지 못하기에 새가 될 수가 없습니다. ❗️이렇게 설계가 된 경우 우리는 리스코프 치환 원칙을 위반했다라고 할 수 있습니다.
정리하자면, 리스코프 치환 원칙은 상속을 받아 만든 하위타입의 제약조건들이 사위 타입에서 먼저 선언한(fly) 조건들과 충돌이 날 결우 유지보수가 힘들어 진다는 문제점이 있기 때문에 만들어진 것입니다. 따라서 계층도간의 is-a 관계를 만족한다고 하더라도 (새-펭귄) 하위 타입에서 가변성을 가지면서 상위 타입에서 정의한 조건과 일치하지 않는다면 상속을 받지 말아야 합니다.
"사용자가 필요하지 않은 것들에 의존하게 되지 않도록, 인터페이스를 작게 유지하라."
사실..함수형에서는 interface당 함수가 1:1의 관계이기에 ISP의 원칙을 위배하기란 쉽지 않습니다
프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다"의존성 주입은 이 원칙을 따르는 방법 중 하나다.
이 역시 직관적으로 이해해봅시다. 우리가 전기기구를 사용하기 위해서는 콘센트에 플러그를 꽃는 방법만 알면됩니다. 실제로 전기의 배선을 붙여가며 전기기구를 사용하지 않습니다. ❗️"전기를 이용하기 위해서는 플러그를 꽃으면 된다"(추상화)라는 추상화된 방법만 전달을 하고 있다면 ❗️플러그에서 실제 전기 배선이 어떻게 되던가에(구체화) 사용자는 관여하지 않아도 됩니다.
보통 우리가 보편적으로 많이 사용하고 있는 ReactComponent와 custom hook과 axios API 정도로 생각을 해보았습니다.
잘 만들어진 구조로 이해서 컴포넌트에서 서버의 데이터를 조작하기 위해서는 적절한 수준의 추상화와 레이어가 존재를 하게 됩니다.
axios의 레이어를 통해서 서버와의 데이터를 주고 받습니다. custom hook을 통해서 Component에서는 서버에 직접 호출하는 구체화보다는 그저 필요한 데이터를 요청하는 형식의 추상화된 layer 계층인 hook을 사용할 수 있습니다.
❗️이렇게 추상화된 레이어를 두는 이유는 컴포넌트 입장에서는 데이터가 필요한거지 그게 반드시 서버의 데이터일 필요는 없기 때문입니다. 이러한 레이어를 통해 언제든 서버가 아니라 로컬의 mock 데이터나 다른 방식으로도 사용하는 쪽의 코드를 변화없이 변경할 수 있게 됩니다.
❗️만약 component에서 axios를 호출하거나 fetch를 바로 호출을 한다면 구체적인 부분의 의존을 하면 안된다는 DIP 원칙에 어긋나기에 좋은 설계가 아닐 수 있다.