깃허브 코드를 정리하고 싶다.
방법
- 깃허브 레포 링크를 gpt한테 준다.
- 해당 레포에 대한 기본 문서화를 진행한다.
- 각 파일에 대한 문서화를 진행한다.
- 리드미를 작성한다.
- 추가적으로 원하는 기능 또는 리팩토링 방향을 문서화한다.
- gpt한테 해당 기능에 대한 문서를 넘긴다.
- 소요시간 및 리소스에 대해 분석한다.
과거의 코드... 과거의 나야 어떻게 했니..?
제 깃허브를 보면 요즘 리액트로만 대부분의 프로젝트를, 아니 모든 프로젝트를 진행하고 있습니다.
리액트가 정말 정말 많이 쓰이는 것도 사실이나, 리액트는 결국 자바스크립트를 편하게 쓰기 위한 프레임워크에 불가하다고 생각합니다. 그래서 바닐라 자바스크립트(ECMA JS)를 사용해서 직접 DOM을 조작하고, 이벤트를 관리해보는 연습을 하고 싶었습니다. 더불어, 이벤트를 통한 DOM 조작을 통해 렌더링에 대한 기본 지식도 다시 상기시키고 싶었습니다.
리제로부터 시작하는(?) 투두리스트
기존 코드: https://github.com/Sleepingoff/dragDropTodoList/tree/main
역시 기본기는 투두리스트입니다. 제 레포에 이미 있는 프로젝트입니다. 무려 23년, 개발을 시작할 때 만든 레포로 여태까지 방치되어 있었죠. 그래서 이 친구를 리팩토링해야겠다! 라고 다짐하고 코드를 뜯어보는데... 주석을 달지 않았구나...
다행히 VSCode에는 코파일럿이 있다!! 일단 코파일럿에게 폴더를 통째로 때려넣고 문서화를 진행했습니다.
https://github.com/Sleepingoff/dragDropTodoList/blob/dev25/client/js/StatusManager.md
화면 구성
화면 구성은 불친절합니다. 상태와 할일을 입력하는 곳에 대한 별도의 설명도 없고, 할일을 입력하고 할일에 대한 상태를 어떻게 할당하는지도 나와있지 않습니다. 그래서 시나리오부터 정리해보려고 합니다.
- 할일을 입력한다.
- 상태를 입력한다.
- 할일을 드래그앤드랍으로 상태에 할당한다.
매우 쉬운 시나리오입니다. 여기에서 좀 더 구체적으로 들어가는 건 다이어그램으로 대체합니다.
여기에서
- 상태: true, false 로 할일을 활성화, 비활성화
- 할일: 텍스트로 이뤄진 사용자가 입력하는 값과 분류 및 상태에 대한 정보를 가지고 있는 데이터
- 분류: 텍스트로 이뤄진 사용자가 입력하는 값으로 인덱스(사용자가 정하는 순서)와 텍스트로 이뤄진 데이터
입니다.
즉, 기존에 가지고 있는 상태라는 개념을 상태와 분류로 다시 나눠서 확실히 역할을 분리할 계획입니다.
추가적으로,
- 초기 분류의 기본값은 1이며, 삭제 시 0이 된다.
- 0으로 분류 시 하루 후 데이터는 완전히 삭제
- 드래그앤드랍으로 분류 내 할일의 순서도 조정 가능
- 단, 0(삭제)는 제외
- 할일을 입력할 시 분류값을 먼저 정할 수 있다.
- 단, 0(삭제)는 제외
- 할일 리스트에는 할일을 추가할 수 있는 입력칸이 있다.
- 단, 0(삭제)는 제외
- 할일 데이터에는 언제든 세부 데이터가 생길 수 있다.
위의 규칙까지 고려해서 리팩토링을 해보려고 합니다.
*상태와 분류라는 개념이 나뉘어졌지만, 기존 코드에서 추가되는 기능은 거의 없기 때문에 리팩토링이라고 보고 있습니다.
확장하게 된다면,
- 대부분의 입력란에 마크다운 문법 적용 가능
- 분류, 상태에 색상 할당 가능
- 각 할일 리스트에 대한 파일로 내보내기 기능
- 각 할일 리스트에 대한 필터링 기능(높은 우선순위)
- 전체 할일 리스트에 대한 필터링 기능(낮은 우선순위)
- 상태별 필터링
- 날짜별 필터링
- 세부 데이터별 필터링
- ...
- 같은 할일 리스트들을 토대로 다른 대분류로 묶어서 표시 ex) 주간, 월간 등
- 챕터형식으로 완전히 별도의 할일 리스트들을 관리하는 기능
- ...
오, 리액트 쓰고 싶다!.. 언젠가 확장까지 가게 된다면 좋을 듯 하네요ㅎㅎ
데이터
이제, 데이터를 좀 더 자세하게 정리해보려고 합니다.
할일
{ id: int, timestamp: string, todo: string, active: bool, todo\_num: int, category\_num: int }
상태는 할일의 세부 데이터로 취급합니다.
분류
{ id: int, timestamp: string, category: string }\[\]
고민) 세부 데이터 추가되거나 삭제되는 경우는 어떻게 하지?
음.. 시간이 지나면서 여기에 추가가 되는 속성이 있다면..?
만약 버전 2에서 만든 할일이 버전 3에서 만든 할일과 속성이 다르다면..?
타입스크립트를 사용하지 않은 만큼 타입이 매우 자유로운 상태에서 관리가 잘 될까..?
일단 생각한 방법:
const todo\_default = {
id: 0,
timestamp: "0000",
todo: "todo",
active: true,
todo\_num: 0,
category\_num: 0
}
const todo\_v2 = {
...todo\_default,
category\_num: null, //category\_num 삭제
category: \[ 0 \] //분류 중복 허용
}
위와 같이 만들고 null 등 속성 값 삭제 혹은 대체를 위해 표시해둔 후 할일을 수정할 때 버전을 마이그레이션하기
-> 최종 데이터가 무엇인지 확인이 어렵지 않은가? 백엔드 데이터 스키마가 별도로 있다면 괜찮을 수도..?
gpt가 짚어준 문제점과 해결 방안:
- 실제로 데이터셋 전체에 적용하고 마이그레이션하는 로직이 없다면, 그냥 중복된 데이터 구조만 남게된다.
- 따라서, version 필드를 명시하는 방향으로 진행하는 걸 추천.
그럼, 버전 1에서 3이나 5 등으로 버전에 대한 차이가 많이 날 경우는 어떻게 할까?
- 누적 마이그레이션: 1 > 2 > 3 등으로 순차적으로 마이그레이션
- 버전의 변화가 명확하고 유지보수에도 유리하지만, 단계가 많아질수록 단계별 마이그레이션 함수가 늘어난다.
- 버전 차이가 클수록 마이그레이션하는 시간이 늘어난다.
- 단일 마이그레이션: 1 > 4 등으로 바로 마이그레이션
- 빠르게 최신 버전으로 전환이 가능하지만, 버전 간 세부 변화를 놓치면 문제가 발생할 수 있다.
- 모든 버전에 대해 최신 버전으로 변경하는 코드를 작성하는 시간이 길어진다. + 헷갈리면 망할 수 있음
음.. 누적 마이그레이션으로 안전하게 가보려고 합니다. 그리고 조금 더 생각해보면, 최신 버전은 5이고 사용자가 가진 할일 버전은 1인 경우, 사용자가 2버전에서 추가된 필드를 사용하고자 할 때엔 5가 아닌 2버전으로 마이그레이션해도 충분하지 않나 하는 생각이 들기도 하구요. 그리고.. 이 투두리스트.. 버전업을 그렇게 할까하는 생각도..ㅎㅎ (사실 버전 5에서 지원하는 기능 쓰려고 마이그레이션하는데 멋대로 버전 2로 해버리고 해당 기능에서 안되버리면 사용자 입장에서는 화가 나니 물론 바로 최신 버전으로 업그레이드하는 게 좋을 수도... 땅굴은 여기까지 파자)
나중에 버전마다 필드에 대한 변화 로그 관리하는 표 만들어보고 싶다
누적 마이그레이션에 대한 예시 코드(GPT)
function migrate\_v1\_to\_v2(todo) {
return {
...todo,
category: \[todo.category\_num ?? 0\],
category\_num: undefined,
version: 2
};
}
function migrate\_v2\_to\_v3(todo) {
return {
...todo,
tag: \[\], // v3에서는 태그가 추가되었다고 가정
version: 3
};
}
function migrateTodo(todo) {
let migrated = { ...todo };
let version = migrated.version ?? 1;
if (version === 1) {
migrated = migrate\_v1\_to\_v2(migrated);
version = 2;
}
if (version === 2) {
migrated = migrate\_v2\_to\_v3(migrated);
version = 3;
}
return migrated;
}
구조
기존에는 클래스를 데이터마다 매니저라는 이름으로 생성을 했습니다. 상태 매니저, 투두 매니저 등이 예입니다. 다만, 이렇게 되면 관리해야 할 데이터가 많아질수록 매우 비효율적인 코드가 될 것입니다. 그래서 데이터에 대한 CRUD에서 좀 더 보편적인 기능을 별도로 만들려고 합니다. 더불어 클래스가 아닌 함수 단위로 관리를 해보려고 합니다.
웹에서 만드는 이상 자바스크립트라는 언어는 불변할 것 같으니, 이를 기반으로 함수들을 만들고, 여기에 조금씩 변화가능성이 있는 것들을 첨부해볼까 합니다. (사실 쏙쏙 함수형 책에서 이런 느낌으로 말함)
아토믹 디자인 패턴처럼 함수도 작은 단위로 시작해서 필요할 때 끌어다가 쓰는 겁니다.
서버에 데이터를 요청하고, 받은 데이터를 토대로 UI를 그리는 과정에서 어떻게 나누면 좋을까?
과정보다는 책임에 초점을 맞춰야 하나?
어디선가 듣기엔 인터페이스 계층을 먼저 한다던데..? 음.. 자바였나..
음.. 코드를 직접 짜보기 전까진 모르겠네요. 전 병아리? 개발자이기 때문이죠!
그렇다면 어떻게 유지보수를 하고 싶냐라는 방향으로 생각을 해보려고 합니다. 일명, 내 욕심대로 해볼게!
- 서버에 요청하는 부분과 ui를 그리는 부분에 대해서 서로 간섭을 하지 않았으면 좋겠다!
아 물론, 데이터를 받아야 ui를 그린다는 경우가 많겠지만, ui를 수정하는 경우와 백엔드와 통신하는 부분을 수정하는 경우를 가능하면 별도로 유지보수하고 싶습니다. 지금은 ui를 그리는 것에 집중하고, 백엔드 쪽은 단순히 변수로 선언해두었다가, ui가 어느 정도 완료되면 파이어베이스를 붙이면서 통신하는 코드를 작성하고 싶습니다.
뭔가 예전에 백엔드에서 스네이크로 작성한 변수명?들을 받고 카멜케이스로 변경할 수 있는 방법을 봤던 것도 같아서 이 부분도 좀 더 찾아본 다음에 적용해보고 싶습니다.
- 어차피 파이어베이스를 써야 하니 데이터 버전에 대해 마이그레이션 하는 함수는 프론트에서 돌릴 수 밖에 없다. 그러니, 마이그레이션 함수를 건들 때, 마이그레이션 함수만 건들고 싶습니다.
버전에 대해서 먼저 선언하는 것이야 필요하겠지만, 마이그레이션이 필요하다고 하면 진짜 딱 그것만 작성하고 싶습니다. 마이그레이션을 위해 요청하거나 데이터를 받는 부분이 바뀌진 않았으면 좋겠습니다.
- DOM 이벤트는 확실하게 잡고 가고 싶습니다. 나름 이 프로젝트를 다시 건드는 이유도 이 프로젝트가 이벤트관련해서 많은 공부가 됐기 때문입니다.
나중에 DOM 이벤트 버블링이나 캡처링 현상을 시각적으로 보여주는 효과 넣고 싶다
지금은 index.js에서 DOM 관련 이벤트를 모두 써버려서 한 파일에 너무 많은 코드가 들어가서 읽기도 찾기도 어렵습니다. 만약 상태 매니저, 투두 매니저 이런식으로 가게 되어도 매니저 내부에서 이벤트를 모두 포함하고 관리하는 경우로 만들고 싶습니다. 그래서 index.js에는 초기화 등 최상위 계층 함수만 호출되는 간결한 형태를 유지하고 싶습니다. 그래서 전체적으로 어떤 프로세스로 돌아가는지 한눈에 확인을 하고 싶습니다.
그래서 이 내용을 고대로 ChatGPT와 상담을 했습니다.
✅ 1단계: 요구사항 정리
프로젝트를 유지보수하기 쉽게 만들고 싶다. 그래서 다음을 지향한다:
- 서버 통신과 UI 로직은 명확히 분리하고 싶다. (서로 간섭 없이)
- 데이터 마이그레이션(버전 업그레이드) 시, 마이그레이션 코드만 건드리고 싶다.
- DOM 이벤트 관리는 깔끔하게 하고 싶다. (index.js는 최소화)
- 함수형 스타일로 작은 단위 함수로 관리하고 싶다.
- 처음에는 백엔드 통신 없이 UI를 우선 완성하고, 이후에 서버를 붙이고 싶다.
✅ 2단계: 요구사항을 위한 기반 지식
위 요구사항을 만족시키기 위해 알아야 할 핵심 개념들:
책임 분리(Responsibility Separation)
➔services/
,handlers/
,renderers/
,utils/
로 파일과 기능을 명확히 나눈다.함수형 프로그래밍(Function Programming)
➔ 상태를 직접 바꾸지 않고, 불변 데이터를 새로 만들어 반환하는 방식을 지향한다.선언형 프로그래밍(Declarative Programming)
➔ "어떻게"가 아니라 "무엇"을 할지에 집중하는 코드를 작성한다. (map
,filter
,reduce
사용 등)초기 큰 흐름 작성 → 세부 함수 쪼개기
➔ 코드를 작성할 때는 먼저 전체 로직을 선언하고, 필요한 작은 함수들을 채워나간다.
✅ 3단계: 요구사항과 기반 지식을 결합한 실행 방안
실제 코딩할 때 이렇게 진행하면 된다:
큰 흐름(상위 로직) 먼저 작성한다.
- 예:
initApp()
,renderPage()
,setupAppEvents()
같은 메인 함수들
- 예:
큰 흐름 안에서 필요한 역할을 명확히 구분한다.
services
는 데이터 요청/저장만handlers
는 이벤트만renderers
는 화면 출력만utils
는 계산/변환만
각 세부 책임을 "순수 함수"로 쪼개고
- 가능한 한 부수효과(Side Effect)를 줄인다.
- 세부 함수는 최대한 짧고, 하나의 역할만 하게 만든다.
선언형 스타일로 작성한다.
- for문 대신 map/filter/reduce로 데이터 다루기
- 상태는 const와 spread로 복제하고 수정하지 않기
🎯 최종 요약 (한 문장)
"나의 요구사항을 만족시키기 위해, 책임을 분리하고, 함수형+선언형 스타일로, 큰 흐름을 먼저 설계한 뒤 세부를 쪼개며 작성한다."
그리고 이후 궁금한 점들과 과하다고 생각할 만한 고민들을 토대로 채팅을 이어나갔고, 정리한 내용은 다음과 같습니다.
✅ 1단계: 서비스-핸들러-렌더러 분리 고민
- 서버 통신과 UI를 독립적으로 유지하려면 책임(Service/Handler/Renderer)을 분리해야 한다고 질문.
- 각각의 역할을 명확히 구분하는 구조가 유지보수에 유리하다고 답변.
- 작은 함수형으로 관리하되 책임 단위로 묶자는 방향 정리.
✅ 2단계: 선언형과 함수형의 관계 질문
- 함수형 프로그래밍은 선언형 스타일을 따르는지 질문.
- 함수형은 선언형의 한 갈래이며, 선언형 스타일(무엇을 할지)에 집중해야 한다고 설명.
- 데이터를 변환하고 처리하는 흐름을 선언적으로 표현하는 것이 함수형의 기본이라고 정리.
✅ 3단계: 상태(Store) 관리 위치 질문
- 전역 상태를 핸들러가 관리할지, 또는 모든 모듈이 접근할지 질문.
- 상태는 Store로 독립시키고, Service/Handler/Renderer가 필요할 때만 접근하는 구조가 최적이라고 답변.
- Store는 단순 저장소로, 차이(diff) 계산 같은 로직은 별도 유틸로 분리하는 게 좋다고 정리.
✅ 4단계: 함수 vs 클래스 고민
- 함수형이라도 무조건 함수만 써야 하는지, 클래스도 고려할 수 있는지 질문.
- 순수 계산은 함수형, 복잡한 상태와 라이프사이클 관리는 클래스로 나누는 게 좋다고 설명.
- 중요한 건 구현 방식이 아니라 책임 분리와 인터페이스 통일이라고 강조.
✅ 5단계: 서비스-핸들러-렌더러-스토어 사이의 인터페이스 고민
- 인터페이스를 중심으로 각 모듈을 연결하는 구조를 고민.
- 실제로는 Ports and Adapters, Interface-Oriented Design과 유사한 설계라고 설명.
- 인터페이스를 공통 규약으로 삼아 독립성과 유연성을 확보하는 방향을 정리.
🚀 전체 요약 한 줄
"구조 분리 → 선언형 스타일 → 상태 독립 → 함수/클래스 균형 → 인터페이스 중심 연결"로 고민을 발전시켰다."
그리고 저는 습관적으로 코드를 작성... 할 수도 있기 때문에 작성하기 전 필수 사항 3가지를 뽑아서 정리해달라고 요청했습니다.
📝 코딩 체크리스트 (v1.0)
1. 책임은 하나로 (Single Responsibility)
- 이 파일(또는 함수)은 한 가지 역할만 하고 있는가?
- 내가 작성하는 것은 Service / Handler / Renderer / Store 중 하나의 책임만 담당하고 있는가?
- 여러 책임이 섞이려고 하면, 주저 말고 분리하고 있는가?
2. 인터페이스만 믿고 연결 (Interface-Centric)
- 외부에서 사용할 함수(또는 메서드)는 명확하고 일관된 이름으로 되어 있는가? (
getState
,setState
등) - 외부 코드가 내부 구현에 대해 몰라도 사용할 수 있게 설계되어 있는가?
- 모듈 간 연결은 직접 참조하지 않고, 인터페이스(약속된 함수/객체)로만 연결하고 있는가?
3. 데이터는 복사해서 다루기 (Immutability)
- 기존 데이터를 직접 수정(mutate) 하지 않고, 새 객체를 만들어서 반환하고 있는가?
- 데이터 변환 시
map
,filter
,reduce
,...spread
등을 적극 활용하고 있는가? - 함수는 외부 상태를 변경하지 않는 순수 함수로 작성하고 있는가?
🎯 작성 전 최종 3초 체크
- 이 코드는 하나의 책임만 가지고 있는가?
- 외부와 주고받는 규약(인터페이스)이 명확한가?
- 데이터를 복제해서 불변성을 지키고 있는가?
✅ 요약 슬로건
"책임은 하나로, 연결은 인터페이스로, 데이터는 복사해서."
음.. 일단 요약 슬로건만 생각하고 작성한 이후에 코드리뷰를 받아야겠습니다ㅎㅎ
'Create' 카테고리의 다른 글
[Matter.js] What's the matter??? (1) | 2024.06.11 |
---|---|
[알고리즘] 이게 되네...? (1) | 2024.06.03 |
[알고리즘] 나 그래도 풀긴 풀었어...? (1) | 2024.05.30 |
[React] 왜 반응형 텍스트박스는 스타일로 만드는가? (0) | 2024.05.11 |
[React] 리팩토링 - 데이터/액션/계산으로 분리하고 쿼리 스트링을 사용하자 (0) | 2024.05.08 |