안녕하세요. 김수보 멘토입니다. 이 글은 2020.01월 피씬을 시작했던 1기 친구들의 "42공통써클 탈출기"입니다. 글쓴이 eunhkim 님의 허락을 얻어 게재합니다. "트랜샌더스"란 공통커리큘럼의 끝판왕 과제로, 복잡한 난이도의 웹게임입니다. 당시에는 백엔드로 Ruby on Rails를 요구해서,지금과는 요구기술이 상이합니다. 다만, 난이도와 풀어가는 과정은 동일한데,이 팀은 좀 너어무 잘한 느낌이라... 암튼 도움이 되면 좋겠습니다.
1. 어쩌다 트랜센던스까지 #1(시작~개발환경) 2. 어쩌다 트랜센던스까지 #2(협업환경) 3. 어쩌다 트랜센던스까지 $3(구현,테스트,프랙티스) 4. 어쩌다 트랜센던스까지 #4(팀원들 회고)

11장. 구현, 테스트, PR
45일간 학습했고, 7일간 실습했으며, 9일간 설계했습니다. 그리고 실제 구현 기간은 42일이었습니다.
공교롭게도 42와 숫자가 같은 이 42일간 저희는 설계에 따라 구현을 진행하고, 해당 모듈을 진행한 팀 내에서 자체적으로 테스트를 진행하고, PR을 올리고, 리뷰를 진행하고, 리뷰에 대응하여 코드를 수정하고, 어프로브된 PR을 병합하는 과정을 반복했습니다.
11.1. 최초보다는 최선을
저희는 구현에 있어서 속도보다 학습을, 품질을 중요한 가치로 판단했습니다.
처음 스터디에 45일을 쏟은 것도 그래서였습니다. 최초보다는 최선에 더 큰 욕심을 내자. 서브젝트의 요건만 충족한다면 빠르게 통과할 수도 있겠지만, 그렇게 공통 서클을 통과했을 때 자신의 역량에 대한 의심이 든다면 그것만큼 허무한 건없을 테니까.
적어도 이 서브젝트가 끝났을 때 42의 교육 과정과 스스로에 대한 의심은 없도록 하자. 주어진 시간 안에서 최선을 다해보기로 했습니다.
그래서 단순히 게임을 설명하는 모달을 만드는 데에도 GIF를 만들어 넣고, 게임 역시도 수십 조합의 배경 그라데이션과 폰트를 실험해가며 비교 끝에 선택했습니다. 메인이 되는 게임 규칙 역시 6가지의 확장을 만들었고요.
SPA의 범위 역시 로그아웃을 할 때 끝나는 것으로 보는 것이 아니라, 다른 계정으로 다시 로그인하여도 추가 로딩 없이 연속적으로 서비스를 이용할 수 있게끔 처음부터 끝까지 모든 작업을 SPA로 구현하였습니다.
이외에도 실제 서비스를 개발한다는 마음가짐으로, 서브젝트에 나와 있지 않더라도 사용자 경험에 필요하다고 판단되는 모든 디테일을 추가로 구현한 영역이 많습니다.

11.2. 테스트
Rspec, Faker, Factory_bot을 이용하였습니다.
다만 학습 비용이 매우 높을 것이라고 잘못 예측해 처음부터 도입하지는 못했고, seed를 이용해 dummy data를 생성한 뒤 브라우저에서 직접 테스트하는 방식을 취하다가 후반에 개선했습니다.
11.3. 테스트 사례
특히 어려웠던 것은 토너먼트입니다.
며칠에 걸쳐 진행되어야 하는 데다, 토너먼트 멤버십 모집을 마감하고, 매치를 메이킹하고, 또 정해진 토너먼트 매치 시간이 되었을 때 해당 매치들을 관리하고, 토너먼트가 끝났을 때는 멤버십과 자기 상태를 관리하고, 보상으로 설정된 타이틀을 수여 하는 등 스케쥴러를 설정해야 하는 작업이 많았습니다. 저희는 이를 루비 온 레일즈의 기본 스케쥴러인 ActiveJob으로 해결을 하였습니다.
문제는 어떻게 수많은 토너먼트 시나리오를 테스트할 것이었냐였죠.
이를 위해 test spec에서는 job을 예약하는 것이 아니라 즉시 실행하되, 인자로 서비스상 실행될 datetime을 넘겨줘서 job 으로 하여금 현재 시각을 해당 시각으로 판단하고 작업을 수행할 수 있게끔 처리했습니다.
또 라운드별 참여율 ratio를 설정하여, 지정한 비율 범위 내에서 참여자들이 임의로 토너먼트 매치에 참여 혹은 불참을 선택할 수 있게끔 설정하여 토너먼트의 전체 과정을 시뮬레이션하는 코드를 만들었습니다. 토너먼트 관련 테스트 코드만 400줄이 넘었습니다.
다양한 ratio 케이스에 대해 정상적으로 유저, 토너먼트, 매치, 멤버십 등 다양한 모델 등의 상태가 마지막 날까지 잘 업데이트되는지, 또 이 테스트가 최소 인원인 8명부터 최대 인원인 32 명 케이스까지 모두 문제없이 통과하는지 모든 인원 케이스에 대해서 검사하도록 처리했습니다.
테스트를 통과하고 나서도 확실하게 하기 위해 로그 파일을 작성했습니다. 토너먼트 첫날부터 마지막 날 자정까지 job을 execute 하면서 처리 흐름과 결과에 대해 로그 파일이 만들어집니다.
첫날에 정상적으로 상태가 progress로 전환되었는지, 모든 토너먼트 매치가 정상적인 시간에 지정된 룰셋을 이용하여 열리는지,참여 여부에 따라 매치가 적절하게 complete or canceled로 업데이트되는지, 마지막 날에는 4강 이상 진출자들에 대해 성적이 제대로 반영되는지 등을 눈으로 확인하고 나니까 비로소 신뢰할 수 있었습니다.

11.4. PR
저희의 PR 원칙은 단순했습니다.
"모든 PR에 대해 모든 사람이 리뷰한다. 모든 사람이 approve한 코드만을 병합한다."
여러 번 언급했듯 학습과 품질이 우선적인 가치이므로 모든 팀원이 코드에 대해 이해하고, 또 모든 팀원이 문제의식이 느끼지 않을 때까지 코드를 개선하는 것이 옳다고 판단했기 때문입니다. 따라서 대충 PR을 작성하고, 대충 merge하는 것은 말이 되지 않는 얘기였습니다.
최종적으로 저희가 작성한 프로그램의 코드 라인 수는 27,309 줄이고 커밋 수는 1,217번입니다. PR은 총 39개였는데, PR 과정에서 일어난 conversation의 message 수만 해도 1,290개에 달하는 양입니다. 평균적으로 PR 한 번에 33개의 메시지가 작성된 것인데, PR된 코드에 문제가 많기 때문이 아니라 저희가 그만큼 스스로 엄격했기 때문입니다.
빠르게 approve를 눌러주기보다 생각지 못했던 문제를 지적해 주는 사람을 ‘좋은 동료’로 여기는 문화를 구축하고 유지했던 것은, 프로젝트 기간이 길어지면서 체력이 저하되거나 애써 구현한 기능을 되돌리거나 크게 수정하면서 의욕이 감퇴하는 부작용을 낳기도 했습니다.
그러나 실보다 득이 훨씬 더 컸고, 또 부작용 역시 엄격한 리뷰 문화가 낳은 문제라기보다 구현 단계에서 모듈 간 커뮤니케이션이 부족했던 문제에서 파생된 것이었습니다.

다른 것보다도 PR 리뷰의 내용 자체를 공유하는 것이 트렌센던스를 어떤 마음가짐으로 진행했는지, 빠르게 공통 서클을 끝내고 취업 준비나 원하는 공부를 하고 싶은 마음을 어떻게 이겨냈는지 보여준다고 생각합니다.

12장. 굿 프랙티스
트렌센던스를 끝내고 결과적으로 보았을 때, 많은 선택이 아쉬웠지만, 또 역시 많은 선택이 옳았다고 생각합니다.
다시 돌아가더라도 같은 선택을 할 것이고, 새로운 프로젝트를 한다고 해도 비슷한 선택을 할 법한 것은 다음과 같습니다. 모두 안정성, 베스트 프랙티스, 사용자 경험, 가독성과 같은 가치를 엄격하게 추구하는과정에서 나온 선택입니다.
우선 앞에서 언급했던 것에 대해 이 장을 빌어 다시 한번 짚자면, 도커라이징을 먼저 하고 개발한 것과 코드포매터를 사용한 것, 라이브쉐어를 위해 VSCode를 사용한 것은 효율적인 선택이었습니다.
기술 학습부터 구현까지 이 문서에서 설명한 것과 같은 방식으로 프로젝트 흐름을 잡은 것도 멋진 선택이었고요.
디스코드를 제대로 활용한 것도 큰 힘이 되었습니다. 필요한 yarn 패키지와 gem을 웬만큼 깔고 시작한 것도 빌드 문제를 덜 겪는 데 도움이 되었습니다.
스케쥴링의 경우 익숙하지 않아 thread를 분기해 sleep을 이용하는 클래식한 방법을 사용할까도 싶었는데, ActiveJob을 발견하고 잘 사용한 것도 좋은 선택이었습니다.
이미지의 경우 ActiveStorage를, 웹소켓의 경우 ActionCable을 적극적으로 활용한 것도 기술 스택 면에서 시간을 아끼면서 요구사항을 충분히 반영할 수 있었던 좋은 선택이었습니다.
프론트엔드에서는 Helper 모듈을 만들어서 활용한 점, 서비스 용도의 API 메서드를 별도로 구현하여 활용한 점, 액션 케이블 channel 연결 메서드를 래핑하여 원하는 순간에 케이블을 연결하고 해제할 수 있도록 한 점, router를 이용하여 main_view를 교체하는 방식으로 뷰 프레임을 잡은 것, import/export를 관리하는 모듈을 별도로 만든 점 등이 회고했을 때 좋은 선택으로 평가되었습니다.
백엔드에서는 기본적으로 데이터베이스 정규화가 잘 진행되었고, 연관 설정 및 유효성 검사를 디테일하게 처리한 사항이 잘한 것으로 평가되었습니다.
error render를 처리하는 메서드를 application controller에 구현하여 코드 가독성 및 편의성을 높인 점, begin/rescue 구문으로 모든 경우에 대해 예외처리를 진행한 점, 에러들은 콘솔이 아니라 별도 로그 파일에서 확인할 수 있도록 처리한 점, lock과 transaction block에 대해 잘 이해하고 사용했다는 점, before action을 활용한 header 검사로 current_user를 특정함으로써 보안 및 실용성(특정하는 과정에 인스턴스 변수에 저장)을 높였다는 점 등이 좋게 평가되었습니다.
라우트도 RESTful하게 설계되었고, 모델 역시 나름대로 객체지향적으로 설계된 점도 만족도가 높았습니다.
부록1. 서비스 스크린샷
생략
부록2. 에러리스트
단언할 수 있습니다. 이 문서를 읽는 여러분이 카뎃이고 아직 트렌센던스를 진행하지 않았다면, 여태껏 겪었던 모든 에러보다 더 많은 에러를 트렌센던스에서 겪게 되리라고.
트렌센던스에서 서비스가 정상적으로 동작하는지, 문제가 없는지 확인하는 방법은 매우 많습니다. 저희는 저희 코드를 대상으로 상상할 수 있는 모든 시나리오를 테스트했고, 그 과정에서 많은 노하우와 인사이트를 얻었습니다.
특히 예외처리가 진행되어 있지 않은 에러를 일으키는 방법에는 어느 정도 패턴이 있는데, 이런 패턴을 따라서 테스트한다면 대부분의 트렌센던스 프로젝트(아쉽게도 저희는 해당 사항이 없지만)에 crash나 error, warning을 일으킬 수 있을 거에요.
1. 무엇이든 더블클릭 또는 연속적으로 클릭하면 동작이 두 번 일어날 수 있습니다. 2. 새로고침, 다른 호스트 url 입력, 브라우저 끄기 등 비정상적인 페이지 이탈을 시도하면 정상적으로 로그아웃 버튼을 누르는 것과 같은 동작이 일어나지 않을 수 있습니다. 3. 뒤로 가기, 앞으로 가기를 반복하면 이상 현상이 일어날 수 있습니다. 데이터나 렌더링된 화면에 문제가 생길 수도 있고, 동적으로 잘못 처리한 경우 뒤로 가기나 앞으로 가기를 눌렀을 때 이전 화면이 아니라 유저 입장에서 이전의 이전 화면으로 갈 수도 있습니다. 4. 서버에서 쿠키/세션 스토리지 사용시 같은 브라우저에서 추가적으로 로그인을 하면 문제가 일어날 수 있습니다. 5. 따로 방어하지 않았을 경우 다른 브라우저라도 같은 계정으로 로그인을 하면 많은 문제가 일어날 수 있습니다. 6. 서버 측에서 서버를 중지하고 다시 가동할 경우 프론트에서 케이블이 다시 연결되면서 이상이 일어날 수 있습니다. 7. form을 통해 user, guild, chat_channel 등 무엇인가를 생성/수정하려고 할 때 하나 이상의 컬럼을 비워놓고 생성/수정하기를 누르면 문제가 생길 수 있습니다. 방어되었을 경우 비우는 것이 아니라 스페이스 바를 눌러 공백만 입력 후 생성/수정하기를 누르면 문제가 생길 수 있습니다. 8. form 작업 시 영어만이 아니라 숫자, 한글, 특수문자, 이모티콘, 공백을 적절하게 섞어서 인자로 전달하면 유효성 검사가 별도로 없을 경우 문제가 발생할 수 있습니다. 괜찮은 것처럼 보이더라도 서비스의 어딘가에서 해당 컬럼을 파싱하거나 조작하려고 할 때 문제가 발생할 수 있으니, 해당 컬럼이 사용될만한 서비스 영역을 방문하면 확인할 수 있습니다. 9. form 작업 시 과도하게 길거나 짧은 길이의 값을 입력해보세요. 혹은 허용되는 정상 한계치에 해당하는 값을 넣어보세요. 예외처리가 따로 안 되어 있을 수 있고, 실제로는 '초과'인데 유저한테 '이상'이라고 표현한다던가 하는 문법 오류를 확인할 수도 있습니다. 최소한 글자가 DOM 요소를 벗어난다던가 정렬이 안 맞는다던가 하는 CSS 이슈는 발생할 가능성이 높습니다. 10. 이미지를 변경하려고 할 때 이미지가 아닌 파일을 업로드하거나, 과도하게 크기가 큰 파일을 넣거나, 세로가 길거나 가로가 짧은 이미지를 넣으면 정상적으로 예외처리가 되지 않거나 업로드된 이미지에 대해서도 CSS가 깨질 수 있습니다. 이미지에 크기 제한이 없을 경우 유저 랭킹 리스트와 같이 이미지 렌더링이 많이 필요한 페이지에 대해 속도가 느려질 수도 있습니다. 11. 모달 뷰에 해당하는 요소가 있다면, 껐다가 켜는 작업을 반복해보세요. 표시되는 데이터의 내용이 달라질 수 있고, 뷰를 close하고 render하는 과정이 정상적으로 처리되지 않아 아예 모달이 뜨지 않을 수도 있습니다. 12. 브라우저 크기를 상하좌우로 자유롭게 조정해보세요. CSS가 깨질 확률이 높습니다. 13. 데이터가 하나도 없는 경우, 혹은 데이터가 지나치게 많은 경우를 가정하지 않고 코드가 짜여졌을 확률이 높습니다. 화면의 각 영역에 대해 데이터를 모두 제거하거나, 아니면 컨테이너가 넘치도록 데이터를 늘려서 확인해보세요. 14. private message나 chat channel의 message에 시각을 표시한 경우 UTC로 처리되었을 수 있습니다. UTC와 관련해 매우 많은 문제가 나타날 수 있는데, 우선 form 작업 시 시간이나 날짜를 입력할 경우, 실제로 생성된 시간을 조회할 때 UTC로 나타날 수 있습니다. 또 토너먼트나 전쟁이 유저가 생성 시 입력한 시각이 아니라 UTC를 기준으로 동작할 수 있습니다. 15. 길드원 관리나 채팅 멤버 관리의 경우 자기 자신에 대한 명령, 혹은 자기보다 높은 등급을 가진 멤버에 대한 명령에 대해 예외처리가 안 되어있을 수 있습니다. 16. form에 대해 select field나 slider bar와 같은 요소가 있는 경우, DOM을 직접 조작하여 option value를 변경하고 생성하기를 눌렀을 때 예외처리가 안 되어있을 수 있습니다. 17. 서비스를 거치지 않고 URL을 직접 입력하거나, 포스트맨 등을 이용하여 서버에 요청했을 때 로그인하지 않았음에도 서비스가 이용 가능할 수 있습니다. 아예 처리가 안 되어있다면 user의 비밀번호나 chat channel의 비밀번호가 조회될 수도 있습니다. 서버에서 최소한의 인증 작업을 거치지 않는다면 본인이 아닌 다른 유저를 길드에서 탈퇴시키거나,게임 결과를 조작하는 등의 작업이 지나치게 쉽게 가능할 수 있습니다. 18. 게임 중 유저가 이탈하는 경우 양쪽에 대해 적절하게 예외처리가 안 되어있을 수 있습니다. 서브젝트 상 몰수패에 해당하는데 승패가 기록되지 않거나, 승급전의 경우 점수가 오르지 않을 수 있습니다. 유저가 이기고 있는 상태에서 나갈 경우 승패가 반대로 기록될 수 있습니다. 19. 지정된 목표 점수에 도달했음에도 게임이 끝나지 않을 수 있습니다. 게임과 관련해서는 공이 화면을 이탈해서 돌아오지 않거나, 관전자에게 제대로 게임 상황이 동기화가 안 되는 등의 다양한 문제가 발생할 수 있습니다. 관전자가 많아졌을 때 관전자 혹은 플레이어의 화면에 어떤 형태로든 문제가 발생할 수 있고, 관전자가 나갔을 때 몰수패가 되어버리는 해프닝이 생길 수도 있습니다. 20. 전쟁 역시 이기고 있던 길드가 미응답 회수 초과로 패배할 경우 전쟁 히스토리에서 승패가 반대로 기록될 수 있습니다. 21. 길드 초대의 경우 이미 길드가 있는 유저가 초대를 수락했을 때, 한 화면에서 여러 장의 초대장에 대해 차례 대로 승낙을 눌렀을 때 문제가 생길 수 있습니다. 두 개의 길드에 가입될 수도 있습니다. 22. validation을 create 기준으로 생각하고 설정한 경우, 토너먼트나 전쟁이 끝나면서 정보를 업데이트하려고 할 때 문제가 발생할 수 있습니다. validation이 ceate만이 아니라 update시에도 통과하는지 확인해야 합니다. 23. admin 권한으로 chatroom의 멤버 권한을 변경하였을 때 챗룸 안에 있던 유저가 권한을 바로 획득/상실하지 않을 수 있습니다. 이외에도 새로운 owner를 임명할 때 기존 owner가 강등되는지, owner를 강등할 때 다른 멤버가 owner가 되는지, 혹은 owner 한 뿐이라면 예외처리가 되는지 확인해야 합니다. 24. 길드 anagram과 관련하여 원래 이름의 글자만으로 재조합되어 있지 않고, 추가로 사용된 글자가 있거나 다른 아나그램과 중복될 수 있습니다. 25. 챗룸의 멤버를 삭제할 때 해당 멤버가 챗룸에 접속 중이었는데 내보내지지 않거나, 해당 멤버가 남겼던 메시지가 정책에 따라 정상적으로 제거 혹은 보존되지 않을 수 있습니다. 26. 한 페이지에 Delete 기능과 Update 기능이 동시에 구현된 경우, Delete 후 Update를 하려고 할 때 예외처리가 안 되어있을 수 있습니다. 27. 프론트에서 URI를 임의로 변경하였을 때 권한이 없는 기능이 동작하거나, 쓰레기 값을 입력하였을 때 예외처리가 진행되어 있지 않을 수 있습니다. 28. 유저가 게임 중일 때 대전 신청을 하거나 private message를 보냈을 때 렌더링되는 요소가 유저의 게임을 지나치게 방해할 수 있습니다. 29. 토너먼트에 참여하는 인원, 등록한 토너먼트의 매치에 참여하는 인원 수에 따라 매치메이킹이 정상적으로 일어나지 않을 수 있습니다. 30. 동시에 3~4명 이상의 유저가 매치를 신청했을 때 매치 메이킹이 정상적으로 일어나지 않을 수 있습니다. 31. 로그아웃 후에 다시 로그인하면 정상적으로 동작하지 않을 수 있습니다. 32. 이외에도 예외가 일어날 수 있는 가장 일반적인 패턴 중 하나는 화면을 유지하는 것입니다. 화면이 렌더링 될 때는 정상적인 상태였지만, 시간이 지나거나 다른 유저에 의해 데이터의 값이 변하면서 유효하지 않게 변할 수 있습니다. 이때 이전에 띄워두었던 화면에서 DOM을 조작하면 많은 예외를 마주할 수 있습니다. 33. 하나의 브라우저에 접속하여 테스트를 진행하는 것보다, 최소한 3개 이상의 브라우저를 조작하여 동시에 같은 기능을 조작할 때 서버에서 문제가 발생할 수 있습니다.
부록3. 학습레퍼런스
생략.
1. 어쩌다 트랜센던스까지 #1(시작~개발환경) 2. 어쩌다 트랜센던스까지 #2(협업환경) 3. 어쩌다 트랜센던스까지 $3(구현,테스트,프랙티스) 4. 어쩌다 트랜센던스까지 #4(팀원들 회고)