Node.js mips simulator js 이야기
최근 진행했던 오픈소스 프로젝트인 mips-simulator-js에 대해 이야기해보고자 한다.
이전 프로젝트는 개인적으로 진행했던 프로젝트인데 반해, 이번 프로젝트는 나를 포함한 4명이 함께 진행한 프로젝트이다.
현재 mips 어셈블리언어를 바이너리 명령어로 변환하는 기능을 만들어 npm에 패키지로 배포한 상태이다. 이하 링크에서 확인할 수 있다.
이번 프로젝트는 지난학기에 수강했던 컴퓨터구조 강의의 연장선에서 진행되었다. 지난학기에 전공 수업으로 컴퓨터 구조 강의를 수강했고, 강의에 주어진 과제들을 바탕으로 오픈소스화를 진행했다.
당시 주어진 구현 과제는 크게 3개였고, '어셈블리 언어를 바이너리 명령어로 변환', '바이너리 명령어를 시뮬레이션', '캐시 시뮬레이션 기능 추가'가 핵심 주제였다.
우리 팀은 크게 두 가지 프로젝트를 기획했다.
먼저, 위 과제 주제를 바탕으로 Node.js 환경에서 사용할 수 있는 assembler와 simulator를 npm 패키지로 배포하고 오픈소스화하는 것이었다. 그리고 해당 npm 패키지를 기반으로 assembler simulator를 GUI로 만들어 웹 사이트로 제작하는 목표를 세웠다.
현재까지는 '어셈블리 언어를 바이너리 명령어로 변환'하는 기능을 제작하여 npm에 배포한 상태이며, 지금은 해당 기능을 GUI에 적용하는 과정을 진행 중에 있다.
이번 프로젝트를 통해, 협업에 필요한 역량을 기르기 위해 노력했다. 특히, npm 패키지를 배포한 경험이 있는 나는, 당시 개인이어서 적용하지 못했던 부분들을 적용하기 위해서 여러가지 시도를 했다. 이 프로젝트를 통해 협업 시스템을 구축하고 협업 방법과 스프린트에 대한 새로운 경험들을 쌓을 수 있었다.
먼저, 내가 주도적으로 시도했던 것들 중 기술적으로 많은 것을 배웠던 CI/CD에 대해 이야기하고 싶다.
지난 npm 패키지 프로젝트인 use-upbit-api에서는 CI/CD를 전혀 고려하지 않고 개발했다. 혼자 개발하다보니 딱히 필요성을 느끼지 못했던 것도 있고, CI/CD에 대한 막연한 두려움 때문이기도 했다.
하지만 협업을 진행하면서, CI/CD의 필요성을 느끼게 되었다.
먼저, CI를 구축하지 않았을 때까지는 여러 사람들의 커밋 기록에 대한 각각의 테스트가 진행되기 어려웠다. 초기에 테스트 코드를 만들고 프로젝트를 진행했지만, 이 테스트 코드는 직접 로컬 환경에서 node 명령어를 통해 실행할 수 있는 코드였고, 이 때문에 매번 PR에서 테스트 과정이 누락되는 경우도 잦았다. 그 결과, 예기치 못한 버그가 지속적으로 발생했고 이를 인지하는데까지 몇번의 커밋이 추가되어야했다. 누군가 우연히 테스트를 통해 버그의 존재를 알아차리기 전까지는 버그를 인식할 수 없는 상황이 발생했다.
사실 프로젝트 규모가 그리 크지 않아, 버그를 발견하는데 그리 오랜 시간이 걸리지는 않았지만, 늘어나는 코드량에 분명 이는 큰 문제로 작용할 수 있을 것이라 생각했다.
또, 코드를 누가 작성하느냐에 따라 코드 퀄리티가 달라졌다. 물론 PR과 코드리뷰를 통해 이를 바로 잡을 수 있었지만, 모든 사람의 코드가 항상 다 읽히지는 않았기 때문에, 코드 퀄리티나 코드 스타일도 들쑥날쑥할 수 밖에 없었다.
결국 이런 하나하나의 사항들이 병목으로 작용했고, 병목은 또 다른 병목을 낳을 수 밖에 없을 것이었다.
그래서 프로젝트 규모가 더 커지기 전에, CI 구축을 시행해야겠다고 생각했다.
github에서 협업을 진행하고 있었기 때문에, github action은 기존 프로젝트에 자동화를 적용시키기에 가장 좋은 선택지였다. github action을 통해 각 commit이나 Pull Request 혹은 main 브랜치에 push하는 이벤트가 발생할 때마다, 미리 작성해둔 스크립트가 실행될 수 있기 만든다면 쉽게 자동화를 구축할 수 있었다.
ESLint는 Javascript의 정적 분석 도구로, 코드를 분석하여 문법적인 오류나 안티 패턴을 찾아주고 일관된 코드 스타일을 유지할 수 있도록 도와줄 수 있는 훌륭한 툴이었다. 여기에 기존에 사용하던 Prettier를 적용시켜 매 PR 마다 협업자들이 모두 일관된 코드 스타일을 유지할 수 있고, 놓치기 쉬운 문법적 오류나 안티 패턴을 감지할 수 있었다.
jest를 통해, 기존에 테스트 코드를 대체하는 과정을 수행했다. 그리고 이를 github action에 추가하여 매번 PR이 올라올때마다, 코드를 테스트하는 과정을 반드시 진행하도록 하였다. 그리고 테스팅 라이브러리를 도입하면서, 코드 coverage 등과 같은 정보도 쉽게 얻을 수 있었다.
위 세가지 툴을 기반으로 CI 구축을 시도했다.
먼저, 현재 진행하고 있는 프로젝트에서 바로 테스트하기보다 새로운 repository를 생성하여 그곳에서 가능한 테스트를 모두 시도해보고, CI 구축 과정을 문서화했다.
이후, Github Action을 통해 PR 시 github의 가상환경에서 node.js 설치 후, package.json의 dependencies를 설치하여 typescript build와 eslint, jest 테스트를 거쳐 merge 버튼을 활성화할 수 있도록 CI를 구축하였다.
이 덕분에, 협업에서 일어날 수 있는 병목도 다소 해소되고 테스트 코드를 쉽게 짜고 수행할 수 있게되었다. 또, 오픈소스의 특성상 현재 contributor가 아닌 사람들이 contribution을 할 수도 있는데, CI 자동화 테스팅 구축 덕분에 그들이 쉽게 테스트를 진행하고 PR 시 검증하기 쉬워졌다.
또 jest test 이후, 각종 coverage 정보를 github pages에 호스팅하였고, 이는 github action을 통해서 자동으로 배포되도록 CD를 구축했다. main branch에 push event가 발생할 때마다 이 작업이 수행될 수 있도록 세팅해두었다.
이번 프로젝트에서 또 많은 것을 배웠던 것은 github을 활용한 협업 방식이다. 기존에 git은 단순히 버전 관리를 위함으로 사용했고, 사실 개인 프로젝트만 진행하다보니 협업에 대해 진지하게 고민하고 협의할 기회가 없었다.
하지만 이번 프로젝트에서는 4명이 함께 작업을 하다보니, commit 규칙, PR 규칙, 코드리뷰 규칙, branch 규칙, 코드 스타일 등등 함께 맞춰나가야할 것들이 많았다.
여러 규칙들을 정해서 실제로 적용했었는데, 간단히 요약하면 다음과 같다.
1. 작업하기 전, Issue에 작업할 내용과 TODO 등록하기
2. 작업할때는 sub branch를 파서 작업하고 정해진 규칙에 따라 branch 명 설정하기
3. commit 시에는 정해진 commitmessage template을 사용하기
4. PR 시에는 반드시 1명 이상의 코드리뷰를 받고 승인한 사람이 merge 하기
5. Prettier, ESLint 등의 코드 스타일 보조 툴 사용하기
이 외에도 작은 규칙들이 점점 늘어났고, 규칙들을 정리해놓은 문서들도 만들어놓기 시작했다.
아래에 그 중 내가 매뉴얼화했던 것들의 PR을 링크해두었다.
이렇게 규칙을 만들고, 시스템을 만들고, 자동화를 해두는 것들을 통해 사소한 병목들을 쉽게 해소할 수 있다는 것을 느낄 수 있었다. 그리고 일관된 작업 방식이 있으니, 핵심적인 작업에 더 많은 에너지를 할당할 수 있어서 능률이 올라가는 것도 느낄 수 있었다.
사실 이외에도 이 프로젝트를 통해 배운 것들이 많고 팀원들에게서도 많은 배움을 얻을 수 있었지만, 회고가 너무 길어지게 될 것 같아 이쯤에서 일단 글을 멈추려한다.
아직 프로젝트가 끝나지 않았으니, 다음기회에 더 많은 것들을 글에 녹여낼 수 있을 것이다.
결론적으로, 이번 프로젝트를 통해 그간 시도해보고 싶었던 것들과 배워보고 싶고 경험해보고 싶었던 것들을 많이 경험할 수 있어서 너무 값진 시간이었다. 그리고 협업을 통해, 혼자서 할 때 부족했던 것들을 채우고 더 많은 것들을 만들 수 있다는 사실도 깨닫게 되었다.