The Dream of Super Surplus Power

May the force be with you.

라이브러리를 만들어 볼까 – 이벤트 객체용 클래스

이벤트 객체는 동기화에 사용하는 객체 중 하나이다. 윈도우 환경에서는 CreateEvent, SetEvent 등을 사용해 구현하는데 사용 예제는 이 문서를 참조한다. C++에서는 흔히 이러한 윈도우 API를 클래스로 잘 포장해 사용하지만, 마침 이벤트 객체가 필요한 터라 C++ 표준 라이브러리로 구현해 봤다.

Read More

라이브러리를 만들어 볼까? – 검색용 2차원 표 템플릿

프로그램을 만들다 보면 switch 문을 2중으로 쓸 일이 있습니다. 물론 switch 문에 사용할 정보를 문자열로 만들어 if 문으로 바꾸는 방법도 생각해 볼 수 있지만 조건문이 많아지는 것도 별로라 이럴 때는 검색용 표를 쓰는 게 좋을 듯합니다. 즉 검색용 2차원 표를 만들고 각 조건별로 호출할 함수 포인터를 표에 저정한 다음 switch 문에서 비교할 정보 둘을 색인으로 바로 호출하는 거죠. 그래서 2차원 배열 내지 표가 필요한데 단순하게 내장 배열이나 vector로 구현하는 것에는 문제가 있습니다. 비교할 정보가 연속적이지 않으므로 불필요한 공간이 생길 수 있다는 거죠. 그래서 map을 사용해 구현해 보기로 했습니다.

Read More

상태(state) 패턴 한번 써 볼까?

일이 일이다 보니 상태(state)를 다룰 일이 아주 많습니다. 상태로 시작해 상태로 끝난다고 해도 과언이 아닐 정도지만, 패턴에 익숙하지 않아 그다지 쓰지 않다 보니 보통 그렇듯 간편하게(?) 상태 값으로 처리합니다. 이미 책과 인터넷에서 많이 찾아 볼 수 있는 내용이긴 하겠지만 상태 패턴을 한번 써 보기로 맘 먹은지라 기억도 되살릴겸 간단히 정리해 봅니다.

Read More

분산형 버전 관리 시스템 작업 흐름 – 게으른 푸시 모델(lazy push model)

Git Branching model

‘지난번’으로 시작하려 했는데 찾아보니 이 글이 벌써 1년이 다 됐네요. 네, 이번에도 작업 흐름에 대한 글입니다만 지난번과 달리 게으른(지연) 푸시가 중심입니다.

1년 전 당시에는 분산형 버전 관리 시스템에 대한 사용법은 알고 있었지만 더 중요한, 운영을 어떻게 할지는 좀 애매한 상태였습니다. 팀 단위 공동 작업을 하는 건 당연한데, 무엇보다 업무 특성상 한 버전 출시 후 다음 버전을 만드는 게 아니라 개발 중이면서 이전 버전에서 새로운 버전을 시작해 먼저 내 보내야 하는 경우도 허다하다는 게 문제입니다.

지난 글에서는 이를 위해 develop 브랜치를 만들었는데 예상은 했지만 실제 운영해 보니 썩 좋진 않았습니다. develop 브랜치가 상황에 따라 너덧 개 정도 생기기도 했는데 이런 경우 버전 번호 붙이기가 참 곤란했습니다. 즉 develop 브랜치 특성상 이전 버전에서 분기해 기능을 추가하게 되는데 상황에 따라 붙여야 할 버전 번호로 제품이 이미 나온 경우도 있다는 거죠. 또한 develop 브랜치는 대체로 개발 기간이 길어지는데 오래 유지할 수록 릴리스 후 다른 브랜치에 변경 내용을 병합할 때 혼란도 많고요.

익숙하지 않은 개발자가 혼란스러워 하지 않도록 개인 개발용 브랜치를 만들지 않고 모든 브랜치를 공동 작업용으로 하다보니 오히려 혼란이 더 생기는 경우라 할 수 있겠습니다. 한마디로 요약하자면 분산형 시스템을 서브버전 같은 중앙 집중형처럼 브랜치를 운영한 사례라 하겠습니다.

서론이 길었는데 그래서 생각해 본 게 게으른 개발자를 위한 게으른 푸시 모델입니다. 다음 그림은 ‘성공적인 Git 브랜치 모델‘이란 글에서 제시하고 있는 브랜치 모델입니다.

Git Branching model

브랜치는 동일하게 쓰니까 이번에는 따로 그리지 않고 이걸로 설명합니다. 일반적으로는 한 버전을 출시한 후 다음 버전을 준비하므로 위 모델이 딱입니다. 하지만 이미 얘기했듯 현재 개발 중인 버전을 마치지 않고 먼저 개발해서 내 보내야 하는 경우가 많으므로 이를 그대로 적용하기엔 무리가 있습니다. 위 모델 역시 모든 기능을 feature 브랜치로 개발하면서 develop에 계속 병합하고 모든 기능 개발을 완료해 release 브랜치로 넘긴 후 새 버전에 넣을 기능은 다시 develop에서 분기해 개발합니다.

기본적인 모델과 게으른 푸시 모델의 차이는 바로 여기에 있습니다. 즉 일반적으로 feature로 분기해 만든 기능은 확인을 마치면 develop에 병합합니다. 하지만 게으른 푸시에서는 병합하지 않고 그대로 둡니다. 해당 버전의 모든 기능 개발을 마치면 그제야 릴리스할 이슈에 해당하는 feature 브랜치를 각자 develop에 병합합니다. develop에 병합을 모두 마치면 release로 분기해 버그 수정을 합니다. 이렇게 하면

  • develop(default) 브랜치에 머무는 시간이 짧으므로 현재 진행 중인 개발 때문에 어쩔 수 없이 브랜치를 새로 만드는 문제가 없습니다.
  • 또한 릴리스 직전에 실제 내보낼 기능을 선택할 수 있습니다.

전체적으로 이슈와 변경 관리가 매우 유연해집니다. 즉 작은 차이가 큰 차이를 만든다고 하겠습니다. 단점은 feature 브랜치가 이슈 수만큼 늘어나 있을 테니 복잡해 보일 수는 있습니다.

그런데 여기서 더 생각해 볼 것이 있습니다. 개발자가 이슈별로 만드는 feature 브랜치는 릴리스 준비 직전, 즉 통합 시험 전까지는 develop에 병합하지 않으므로 연관된 기능을 여러 이슈로 나눠 작업하는 경우 문제가 됩니다. 내가 변경하고 있는 내용이 다른 이슈에 의존하면 그 이슈에 해당하는 변경 내용을 내 저장소로 가져와야 확인할 수 있으니까요. 이를 위해서는 개발자별로 원격 저장소를 만들어 필요한 feature 브랜치만 서로 공유합니다.

저장소 공유 개요

원격에 있는 개발자별 저장소는 개발자 사이에서 feature 브랜치를 공유하기 위한 곳이므로 주 저장소에서 복제해서 만들든 개발자 자신의 지역 저장소 내용을 올려 만들든 상관 없습니다. 중요한 것은 저장소를 공통적으로 만든 이후부터는 보내야 할 특정 브랜치만 자신의 원격 저장소로 보내고, 상대 개발자는 필요한 브랜치만 받아 오면 됩니다. 그림만으로 이해하기에는 어려우므로 다음 시나리오를 생각해 보죠.

# 원격 저장소 만들기

  • 개발자 A: Main Repo를 복제해 Repo_A 생성
  • 개발자 B: Main Repo를 복제해 Repo_B 생성

# 지역 저장소 만들기

  • 개발자 A: Repo_A를 복제해 Dev_A 생성
  • 개발자 B: Repo_B를 복제해 Dev_B 생성

# 개발 단계

  • 개발자 A: develop에서 issue-1 브랜치를 만들어 변경 내용을 Dev_A에 추가(commit)
  • 개발자 B: develop에서 issue-2 브랜치를 만들어 변경 내용을 Dev_B에 추가
  • 개발자 A: develop에서 issue-3 브랜치를 만들어 변경 내용을 Dev_A에 추가
  • 개발자 B: issue-2 브랜치 내용을 공유하기 위해 Repo_B로 보냄(push)
  • 개발자 A: Repo_B 에서 issue-2 브랜치 변경 내용만 가져옴(pull)
  • 개발자 A: 작업 영역(work space)을 issue-2 브랜치로 갱신(update)
  • 개발자 A: issue-1 브랜치를 병합
  • 개발자 A: issue-1 브랜치 변경 내용이 올바로 작동하는지 확인
  • [issue-1 브랜치에 오류가 있으면]
  • 개발자 A: 작업 영역을 issue-1 브랜치로 갱신
  • 개발자 A: issue-1 브랜치에 변경 내용을 추가
  • 개발자 A: 작업 영역을 issue-2 브랜치로 갱신 후 issue-1에 추가 변경한 내용을 병합해 확인

# 병합 단계

  • 개발자 A: Main Repo, develop 브랜치에서 변경 내용을 가져옴
  • 개발자 A: 작업 영역을 develop 브랜치로 갱신
  • 개발자 A: issue-1 브랜치를 병합
  • 개발자 A: issue-3 브랜치를 병합
  • 개발자 A: develop 브랜치에 병합한 내용만 Main Repo에 보냄
  • 개발자 B: Main Repo, develop 브랜치에서 변경 내용을 가져옴
  • 개발자 B: 작업 영역을 develop 브랜치로 갱신
  • 개발자 B: issue-2 브랜치를 병합
  • 개발자 B: develop 브랜치에 병합한 내용만 Main Repo에 보냄

# 병합할 때

  • feature 브랜치를 develop(default) 브랜치에 병합할 때
    • 변경 내용이 하나인 간단한 브랜치는 rabase
    • 변경 내용이 여럿인 브랜치는 merge
  • release, hotfix 브랜치 등에서는 (필요에 따라 브랜치로) 개발 후 rabase 사용해 이력을 가지런히
    • 특별한 이유 없이 한 이슈에 변경 내용을 여럿 만들지 말 것
    • 브랜치 내에 변경 내용을 여럿 만들어야 하는 경우에는 merge

시험을 위해 feature 브랜치를 병합해야 할 경우 hg에서는 MQ(mercurial queue)를 사용하면 매우 편리합니다. 복잡한 병합 이력을 줄일 수 있고 변경 내용을 편리하게 옮길 수 있으니까요. 병합 후 git에서는 feature 브랜치 이름을 삭제해도 됩니다. 특히 rebase를 한 경우에는 브랜치 이름을 삭제해 보기 좋게 정리하는 게 좋습니다.

이런 식으로 develop(default)에 병합을 모두 마치면 release 브랜치로 다시 옮겨 전체 통합 시험을 하며 버그 수정을 하고 이후 과정은 ‘성공적인 Git 브랜치 모델’ 내용을 따릅니다.  Release, hotfix 브랜치에서 분기해 개발한 브랜치 역시 해당 브랜치를 남겨야 하는 경우가 아니라면 rebase를 사용해 이력을 가지런히 하는 게 좋습니다.

마지막으로 빌드 프로젝트도 이전에는 develop 브랜치가 늘어남에 따라 계속 추가해야 했는데 이 경우에는 develop(default), release, stable(master) 브랜치에 대해서만 고정적으로 운영하면 될 것으로 생각합니다. develop에서는 가능한 자주 Alpha를 생성해 병합 과정에서 생기는 오류를 빨리 걸러내고, release는 하루 한 번 변경 내용이 있을 때 Beta 생성, master는 하루 한 번 변경 내용이 있을 때 GA 생성으로 하면 되겠죠.

마지막으로 브랜치 관리에 대한 내용만 다시 요약해 봅니다.

# 브랜치 관리 요약

  • master(stable) 브랜치
    • 주 브랜치이며 언제나 제품으로 내보낼 수 있는 상태여야 함
    • GA 버전 생성
  • develop(default) 브랜치
    • 다음 릴리스에 대한 최신 변경 내용을 반영하는 주 브랜치이며 통합 브랜치라고도 한다
    • 개발한 기능을 모두 통합하며 빌드에 이상이 없어야 함
    • Alpha 버전 생성
  • feature 브랜치
    • 개발자가 이슈별로 만들어 개발 진행하는 브랜치
    • develop(default) 브랜치에서 분기해 develop(default) 브랜치로 다시 병합하며 불필요하면 버릴 수 있음
    • feature 브랜치는 서버에서 관리하는 저장소에 전송하면 안 되며 지역 저장소와 개발자별 저장소에만 유지
    • 기능 확인 마친 후 팀 전체가 develop(default) 병합 단계로 넘어갈 때 병합
    • issue-1 처럼 issue-* 형식으로 명명(이슈 번호 사용) 하는 것을 권장하며 필요에 따라 feature- 다음에 적절한 이름 지정
  • release 브랜치
    • 릴리스를 준비하는 브랜치이며 develop(default) 브랜치에서 분기하고 develop(default) 브랜치와 master(stable) 브랜치에 병합
    • 통합 시험, 버그 픽스 등 진행하며 기능 추가 금지
    • release-1.2 처럼 release-* 형식으로 명명(major.minor 버전 지정)하며 release 브랜치로 분기하면 develop(default) 브랜치는 다음 릴리스를 반영하기 시작함
    • release 브랜치를 마치면
      • master(stable) 브랜치에 병합
      • develop(default) 브랜치에 병합
    • Beta 버전 생성
  • hotfix 브랜치
    • 긴급하게 버그 수정 을 해야 할 때 사용하며 master(stable) 브랜치에서 분기하고 develop(default)와 master(stable) 브랜치에 병합
    • 예외: release 브랜치가 있을 때는 develop(default) 브랜치가 아니라 release 브랜치에 병합. 만약 develop(default) 브랜치에 긴급하게 수정 내용을 반영해야 한다면 그렇게 해도 됨
    • 지침
      • 최신 버전으로 해결할 수 있으면 hotfix 생성하지 않고 최신 버전 S/W 사용
      • 가능한 최신 버전에서 분기해 수정
      • 정말 긴급한 사태가 아니면 만들지 않으며 되도록 현재 진행 중인 개발에 포함해 내보내도록 한다
    • hotfix-* 형식으로 명명(major.minor.patch 버전 지정)

# feature 브랜치 병합 시점에 대해 (2013.8.20.)

앞에서 얘기했듯 게으른 푸시에서는 기본적으로 feature 브랜치 병합 단계를 따로 가져가므로 그 시점까지는 feature 브랜치를 여럿 유지해야 하는 불편도 있습니다. 특히 통합이 늦어지는 단점이 큰데 이를 보완하기 위해 다름처럼 할 수도 있습니다.

  • feature 브랜치에서 작업을 마칠 때마다 develop(default) 브랜치에 바로 병합합니다.
  • 단, develop(default) 브랜치에 병합할 feature 브랜치는 그 자체로 완전한 기능이어야 합니다.

즉 feature-A, feature-B 두 브랜치에서 작업하고 있을 때 두 브랜치 모두를 마쳐야 온전히 작동하는 기능이라면 두 브랜치를 모두 마쳤을 때 develop(default)에 병합해야 한다는 뜻입니다. 그러므로 feature 브랜치는 그 자체로 온전한 기능을 구현할 수 있도록 해야 하고, 필요에 따라 여러 이슈로 나눠 작업해야 한다면 그 이슈에 대한 변경 내용을 해당 feature 브랜치에 계속 추가하면 됩니다.

전체적으로는 예닐곱 개 이상
심지어 feature 브랜치까지
Hg를 쓴다면 develop = default, master = stable 입니다
자신이 개발 중인 isuue-1 브랜치에는 자신이 변경한 것만 넣기 위해 상대 개발자의 브랜치를 기준으로 병합합니다. 또는 시험을 위한 브랜치를 따로 만들어 해도 됩니다. 중요한 것은 시험에 사용한 브랜치는 절대 원격 저장소로 보내지 않습니다.
Pages:1234567