프로젝트 요구사항 중 수천~수만 개의 문장이 담긴 문서(document)에 문장 별로 책갈피(bookmark)한 문장을 별도 조회할 수 있는 기능이 추가되었고, 이에 대한 화면을 구현하는 중이었다. 서버에서는 다음과 같은 모양의 bookmark 데이터들이 담긴 배열을 보내준다. (참고로 데이터 필드명이나 구조는 프로젝트 코드와 정확히 일치하는 것이 아닌 포스팅을 읽는 이의 이해를 돕기 위한 예시이다.)
{
id: 1,
source: 'Before we begin.',
target: '시작하기 전에.',
memo: '',
document: {
id: 3,
name: 'Dance with Chance #1',
},
}
위와 같은 데이터가 담긴 배열(이하 bookmarks라고 통칭)을 GET 요청으로 받아온 다음 화면에는 전체 책갈피와 문서별 책갈피를 탭으로 보여주어야 했다.
이를 위해 다음과 같은 의사코드를 작성했다.
1. 서버에서 받아온 bookmarks 배열을 documentId별로 그루핑을 한 새로운 형태의 객체로 가공한다.
type BookmarksGroupedByDocument = {
all: { documentName: '전체'; bookmarks: Bookmark[] };
[documentId: number]: { documentName: string; bookmarks: Bookmark[] };
};
2. BookmarksGroupedByDocument 타입인 데이터를 Tab 컴포넌트의 data prop으로 내린다.
3. Tab 컴포넌트에서 탭 아이템들을 담는 배열인 items prop을 내릴 때
1) all 이 제일 앞으로 오게 정렬하고,
2) 나머지 documentId를 key로 가진 프로퍼티들은 최신순으로 정렬한다.
이 때 문제가 되는 부분은 '최신순 정렬'이었다. 최신순으로 정렬하기 위해서 처음에 생각한 방식은 documentId 정보를 활용하는 것이었다. 서버에서 보내주는 documentId 값은 숫자다. 처음 생성된 documentId 값은 0이고, 그 다음 생성된 documentId는 1 등 순차적으로 id 값이 +1씩 증가하는 식이다. 따라서 documentId 값이 클수록 최근에 생성된 문서이기 때문에 documentId를 기준으로 내림차순 정렬을 하면 최신순 정렬이 될 것이라고 생각했다.
하지만 이후에 documentId가 항상 숫자일 거라는 가정으로 작성된 코드에 위험이 존재하니 document의 createdAt 날짜 정보를 활용해서 정렬하는 게 좋겠다는 팀원 분의 피드백을 들었다. 서버가 없는 개인 프로젝트를 할 때에는 주로 uuid()를 사용해서 데이터에 아이디 값을 주었고, uuid에서 반환해 주는 id의 타입은 문자열이다. 하지만 서버에서 보내주는 아이디 값이 생성 시점에 따라 순차적으로 증가하는 숫자여도 충분히 고유성을 담보할 수 있다고 생각하고 이에 대해 의문을 품어본 적이 없다는 것을 처음으로 인지하게 되었다! (물론 삭제된 데이터로 인해 아이디 값이 비는 자리가 생겨도 새로 생성된 데이터는 빈 자리에 들어가는 것이 아니라 지금까지 생성된 마지막 숫자 값 + 1로 생성된다는 가정 하에서 숫자 아이디의 고유성이 보장된다.)
id 값이 숫자인 것에 오히려 익숙했다. 왜 그럴까 곰곰이 생각해보니 학창시절 학생 한 명 한 명을 식별하기 위한 학년/반/번호 체계도 숫자이고, 주민등록번호도 숫자 형태이기 때문이다. 하지만 주민등록번호라는 데이터를 입력으로 받는다면? 그 데이터는 숫자일까? 아니다. 'YYMMDD-G(성별)HIJK(지역코드)L(등록순서)X(검증번호)'라는 형태의 문자열이다. 연도-월-일 형태의 주민등록번호 앞 여섯자리 숫자만 봐도 '231203', '23-12-03', '23/12/03', 'Dec 12, 2023' 등 다양한 형태의 문자열로 데이터를 관리할 수 있다. 주민등록번호의 뒤 일곱자리도 '성별-지역고유번호-등록순서-검증번호'로 구성된 데이터일 뿐 이를 'female-seoul-randomString'이라고 처리해도 그 의미가 달라지지 않는다. 즉 이제까지 숫자라고 생각한 것은 사전에 약속된 코드일 뿐, 이를 숫자로 표현할지 문자열로 표현할지는 선택의 문제이다. 가격이나 사물의 길이 등 사칙연산을 할 수 있는 데이터를 생각해보면 똑같이 숫자로 적기는 하지만 주민등록번호와 데이터의 성격이 다르다는 것을 조금 더 쉽게 알 수 있을 것이다.
이러한 지점을 생각하면서 데이터베이스의 기본키(primary key)와 외래키(foreign key)라는 개념도 슬쩍 찾아보게 되었고, 성능 면에서 숫자 키와 문자열 키가 차이가 있다는 얘기도 귓동냥으로 들었다. 이번 에피소드를 통해서 '왜'라는 질문을 '왜' 끊임없이 해야하는지 다시 한 번 생각해 보는 계기가 되었다. 개발을 함에 있어서 당연한 것은 없다는 생각이 든다. '왜'라는 질문을 통해 문제의 근원에 한 발자국 다가가고, 각각의 선택지가 가진 장단점을 파악해 현 상황에서 최적이라고 판단한 선택지를 취하는 것이 프로그래밍인 것 같다.
+)
위의 bookmarks는 '최신순이란 무엇인가'라는 질문을 한 번 더 받았다. 처음 서버에서 받는 bookmarks 정보에 documentId 밖에 없었기 때문에 documentId가 생성 시점 기준으로 +1씩 증가하는 데이터라 document의 생성일이 최신의 기준이 된다고 생각했는데, 북마크 자체의 updatedAt을 최신순으로 정렬해야 하는 것 아니냐는 피드백을 받았다. 결국 서버에서 보내주는 데이터 필드에 updated 날짜를 추가하기로 했고, updatedAt을 기준으로 날짜를 비교할 수 있는 helper 함수를 작성해 정렬하는 방식으로 리팩토링을 했다는 후문.
function compareDatesMostRecentFirst(a: string, b: string): number {
return new Date(a).getTime() - new Date(b).getTime();
}
'돌멩이 하나 > 셀프 크리틱' 카테고리의 다른 글
무한 스크롤과의 사투 (2) - tanstack/react-virtual 적용 (0) | 2024.04.15 |
---|---|
무한 스크롤과의 사투 (1) (1) | 2024.03.31 |
다크 모드 - 화면 깜빡임(FOUC) 이슈 해결 및 시스템 설정과 연동 (0) | 2023.06.14 |
[기능 구현 챌린지] Chart Component (1) | 2023.05.22 |
to do list를 만들면서 한 고민들 (0) | 2023.02.09 |