Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Pull Request를 이용한 개발 흐름을 적용해 보고 나서...

현재 만들고 있는 VOLO 서비스에서 소스 저장소로 GitHub을 사용하고 있다. 도구에 대한 선택권을 내가 가지게 된 이후 내가 좋아하는 도구로 모두 갈아치웠었는데 거의 고민 없이 선택한 서비스 중 하나가 GitHub이고 Enterprise를 사용할 규모는 아니므로(요즘은 국내에서도 GitHub 엔터프라이즈를 사용하는 회사가 좀 생기고 있다.) Organization plans을 사용해서 저장소를 이용하고 있다.(뭔가 시작이 GitHub 서비스 소개 느낌이...)

처음에는 혼자 개발했으므로 개인 프로젝트와 다를 바 없이 원격 저장소를 하나 두고 혼자 커밋하고 푸시하는 식으로 개발했다. 그렇게 하다가 서버 쪽에 같이 개발할 사람이 한 명 들어오면서 둘이 개발을 하게 되었고 어느 정도 영역을 나누어서 개발하기는 했지만 바쁘다 보니 개발 프로세스를 만들지 않고 둘 다 원격 저장소에 푸시하는 방식으로 개발을 여러 달 했다. 일정상 어쩔 수 없었지만 이렇게 개발하다 보니 내가 혼자 작성해서 100% 이해도를 가지고 있던 소스에서 계속해서 모르는 부분이 생겨나는 것이 불안하게 느껴지기 시작했고 약간 여유가 생기자마자 코드를 Pull Request 기반으로만 저장소에 적용하는 방식으로 바꾸었다.(풀 리퀘스트 방식으로 해보고 싶은 욕심도 있었다.)

풀 리퀘스트를 이용한 개발 흐름

GitHub의 풀 리퀘스트를 안 써본 사람을 위해서 간단히 설명하자면 기존에는 GitHub의 원격 저장소를 클론 받아서 master에 커밋을 한 뒤에 이를 원격저장소에 다시 푸시를 하고 중간에 다른 사람의 커밋을 fetch해서 머지하는 방식으로 개발했다. 여기서 로컬저장소에서 브랜치 관리를 어떻게 하는 지는 상관없다.

새로 적용한 풀 리퀘스트를 이용한 흐름은 GitHub의 원격 저장소를 각자의 계정으로 Fork해 간 뒤에 fork 한 저장소를 클론 받아서 작업용 브랜치를 하나 만들어서 작업하고 이를 Fork 한 저장소에 푸시해서 풀 리퀘스트로 원격 저장소에 보내는 방식이다. 로컬에서 master 브랜치에 작업해도 되지만 이렇게 하면 풀 리퀘스트를 나누어서 작업하기 쉽지 않으므로 Pull Reqeust마다 새로운 브랜치를 만들어서 Fork 저장소에 푸시한다. 원래의 원격 저장소를 remote로 추가해서 새로운 변경사항을 받아와서 적용한다. 이 과정은 일반적으로 Push 권한이 없는 오픈 소스 프로젝트에 기여를 할 때 이용하는 흐름과 같은데 자세한 내용은 Github를 이용하는 전체 흐름 이해하기를 참고하면 된다.

Pull Request 아이콘

한 1년 정도 풀 리퀘스트 기반으로 개발을 진행해 보니 전체적으로 만족하게 되어서 정리를 해본다.

어떤 작업이 소스에 적용되는지 알 수 있게 되었다.

단둘이서 진행하는 것이지만 기존에 푸시로 진행할 때는 다른 사람의 커밋을 볼 일이 많지 않으므로 일부러 커밋로그를 보거나 푸시를 하기 위해서 기존 master 브랜치와 머지를 할 때 그동안 올라온 커밋을 보게 된다. 매일 아침 혹은 며칠만이라도 다른 사람의 커밋을 읽어보는 것이 좋은 습관이라고 생각하지만(#훌륭한습관 어제 동료가 커밋한 코드 읽기 참고) 바쁘다 보니 적용하지는 못했다. 간단하게 자신의 풀 리퀘스트는 급한 이유가 아니면 직접 머지하지 않는다는 규칙만으로도 다른 사람의 풀 리퀘스트를 계속 신경 써야 했고 코드를 자세히 보지 못하더라도 어떤 작업이 언제 적용되는지 자연스럽게 알게 되었다. 덕분에 적용된 지 몰랐던 내용 때문에 코드 수정 후 당황하는 문제가 많이 줄어들었다.

자연스럽게 코드 리뷰를 진행할 수 있다.

풀 리퀘스트 방식으로 바꾼 목표 중 하나가 코드 리뷰였다. 이전에 내가 속했던 팀에서도 코드 리뷰를 진행했던 적이 몇 번 있었지만 난 오프라인 코드 리뷰는 완전히 반대하는 편이고 온라인에서 진행했을 때도 도구가 너무 불편해서 제대로 하기가 어려웠다.(난 온라인 코드 리뷰를 지지한다.) 내가 아는 선에서 GitHub의 풀 리퀘스트는 코드 리뷰를 개발 흐름에 자연스럽게 넣기 좋게 만들어져 있었으므로 꼭 적용하고 싶었다.(리뷰 시스템인 Gerrit같은 경우는 사용하기도 불편했지만 리뷰를 커밋단위로 하는 것을 이해할 수 없었다. GitHub는 풀 리퀘스트 단위이다.)

처음에는 다른 사람이 작성한 코드에 대해서 리뷰하는게 생각보다 쉽지 않았지만 간단한 관례나 변수명 등 간단한 부분부터 조금씩 진행하다 보니 점점 코드리뷰를 할 수 있는 영역이 늘어갔다. 세세한 코드나 의도를 모두 아는 것은 아니지만 이렇게 진행하다 보니 코드 수정이 계속 진행되어도 어느 정도 이상의 이해도는 계속 가질 수 있게 되었다. 아쉬운 점은 원래는 양방향 코드리뷰를 원했지만 연차의 차이 혹은 개발 영역의 차이 때문에 실제 코드리뷰는 거의 단방향으로만 진행되어서(내가 다른 사람의 코드를 리뷰하는 방식으로) 아쉽다.

코드에 대해 논의를 할 수 있다.

처음 적용했을 때는 원격저장소에 푸시를 하듯이 작업이 완료되면 풀 리퀘스트를 올리는 방식으로 진행했는데 이렇게 하니까 한꺼번에 너무 많은 코드가 올라와서 리뷰를 하기가 쉽지 않았고 보통 배포시기에 맞춰서 올라오므로 리뷰를 제대로 못 하고 머지부터 하는 경우가 많았다. 그래서 작업을 시작할 때 혹은 커밋을 한두 개정도 하기 시작하면 일단 풀 리퀘스트를 올리고 작업을 하는 식으로 바꾸었고 이에 대한 표시로 "WIP"와 "DONE"이라는 레이블을 만들었다. WIP(work in progress)는 아직 작업 중이라는 표시이고 DONE 레이블을 붙이면 마지막으로 리뷰를 하고 문제가 없으면 머지를 한다. 이렇게 했을 때 좋은 점은 중간중간 코드리뷰를 하니까 점진적으로 리뷰를 할 수 있어서 부담이 줄어들고 진행상황을 이해할 수 있다는 점이고 새로운 기능 등에 대해서는 코드에 대해서 논의를 할 수도 있게 되었다. 그리고 풀 리퀘스트 목록을 보면 현재 작업 중인 내용을 파악할 수도 있다.

작업이 완료되기 전에 코드 검증을 할 수 있다.

코드를 테스트하기 위해서 보통 테스트 코드를 작성하고 이를 CI에서 매번 테스트 결과를 확인하고 있지만(CircleCI를 사용한다.) master 브랜치에 바로 푸시할 때는 올리고 나니 빼먹은 커밋이 있다거나 새로운 코드 변경으로 테스트 케이스가 깨지거나 하는 등의 문제가 발생해서 계속 이를 수정하는 커밋을 추가로 수행해야 했다. 풀 리퀘스트를 사용하면 master에 머지하기 전에 CI에서 풀 리퀘스트에 대한 테스트를 진행하므로 작업자가 이런 실수를 수정하고 master에 머지할 수 있다.

GitHub의 Pull Request 상태 표시

GitHub에서 연결된 서비스의 상태를 잘 보여주기 때문에 테스트를 통과 못 한 풀리퀘스트를 쉽게 파악할 수 있다. 물론 여기에 코드 커버리지나 다른 서비스를 연동할 수도 있지만, 현재는 그렇게 하고 있지 않다.(private 저장소에 뭔가 연결하려면 꽤 비싸다.) CI 테스트 전에 코드 관례 등을 검사하는 테스크를 넣어서 같이 확인할 수도 있지만, 이는 피로한 부분도 있어서 지금은 적용하고 있지 않고 대신 테스트 케이스를 작성하라고 onlyskip처럼 일부 테스트만 수행하도록 하는 코드를 실수로 커밋하는 경우가 많아서 이 부분을 CI에서 검사하고 있다.

원격 저장소에 바로 푸시하는 방식으로 개발할 때는 보통 로컬에서 자신의 작업을 하고 테스트나 그 외 검사를 완료하더라도 다른 사람이 어떤 작업을 진행하고 있는지는 모르므로 푸시를 시도하다가 다른 코드가 올라왔으면 받아서 머지하고 다시 푸시를 하게 된다. 이때 발생하는 문제는 머지하기 전에 테스트 등의 검증을 했다는 것이다. 그래서 코드를 합치면서 발생한 문제는 확인하지 못하고 올리는 경우가 많이 생긴다. 이 문제는 풀리퀘스트가 흐름에 있어야 해결되는 문제는 아니지만, 평소에 풀리퀘스트를 올려놓고 서로 간의 코드를 확인하는 흐름에서 자연스럽게 해결되는 부분이 많다고 느껴진다. 풀리퀘스트를 쓰던 그냥 로컬에서 브랜치로 작업하던 새로운 변경사항을 가져와서 새 코드에 계속 합쳐주는 것이 좋지만, 평소에 풀리퀘스트로 코드를 리뷰하면서 다른 사람의 코드에 대한 이해도가 높아지면서 이 문제도 자연히 줄어들게 되는 것 같다.

자신의 커밋이나 코드를 다시 생각해 보게 되었다.

다른 사람의 코드를 리뷰하다 보니 뜻밖에 내 코드도 다시 돌아보게 되었다. 이해하기 어려운 설명이나 코드를 리뷰하면서 어떻게 정리해야 리뷰할 때 편한지에 대해 생각하게 되어서 내가 풀리퀘스트를 올릴 때도 적용을 하게 되었고 어떨 때는 이렇게 하면 안 된다고 적다가 갑자기 "나는 어떻게 했더라?"라는 생각이 들어서 돌아가서 내 코드를 다시 살펴보고 고치기도 했다.

풀 리퀘스트를 이용한 개발 흐름을 사용할 때 고민했던 부분

풀 리퀘스트를 이용하더라도 위에서 말한 방식만 있는 것은 아니다.(GitHub flow, GitLab Flow등이 있다.) 오픈 소스 프로젝트가 많은 GitHub의 개발 흐름을 이해하기에 가장 기본적인 흐름이라고 생각하기 때문에 fork한 뒤에 풀 리퀘스트를 보내는 방식을 적용했다.

실제로 사용할 때 고민했던 부분 중 하나는 Pull Request를 Pull Reqeust를 올린 사람만 커밋을 수정할 수 있다는 부분이다. 보통은 댓글로 어떤 부분을 수정했으면 좋겠다 하는 얘기를 하고 풀 리퀘스트를 올린 사람이 수정하게 되지만 당사자가 부재중이거나 직접 수정하는 것이 더 빠른 경우에는 바로 추가 커밋을 올리고 싶은데 이를 하기가 쉽지 않았다. 물론 해당 풀리퀘스트를 로컬에 브랜치로 받아서 새로운 풀리퀘스트를 만들어 올릴 수도 있지만 이렇게 하면 원래 풀리퀘스트를 올린 사람이 다시 수정할 수가 없다. 이는 해당 풀리퀘스트가 Fork한 저장소의 브랜치이기 때문에 발생하는 문제인데 이 문제를 해결하는 방법이 2가지가 있다.

  1. fork한 저장소에 대한 푸시권한을 개발에 참여하는 사람이 모두 나누어 가진다. 내가 A라는 사람의 브랜치에 커밋할 권한이 없는 것이므로 권한을 가지면 되는 것이다. 이는 지금 내가 속한 상황처럼 개발팀원이 많지 않다면 쉽게 적용할 수 있지만, 사람이 조금만 많아지면 관리하기가 쉽지 않아질 것이고 팀원 교체가 수시로 있다면 더욱 관리하기가 쉽지 않다.

  2. fork 기반으로 풀리퀘스트를 하지 않고 원격 저장소에 브랜치를 올려서 커밋을 한다. 즉, 앞에서 얘기한 흐름에서 fork를 한 뒤 자신의 저장소에서 풀리퀘스트를 올리는 것이 아니라 원격저장소를 하나만 사용하는데 브랜치를 매번 만들어서 푸시하고 여기서 풀 리퀘스트를 만드는 식이다. 원본 원격 저장소에는 모두 푸시권한이 있으므로 이렇게 하면 풀 리퀘스트의 커밋을 모두 수정할 수 있게 된다.

나는 원본 원격 저장소의 브랜치가 더럽혀(?)지는 것을 안 좋아하기 때문에 fork 방식을 더 선호하는 편이긴 하다.

2016/02/27 22:43 2016/02/27 22:43