마이너 릴리스
고친 사람 github-actions[bot]
마이너 릴리스는 기존 코드를 깨지 않는 새 기능을 더한 판, 또는 그런 판을 내는 일입니다. 받아 쓰는 쪽은 자기 코드를 고치지 않고 새 판으로 올릴 수 있습니다. 새 기능은 필요한 쪽만 골라 씁니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 기존 코드를 깨지 않는 새 기능을 담은 판입니다. 라이브러리 2.3.1 에 함수 하나를 더해 2.4.0 으로 내놓는 것이 한 예입니다.
왜 이렇게 하나 — 새 기능이 들 때마다 「깨질 수 있는 판」으로 내면 받는 쪽은 업데이트를 미룹니다. 고침만 든 판으로 내면 고침만 기대한 쪽이 모르는 변화를 떠안습니다. 버전 번호에 「새 것이 들었지만 깨지지는 않는다」를 뜻하는 자리를 따로 두면 받는 쪽이 번호만 보고 안심합니다.
어떻게 도나
- 기존 함수와 동작은 남긴 채 새 기능을 더합니다
- 버전 번호의 가운데 칸을 하나 올리고 끝 칸은 0 으로 돌립니다
- 받는 쪽은 판을 올려도 기존 코드가 돕니다
- 새 기능은 쓰고 싶은 쪽만 불러 씁니다
버그만 고친 판은 끝 칸을, 기존 코드를 깨는 판은 첫 칸을 올립니다. 가운데 칸은 깨지지 않는 새 기능이 든 판의 몫입니다.
대가 — 「깨지지 않는다」는 약속을 지키려면 지우고 싶은 옛 기능도 다음 큰 판까지 남겨야 합니다. 더하기만 해도 받는 쪽이 깨지는 경우가 있습니다. 그래서 무엇을 더할지 따져야 합니다.
상세
단골이 많은 식당이 메뉴판에 새 메뉴 두 가지를 올렸다고 해 봅시다. 원래 있던 메뉴는 이름도 값도 맛도 그대로입니다. 단골은 늘 시키던 것을 시키면 됩니다. 새 메뉴가 궁금한 손님만 새 메뉴를 고릅니다.
릴리스는 소프트웨어를 다른 사람이 받아 쓸 수 있게 한 판으로 묶어 내놓는 일입니다. 판마다 2.3.1 같은 버전 번호를 붙여 어느 판인지 가립니다. 한 프로젝트도 시간이 지나면서 여러 판을 냅니다.
마이너 릴리스는 그중 새 기능을 더하되 기존 쓰임을 깨지 않는 판입니다. 식당으로 치면 새 메뉴가 새 함수이고, 늘 시키던 메뉴가 안 바뀐 기존 함수입니다. 날짜 라이브러리가 2.3.1 에 「두 날짜 사이의 영업일 수를 센다」는 함수를 하나 더해 2.4.0 을 냈다면 그 판이 마이너 릴리스입니다. 원래 있던 함수는 이름도 인자도 동작도 안 바뀌었습니다.
기존 코드를 깰 수 있는 큰 판을 메이저(major) 릴리스라고 부릅니다. 마이너(minor)는 그에 견줘 「작은」 판이라는 뜻으로 붙었습니다.
「작다」가 가리키는 것은 변경 분량이 아니라 받는 쪽이 치르는 부담입니다. 새 기능이 수십 개 든 판도 깨지는 것이 없으면 마이너 릴리스입니다.
버전 번호의 가운데 칸
마이너 릴리스가 무엇인지는 버전 번호와 함께 볼 때 또렷해집니다. 이 소절은 널리 쓰이는 번호 규칙 하나에 세 가지 릴리스를 나란히 놓고 견줍니다.
시맨틱 버저닝(Semantic Versioning)은 버전 번호를 점으로 나뉜 세 칸으로 적는 규칙입니다. 칸마다 언제 올리는지도 정해 둡니다. 2.3.1 이면 앞에서부터 메이저 2, 마이너 3, 패치 1 입니다. 번호만 보고 이번 판에 무엇이 바뀌었는지 짐작하게 하려는 규칙입니다.
칸을 가르는 기준은 하위 호환입니다. 하위 호환은 새 판으로 바꿔도 기존 코드가 고치지 않은 채 도는 성질입니다. 이 성질을 깨는 변경은 메이저 릴리스로 냅니다. 성질을 지키면서 버그만 고치면 패치 릴리스로 냅니다.
| 릴리스 | 올리는 칸 | 담는 것 | 받는 쪽이 할 일 |
|---|---|---|---|
| 메이저 릴리스 | 첫째 · 2.3.1 → 3.0.0 |
기존 코드를 깨는 변경 | 코드를 고쳐야 할 수 있다 |
| 마이너 릴리스 | 둘째 · 2.3.1 → 2.4.0 |
하위 호환되는 새 기능 | 새 기능을 쓸 때만 손댄다 |
| 패치 릴리스 | 셋째 · 2.3.1 → 2.3.2 |
하위 호환되는 버그 고침 | 판만 올린다 |
마이너 릴리스는 가운데 줄입니다. 둘째 칸을 올리면 셋째 칸은 0 으로 돌아갑니다. 2.3.1 다음의 마이너 릴리스는 2.4.1 이 아니라 2.4.0 입니다.
마이너 릴리스에도 버그 고침이 함께 들 수 있습니다. 새 기능이 하나라도 들면 둘째 칸을 올립니다.
첫째 칸이 0 인 판은 사정이 다릅니다. 시맨틱 버저닝은 이런 판을 아직 처음 만드는 단계로 봅니다. 그 동안에는 무엇이든 바뀔 수 있다고 둡니다. 0.4.0 에서 0.5.0 으로 가는 판은 마이너 릴리스처럼 생겼어도 기존 코드를 깰 수 있습니다.
새 기능을 따로 묶어 내는 까닭
새 기능을 담는 칸이 따로 없다고 해 봅시다. 그러면 새 기능이 든 판을 메이저 릴리스나 패치 릴리스 가운데 하나로 내야 합니다. 둘 다 받는 쪽을 곤란하게 합니다.
메이저 릴리스로 내면 받는 쪽은 「코드가 깨질 수 있다」는 신호를 받습니다. 변경 내역을 읽고 시험을 다시 돌릴 때까지 업데이트를 미룹니다. 깨지는 것이 하나도 없는데도 받는 문턱만 높아집니다.
패치 릴리스로 내면 반대 문제가 생깁니다. 패치 릴리스는 고침만 든 판이라서 많은 프로젝트가 따져 보지 않고 받습니다. 그 판에 새 기능이 섞이면 받는 쪽은 모르는 사이에 새 코드를 떠안습니다. 새 코드에는 새 버그가 따라올 수 있습니다.
마이너 릴리스는 이 둘 사이에 칸을 하나 둡니다. 「새 것이 들었지만 기존 코드는 돈다」는 신호입니다. 받는 쪽은 새 기능이 필요할 때 올립니다. 급하지 않으면 다음에 올려도 됩니다.
하위 호환되는 추가와 아닌 추가
어떤 변경이 마이너 릴리스에 들어가는지는 한 가지 물음으로 가립니다. 기존 코드가 고치지 않은 채 도느냐입니다. 이 소절은 그 물음을 라이브러리의 함수와 웹 API 의 흔한 변경 여섯 가지에 대어 봅니다.
API(Application Programming Interface)는 한 프로그램이 다른 프로그램을 부를 때 지키는 약속입니다. 라이브러리가 내놓는 함수 목록도 API 입니다. 그 약속을 웹 요청과 응답으로 주고받으면 웹 API 라고 부릅니다.
엔드포인트는 웹 API 에서 요청 하나를 받는 주소입니다. GET /orders 같은 것이 엔드포인트 하나입니다. 웹 API 에 새 기능을 더한다는 것은 흔히 엔드포인트를 하나 더 연다는 뜻입니다.
웹 API 의 응답은 흔히 JSON(JavaScript Object Notation)이라는 글자 형식에 담깁니다. JSON 은 이름과 값의 짝을 늘어놓은 꼴입니다. 그 짝 하나를 필드라고 부릅니다.
기본값은 부르는 쪽이 값을 안 넘겼을 때 대신 쓰는 값입니다. 인자에 기본값이 있으면 부르는 쪽은 그 인자를 빼고 불러도 됩니다.
아래 표는 여섯 변경을 마이너 릴리스에 넣어도 되는지로 가른 것입니다.
| 변경 | 마이너 릴리스에 | 까닭 |
|---|---|---|
| 함수나 엔드포인트를 새로 더하기 | ✓ | 기존 코드는 새것을 안 부른다 |
| 기본값이 있는 인자를 끝에 더하기 | ✓ | 안 넘기면 전과 같이 돈다 |
| 응답 JSON 에 필드를 더하기 | ✓ | 받는 쪽이 모르는 필드를 무시하면 안전하다 |
| 기본값 없는 인자를 더하기 | ✗ | 기존 호출이 인자를 빠뜨린 꼴이 된다 |
| 응답 필드를 지우거나 이름 바꾸기 | ✗ | 그 필드를 읽던 코드가 깨진다 |
| 같은 입력에 다른 결과를 내기 | ✗ | 기존 코드가 기대한 동작이 바뀐다 |
✗ 로 표시한 변경은 하위 비호환 변경입니다. 메이저 릴리스로 미뤄야 합니다.
더하기만 했는데 깨지는 경우
표만 보면 「더하기는 언제나 안전하다」로 읽힙니다. 그렇지 않은 경우가 둘 있습니다. 받는 쪽이 새로 더한 것을 부르지 않고 채울 때, 그리고 응답을 엄격하게 읽을 때입니다. 둘째는 표의 셋째 줄(응답 JSON 에 필드를 더하기)에 붙은 조건이 깨지는 경우입니다.
인터페이스는 「이런 메서드를 갖춰라」는 목록입니다. 라이브러리가 인터페이스를 내놓으면 받는 쪽이 자기 클래스에서 그 메서드들을 채웁니다. 아래는 도형 라이브러리가 2.4.0 에서 인터페이스에 메서드 하나를 더한 모습입니다.
interface Shape {
double area();
double perimeter(); // 2.4.0 에 추가
}
받는 쪽은 2.3.1 시절에 area 하나만 채운 클래스를 짜 두었습니다.
class Square implements Shape {
public double area() { return s * s; }
} // 2.4.0 에서 컴파일 오류
Square 는 새로 생긴 perimeter 를 안 채웠습니다. 그래서 판만 올려도 컴파일이 안 됩니다. 라이브러리 쪽에서는 하나를 더했을 뿐이지만 받는 쪽에게는 깨지는 변경입니다.
이런 인터페이스에 메서드를 더하려면 언어가 허락하는 한 기본 구현을 함께 줍니다. 기본 구현은 인터페이스가 메서드의 몸통까지 함께 들고 있는 것입니다. Square 가 perimeter 를 안 채우면 그 몸통이 대신 쓰여 컴파일 오류가 나지 않습니다. 그럴 수 없으면 메서드를 담은 새 인터페이스를 따로 냅니다.
둘째 경우는 응답 JSON 의 필드 추가입니다. 받는 쪽이 「모르는 필드가 오면 오류를 낸다」로 응답을 읽고 있으면 필드 하나가 늘어난 순간 깨집니다. 그래서 API 를 쓰는 쪽은 모르는 필드를 무시하게 짜 둡니다. 내는 쪽은 그것을 미리 문서에 약속해 둡니다.
사용 중단 예고를 싣는 판
마이너 릴리스는 기능을 더하는 판이지만 기능을 빼는 일의 첫 단계도 맡습니다. 이 소절은 지우고 싶은 함수 하나를 두고 판이 어떤 순서로 이어지는지 따라갑니다.
곧 없앤다고 미리 알리는 일을 사용 중단(deprecation)이라고 부릅니다. 사용 중단된 함수는 아직 돕니다. 대신 부를 때마다 경고를 내거나 문서에 표시가 붙습니다. 쓰는 쪽은 그 경고를 보고 새 방식으로 옮깁니다.
함수를 지우는 것은 하위 비호환 변경이라서 마이너 릴리스에 못 담습니다. 반면 새 함수를 더하고 옛 함수에 사용 중단 표시를 붙이는 것은 아무것도 깨지 않습니다. 그래서 2.4.0 에서 새 함수를 더할 때 옛 함수에 경고를 붙입니다. 옛 함수는 3.0.0 에서 지웁니다.
두 판 사이의 기간이 쓰는 쪽에게 주는 옮겨 갈 시간입니다. 쓰는 쪽은 2.4.0 으로 올려 경고를 봅니다. 경고가 사라질 때까지 코드를 고칩니다. 그 뒤에 3.0.0 으로 올리면 깨질 것이 없습니다.
받는 쪽에 마이너 릴리스가 들어오는 길
마이너 릴리스가 받는 쪽 프로젝트에 언제 들어오는지는 의존성을 적는 방식이 정합니다. 판을 올리는 일을 도구가 하느냐 사람이 하느냐가 갈림길입니다.
프로젝트가 가져다 쓰는 남의 코드를 의존성이라고 부릅니다. 의존성을 내려받고 판을 맞추는 도구가 패키지 매니저입니다.
많은 패키지 매니저는 의존성의 판을 하나로 박지 않고 범위로 적게 해 줍니다. 이것을 버전 범위라고 부릅니다. 자바스크립트의 패키지 매니저인 npm 에서 ^2.3.1 로 적으면 2.3.1 이상이면서 3.0.0 미만인 판이 범위에 듭니다. 아래는 새 판이 나왔을 때 그 범위에 드는지를 적은 것입니다.
2.4.3 // 패치 · 들어온다
2.5.0 // 마이너 · 들어온다
3.0.0 // 메이저 · 막힌다
마이너 릴리스와 패치 릴리스는 저절로 들어옵니다. 메이저 릴리스만 사람이 골라 올립니다. 내는 쪽이 둘째 칸의 약속을 지킨다고 믿고 첫째 칸만 닫아 둔 셈입니다. 그 약속이 한 번 깨지면 아무도 코드를 안 고친 날에도 받는 쪽 빌드가 깨집니다.
이것을 막으려고 받은 판을 하나하나 적어 두는 락 파일을 함께 씁니다. 락 파일이 있으면 새 마이너 릴리스도 저절로 들어오지 않습니다. 누군가 락 파일을 갱신해야 들어옵니다. 그때 시험을 돌려 깨지는지 확인합니다.
마이너 릴리스가 여는 패치 줄기
마이너 릴리스는 혼자 끝나는 판이 아닙니다. 흔히 그 판 위에 패치 릴리스가 여럿 이어집니다. 이 소절은 코드 저장소에서 그 줄기가 어떻게 생기는지를 브랜치로 보입니다.
커밋은 코드 변경 한 묶음을 기록으로 남긴 것입니다. 커밋이 차례로 쌓인 갈래를 브랜치라고 부릅니다.
개발은 대개 메인 브랜치 하나에 쌓입니다. 메인 브랜치는 모두가 자기 커밋을 모으는 갈래입니다.
2.4.0 을 낸 뒤에도 메인 브랜치에는 다음 마이너 릴리스에 들어갈 새 기능이 계속 쌓입니다. 이때 2.4.0 의 버그를 고쳐 내려면 새 기능이 섞이지 않은 갈래가 따로 있어야 합니다. 그래서 마이너 릴리스를 낼 때 그 판에서 릴리스 브랜치를 갈라 둡니다.
flowchart TD
subgraph 메인["메인 브랜치"]
M1["2.4.0 · 마이너 릴리스"] --> M2["새 기능 커밋"] --> M3["2.5.0 · 마이너 릴리스"]
end
subgraph 갈래["2.4 릴리스 브랜치"]
B1["2.4.1 · 패치 릴리스"] --> B2["2.4.2 · 패치 릴리스"]
end
M1 --> B1
그림에서 2.4.0 은 두 갈래로 나뉩니다. 메인 브랜치는 새 기능을 모아 2.5.0 으로 갑니다. 2.4 릴리스 브랜치는 버그 고침만 받아 2.4.1 과 2.4.2 를 냅니다. 마이너 릴리스 하나가 패치 릴리스 줄기 하나를 여는 셈입니다.
줄기가 늘수록 돌볼 갈래도 늘어납니다. 그래서 프로젝트는 어느 마이너 릴리스에 언제까지 패치를 내줄지 미리 정해 둡니다. 약속한 기간이 끝난 줄기에는 더 이상 패치가 나오지 않습니다. 이것을 지원 종료라고 부릅니다.
번호만 믿으면 안 되는 경우
버전 번호의 칸은 내는 쪽의 약속이지 도구가 검사해 주는 사실이 아닙니다. 모든 프로젝트가 같은 약속을 하지도 않습니다. 받는 쪽이 번호를 어디까지 믿을지는 경우를 나눠 가려야 합니다.
파이썬은 3.11 에서 3.12 로 둘째 칸만 올리면서 사용 중단 기간이 끝난 distutils 모듈을 지웠습니다. 첫째 칸은 아주 드물게만 올리기 때문입니다. 이런 프로젝트에서 둘째 칸이 바뀐 판은 시맨틱 버저닝의 마이너 릴리스보다 무겁습니다.
그래서 판을 올리기 전에 변경 로그를 읽습니다. 변경 로그는 판마다 무엇이 바뀌었는지 적은 기록입니다. 그 프로젝트가 시맨틱 버저닝을 따른다고 밝혔는지도 함께 봅니다.
내는 쪽도 실수합니다. 깨지는 변경인 줄 모르고 마이너 릴리스에 넣는 일이 생깁니다. 그것을 막으려고 공개한 함수와 타입 목록을 앞 판과 견주는 검사를 릴리스 전에 돌리기도 합니다. 러스트의 cargo-semver-checks 가 그런 도구입니다.
관련 항목
마이너 릴리스와 함께 버전 번호를 나누는 릴리스
마이너 릴리스가 따르는 버전 규칙
시맨틱 버저닝 · 버저닝 · 하위 호환 · 하위 비호환 변경 · 브레이킹 체인지 · 공개 API · 안정성 수준
마이너 릴리스에 담기는 하위 호환 변경
엔드포인트 · 기본값 · 선택 인자 · 인터페이스 · 기본 메서드 · API 진화
마이너 릴리스가 여는 기능 제거 단계
사용 중단 · 폐기 예고 · 사용 중단 경고 · 유예 기간 · 기능 플래그
마이너 릴리스를 받아 쓰는 도구와 장치
패키지 매니저 · 의존성 · 버전 범위 · 락 파일 · 의존성 관리 · 의존성 지옥
마이너 릴리스를 내고 돌보는 브랜치 작업
커밋 · 브랜치 · 메인 브랜치 · 릴리스 브랜치 · 태그 · 체리픽 · 백포트
마이너 릴리스를 내기 전후의 확인과 기록
변경 로그 · 릴리스 노트 · 회귀 테스트 · API 호환성 검사
마이너 릴리스를 내는 시점과 기간
릴리스 주기 · 장기 지원 · 지원 종료 · 릴리스 트레인
마이너 릴리스가 속하는 상위 분야
다른 이름: minor release · 마이너 버전 · minor version · 마이너 업데이트 · 기능 릴리스