회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
베테랑 개발자로 유명한 조엘 스폴스키는 사람이 일에 몰입하는데 걸리는 시간이 약 15분이라고 말합니다. 그리고 개발자는 평균 11분을 못 넘기고 몰입을 방해받는다고 합니다. 하루 종일 일을 하더라도 실제 몰입할 수 있는 시간은 많지 않다는 이야기입니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
베테랑 개발자로 유명한 조엘 스폴스키는 사람이 일에 몰입하는데 걸리는 시간이 약 15분이라고 말합니다. 그리고 개발자는 평균 11분을 못 넘기고 몰입을 방해받는다고 합니다. 하루 종일 일을 하더라도 실제 몰입할 수 있는 시간은 많지 않다는 이야기입니다.
조엘의 의견은 최근 주목받는 개발자 경험(Develop Experience)과 겹치는 부분이 있습니다. 회사는 적지 않은 임금으로 고용한 개발자들이 문제 해결에 집중하여, 서비스 품질을 향상시키기를 원합니다. 또한 능동적이고 동기 부여가 강한 개발 문화를 정착하고, 생산성을 하락시키는 요소들은 제거하기를 바라죠. 그렇지만 회사에는 몰입을 방해한다고 미뤄서는 안 될 일들이 있습니다. 회의나 동료의 도움 요청 등이 그렇습니다. 이 글에서는 개발자들의 협업 중 일어날 수 있는 일을 중심으로 깃(Git) 과 깃허브(Github)가 문제 해결에 어떤 도움을 줄 수 있는지 알아보겠습니다.
우리의 일상에선 많은 일들이 반복됩니다. 아침에 일어나 씻고, 휴대폰과 지갑을 챙겨 출근길을 나서는 일련의 과정이 매일 반복되죠. 이러한 반복이 익숙해지면 편리함을 주기도 합니다. 보통 출근길은 몸이 기억하는 대로 지도를 보지 않고도 갈 수 있습니다. 하지만 입사 첫 날엔 위치를 잘 모르니 걷다가 지도 앱 켜기를 반복했을 것입니다. 이렇듯 우리 몸이 기억하는 익숙함 덕분에 지도 앱을 켜는 시간과 데이터 사용 비용을 아낄 수 있는 것이죠.
이런 반복과 익숙함은 개발자의 일상에도 존재합니다. 개발자는 매일 코드를 작성하고, 원격 리포지토리에 병합 요청(Pull Request)을 합니다. 그리고 버그 원인을 찾기 위해 히스토리를 뒤적거립니다. 지금부터 시니어 개발자와 주니어 개발자가 주고받은 대화 내용을 관찰하며, 문제를 분석해 보겠습니다.
시니어 개발자 둘리와 주니어 개발자 또치는 오전에 프로젝트 매니저로부터 버그 리포트를 전달받았습니다. 두 사람이 소속된 프론트엔드 팀은 얼마 전 린트(코드 스타일 규칙)와 유닛 테스트를 도입해, 공통된 코드 스타일과 검증 가능한 개발 환경을 구축했습니다.
[개발자들의 대화] 또치: 둘리님, 아침에 전달받은 이슈 해결해서 PR 했습니다. 리뷰 부탁드릴게요. 둘리: 네 또치님, 수고하셨어요. 바로 확인해 보겠습니다. 음… 또치님? 이번에 작성하신 코드에 린트 적용이 안된 것 같은데요? push 전에 eslint -—fix 한번 실행해 주시겠어요? 또치: 앗, 깜빡했네요. 린트 적용 후에 다시 push 할게요. (5분 후) 둘리님 린트 적용했습니다. 리뷰 부탁드릴게요. 둘리: 네. 또치님? 이번 작업으로 유닛 테스트 하나가 실패했는데요. 한 번 더 확인해 주시겠어요? 또치: 앗, 미처 확인을 못했네요. 확인하겠습니다. |
아직 업무에 익숙하지 않거나, 급하게 코드를 수정할 때면 자주 겪는 일입니다. 만약 코드 리뷰가 없었다면 다음날 또 다른 버그 리포트를 전달받았을 것입니다. 코드를 원격 저장소에 업로드하기 전 자동으로 린트를 적용하고, 유닛 테스트를 실행하면 실수를 방지할 수 있습니다.
git 은 commit, push 등의 시점에 자동으로 스크립트를 실행하는 훅 기능을 제공합니다. 예를 들어 git commit 명령어로 커밋을 생성하기 전엔 pre-commit, post-commit 훅이 실행됩니다.
pre-commit
그럼 pre-commit 스크립트를 직접 작성해 보겠습니다. Git으로 관리중인 여러분의 프로젝트 루트 경로에서 .git/hooks를 열어보면, 다음처럼 .sample 확장자의 파일들이 보입니다.
.git/hooks |- applypatch-msg.sample |- commit-msg.sample |- fsmonitor-watchman.sample |- post-update.sample |- pre-applypatch.sample |- pre-commit.sample |- pre-merge-commit.sample |- pre-push.sample |- pre-rebase.sample |- pre-receive.sample |- prepare-commit-msg.sample |- push-to-checkout.sample |- update.sample |
pre-commit.sample 파일을 열어봅니다.
# !/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 ... |
샘플 파일의 주석 중 ‘To enable this hook, rename this file to "pre-commit"’ 문장을 확인할 수 있습니다. git 은 git init 명령어에 의한 초기화 시점에 샘플 훅 파일들을 .git/hooks 아래에 생성합니다. 실행할 훅과 대응하는 .sample 파일에 스크립트를 작성하고 .sample 확장자를 제거하면 git hook 기능을 사용할 수 있습니다. 추가로 스크립트의 결괏값이 0이 아니면 커밋을 멈춘다는 내용도 주석에서 확인할 수 있습니다.
이제 린트 적용 스크립트를 pre-commit 훅에 작성하겠습니다. 이 글의 예시는 js 프로젝트로 가정합니다. 매 커밋마다 수정하지 않은 js 파일까지 린트를 적용하는 건 비효율적이므로 수정한 js 파일에만 린트를 적용합니다.
// pre-commit.sample STAGED_FILES=($(git diff --cached --name-only --diff-filter=ACM | grep ".js$")) |
eslint 대상에 수정한 js 파일을 포함, 실행합니다.
// pre-commit.sample STAGED_FILES=($(git diff --cached --name-only --diff-filter=ACM | grep ".js$")) npx eslint --fix "${STAGED_FILES[@]}" ESLINT_EXIT="$?" if [[ "$ESLINT_EXIT" == 0 ]]; then printf "\\nlint success\\n" else printf "\\nlint failed\\n" exit 1 fi |
변수 ESLINT_EXIT 은 npx eslint --fix "${STAGED_FILES[@]}"의 실행 결과를 저장합니다. 만약 어떠한 이유로 린트 에러가 발생하면 ESLINT_EXIT의 값은 2(stderr)가 되고, 스크립트의 else 분기문과 함께 pre-commit 은 1을 반환합니다. 스크립트 결과가 0이 아니면 커밋은 취소됩니다.
유닛 테스트 실행도 같은 패턴으로 작성해 보겠습니다. 테스트가 실패하면 1을 반환해 커밋을 취소합니다. 이렇게 테스트 실패 코드의 원격 저장소 업로드를 미연에 방지할 수 있습니다.
// pre-commit.sample ... 린트 적용 npx jest TEST_EXIT="$?" if [[ "$TEST_EXIT" == 0 ]]; then echo "test success" else echo "\\ntest failed\\n" exit 1 fi |
작성을 마친 후 pre-commit.sample 파일의 이름을 pre-commit으로 바꿉니다. 코드를 수정하고 git commit 명령어를 입력하면 린트와 테스트가 실행됩니다. 만약 피치 못할 사정으로 pre-commit 훅을 실행하고 싶지 않다면, 커밋 명령어에 --no-verify 옵션을 더해 무시할 수 있습니다.
pre-push
이번에는 pre-push 훅을 사용해 master 브랜치로의 직접 업로드를 방지해 보겠습니다. master 브랜치를 서비스 배포용 브랜치로 사용하고 있을 때 유용합니다. .git/hooks 디렉터리의 pre-push.sample 파일을 열어봅니다.
// pre-push.sample #!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed ... |
pre-push 훅 파일의 주석에서 역시 스크립트의 결괏값이 0이 아니면 명령이 실패할 것임을 확인할 수 있습니다. master 브랜치 외에는 업로드를 허용해야 한다는 점을 고려해 스크립트를 작성하겠습니다.
// pre-push.sample protected_branch='master' current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\\(.*\\),\\1,') if [ $protected_branch = $current_branch ]; then echo "You cannot push to $protected_branch" exit 1 else exit 0 fi |
현재 브랜치 이름은 git symbolic-ref HEAD 명령어와 sed로 추출합니다. 만약 현재 브랜치가 master 라면 스크립트는 1을 반환합니다. 그런데 한 가지 의문이 생깁니다. 작성한 git 훅 스크립트는 자신에게만 적용될 뿐 동료 개발자들에게는 공유되지 않았습니다. .git 디렉터리는 원격 저장소로의 업로드 대상이 아니므로 .git/hooks 이하 파일 역시 업로드할 수 없습니다.
우선 동료들과 공유할 git 훅들을 하나의 디렉터리에 모읍니다. 예시로 루트 경로에 .githooks 디렉터리를 만들고 작성한 pre-commit과 pre-push 파일을 .githooks로 옮기겠습니다. .githooks를 추가한 커밋을 원격 저장소로 업로드합니다.
/project-root |- src |- .githooks |- pre-commit |- pre-push |
git 훅은 .git/hooks 를 기본 디렉터리로 지정하지만, core.hooksPath 설정을 활용해 바꿀 수 있습니다. git config core.hooksPath [훅 디렉터리] 명령어는 git 훅 디렉터리의 경로를 [훅 디렉터리]로 지정합니다. .githooks 가 추가된 최신 커밋을 다운로드한 동료 개발자들 역시 core.hooksPath를 설정하면 같은 git 훅을 공유하게 됩니다.
> git commit hint: The '.githooks/pre-commit' hook was ignored because it's not set as executable. |
만약 위처럼 훅 파일 실행의 권한 에러가 발생하면, OS 환경에 맞게 실행 권한을 추가해주시기 바랍니다.
이번엔 둘리와 또치의 프론트엔드 개발팀에 새로운 경력직 개발자 도우너가 합류했습니다. 도우너는 자동화된 린트와 유닛 테스트 환경이 구축된 새 팀에 만족하며 첫 과제를 전달받았습니다.
[개발자들의 대화] 도우너: 둘리님, 어제 전달받은 과제 마무리해서 PR 했습니다. 코드 리뷰 부탁드립니다. 둘리: 네 도우너님, 확인해보겠습니다. 음… 도우너님. 저희 팀은 PR 메시지에 항상 이슈 번호를 참조합니다. 구체적인 요구사항이 적힌 관련 이슈에 쉽게 접근할 수 있으니까요. 그리고 PR 메시지에는 되도록 구현 핵심만 적어둡니다. 도우너: 아 그렇군요. 이슈 번호도 같이 참조하겠습니다. 다른 규칙도 있다면 알려주세요. 둘리, 또치: 규칙들을 어디에 정리해 뒀더라? |
어떤 팀이든 팀 나름대로의 체계와 규칙이 있습니다. 개발팀에는 코드 스타일, 디렉터리 관리, 변수명 짓기, 효율적인 커뮤니케이션 관습 등이 있습니다. 규칙을 잊지 않고 지키는 것은 비용입니다. 앞서 출근길의 익숙함과 비용 절감 이야기를 기억하시나요? 상기하지 않아도 될 만큼 규칙이 익숙해지면 비용도 발생하지 않을 것입니다.
보고서를 자주 작성하시는 분들이라면 회사의 공용 서식을 복사해 내용만 추가하는 업무에 익숙하실 것입니다. 독자 입장에서 공용 서식은 원하는 정보를 빠르게 얻는 탁월한 방법입니다. 글의 어디쯤 관심있는 정보가 있는지 익숙한 독자는 그렇지 않은 독자보다 읽기 생산성이 높습니다. 같은 서식의 여러 보고서를 차례로 읽을 때 그 효과는 더 높습니다.
이슈 템플릿
깃허브(Github)는 읽기 생산성에 도움을 주는 이슈 템플릿 기능을 제공합니다. 유명 라이브러리의 github 이슈를 자주 접한 분들은 모든 이슈가 같은 형식으로 작성되어 있는 것을 보셨을 겁니다. 아래는 번들 라이브러리 webpack의 한 이슈입니다.
webpack의 버그 리포트는 항상 4개의 단락으로 구분됩니다. webpack 개발자는 버그 리포트를 읽을 때마다 필요한 정보의 위치를 예상할 수 있습니다.github에서 이슈 템플릿을 설정하는 방법은 간단합니다.
먼저 리포지토리 메인 페이지의 우측 상단 설정 탭을 클릭합니다.
스크롤을 내리면 Features 섹션의 Issues 항목이 보입니다. Issues 항목의 Set up templates 버튼을 클릭합니다.
위 화면이 나타나면 Add template: select를 클릭합니다.
그럼 콤보 박스에 세 개의 선택지가 나타나는데요. Bug report를 클릭하면 미리 만들어진 버그 리포트 템플릿을, Feature request를 클릭하면 미리 만들어진 기능 추가 요청 이슈의 템플릿 생성 과정으로 이어집니다. 마지막 Custom template를 클릭하면 빈 템플릿을 만들 수 있습니다. 예시로 직접 우리 팀의 템플릿을 만들어보겠습니다.
Custom template를 클릭하면 이슈 템플릿 카드가 만들어집니다. 그다음 Preview and edit 버튼을 클릭합니다.
이슈 템플릿 미리보기가 나타나면, 연필 아이콘을 클릭해 편집을 시작합니다.
위에서 아래로 차례대로 템플릿 이름, 설명, 서식 마크다운을 입력합니다. 하단의 Labels 설정 아이콘을 클릭하면 이슈 템플릿에 라벨을 추가할 수 있습니다.
편집을 마쳤으면 Template name 오른쪽의 닫기 버튼을 누르고, 이슈 템플릿 미리보기로 전환합니다. 그리고 우측 상단의 Propose changes 버튼을 클릭합니다.
다음으로 커밋 편집 영역과 Commit changes 버튼이 나타나는데요. 원하는 커밋 메시지가 있다면 입력해 주세요. 생성한 이슈 템플릿은 리포지토리의 .github/ISSUE_TEMPLATE 경로에 마크다운 파일로 저장되기 때문에 커밋이 필요합니다.
커밋 후 리포지토리 메인 페이지로 이동하면 위와 같이 .github/ISSUE_TEMPLATE 디렉터리가 추가된 것을 확인할 수 있습니다. 디렉터리에는 방금 만든 이슈 템플릿의 마크다운 파일이 있습니다.
이제 Issues 탭에서 이슈 생성 버튼을 클릭하면, 위와 같이 템플릿을 선택하고 이슈를 작성할 수 있습니다.
이번에는 PR(Pull Request) 메시지 템플릿을 만들어 보겠습니다. PR 메시지 템플릿은 이슈 템플릿처럼 별도의 생성 페이지가 없습니다. 리포지토리 메인 페이지에서 Add file 버튼을 클릭하고 콤보 박스의 Create new file을 클릭합니다.
파일 이름 입력창에 .github/pull_request_template.md 을 입력하고, 파일 편집 영역의 서식 마크다운을 작성합니다. 작성을 마치면 커밋 버튼을 클릭해 PR 메시지 템플릿을 리포지토리에 추가합니다.
이제 PR을 요청할 때마다 위와 같이 준비된 템플릿 기반으로 내용을 작성할 수 있습니다.
그렇게 입사 후 바쁘게 업무를 소화하던 도우너는 어느 날 예외 처리로 가득한 코드를 마주합니다. GitLens를 통해 또치가 최근 수정했음을 알게 된 도우너는 또치에게 문의 메시지를 보냅니다.
[개발자들의 대화] 도우너: 또치님, 얼마 전 결제 로직에 “결제 모듈 수정” 커밋 메시지와 함께 예외 처리를 넣으셨던데요. 어떤 이슈로 수정하신 건가요? 또치: 아, 며칠 전에 린트 적용 후 코드 스타일만 바꿨습니다. 예외 처리는 그 전에 둘리님이 추가하신 것 같아요. 도우너: 둘리님, 결제 로직에 “결제 모듈 리팩토링” 커밋 메시지와 함께 추가하신 예외 처리는 어떤 이슈 때문이었나요? 둘리: 글쎄요. 최근에 파일 이름 변경과 리팩토링으로 달라진 부분들이 많아서요. 그 예외 처리는 커밋 로그를 추적하면서 파악해야 할 것 같아요. 도우너: 아… 네. |
커밋은 최대한 작게, 한 가지 수정만 반영하는 게 좋다는 것을 다들 알고 있을 겁니다. 이때 작업 내용을 간단명료하게 한 문장으로 요약하고, 커밋 메시지로 기록한다면 더 좋습니다. 물론 바쁜 와중에 글 솜씨까지 발휘하기는 쉽지 않은 일입니다.
<type>[optional scope]: <description> |
그래서 앵귤러(Angular) 프로젝트의 커밋 메시지 규칙에서 영감을 받은 Conventional Commits은 정돈된 메시지 작성법을 제안합니다. 규칙은 작업의 종류(type), 범위(optional scope) 그리고 서술(description)을 포함합니다.
fix(pay): API 연동 중 비사업자 대상 예외 처리 추가 |
예를 들어, 결제 모듈의 API 연동 버그를 수정한 커밋이라면 위와 같이 메시지를 작성할 수 있습니다.
feat(pay): Face ID 본인 인증 추가 |
결제 모듈에 새로운 본인 인증 기능을 추가한 커밋이라면 위와 같이 메시지를 작성할 수 있습니다.
그 외에 빌드 설정, 스타일 변경 커밋은 build, style 과 같은 type으로 작업 종류를 표시할 수 있습니다. 그런데 사용 가능한 type 종류를 목록으로 정리하고, 팀 내 개발자들이 항상 상기해야 하는 또 다른 비용이 발생합니다. 이때 git 메시지 템플릿을 사용하면 좋습니다. git commit 명령어를 입력하면 메시지를 작성할 때 아래와 같은 기본 템플릿이 나타납니다.
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch foo # Changes to be committed: # modified: src/someFile.js |
commit.template 설정을 활용하면 기본 템플릿 대신 원하는 템플릿 기반으로 커밋 메시지를 작성할 수 있습니다. 먼저 원하는 경로에 템플릿 파일을 생성합니다. 예시로 프로젝트 루트 경로에 .gitmessage.txt를 템플릿 파일로 생성하겠습니다. type 목록과 그 의미를 적어둔 뒤 문장을 주석 처리합니다.
// .gitmessage.txt # feat: 새로운 기능 # fix: 버그 수정 # build: 빌드 설정 수정 # style: 코드 스타일 수정 |
git config commit.template .gitmessage.txt |
작성을 마쳤으면 위 명령어로 커밋 메시지 템플릿의 경로를 지정합니다.
# feat: 새로운 기능 fix: API 연동 중 비사업자 대상 예외 처리 추가 # build: 빌드 설정 수정 # style: 코드 스타일 수정 # Please enter the commit message for your changes. Lines starting # with '#'will be ignored, and an empty message aborts the commit. # # On branch foo # Changes to be committed: # modified: src/someFile.js |
앞으로 작업을 마친 뒤 git commit 명령어를 입력하면 .gitmessage.txt 템플릿 기반으로 메시지를 작성할 수 있습니다. 원하는 type 메시지의 주석을 해제하고 내용을 입력하면 됩니다. 새로 합류한 팀원의 이해를 돕기 위해 optional scope 목록, 메시지 길이 제한 등을 주석으로 남겨도 좋습니다.
소스 코드 관리에 대한 개발자들의 궁금증은 곧 ‘어떻게 Git을 쓰면 되는지’와 같은 의미일 정도로, 이미 Git과 Github는 공공재로서의 역할을 하고 있습니다. 이 도구들은 히스토리 추적, 브랜치 관리 등의 기본 기능에 더해, 생산성을 향상시키는 부가 기능을 제공합니다.
많은 회사에서 개발팀의 생산성 향상을 위해 여러 가지 도구를 도입했거나, 준비 중일 것입니다. 상용 도구들이 제공하는 기능들도 나쁘지 않지만, 먼저 Git, Github의 풍족한 기능들을 살펴보는 것을 추천합니다. 적어도 오늘 소개한 기능들은 무료니까요!
요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.