(썸네일 출처: @yeolyii 인스타)
지금까지 신입 개발자로서 항상 앞만 바라보며 '최적화', '확장성'
등의 키워드만 보면 눈이 뒤집어져서 돌진하곤 했었다.
어떻게 보면 현재 상황에 꼭 필요한 것이 아님에도 불구하고 뭔가 고차원적인 것처럼 보이거나 이제껏 본 적 없는 로직을 가지고 있는 코드라면 무조건 받아들이기에만 급급했던 것이다.
발전이라는 명분 하에 그 방향성이 확립되지 않은 신입으로서 종종 일어나는 일인 것 같다.
그리고 나는 이런 쪽에서 항상 선두주자였다.
IT동아리 DDD 활동이 끝나고, 프로젝트가 완전히 종료된 이후 내 코드를 처음부터 끝까지 되돌아보는 시간을 가져 보았다.
왜냐하면 나는 항상 코드를 짤 때 아래의 과정을 항상 반복해 왔다.
기한 내에 우선 돌아가게끔 만들고 → 이후 리팩토링
(사실 거의 안함)
즉, 프로젝트가 완성된 직후의 내 코드에는 분명 문제가 도사리고 있다는 것이다.
???: 구현 다 됐고 테스트도 별 문제 없으니 PR날릴게요.
아니나 다를까, 당시에는 아주 자랑스럽게 구현했던 코드에서 아주아주 큰 문제를 발견하였다.
그러므로 이번 기회에 처음으로 ‘최적화’, '확장성', '수치화' 라는 타이틀에 대한 강박을 벗어던지고
앞으로 나아가기보다, 오히려 기본으로 회귀하며 ‘좋은 코드’ 에 대해 고찰해볼 수 있는 경험을 가질 수 있었다.
'좋은 코드'란 정말 많은 의미를 함축하고 있다.
수많은 블로그에 쏟아지는 글 속에서 각자 '좋은 코드'에 대한 기준과 정의는 저마다 다를 수 있다. 어떤 이는 효율성을, 어떤 이는 가독성을, 또 다른 이는 선능을 우선시할 것이다.
그렇지만 나의 경우에 대입해 본다면, 무작정 앞으로 나아가지 않고 항상 기본으로부터 출발하는 것이었다.
항상 자극적인 문구로 스스로에 대한 성과를 어필하기 위해 개선된 수치나 최적화된 결과만 바라보며 앞으로만 달려왔지만
이번 기회에 처음으로 뒤돌아보며 내가 달려온 방향이 맞는지 회고를 해 보았고,
보여지는 것에 급급한 나에게 단순히 기능적으로 뛰어난 것에 머무는 것보다 가장 기본적이면서도 이해하기 쉬운 형태로 돌아가는 것이 필요했다.
난 신입 개발자를 준비하고 있으므로 아직은 현업 경험이 없다. 그러므로 넓은 인사이트를 가지고 있지는 않지만
작은 경험으로나마 생각해본 결과 아래의 코드가 좋은 코드라 생각한다.
" 코드는 혼자가 아닌 팀과 함께 만들어가는 것입니다.
팀원들이 쉽게 이해하고 유지보수할 수 있기 위해 아래의 원칙을 지킨 코드가 좋은 코드라고 생각합니다."
- YAGNI(예측해서 만들지 마라)
- KISS(최대한 간단하게 만들어라)
- DRY(반복해서 만들지 마라)
이처럼 팀원들이 쉽게 이해하고 수정할 수 있는 구조와 필요 이상의 복잡성을 배제한 간결함이 결국 팀 단위 목표에 기여할수 있는 좋은 코드며 이게 '좋은 코드' 라고 생각한다.
해당 프로젝트에서는 아래와 같이 각 카드에 대해 수정, 삭제 와 같은 메뉴를 사용하고 있었다.
근데 사진을 자세히 보면 뭔가 문제가 존재한다.
눈치 빠른 사람들은 바로 발견했듯이, 분명 각 카드마다 메뉴 모달은 독립적으로 열려야 하는데 사진을 보면 모든 카드의 메뉴가 동시에 열리고 있다.
원인은 간단하다.
그 당시 다른 모달창에 사용된 로직을 재사용하기 위해 각 메뉴 열림 상태를 Zustand로 관리했다
모달 호출 여부 상태를 하나의 전역 스토어로 관리했기 때문에, 모든 메뉴가 같이 호출되는 문제가 발생한 것이다.
당시 생각의 흐름을 서술해 보자면,
‘전역 상태’ 라는 기술과 그 당시 필요하지도 않은 상황에서 '확장성', '최적화' 키워드에 집착했다.
간단히 말하면 구조는 다음과 같다.
최상위 부모 컴포넌트 <FlyoutMenu />
에는 Context API로 메뉴 모달 호출 상태를 관리한다
이는 각 카드마다 메뉴 모달에 사용할 Context Provider를 매번 생성한 것이다.
중첩된 자식 컴포넌트로는 세부 카테고리들을 사용했다.
- <FlyoutMenu.ToggleButton>
(메뉴 호출 버튼)
- <FlyoutMenu.MenuItem>
(수정, 삭제 버튼)
각 메뉴를 담당하는 자식 컴포넌트에서는, 부모 컴포넌트의 모달 호출 상태를 구독하며 자기 자신을 조건부 렌더링한다.
벌써부터 코드가 요란하다.
중첩 컴포넌트 형태로, 최상위 컴포넌트에서 Context Provider 트리를 생성하고
내부에는 자식 컴포넌트로 메뉴 컨텐츠를 제공하고 있다.
자식 컴포넌트 내부에서는 최상위 컴포넌트의 Context 상태에 접근해서 자신만의 로직을 수행하게 된다. 즉, 일관성도 없고 매우 복잡한 구조이다.
위 코드를 적용했을 때, 별다른 문제 없이 동작이 아주 잘 됐다.
심지어 저 상태 그대로 출시까지 했었고 이후 이 프로젝트는 종료됐다.
이후 수많은 면접 준비를 하며 최소한 내가 어느 생각을 가지고 해당 기술들을 구현했는지 되짚어보는 시간을 가지게 되었다.
그러기 위해선 과거에 내가 구현한 기술들에 대해 다시 고찰해봐야만 했었고 다시 이 코드를 들여보다보는 순간 너무나도 잘못 되었다는 생각이 들었다.
불필요한 의존성이 존재한다.
FlyoutMenu의 호출 여부에 대한 Context를, FlyoutMenu가 아닌 외부 컴포넌트 (카드 컴포넌트인 CardItem)에서 호출하고 있다.
FlyoutMenu의 열림 및 닫힘에 대한 상태를 외부에 의존하지 않고 내부에서 독립적으로 처리해야 하는게 더 낫다고 생각한다.
❗❗ 아무리 생각해도 Context API를 여기서 쓸 필요가 없다. ⇒ compound components 패턴은 의미가 없다.
호출 상태를 Context API로 다뤄야 하는 이유가 전혀 없다.
자식 컴포넌트들 (토클 버튼, 수정 삭제 버튼)이 부모 컴포넌트의 상태까지 직접 접근할 필요가 없다.
그냥 부모 컴포넌트에서 호출 여부를 담당하는 단일 useState하나만 생성해서 자식 컴포넌트들을 조건부 렌더링하면 끝 아닌가?
그나마 의도했던 compound components 패턴의 확장성 역시 불필요했다
기존에 compound components 패턴을 도입한 이유는 확장성을 의도한 것이었다.
그러나 만약 메뉴 모달의 내부 로직이 변경된다면 (=수정, 삭제버튼에 대한 추가적인 로직이 적용 될 경우)
직접 <FlyoutMenu.MenuItem>
컴포넌트 내부로 들어가서 내부 로직을 일일이 수정해야 한다.
확장성 역시 실제로 의도한 것과 정 반대로 흘러가고 있다.
가독성이 최악이다.
간단한 메뉴 모달 하나 호출하는데 실제 코드가 100줄이 넘어간다.
심지어 각 메뉴에 대한 onClick 로직 역시 여러곳에서 중복돼서 다뤄지고 있다.
<FlyoutMenu.MenuList>
에도 onClick이 있고<CardItem />
에서 넘겨주는 button에도 onClick이 있다"YAGNI(예측해서 만들지 마라), KISS(최대한 간단하게 만들어라), DRY(반복해서 만들지 마라)." 원칙에 완전히 위배됐다.
이제 앞서 말한 나만의 깨달음을 바탕으로 한번 기존 코드를 개선해보자
메뉴 모달 호출 상태는 Context API대신 useState 훅으로 부모 컴포넌트에서만 일괄 처리한다.
즉, 메뉴 모달 컴포넌트 자체는 모달 호출 여부에 따라
띄워지고, 사라지는 역할만
담당하는 것이다.
이는 동시에 컴포넌트와 내부 컨텐츠 버튼들의 로직의 관심사를 의존성 없이 완벽히 분리할 수 있게 된다.
내부 컨텐츠와 그 로직들은 전부 외부에서 주입받는다
- 토글 버튼으로 사용할 버튼은 props로 넘겨받고
- 내부 메뉴 (수정, 삭제) 버튼들에 대한 내용은 외부에서 독립적으로 생성한 뒤, {children} 으로 주입받는다.
완성된 코드는 아래와 같다.
코드 이미지 크기부터가 다르다. 매우 간단해졌다.
<FlyoutMenu />
컴포넌트를 호출하고
FlyoutMenu 컴포넌트에서는 모달이 띄워지고 사라지는 역할만 담당한다.
내부 컨텐츠들은 외부에서 미리 생성해서 넘겨주게 되면 그것들을 렌더링만 해주는 것이다.
호출 상태에 대한 외부 의존성도 줄어들었고
불필요하게 거창한 Context api나 compound components 패턴 없이 아주 간결하게 처리하고 있다.
onClick에 대한 로직 역시 분산되지 않고 외부 주입 단계에서 일괄처리하고 있으며
심지어, 모달에 대한 내부 컨텐츠들에 대해 매번 FlyoutMenu컴포넌트 내부에 로직을 우겨넣지 않고
외부에서 전부 자유롭게 주입할 수 있다.
흔히들 말하는 관심사의 분리 역시 지켜지고 있다.
개발을 하면서 사실 앞으로만 나아가는 법을 배웠지만, 앞으로 나아가는 것보다
어느 방향성으로 나아가느냐가 더 중요하다는 것을 처음으로 체감하게 된 것 같다.
항상 ‘클린코드’, ‘가독성’ 이라는 단어를 볼 때마다 수동적인 자세로만 남들 하는대로만 따라하기에 급급했는데 보다 나만의 명분을 가지고 필요성을 느끼며 능동적으로 해결해보니 감회가 새롭다.
괜히 신입 개발자 면접에서 “너가 뭘 했냐” 보다 “너가 어느 생각을 가지고 있냐”를 더 중요하게 보는지 알 것 같다.
킹갓제네럴엠페러충무공마제스티하이퍼울트라판타스틱익스트림지니어스화룡정점마스터더엠비션리마스터성골판타스틱엘라스틱레전더리충무공6인궁 코드네요 ㄷㄷ