
아름답게 앱 버전 설정하기
안녕하세요! 개발을 하다 보면 늘 마주치는 고민 중 하나가 바로 “앱 버전”을 어떻게 관리할 것인가 하는 점인데요. 단순히 숫자를 올리는 것 같지만, 그 안에는 생각보다 많은 의미와 약속이 담겨있어야 합니다. 오늘은 제가 고민하고 경험하며 정착한 “아름다운 앱 버전 설정” 방법에 대해 공유하고자 합니다.
목차
- 앱 버전이란 무엇인가?
- 왜 이 글을 작성하게 되었는가?
- 기존 Semantic Versioning의 아쉬움: 무엇이 문제였을까?
- 더 나은 앱 버전 관리를 위한 고민: 어떻게 개선할까?
- 고려해야 할 사항들
- 충분히 직관적인가?
- 각 버전이 적절한 의미를 내포하고 있는가?
- 고려해야 할 사항들
- 우리가 선택한 최종적인 앱 버전 설정 방식
- 회고 및 마무리
1. 앱 버전이란 무엇인가?
앱 버전이란, 소프트웨어의 특정 상태를 식별하는 고유한 이름 또는 번호입니다. 사용자는 버전을 통해 자신이 어떤 상태의 앱을 사용하고 있는지 알 수 있고, 개발팀은 버전을 기준으로 변경 사항을 추적하고, 문제를 해결하며, 배포를 관리합니다. 일반적으로는 많은 분들이 아시는 Major.Minor.Patch
형태의 Semantic Versioning을 많이 사용합니다.
2. 왜 이 글을 작성하게 되었는가? (고민의 시작)
제가 Flutter 앱 개발을 하면서 버전 관리에 대해 꾸준히 고민했던 이유는 다음과 같습니다.
- 빌드 번호만으로는 부족하다: Android 내부 테스팅 채널이나 TestFlight에서 테스터들에게 앱을 배포할 때, 종종 빌드 번호보다는 “앱 버전”이 더 눈에 잘 띄게 표시됩니다. 만약 앱 버전을 소홀히 하고 빌드 번호만 계속 올린다면, 테스터나 내부 구성원이 “이게 지금 테스트해야 할 최신 버전이 맞나?”, “이전 버그가 수정된 버전인가?” 혼란을 겪을 수 있습니다. 앱 내부에 별도 정보를 심어두지 않는 이상, 앱 아이콘만으로는 지금 설치된 앱이 개발자가 의도한 최신 테스트 버전인지, 아니면 이전 버전인지 알기 어렵기 때문입니다.
- 빠른 개발 속도와 버전 관리의 딜레마: 특히 스타트업 환경에서는 기능 추가와 버그 수정이 동시에, 그리고 매우 빠르게 이루어집니다. 이런 환경에서 전통적인 버전 관리 방식이 항상 효율적이지는 않다는 결론에 이르렀습니다.
이런 고민들을 해결하고, 팀원 모두가 버전을 통해 명확하게 소통할 수 있는 방법을 찾고 싶었습니다.
3. 기존 Semantic Versioning의 아쉬움: 무엇이 문제였을까?
가장 널리 쓰이는 Semantic Versioning(SemVer)은 Major.Minor.Patch
(주.부.수) 세 부분으로 구성됩니다.
- MAJOR: 하위 호환성이 없는 API 변경
- MINOR: 하위 호환성을 유지하면서 기능이 추가된 경우
- PATCH: 하위 호환성을 유지하면서 버그를 수정한 경우
이 세 가지 구분은 명확해 보이지만, 실제 개발 현장에서는 다음과 같은 문제점들이 있었습니다.
- Minor와 Patch의 모호한 경계: “간단한 기능 업데이트”는 Minor이고, “사소한 버그 픽스”는 Patch라고 하지만, 이 둘의 경계가 명확하지 않을 때가 많습니다. 예를 들어, 사용자 경험(UX) 개선을 위한 UI 수정은 기능 추가일까요, 버그 수정일까요? 작은 기능을 추가하면서 발견된 버그를 함께 수정했다면, 이는 Minor 업데이트일까요, Patch 업데이트일까요?
- 스타트업의 빠른 개발 주기: 시장의 요구에 빠르게 대응해야 하는 스타트업에서는 매 배포마다 Minor와 Patch를 엄격하게 구분하여 버전을 올리는 것이 번거롭고, 때로는 개발 속도를 저해하는 요인이 되기도 합니다.
- 복합적인 변경 사항: 실제 개발에서는 버그 수정과 기능 추가가 하나의 배포에 함께 포함되는 경우가 흔합니다. 이런 상황에서 단순히 “버그 픽스” 또는 “기능 추가”로 버전을 구분하는 것은 해당 버전의 변경 내용을 충분히 반영하지 못한다고 생각했습니다.
결국, SemVer의 정의 자체는 훌륭하지만, 우리 팀의 개발 문화와 속도에는 100% 부합하지 않는다는 결론에 이르렀습니다.
4. 더 나은 앱 버전 관리를 위한 고민: 어떻게 개선할까?
기존 방식의 문제점을 인식하고, 우리 팀에 맞는 새로운 버전 관리 전략을 고민하기 시작했습니다. 다음은 저희가 중요하게 생각했던 고려 사항들입니다.
- 고려해야 할 사항들
- 충분히 직관적인가?: 개발자뿐만 아니라 QA팀, 기획팀 등 내부 구성원 누구나 버전을 보고 어떤 상태의 빌드인지 쉽게 유추할 수 있어야 합니다. “이 버전은 몇 번째 배포된 거지?”, “이건 QA용인가, 내부 확인용인가?” 같은 질문이 최소화되어야 합니다.
- 각 버전이 적절한 의미를 내포하고 있는가?: 버전의 각 부분이 단순한 숫자 나열이 아니라, 명확한 정보를 전달해야 합니다. 예를 들어, 몇 번째 공식 릴리즈인지, 해당 릴리즈의 몇 번째 내부 테스트 빌드인지 등을 알 수 있다면 매우 유용할 것입니다.
- (추가) 배포 자동화와의 연동성: 버전 체계가 복잡하면 배포 자동화 과정에서 실수가 발생하거나 구현이 어려워질 수 있습니다. 최대한 단순하면서도 필요한 정보를 담을 수 있도록 고민했습니다.
5. 우리가 선택한 최종적인 앱 버전 설정 방식
수많은 고민과 논의 끝에, 저희 팀은 다음과 같은 앱 버전 명명 규칙을 정립했습니다.
{MAJOR}.{RELEASE_SEQUENCE}.{INTERNAL_BUILD_SEQUENCE}
각 부분의 의미는 다음과 같습니다.
MAJOR
: 기존 SemVer의 Major와 동일합니다. 앱의 근간을 바꾸는 대규모 업데이트나 하위 호환성이 깨지는 변경이 있을 때 올립니다. (예:1
.x.x ->2
.x.x)RELEASE_SEQUENCE
: 현재 유저에게 N번째로 릴리즈되는 버전인지를 나타냅니다. 새로운 기능이나 개선 사항이 포함되어 사용자에게 공식적으로 배포될 때마다 1씩 증가합니다. (예: 1.5
.x -> 1.6
.x)- 이 부분을 통해 앱이 사용자에게 몇 번째로 선보이는 버전인지 직관적으로 파악할 수 있습니다.
INTERNAL_BUILD_SEQUENCE
: 해당RELEASE_SEQUENCE
버전 내에서 내부적으로 테스트하는 빌드의 순번입니다. QA를 진행하거나 내부 테스트를 위해 빌드할 때마다 1씩 증가하며, 새로운RELEASE_SEQUENCE
로 넘어가면 다시 1부터 시작합니다. (예: 1.5.1
-> 1.5.2
-> (릴리즈 후) -> 1.6.1
)- QA팀은 이 번호를 통해 어떤 빌드를 테스트해야 하는지, 어떤 버그가 어떤 내부 빌드에서 발생했는지 명확히 알 수 있습니다.
- TestFlight나 Firebase App Distribution 같은 도구에서도 버전에 따라 설치할 빌드를 명확하게 선택하고 관리할 수 있습니다.
빌드 번호(Version Code / Build Number)는 어떻게 관리할까?
앱 버전(Version Name)과 별개로, 스토어에 업로드할 때는 유일하고, 이전보다 항상 높은 정수형 빌드 번호가 필요합니다. (Android의 versionCode
, iOS의 buildNumber
)
이 문제를 해결하기 위해 저희는 Git 커밋 수를 활용합니다.
- 유일성 및 증가 보장:
git rev-list --count HEAD
명령어는 현재 브랜치의 총 커밋 수를 반환합니다. 새로운 배포를 위해 커밋을 하면 이 숫자는 자동으로 증가하므로, 유일성과 이전 버전보다 높은 값을 자연스럽게 보장할 수 있습니다. - 자동화 용이성: 이 명령어는 쉘 스크립트를 통해 매우 쉽게 구현할 수 있으며, Fastlane과 같은 배포 자동화 도구와 연동하면 사람의 실수(Human Error)를 크게 줄일 수 있습니다.
다음은 pubspec.yaml
업데이트 및 배포 시 사용할 수 있는 간단한 쉘 스크립트 예시입니다.
#!/bin/bash
# 사용 예시:
# VERSION="1.2.3" ./update_version.sh
# 위와 같이 VERSION 환경 변수를 설정하고 스크립트를 실행하거나,
# 스크립트 내에서 VERSION="1.2.3" 와 같이 직접 설정할 수 있습니다.
# 여기서 VERSION은 사람이 수동으로 "{MAJOR}.{RELEASE_SEQUENCE}.{INTERNAL_BUILD_SEQUENCE}" 형식에 맞춰 설정합니다.
# 현재 브랜치의 총 커밋 수를 가져와 BUILD 환경 변수에 저장
export BUILD=$(git rev-list --count HEAD)
# pubspec.yaml의 version 필드를 업데이트합니다.
# 예를 들어 VERSION이 "1.2.3"이고 BUILD가 "101"이라면 "version: 1.2.3+101"로 변경됩니다.
# !!주의: Mac의 sed는 -i 옵션 뒤에 '' 백업 파일 확장자를 명시해야 합니다. Linux에서는 '' 없이 -i 만 사용 가능합니다.
if [[ -z "$VERSION" ]]; then
echo "오류: VERSION 환경 변수를 설정해주세요."
echo "예: VERSION=\"1.2.3\" $0"
exit 1
fi
sed -i '' "s/version: .*/version: ${VERSION}+${BUILD}/" pubspec.yaml
echo "Updated pubspec.yaml with Version: $VERSION, Build Number: $BUILD"
# 이후 Flutter 빌드 및 배포 과정 진행...
fastlane ios beta version:"$VERSION" build_number:"$BUILD"
fastlane android beta version:"$VERSION" build_number:"$BUILD"
실제 pubspec.yaml
의 version
필드는 version: {VERSION}+{BUILD}
형태로 업데이트됩니다. 여기서 {VERSION}
부분(예: 1.2.3
)은 위에서 정의한 {MAJOR}.{RELEASE_SEQUENCE}.{INTERNAL_BUILD_SEQUENCE}
에 해당하며, 개발자가 스크립트 실행 시점에 직접 또는 환경 변수를 통해 수동으로 설정합니다. {BUILD}
부분(예: 101
)은 git rev-list --count HEAD
명령어를 통해 자동으로 생성된 커밋 기반 빌드 번호입니다.
이렇게 함으로써, 사람이 읽고 이해하기 쉬운 버전 이름과 시스템이 요구하는 고유하고 증가하는 빌드 번호를 효과적으로 분리하고 관리할 수 있게 되었습니다.
6. 회고 및 마무리
새로운 버전 관리 시스템을 도입한 후, 저희 팀은 다음과 같은 긍정적인 변화를 경험했습니다.
- 명확한 소통: 버전 번호만으로도 현재 빌드의 상태와 목적을 명확히 알 수 있게 되어 팀 내 커뮤니케이션 오류가 줄었습니다.
- 효율적인 QA: QA팀은 어떤 버전을 테스트해야 하는지, 어떤 버전에서 버그가 수정되었는지 빠르게 파악하여 효율적인 테스트가 가능해졌습니다.
- 배포 관리 용이성: TestFlight나 내부 배포 시 버전을 혼동하는 일이 사라졌습니다.
- 개발자 만족도 향상: 더 이상 Minor와 Patch 사이에서 고민하지 않아도 되어 개발자들이 핵심 기능 개발에 더 집중할 수 있게 되었습니다.
물론 이 방법이 모든 팀에게 완벽한 정답은 아닐 수 있습니다. 팀의 규모, 개발 문화, 프로젝트의 특성에 따라 최적의 버전 관리 전략은 달라질 수 있습니다. 하지만 중요한 것은 “왜 버전을 관리하는가?”라는 본질적인 질문에 답하고, 팀원 모두가 동의하고 이해할 수 있는 “아름다운” 규칙을 만드는 과정이라고 생각합니다. 현재로서는 이 버저닝을 어떻게 부를지는 생각하지 않았지만, 좋은 네이밍이 있다면 추후 업데이트 해보겠습니다 ㅎㅎ
이 글이 Flutter 앱 버전을 어떻게 관리해야 할지 고민하는 많은 개발자분들께 조금이나마 도움이 되었으면 좋겠습니다. 여러분의 팀은 어떤 방식으로 버전을 관리하고 계신가요? 더 좋은 아이디어가 있다면 댓글로 공유해주세요!
참고 아티클: LINE Engineering - 제품팀을 위한 새로운 버전 관리 시스템 HeadVer