토스 Frontend Fundamentals 메모

가독성이라는 게 환상이라는 말도 있지만 요즘 읽기 좋은 코드를 짜기 위해 생각이 많다. 그래서 많이 들었지만 읽은 적이 없는, 토스에서 나온 Frontend Fundamentals 문서를 읽어보기로 했다.

https://frontend-fundamentals.com/code-quality/

좋은 코드란 결국 변경하기 쉬운 코드. 그 기준은

단 이 기준들이 상충하는 경우가 있다. 중복 코드를 허용하면 가독성이 좋아지고 코드의 영향 범위가 줄어들 수 있지만 중복된 코드가 다함께 수정되어야 하는 경우가 있을 수 있어 응집도가 떨어진다. 이런 결정은 정답이 없고 늘 고민이 필요

가독성

컴포넌트 하나에는 한번에 실행되는 코드만. 결국 한번에 고려해야 할 맥락을 줄이고 위에서 아래로 자연스럽게 읽히는 코드가 되어야 한다는 것.

권한에 따라 분기를 쳐서 서로 다른 컴포넌트를 보여주는 컴포넌트인데 만약 useEffect 같은 데서도 권한에 따라 다른동작을 한다면 위에서 아래로 읽히는 흐름이 깨진다.

물론 권한에 따라 보여주는 컴포넌트가 같은 역할일 수도 있는데, 이럴 경우 컴포넌트 자체를 분리하면 나중에 둘 다 수정할 때 고려할 맥락이 많아지고 응집도를 떨어뜨릴 수도 있다. 어떤 걸 컴포넌트의 단위로 보느냐에 따라 트레이드오프가 있다.

코드에서 한 번에 고려해야 할 맥락 줄이기. 가령 초대에 쓰이는 버튼이 있다면 handleInviteButtonClick 같은 함수를 Button 컴포넌트에 넘기는 방법도 있지만, 이 경우 버튼 컴포넌트의 위치(return문 내부)와 함수의 위치가 멀어져서 맥락이 분리된다.

InviteButton 같은 컴포넌트를 만들어서 버튼 컴포넌트의 위치와 함수의 위치를 가깝게 유지하면 코드를 읽으면서 한번에 고려해야 할 맥락이 줄어든다.

이렇게 한번에 고려할 맥락을 줄이기 위해서는 추상화가 필요하다.

토스 프론트엔드 챕터에서는 선언적인 코드를 “추상화 레벨이 높아진 코드”로 생각하고 있습니다.

https://toss.tech/article/frontend-declarative-code

물론 추상화를 많이 한다고 좋은 건 아니다. 많은 걸 하나의 컴포넌트로 표현할 수 있지만 그 대가로 props가 끔찍하게 많아진 코드를 본 적도 많으니까.

역할을 좀 더 작은 단위로 나누기. 예를 들어 "페이지의 모든 쿼리 파라미터 관리" 보다는 "페이지의 특정 쿼리 파라미터 관리" 같은 식으로.

이 역시 한번에 고려할 맥락을 줄이기.

복잡한 조건문이나 로직이 있으면 중간중간 설명하는 변수를 만들어서 이름을 붙이기. 예를 들어 여러 유효성 검사 로직이 있는 form이라면 isEmailValid, isPasswordStrong 같은 변수를 만들어서 각 조건의 의미를 명확히 할 수 있다. 또한 이들을 모두 엮는 canSubmit 같은 변수를 만들어서 전체 form의 유효성을 판단할 수도 있다. 그냥 모든 로직을 한 줄에 넣는 것보다 훨씬 가독성이 좋아진다.

tidy first?나 리액트 공식 문서에서도 비슷한 얘기를 한다.

이건 응집도 - 매직넘버 없애기에도 나옴

위에서 아래로 읽히는 흐름을 깨는 시점 이동을 줄이기. 조건을 컴포넌트 렌더링을 하는 코드에 그대로 넣거나 (disabled = {role !== 'admin'}처럼) 조건을 다루는 로직을 컴포넌트 내에서 따로 선언한 객체로 빼내서 한눈에 볼 수 있게 하는 방법이 있다.

당연한 얘기지만 복잡한 삼항 연산자보다는 if문이나 switch문을 사용하는 게 좋다. 삼항 연산자가 중첩되면 가독성을 떨어뜨릴 수 있다.

조건부 렌더링 처리의 좋은 방식에 관한 토론도 명예의 전당에 올라있다

https://github.com/toss/frontend-fundamentals/discussions/4

개인적으로는 위에서 아래로 읽을 수 있는 흐름이 좋다고 생각하기에 거기 제시된 대부분의 방식에서 <If> 컴포넌트 같은 거만 빼고 좋아한다.. 다만 팀 내에서 일관성 있게 작성하는 게 중요하다고 생각한다.

예측 가능성

같은 이름을 갖는 함수나 변수는 동일한 동작을 하도록, 예측할 수 있는 코드를 작성하는 것이 중요하다.

말 그대로다. 가령 useQuery훅을 사용해서 쿼리 결과를 가져오는 함수가 있다고 할 때 어떤 곳에서는 await을 해서 사용하고, 어떤 곳에서는 result.data를 리턴하고 그런 식이라면 예측하기 어려운 코드가 된다.

따라서 같은 종류의 함수는 반환 타입을 통일하는 게 좋다.

"일관성이 왕이다"

함수나 컴포넌트의 외부만 보고 알 수 없는 숨은 로직은 드러내는 게 한눈에 파악 가능한 코드에 좋다.

가령 fetch 요청을 보내는 함수에 로깅을 같이 넣는 게 아니라 로깅을 위해 감싸는 함수를 따로 만들거나, 이벤트 핸들러에 로깅을 넣거나, 아니면 모든 API 호출 시 로깅이 필요하다면 미들웨어 등으로 해결해볼 수 있다.

응집도

응집도를 높이기 위한 것. 함께 수정되는 코드 파일을 하나의 디렉토리 아래에 두어 코드 사이의 의존관계를 파악히기 쉽게 하기

위와 똑같다. 매직 넘버에 의미를 갖는 이름을 부여하는 것이 응집도를 높이기도 한다. 수정되어야 할 코드를 한 곳에(매직 넘버에 이름을 붙인 곳)에 모아두는 것이기 때문이다.

사용자가 값을 입력하는 폼은 어떻게 응집도를 관리할까?

필드별로 관리하는 방식이 있고 폼 전체 단위를 관리하는 방식이 있다.

필드별로 관리할 경우 각 필드가 고유의 검증 로직을 가진다. 각각이 독립적으로 동작하여 다른 필드에 영향을 안 주고, 비슷한 필드가 여러 곳에서 쓰인다면 재사용도 가능

폼 전체 응집은 모든 필드의 검증로직이 폼에 종속된다. 전체 폼의 검증이 한 곳에서 관리되므로 로직이 간결해지고 전체 그림을 보기 쉬워진다. 다만 폼 결합도가 높아지므로 재사용성은 떨어질 수 있다. 필드 간 의존성이 있거나 복잡한 폼의 경우 장점이 있다.

결합도

역시 함수가 하나의 책임만 지도록, 너무 많은 책임을 지지 않도록 관리하는 것. 가독성 파트에도 있다.

여러 곳에서 반복해서 쓰이는 코드를 훅이나 컴포넌트로 공통화하는 경우가 많다. 그런데 이런 코드의 요구사항도 변경되기 마련이고 그러다 보면 공통화한 코드가 점점 props가 많아지고 복잡해지는 경우가 발생한다.

즉 경우에 따라 다르겠지만, 이후의 코드 변경을 생각하여 중복 코드를 허용하는 것도 고려할 수 있다.

여기 댓글에 좋은 글이 많음

https://frontend-fundamentals.com/code-quality/code/examples/use-bottom-sheet.html

children을 받는 조합 컴포넌트를 이용해서 없애거나 props drilling을 없애는 다른 방법(render props 등)을 고려해 보고 content API를 사용하는 걸 마지막으로 고려.

이건 댓글이 진짜다

https://frontend-fundamentals.com/code-quality/code/examples/item-edit-modal.html

추후 관련 문서들을 보며 정리 예정

전역 상태를 사용하는 기준이 있으신가요? 등등