[참고]
채팅 웹 서비스를 만들기 시작한 지 벌써 1년이 되었다.
회사를 다니면서도 공부를 시작을 했던 것은 좋았다. 그렇지만 리액트가 어떤 이념을 갖고 만들어진 언어인지, 자바스크립트가 무슨 특성을 갖고 있는지, 타입스크립트를 사용하는 의의인지 하나도 모르고 시작했다.
리덕스도 얼레벌레 적용해서 네이밍이 엉망이었고, 어떤 데이터를 어떤 이름으로 저장하는지도 헷갈리는 지경에 이르렀다. 그런 상황에서 socket.io를 추가하니 눈덩이처럼 불어난 기술부채는 나를 압도했다. 머릿속에 떠오르는 문장은 단 하나 뿐이었다.
회사를 다니면서 편집자로 일하고, 저녁에 퇴근하고 집에서 개발을 했다. 그런데 기술에 관한 지식이 없는 상태에서 개발을 시작하니 무엇을 모르는지, 해결 방법을 어떤식으로 접근해야 하는지 계속 혼란스러웠다. 그리고 이 글을 쓰기 시작했다.
발단: 이벤트 식별자가 문자열이어서 멘탈이 터진 건에 대하여
node 환경에서의 라이프사이클 및 이벤트(event)라는 개념
채팅과 관련된 라이브러리인 socket.io
, REST 프레임워크인 express
, 심지어 웹 페이지에서의 ‘클릭’도 이벤트이다. 알고리즘 프로그램을 포함하여 내가 지금까지 경험한 코드들은 위에서 아래로 흐르는 코드 뿐이었다.
‘답은 하나 뿐. 갈아엎자.’
전개: 문서화의 시작과 redux
어디서부터 손을 대야할 지 몰랐지만 프로젝트가 스파게티 짬뽕이 된 이유 중 가장 분명한 것들부터 고치기 시작했다.
socket.io 이벤트별 기능 문서화
난관의 시작은 socket.io
를 도입하면서부터다.
같은 콜백함수(서버의
connection
이벤트) 내에서 어떻게 다른 플로우(클라이언트 연결 시socket
객체의 내용)가 생길 수 있는가?
나는 이제까진 typescript를 이용함으로써 socket.io
에
action명 개선 및 redux-actions 라이브러리 도입
action명을 보고도 무슨 데이터를 줘야 하는지 기억이 나지 않아서, action.payload
코드 한 번 보고, reducer
코드 한 번 보고. 한참 생각하다가 코드 한 줄 적고.
아오 헷갈려. redux의 store도 가독성이 엉망이구나. 이왕 이렇게 된 거 프로젝트 구조를 확실히 잡고 가자.
갈아엎기 위해선 아키텍처 공부, redux
의 action
정비를 위한 플로우 차트, 상태 다이어그램, 클래스 다이어그램, 네이밍 규칙 세우기가 필요했다.
위기
‘1기능 1함수’, ‘모듈과 컴포넌트의 관계’에 대한 것들은 학부 때부터 귀에 딱지가 앉을 정도로 들었던 말이지만, 실제 코드를 작성하면서 ‘1기능’을 내 맘속에서 정의하기가 너무나 어려웠다. 이를테면 채팅방 기능의 JSX 모듈을 만든다고 생각해 보자. 이 모듈을 크게 나눈다면 아래와 같을 것이다.
- 채팅방 정보를 표시하는
Header
영역 - 메시지를 표시하는
Message Container
영역 - 채팅 메시지를 입력하고 전송하는
Message Input
영역
간단한 코드로 나타내면 이렇게 된다.
1
2
3
4
5
6
7
8
9
function Chat() {
return (
<div>
<ChattingHeader/>
<MessageContainer/>
<MessageInput/>
</div>
)
}
여기까지 적고 나의 고민이 시작된다.
- 다른 사람의 메시지를 수신하는 이벤트는 어디에 달아야 하는가?
MessageContainer
일까,Chat
일까?ChattingHeader
에서도 인원 수 정보를 표기하려면axios.get()
을 어디에 달아주어야 할까? - 카톡을 생각하면
MessageContainer
에서도 들어오고 나가는 정보는 표기해줘야 할텐데,Chat
에 한꺼번에 달아주는 것이 맞을 것 같은데. 하지만MessageInput
에서는 필요 없는 정보이다. MessageInput
에서 보낸 메시지를MessageContainer
에서도 알아야 한다.- 아… 그러면
MessageContainer
과MessageInput
모듈을 합치는 게 낫지 않을까?
이런 저런 고민을 하다 보면 State에 불필요한 엘리먼트를 추가하고, Chat
모듈에서 실행하게 될 코드는 어마어마하게 늘어난다. 이 묵직한 코드 덩어리를 만들어 놓고 개인적으로 중요한 일들이 몇 가지 지나갔고, 틈틈이 코드를 쳐다보며 몇 달을 고민한 것 같다.
프로젝트 구조 재정립
이대로 두면 앞으로 나아갈 수 없을 것이라 생각하여 팔을 걷어붙였다. 자바스크립트의 이념을 이해해야 이걸 뜯어 고치려면 코드 작성 규칙을 확실히 만들고, 컴포넌트를 분리할 때의 명확한 기준이 필요했다. 소프트웨어 아키텍처, 클린 코드, 자바스크립트 몇 가지 책을 살펴보던 중 문득 깨달았다. ‘1기능 1함수’란 모듈 간의 ‘책임’을 분산시켜 디버깅과 유지보수를 편하게 하자는 의미였다는 것이란 것을. Chat
모듈의 코드를 다음과 같이 고쳐보기로 마음먹었다.
Chat
모듈은ChattingHeader
,MessageContainer
,MessageInput
을 묶는 모듈이다.
###
jest 도입
프로젝트에 모듈이 너무 많아졌다! 유닛 테스트 코드를 도입하자.
나는 jest 코드를 만들었을 때만 해도 이 위기에 훌륭하게 대처하고 있다고 생각했다.
절정
ESM과 CJS가 혼재된 끔찍한 코드를 만든 대가
라이브러리를 불러올 때 import 방식과 require 방식 중 하나로 통일되지 않은 데에는 이유가 있다. 두 방식은 ‘아예 다른’ 방식이었다. 동일하게 작동했기 때문에 크게 개의치 않고 작성해왔다. jest 테스트를 하면서 tsconfig.json
에서 옵션으로 줬던 es-module
에 의해
라이브러리 종속성에 의한 빌드 오류
jest에서 JSX 모듈을 테스트 할 수 없다니!
결말: 돌고 돌았지만…
해결하고 싶은 문제를 문장으로 적어두는 습관을 들이자
적어둔 문장에 우선순위를 매겨 정렬해 보자
바퀴부터 만들려 들지 말자
지금 생각하면 구현 기간을 생각해서 JSP로 후딱 조져도 되었을 것 같다.
공식 문서에서 하지 말라고 하면 하지 말자
결국 애플리케이션을 만들 때 설계는 반드시 필요한 작업이며, 코드를 만드는 것은 설계를 이행하는 것 뿐이다.
빠르게 만들기 위한 방법은 제대로 만드는 것, 하나 뿐이다.
하지만 ‘Doing something is better than nothing’인지라, 1년동안 계획만 짜고 있었을 수 있다고 생각하니 마음이 편하다.
- 내가 겪고 있는 어려움의 본질은 무엇인가? 문법을 모르는가, 해당 라이브러리의 이념을 모르는가(해당 분야의 생태계)
스스로 해결하는 것 vs 누군가에게 조언을 구하는 것
중 어떤 방법이 조직 방면에서 효율적으로 작용하는가?- 스스로 해결하되 다른 이에게 주는 귀찮음을 최소화하려면 어떻게 해야 하는가?
- 이 질문에 대답을 줄 수 있는 적절하고 적당한 상대는 누구인가
다양한 에러를 경험해봤으니 다음엔 더 잘 대응할 수 있으리라는 믿음을 가져본다… 게다가 이제 와서 후회해본들 돌이킬 수 없으니 다음 프로젝트에서는 꼭꼭 실행 순서 지켜서 만들어보기.