ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 원티드 12월 프론트엔드 챌린지 3일차
    Front-end 개발 2023. 12. 12. 23:24

    프론트엔드 챌린지 12월

     

    인트로

    - 비즈니스 로직은 무언가 엮여 있어서 다른 곳으로 이동해서 사용하기 어려운 것

    - 아무리 쪼개서 Props만 많이지고 추상화가 되는 것 같지 않다?

    - 제어권 역전을 사용하면 제법 멋드러지게 추상화를 할 수 있다.

    - 클린 아키텍처를 읽기 위한 2번의 시도 중에 SOLID 개념을 알아야 했다.

     

    SOLID한 컴포넌트 만들기

    1. 내 손에 익은 공구상자

    - 공구가 들어있는 인생 툴벨트를 차고 다니는 이유는 필요할 때 바로 쓸 수 있도록 하기 위함이다.

    - 내가 만드는 컴포넌트을 만드는 시간을 줄일 수 있을까? 뭘 쓸지 검색할 시간? 쉽게 가져다가 쓸 수 있을까?

    - 리드 타임을 줄일 수 있을까? 내가 컴포넌트를 만들 때 프로페셔널하게 만들 수 있을까?

    - 내가 쓰는 도구들로 인해 그런 일이 발생할 수 있으므로 항상 잘 챙겨 다녀야 한다. (도구 가지려 집에 가기...)

    - React로 정성껏 코드를 짠다면 어떤 도구들을 사용할 것인가

    - 전역 상태 라이브러리 (jotai, zustand)

    - 서버 상태 관리용 라이브러리 (tanstack query, SWR)

    - CSS-in-JS (emotion), tailwind

    - Next.js App Router

     

    2. 장인은 같은 도구도 다르게 쓴다.

    - radix에서 Dialog 컴포넌트를 사용하여 모달 요소를 그리는 예제를 보며 캡슐화된 컴포넌트를 확인할 수 있다.

    - 컴파운드 패턴 컴포넌트를 통해서 구현했고, 높은 응집도(Cohesion)과 낮은 결합도(Coupling)을 가지고 있다.

    - radix의 추상화 철학을 꼭 읽어보고 살펴보시라

    - asChild prop을 사용한다면 별다른 처리 없이도 Trigger와 Close 컴포넌트가 가진 기능을 children으로 넣어준 요소에 전달하여 간단히 컴포넌트 합성(Composition)을 할 수 있다.

    - Context API를 사용할 때 constate와 같은 보조 도구를 사용하면 useState 처럼 쉽게 사용할 수 있다.

    - Class의 경우 어색하고 함수형 프로그래밍인 React 답지 않다고 생각한다.

    - Class 문법이 가지고 있는 장점과 한계를 명확히 인식하고 사용하는 경우엔 위와 같은 이유는 너무 빈약한 이유다.

    - Class란 '상태와 액션을 결합하고 캡슐화하는 문법적인 도구'이다.

    - 우리가 흔히 '별로야'라는 도구들 조차도 코드를 작성하는 원리와 목적에 맞게만 사용하다면 좋은 결과를 낼 수 있다.

    - 도구의 장단점을 상황에 맞게 사용해야 한다.

     

    3. React Hook 캡슐화 vs Class 캡슐화

    - React 라이브러리에 종속되고, React 렌더링(생명주기)에 얽매이게 된다.

    - React에서는 불변성을 매우 중요하게 생각하고 있다.

    - 상태가 없다면 클래스 없이 모듈화만으로 캡슐화하는 방법도 괜찮다.

     

    SOLID 라는 이름의 통찰에 관하여

    1. 프론트엔드에서 구조를 추구하면 안되는 것까?

    - 비즈니스 로직, 캡슐화, 모듈성, 추상화 ... 등등을 고민해봤는데 그걸 어떻게 하냐? 차근차근 해보자

    - SOILD 원칙이 먼 나라 이야기가 아니라 내가 어제 고민했던 문제의 '중요한 해결책'으로 다가오도록 만들어주는 마법의 조미료가 되어줄 것이다.

    - 혹자는 프론트엔드에서 아키텍쳐가 필요없다고 말하며, React 커스텀 훅만 있으면 된다? UI는 자주 변경되므로 구조를 적용하는 것은 오버 엔지니어링이다. 백엔드 개발자들이 쓴느 것이고 함수 중심인 프론트엔드에는 적절치 않다. (지식이 부족해 수긍하지 말자)

    - 아직 정립되지 않았을 뿐, 구조가 필요하다. 엔터프라이즈 급의 성숙도가 높아질 수록 워라벨과 기술적 성장을 위해서는 아키텍쳐가 필요하다.

    - 우리의 코드가 유지보수 하기에 적절한 상태인가 자문해보았으면 좋겠다. 강사님은 구조 뭐든 대책이 필요하다고 느꼈다.

    - 여러가지 방법을 찾다보니 비슷한 위기감을 느낀 선배 개발자들이 이미 제품을 만들 때 지키면 좋을 원칙들에 대해 오랜 시간 열심히 고민해왔음을 알 수 있었다. 그런 노력들 속에 많은 이론과 원칙들이 탄생했는데, 이론이 먼저 존재했다기 보다는 실무 속에서 겪었던 어려움들을 여러 각도로 정의하고 재해석 하면서 다음어 온것에 가까웠다.

    물론 어떤 구조든 그 정도가 어떻든 본질적으로는 ‘제약’을 두는 것이기 때문에 혼란스럽고 불편하게 느껴질 수 있습니다. 구조를 적용하기에 지금이 적절치 않은 시점일 수도 있습니다. 하지만 ‘프론트엔드는 너무 빨리 변화한다’며 깊이의 부족을 합리화 하거나 무용론을 펼치는 것이 바람직한 태도는 아닐 것 같습니다.

     

    2. 레고가 하나의 블록에서 시작하다면, 개발은? 

    - 초보일 수록 생각하는 시간이 짧고, 코드를 쓰는 시간이 길다. 숙련자는 반대이다.

    - 놀랍게도 정말 많은 회사의 프론트엔드 코드들이 이런 고민 없이 순전히 '눈물겨운 단순 노동'을 기반으로 만들어지고 유지보수 되고 있다는 사실에 대해 우리는 경각심을 넘어 비판 의식을 가져야 한다. 

     

    3. SOLID 원칙

    - SOLID는 다섯 가지 원칙의 이니셜을 따라 쉽게 기억할 수 있도록 만든 객체 지향적 코드 설계의 원칙들이다.

    - 하지만 꼭 객체 지향 패러다임 하에서만 사용할 수 있는 것은 아니다.

    - SRP : 단일 책임 원칙 (Single Responsibility Principle). 각 소프트웨어 모듈은 변경의 이유가 단 하나여야만 한다.

    - 하나의 모듈은 하나의 사용자 또는 이해관계자에 대해서만 책임져야 한다.

    - OCP :  개방-폐쇄 원칙 (Open-Closed Principle). 기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 소프트웨어 시스템을 쉽게 변경할 수 있다는 것

    - LSP : 리스코프 치환 원칙 (Liskov Substitution Principle)

    - ISP : 인터페이스 분리 원칙 (Interface Segregation Principle)

    - DIP : 의존성 역전 원칙 (Dependency Inversion Principle). 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안된다. 대신 세부사항이 정책에 의존해야 한다.

     

    4. SRP : 단일 책임 원칙

    - 포인트는 '그렇다면 이해관계자가 무엇을 원하는지 어떻게 알 수 있는가?'이다. 당연하게도 '소통'이다.

    - '시스템은 그것을 설계한 조직의 커뮤니케이션 구조를 반영한다' (콘웨이의 법칙)

    - 적극적인 소통을 통해 설계 단계(구현 이후가 아니라 초기 단계)에서 부터 컴포넌트를 어떻게 관리할지에 대한 논의가 반드시 필요한 이유이다.

    - SOLID 원칙은 사실상 SRP가 핵심이고 나머지는 SRP를 수식하는 내용이라고 할 정도로 중요한 원칙이므로 항상 유념하며 코드에 적용하자

    - '응집된(Cohesive)'라는 단어가 SRP를 암시한다. 단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성(cohesion)이다. (클린 아키텍처, 67p)

     

    5. OCP: 개발-폐쇄 원칙 (Open-Closed Principle)

    - '확장에는 열려 있고, 수정에는 닫혀 있도록 한다.'는 문장으로 정리될 수 있다.

    - 우리가 알고 있는 개념 중에 가장 이 원칙에 부합하는 걸 꼽으라면 단연 컴파운드 컴포넌트 패턴(CCP)이다.

    - 타입스크립트 업계 표준이라고 생각하시고 꼭 공부하시라

    - 기존 컴포넌트에 기능을 추가하거나 변경하고 싶을 때 코드를 전혀 건드리지 않고도 새로운 컴포넌트를 추가함으로써 기능을 수정하고 확장할 수 있는 설계가 된다.

     

    6. LSP: 리스코프 치환 원칙 (Liscov Substitution Principle)

    - LSP를 준수하는 로직에서는 공통적인 속성으로부터 파생된 요소들 간에 교체가 쉽다.

    - 대체 가능성: 하위 클래스의 인스턴스는 프로그램의 정확성을 해치지 않으면서 상위 클래스의 인스턴스를 대체할 수 있어야 한다.

    - 계약에 의한 설계: 하위 클래스는 상위 클래스가 정의한 계약(ex. 메소드 사양)을 준수해야 한다.

    - 행동 호환성: 하위 클래스는 상위 클래스의 행동(ex. 메소드가 반환하는 값의 범위, 예외 처리)을 보존해야 한다.

    - 내부 구현이 변경되더라도 외부 인터페이스와 행동은 변하지 않아야 한다.

     

    7. ISP: 인터페이스 분리 원칙 (Interface Segregation Principle)

    - 꼭 필요한 것에만 의존하도록 만들어야 한다는 원칙. props를 넘길 때 가급적 꼭 필요한 값들에 대해서만 넘기도록 하는 것

     

    8. DIP: 의존성 역전 원칙 (Dependency Inversion Principle)

    - 의존성 역전 원칙에서 말하는 '유연성이 극대화된 시스템'이란 소스 코드 의존성이 '추상(abstraction)'에 의존하며 '구체(concretion)'에는 의존하지 않는 시스템이다. (...) 구체적으로 변동성이 크다면 절대로 그 이름을 언급하지 말라. (클린 아키텍처, 92~94p)

    - 의존성 역전이란 '소스코드의 의존성이 제어흐름과는 반대 방향으로 역전되는 것'을 뜻한다.

    - 즉, 필요한 정보를 내부에서 구체적으로 정의하지 말고, 외부에서 추상의 형태로 주입받아 쓰라는 뜻이다.

    - 비즈니스 로직이 내부에 존재한다면, 확장성이 떨어지게 된다.

    - 코드에 DB를 mysql에서 postgreSQL이나 NoSQL 등으로 교체할 수도 있다.

    - JWT 외의 다른 인증 방식의 토큰 서비스를 사용할 수도 있다.

    - 구현체를 인터페이스로 호출하여 해결할 수 있다. (구체적인 내용은 외부에서 정의) 

    - React 측면에서 DIP를 좀 더 언급해보자면, 컴포넌트에 props를 넘겨주거나 Context API로 값을 주입해주는 것, children으로 합성하는 것 모두 DIP의 일종으로 볼 수 있다.

    - 다만 이런 방법들은 DIP를 구현할 수 있는 수단이지 항상 DIP를 온전하게 구현하고 있다고는 볼 수 없다.

    - 왜냐하면 우리가 일반적으로 외부에서 컴포넌트 내부로 값을 전달할 때 매우 구체적인 값들을 전달하는 경우가 빈번하기 때문이다. 중요한 것은 '의존성'이고, 의존성이 추상적일수록 컴포넌트는 자유를 얻어 더 다양한 상황에 유연하게 대응할 수 있는 능력을 가지게 된다.

    - 체크 리스트 : 인터페이스 기반 설계, 컨텍스트를 통한 의존성 주입, 유연성 및 확장성

     

    [실전 스킬3] 컴포넌트 SOLID 시공법

    1. 제어의 역전 (IoC, Inversion of control)

    - SOLID에는 속하지 않지만 이 모든 원칙들을 관통하는 또 하나의 주용한 개념으로서 IoC를 꼽을 수 있다.

    - 콜백 등의 패턴을 사용하여 함수 외부로 제어권을 넘기는 상황을 의미하여 우리가 HOF(Higher Order Function, 고차 함수) 등에서 흔히 발견할 수 있는 개념이다.

    - 이러한 일(아키텍처 안에 담긴 소프트웨어 시스템이 쉽게 개발, 배포, 운영, 유지보수되도록 만드는 것)을 용이하게 만들기 위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다. (클린 아키텍처, 142p)

    - 구체적인 구현보다 전체적인 설계와 인터페이스를 먼저 생각하고, 제어를 외부로 넘겨 '구현은 네가 해달라'는 미루기를 통하여 '미루기를 하며 개발하는 전략이라 볼 수 있다.

     

    2. React Component Patterns

    - 컴파운드 컴포넌트 패턴 (Compound Components Pattern). CCP는 OCP와 연관이 있다.

    - 확장에는 열려 있고, 수정에는 닫혀 있도록 한다. 수정보다는 추가하는 방식으로 확장할 수 있도록 하라는 원칙

     

    3. React Children Props

    - Children Props는 구현을 컴포넌트 내부에 정의하기 보다 제어권을 컴포넌트 외부로 넘겨 사용처에서 합성하도록 하는 컴포넌트 패턴이다.

    - SOLID 원칙을 근거로 패턴을 평가해볼 수 있다. (SRP, OCP, ISP, DIP) 

     

    4. Render Porps

    -  Render Props는 컴포넌트의 children prop 자리에 함수를 전달하고, 컴포넌트 내부에서는 이 함수의 인자로 값을 전달하는 패턴입니다. 모든 상황에서 필요하진 않겠지만 로직을 컴포넌트 내부로 캡슐화 하고, 그 결과물만 렌더링 로직에 넘겨주고 싶을 때는 좋은 선택지가 될 수 있다.

     

    React 컴포넌트 패턴들이 존재한다.

    SOLID와 IoC만 기억하자. 이것만 잘 알아두더라도 거의 대부분의 상황에서 설계 상의 좋고 나쁨에 대해 이야기 하고 있는 자신을 발견하고 놀라고 있을지도 모른다.

     

Designed by Tistory.