목표와 회고

우테코 레벨1 학습 내용 총정리

Creative_Lee 2023. 4. 6. 21:56

레벨1 학습 내용 총정리🤪

말도 많고 탈도 많았던 레벨1을 성공적으로 마무리 했다.

폭풍처럼 지나간 8주... 배운 것이 정말 많다. 

방대한 학습 내용을 정리하며 지식을 조각모음 해보는 시간을 가져보자!


그래서 뭘 배웠어?🤷‍♂️

개인적으로 몰입했던 키워드는 다음과 같다.

  • 관심사의 분리(feat. MVC 패턴)
  • 컴포넌트
  • 테스트 

1. MVC 패턴

다음은 MDN에 나와있는 MVC 패턴의 정의이다.

MVC는 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 소프트웨어 디자인 패턴입니다.
소프트웨어의 비즈니스 로직과 화면을 구분하는데 중점을 두고 있습니다. 

1-1. why MVC?🧐

'MVC? 프론트엔드에 적용하기엔 구리다는데...' 숱하게 들었던 말이다. 

 

하지만 '비즈니스 로직과 UI로직의 분리'에 대해 감이 오지 않았다.

전체 미션 요구사항 중 어쩌면 가장 중요하다고 할 수 있었던 핵심이었다.

때문에 많은 사람들의 노력으로 만들어진 정형화된 패턴을 따라 코드를 작성하며 감을 잡고 싶었다.

그중 레퍼런스가 가장 많았고, 여러 디자인 패턴의 근본이 된다고 생각하여 MVC 패턴을 선택해 도입하게 되었다.

 

이를 미션에 적용하며 직접 느낀 장점, 단점은 다음과 같다.


1-2. MVC 패턴의 장점😏

  • 명확한 관심사의 분리

console 기반 App을 web 기반으로 변경하는 과정에서 위 장점을 체감했다.

1단계에서 구현한 도메인 로직을 (최대한) 수정하지 않고, UI만 변경한다.
- 로또 미션 2단계 요구사항 중 -

View를 변경함에 있어서, domain로직의 큰 변화 없이 거의 모든 로직을 재사용할 수 있었다.

서로의 관심사가 잘 나뉘어져 있었기에 가능한 일이었다.


  • 가독성 및 코드 흐름 개선

디렉터리 및 파일이 각 역할에 맞게 분리되었고, 이에따라 자연스레 코드의 가독성이 좋아졌다.

App의 전반적인 흐름 또한 파악하기 쉬웠다.

패턴 도입 이전의 코드와 비교하면 정말 월등히 좋아졌다고 생각했다.

 

그렇다면 단점은 무엇이었을까?🙄


1-3. MVC 패턴의 단점(web)😫

  • 너무 많은 View 로직

web 환경에서는 DOM 조작, 사용자 입력 등 컨트롤해야 할 View로직이 너무 많았다.

이에 따라 자연스럽게 View가 커졌고, 이를 분리해야 했다.

App의 규모가 조금만 커져도 무수히 많은 View가 필요했을 것이고, 관리가 힘들었을 것이다.


  • 자연스레 커진 Controller

View가 많아지면서, 자연스레 이를 관리하는 Controller 또한 커졌다.

View와 Model 사이의 흐름 제어 역할을 수행하기에 원래도 비교적 로직이 많고 복잡했는데, 더 커졌다.

점차 가독성이 나빠졌다.


  • 이벤트 관련 코드를 View, Controller로 나누기 애매함

사용자와 인터렉션하는 코드를 작성할 때 View와  Controller의 경계가 명확하지 않았다.

input요소로 사용자 입력을 받아 Model을 변화시키려고 한다.
이때 위 이벤트의 트리거는 button 요소의 click이다.  
document.querySelector('button').addEventListener('click', (e) => {
	// input 요소의 값을 통해 Model 로직 실행 
})

button의 클릭이 곧 이벤트의 발생이고, 이벤트의 발생이 곧 Model의 변화였기 때문에

이벤트 등록 코드 및 핸들러 코드의 위치를 나누기 애매했다.

Controller의 개입이 애매하지 않나... 생각했다. 


1-4. 결국 중요한것은 관심사의 분리🧐

MVC를 web에서 적용했을 때의 단점을 체감한 후 더 이상 사용하고 싶지 않았다.

마침 컴포넌트 기반의 미션이 주어지면서 또 한번 사고의 전환이 필요했고, 조금씩 MVC의 틀을 허물게 되었다.  

 

이후 제일 중요한 핵심은 '관심사의 분리' 라고 생각했고,

'비즈니스, UI 로직 분리'와 같은 본질적인 부분에 집중하기로 했다.


2. 컴포넌트

재사용 가능한 독립 모듈, App을 구성하는 기본단위이다.

블럭!


2-1. 컴포넌트에 대한 관점 변화🤔

컴포넌트 기반의 사고를 요구했던 미션들을 통해 다음과 같이 개념을 확장했다.

  • 미션3 초반) 도메인 데이터를 통해 동적으로 랜더 가능하면 컴포넌트!
  • 미션3 중반) 상수 데이터로 랜더 가능한, 반복되는 태그들(ex. select - option)도 분리 - 이것도 컴포넌트!
  • 미션3 마지막) 자신의 이벤트를 가지면서 핸들러로직을 주입받아 랜더 + 이벤트 등록 가능해야 컴포넌트!
  • 미션4) 메인과 관련없이 재사용 가능한 범용 컴포넌트 VS 도메인 종속적인 컴포넌트

2-2. 범용 컴포넌트 vs 도메인 종속 컴포넌트

범용 컴포넌트도메인 종속 컴포넌트 개인적으로 가장 의미있었고, 또한 중요한 개념이라고 생각한다.

어쩌면 당연한 개념이지만, 미션중에는 개념이 확립되지 않고  마구 뒤섞여 혼동이 왔었다. 

 

미션4 step2 PR에 첨부한 구조도를 보면 혼동과 고통의 흔적이 남아있다.  

장장 3일간의 사투를 복기해보자.


2-2-1. 바라던 구조(하위 컴포넌트를 상위에서 전달 / 범용 컴포넌트)

바라던 구조

MovieList, Modal처럼 컨텐츠가 있는 컴포넌트는

상위 컴포넌트인 App을 통해 하위 컴포넌트(Card, DetailCard)를 전달 받는 구조로 구성하고 싶었다.

이유는 간단하다.

Modal은 컨텐츠가 무엇인지는 관심이 없고, 어떤 컨텐츠던 전달 받아 랜더링 하는 역할만 가지도록 하고 싶었다.

즉, 도메인에 종속되지 않고 재사용 가능한 범용 컴포넌트로 만들고 싶었다는 말이다.

 

피드백을 받기 전까지는 위 구조가 맞다고 생각했다.

반드시 전달 하는 구조로 구성하고 싶었지만, 결과는 실패

읽는 입장에서 여러 의문이 들겠지만 잠시 접어두고 최종 구조를 먼저 보자.


2-2-2. 최종 구조(하위 컴포넌트를 직접 import / 도메인 종속 컴포넌트)

최종 구조

MovieList, Modal 컴포넌트가 하위 컴포넌트(Card, DetailCard)import 하여 직접적으로 알고 있다.

import를 통해 컨텐츠를 직접 알아버리면, 도메인 종속적인 컴포넌트가 되어버려 재사용 할 수 없다...

 

피드백을 받기 전까지는 틀린 구조라고 생각했다.


2-2-3. 간단했던 해결 방법😧

오랜 시간 고민했던 부분이고, 틀렸다고 생각했기에 리뷰어어게 도움을 청했다.

다음은 리뷰어의 피드백 내용중 일부이다.

최종 구조가 일반적인 컴포넌트 멘탈모델에 부합하지 않나 싶은데...
모든 컴포넌트가 App에서 관리되어야한다고 생각한 이유가 궁금합니다.

Modal을 extend하는 MovieDetailModal을 별도로 구성해보면 어떨까 싶어요.
그렇게한다면 Modal자체는 도메인을 알지 않는 일반적인 UI컴포넌트로 남을 수 있을거라는 생각이 있어요. 

오히려 최종 구조가 일반적이라는 말씀을 해주셨다.

Modal은 내가 원하던 대로 범용적으로 유지하고,

이를 상속받는 도메인 종속적인 컴포넌트를 만들어 사용하면 되는 것이었다...!

고민이 무색하게도... 해결방법은 간단했다.


2-2-4. 틀리지 않았다. 정답도 없다.

Modal을 범용 컴포넌트로 잘 설계 했음에도,

'상위에서 컨텐츠를 전달받아야 한다'라는 스스로 세운 규칙 때문에 더 나아가지 못했다.

행동을 과하게 제한하는 규칙에 대해 의심을 하지 못했다.

오히려 규칙에 맞지 않는 방법들을 틀렸다고 단정지었다...🤦‍♂️   

 

이번 삽질 경험으로 범용 컴포넌트, 도메인 종속 컴포넌트에 대한 개념을 확실하게 채울 수 있었다.

또한 현재 컴포넌트에 대한 개념을 실시간으로 확장중이다.

컴포넌트와 관련된 여러 아티클을 보며 생각을 넓혀나가고 있다. 

 

실질적인 코드의 차이 없이 이름만으로도 구성이 맞고 틀리고가 가능하다는걸 알아가면 좋지 않을까 생각해봅니다.
- 리뷰어의 한마디 -

레벨1에서의 삽질이 레벨2의 나에게 큰 도움이 될거라고 생각한다.


3. 테스트

작성한 코드가 의도대로 동작하는 지 검증할 수 있는 수단이다.
개발자들에게는 프로덕션 코드의 설명서 역할로 사용되기도 하고, 코드에 대한 검증을 통해 심리적 안정을 주기도 한다.
리팩토링이 필요한 상황에서 유연한 대응을 도와주는 역할도 한다.

테스트 피라미드


3-1. 단위 테스트(Unit) - 테스트 하기 좋은코드

단위테스트의 장점들을 체감한 이후로는, '테스트 하기 좋은 코드란 무엇인가?' 에 대해 고민했다. 

내가 생각하는 테스트하기 좋은 코드는 다음과 같다.

  • 같은 input에 대해 같은 output을 보장하는 코드(함수)
    • output을 예측하기 쉬운 코드 === 코드가 의도대로 동작하는지 검증하기 쉽다. 
  • 제어할 수 없는 값, 동작에 의존하지 않는 코드
    • 제어할 수 없는 값, 동작 -> Random / Date , 사용자 input, API 호출 등의 코드
      테스트 대상 코드(함수)에 위 동작이 혼재하면, 테스트 하기 어렵다. (mocking등 추가적인 처리를 해주어야 함)

제어할 수 있는 값(제어해야 하는 값)만 제어하는 코드는 관심사가 잘 분리되었을 확률이 높고

이런 코드는 다시 테스트 하기 좋은 코드로 귀결된다.

즉, 테스트 하기 좋은 코드 == 좋은 코드라고 생각한다.

 

단위테스트 시 mocking이 필요하다면, 먼저 관심사가 제대로 분리되지 않은 코드인지 생각해 보자.

다행히?! 아직까지는 mocking이 필요한 적이 없었다. (Domain 로직에 대해서는 분리를 잘 하지 않았나...) 

앞으로도 최대한 경계해 볼 생각이다.


3-2. E2E 테스트(feat. cypress)

생각보다 어렵지 않았던 E2E 테스트(빙산의 일각이라 그런 것 같다..ㅎ)

서비스를 이용하는 사용자의 입장에 서서 flow를 테스트 했다.

 

API 요청에 대해 미리 만들어 놓은 고정된 응답(fixture)을 받게 해주는 intercept()도 사용해 보았다.

cypress Custom command를 만들어 가독성을 높여보기도 했다.

 

하지만 E2E 테스트를 작성하며 의미있는 수준의 장, 단점을 느끼지는 못했다.

unit 테스트에 비해 아직 경험치가 낮아 그렇다고 생각한다.

레벨2 미션에서도 추가로 적용하고 학습해 보자. 


🍚 꽉꽉 눌러담은 레벨1 🍚

아쉬운 부분이 있긴하지만 정말 꽉~ 채워 학습했다.

푸짐하다 푸짐해!

총 정리 포스팅을 통해 학습했던 지식이 넘쳐 흐르지 않도록

잘 눌러 담은 것 같아 기분이 좋다.

잘 쉬고 잘 소화해서 레벨2에도 가득 먹어보자!