오늘은 다른 이야기보다 어제 마친 첫 팀프로젝트에 대한 이야기를 해볼까 한다. 생각 외로 잘 됐던 점도, 아쉬웠던 점도 기록해두자. 더 나은, 제출직전의 후달림이 없는 프로젝트를 진행하기 위해.
팀원 모두가 가장 기초적인 git 이용법 add
, commit
, push
, pull
, merge
, log
, status
정도를 배우고 프로젝트에 임했다. 이마저도 한정적인 용법밖에 알지 못하고 실수도 많았어서 git graph가 많이 난잡해졌다. 다른 팀원의 경우는 차치해두고 당장 내 경우 팀원이 PR을 등록했을때
dev
브랜치에 merge
한 뒤에 git checkout dev
로 작업중인 브랜치를 변경하고git pull origin dev
로 remote repo의 변경사항을 dev
브랜치에 적용한 뒤에 git checkout feature/<기능이름>
으로 돌아와 기능을 계속 개발add
, commit
, push origin feature/<기능이름>
으로 PR 만들기git checkout dev
로 돌아온 후 git merge feature/<기능이름>
를 통해 병합이렇게 개발을 진행했어야 하는데 중간에 개발하던 브랜치에서 dev
브랜치로 돌아오는게 귀찮아 pull
을 개발중인 브랜치에 받기도 하는 등 미숙한 점이 많았다. 이러다보니 나중에 git graph 를 읽을때 눈이 빠져버리는 줄 알았다. 다른 팀원에게 cherry-pick
같은 기능도 배웠는데 그런 건 써볼 여력도 없었다.
조금 번거롭거나 귀찮다고 꼭 필요한 루틴을 깨지 말자.
과제 제출 당일, 필수 구현 사항을 며칠 전에 진작 완성하고 추가 기능을 구현하던 우리는 '여유가 있으니 각자 개발해보고싶은 기능을 마음껏 해보자'고 이야기하고 각자의 시간을 가졌다. 그러다 어느덧 제출 시간이 가까워졌고 미리 배포용 버전(main
) 을 만들어두지 않은 우리는 뒤늦게 전체적으로 점검하는 시간을 가졌다.
그런데 몇몇 기능이 예상한 것과 달리 작동했다. 이벤트 리스너와 관련된 문제였고 우리는 바로 수정에 들어갔다. 하지만 클래스명이 꼬인 상황에서 이 문제를 해결하는 것은 쉽지 않았고 결국 문제를 해결하지 못한 채 제출해버렸다. 시간이 많이 남았기에 더 아쉬운 결과였다.
개발이 어느 지점을 넘어가고 있다고 느낄 때 각자의 기능 테스트가 아닌 전체 점검을 꼭! 시행하자.
처음 프로젝트를 시작할 때 가장 먼저 변수 및 함수 네이밍에 대한 약속을 했었다. 서로 다른 네이밍 스타일로 변수를 선언하면 코드를 작성하다가 중간중간 멈칫하게 되는 일이 발생할 것이라 생각했기 때문이다. 헌데 vanillaJS 로 코드를 작성하는 프로젝트라면 필요한 다른 약속이 또 있었다. 바로 클래스 네이밍이었다. 위의 단락에서 발생한 문제도 클래스 네이밍 약속을 했다면 예방할 수 있었을 것이다.
페이지 내 다양한 기능들은 DOM 트리 내의 객체(이하 DOM 객체로 칭한다)와 그 속성을 참조, 변경함으로써 주어진 역할을 수행한다. 이 때 가장 많이 쓰이는 메서드를 꼽자면 querySelector(selectors)
메서드일 것이다. 태그 이름, 클래스명, 아이디 할 것 없이 선택자로 DOM 객체를 집어낼 수 있는 편리한 메서드이기 때문이다. 그리고 선택자들 중에서도 클래스 선택자를 많이 사용한다. 모든 팀원이 제각기 다른 구조의 클래스 네임을 선언해둔 결과는 아비규환이었다.
document.querySelector(".movie-card-content");
document.querySelector(".card-list-movie");
document.querySelector(".topRate-container");
document.querySelector(".card-list");
// 놀랍게도 다 비슷한 위치의 DOM 객체에 접근하는 선택자이다...
물론 개인이 브랜치에서 작업중일 때에는 이렇게 해도 문제가 없다. 문제는 dev
및 main
브랜치에 merge
를 할 때 발생한다. 이벤트 리스너를 붙일때에도 헷갈리고, 문제가 발생했을때 당황한 와중에 해결하려다 다른 쪽 코드를 건드리는 일도 발생한다. 이런 일련의 사고를 겪으며 클래스 네이밍도 약속이 필요할 수 있겠다는 결론을 내렸다. 그런 김에 BEM 네이밍 컨벤션 을 읽어보니 많은 사람들이 약속한 클래스 네이밍 컨벤션을 알 수 있었다.
block-name__elem-name_mod-name_mod-val
요는 이것이다. 이것을 적용해 가상의 HTML 요소를 만든다 가정해보자.
<div class="menu">
...
<span class="menu__item menu__item_visible menu__item_type_radio">
...
</span>
</div>
"menu"
라는 클래스를 가진 요소의 하위 요소인 span
의 클래스명은 상위 요소의 클래스명을 이어붙인 "menu__item"
이 된다. 이 아이템이 어떤 이유로 숨겨져있어야 한다면 이는 "menu__item_hidden"
이 될 것이다.
...오히려 어려워진 것 같다. 하지만 원래 처음 질서를 잡는 것은 어렵다. 이런 제각기 다른 이름을 가진 HTML 요소가 수십수백이 된다면 코드 뭉치 속에서 이런 네이밍 구조를 통해 원하는 클래스를 찾을 수 있을 것이다. React를 배우기 시작한 지금에서도 쓸모가 있을지 어떨지는 잘 모르겠지만.
변수, 함수 네이밍 뿐 아니라 css 클래스 네이밍도 약속하자.
프로젝트 설계 단계에서 Session Storage와 Local Storage를 모두 사용해보자는 의견이 나왔고 그 때까지 나는 이게 뭔지도 몰랐지만 기능 구현 단계에서 사용해보며 아주 유용한 도구라는 것을 알았다.
세션 스토리지는 window
인터페이스의 read-only 프로퍼티로, getter와 setter를 통해 데이터를 보관, 접근하거나 변경이 가능하다. 세션 스토리지는 페이지 세션이 끝날 때, 즉 윈도우나 탭이 닫힐때까지 유지되며, key-value 페어의 형태로 저장된다. 우리 팀은 이 프로퍼티를 사용자가 클릭한 영화/드라마의 id
값을 저장하는데 사용했고, 덕분에 전역 변수를 선언하지 않고도 항상 id
값에 접근할 수 있었다.
로컬 스토리지는 세션 스토리지와 마찬가지로 key-value 페어로 저장되는 window
인터페이스의 read-only 프로퍼티인데, 세션 스토리지와 달리 사용자가 직접 초기화하지 않는 한 만료 기한이 없다. 우리 팀은 영화, 드라마 및 인물의 상세 페이지에 사용자들이 남긴 댓글들을 저장해두는 용도로 이 스토리지를 사용했다.
value 로 문자열만 저장할 수 있는 한계를 JSON.stringify(item)
과 JSON.parse(item)
을 이용해 극복하고 객체를 넣어두고, 꺼내서 가공하는 식으로 사용해 댓글을 수정했고 localStorage.removeItem(key)
를 이용해 삭제 기능을 구현했다.
프로젝트가 끝나고 바로 React 학습에 들어가서 일단 덮어뒀지만, 팀원들끼리 계속 아쉬워하며 넣고 싶었던 기능이다.
위에서 다룬 세션 스토리지와 로컬 스토리지를 더 사용해 사용자의 마이 페이지 기능을 넣고싶었다. 러프한 로직은 이렇다. 실제 사용자를 염두에 둔 프로젝트가 아닌 만큼 보안상의 문제는 일단 제쳐두고 생각해보았다.
BEM naming convention
MDN - Local Storage
MDN - Session Storage