Chia sẻ của một nhà phát triển back-end tại công ty phát triển ứng dụng làm đẹp Hwahae nổi tiếng ở Hàn Quốc.
Là một nhà phát triển, chắc hẳn sẽ có những kiến thức bạn biết được một cách tự nhiên cho dù không ai nhắc đến. Trong bài viết này, tôi muốn trao đổi về chiến lược phân nhánh (branching) trong số đó. Hãy cùng phân tích xem chiến lược phân nhánh chúng ta đang sử dụng có phù hợp với quy trình phát triển phần mềm của mình hay không.
Phân tích tình hình chung trước khi xây dựng chiến lược phân nhánh
Một thứ không thể thiếu khi các nhà phát triển viết mã nguồn chính là Version Control Systems (VCS) – các hệ thống quản lý phiên bản. Ngoài việc lưu lại lịch sử các thay đổi trên mã nguồn, đây còn là công cụ hữu ích hỗ trợ việc cộng tác. Nếu không có VCS, tình huống có lỗi phát sinh và cần phải quay trở lại trạng thái mã nguồn cũ sẽ trở nên vô cùng khó khăn. Trường hợp cần quản lý nhiều phiên bản mã nguồn khác nhau cũng tương tự vậy. VCS là một phương tiện giúp các nhà phát triển xử lý những vấn đề thường xuyên xảy ra trong quá trình phát triển.
VCS rõ ràng là một công cụ rất hữu ích, tuy nhiên bạn có thể đối mặt với các hạn chế nếu áp dụng một chiến lược không phù hợp. Trong trường hợp cần phát hành định kỳ, nếu bạn cố định một nhánh làm nhánh test tổng hợp thì chắc chắn tình huống code freezing sẽ phát sinh. Đối với dự án dài hạn, thời gian chờ đợi QA chắc hẳn sẽ rất dài, lúc này sẽ phát sinh tình huống không thể phát hành các dự án khác trước khi thực hiện pre-release cho dự án đang chờ. Nếu gặp trường hợp cần phát hành một bản hotfix thì sẽ càng khó hơn nữa. Tuy nhiên, chiến lược đối phó với cả những tình huống không bao giờ xảy ra cũng sẽ chỉ làm lãng phí tài nguyên phát triển của bạn.
Do đó, để sử dụng VCS một cách hiệu quả, trước tiên bạn cần xây dựng một chiến lược phân nhánh phù hợp với các điều kiện của dự án. Trước tiên, ta hãy phân tích so sánh các chiến lược phân nhánh phổ biến. Nếu bạn đã từng tìm hiểu về một quy trình làm việc (workflow) dựa trên Git, chắc hẳn bạn không thể không biết ba cái tên tiêu biểu sau.
Git-flow

Git-flow bắt đầu được biết đến vào năm 2010, sau khi được một nhà phát triển có nickname nvie giới thiệu trong một bài blog cá nhân. Nhà phát triển nvie nói rằng mình đã tạo ra một mô hình phát triển thành công dựa trên nền tảng git: feature - develop - release - hotfix - master
và kể lại kinh nghiệm 1 năm phát triển với mô hình này. Từ đó, mô hình được chia sẻ và sử dụng rộng rãi, bắt đầu từ những người ấn tượng với câu chuyện của nhà phát triển nvie, sau đó dần trở thành quy trình làm việc tiêu biểu. Cho đến ngày nay, mặc dù đã mười năm trôi qua, mô hình vẫn còn nổi tiếng đến mức rất nhiều nhà phát triển xem nó là tiêu chuẩn.
Lo lắng về phản ứng này, nhà phát triển nvie đã chia sẻ một câu chuyện được giấu kín bấy lâu nay vào tháng 3 năm 2020.
“Git-flow đã trở nên vô cùng nổi tiếng trong nhiều nhóm phát triển phần mềm, đến mức người ta bắt đầu xem nó như một tiêu chuẩn – nhưng đồng thời cũng thật bất hạnh: một giáo lý hay một liều thuốc chữa bách bệnh.”
“Các ứng dụng web thường được phát hành liên tục, không khôi phục, và bạn không cần hỗ trợ nhiều phiên bản triển khai trong thực tế.”
“Đây không phải loại phần mềm tôi nghĩ đến khi viết bài blog 10 năm trước. Nếu nhóm của bạn đang phát triển phần mềm được phát hành liên tục, tôi đề xuất bạn áp dụng một quy trình làm việc đơn giản hơn (giống như GitHub) thay vì cố gắng đưa Git-flow vào nhóm của mình.”
Như vậy, Git-flow là quy trình làm việc phù hợp với các ứng dụng hay giải pháp cần quản lý phiên bản hoặc các public API và không phải chiến lược phù hợp với các ứng dụng web.
Tóm lại, Git-flow phù hợp với:
- Phát hành định kỳ
- Ứng dụng cần quản lý phiên bản (versioning)
GitHub-flow

Năm 2011, CIO Scott Chacon của GitHub lần đầu tiên giới thiệu GitHub và chỉ ra vấn đề lớn nhất của Git-flow. Ông cho rằng quy trình Git-flow phức tạp hơn những gì nhà phát triển cần. Hay nói cách khác, quy trình càng đơn giản thì các nhà phát triển sẽ càng dễ tiếp nhận. Thêm vào đó, Scott Chacon cho rằng càng đơn giản thì càng không phát sinh tình trạng overhead và những vấn đề phát sinh do không hiểu rõ về chiến lược phân nhánh.
GitHub-flow loại bỏ hoàn toàn tính phức tạp của Git-flow. Nhánh cần bảo lưu duy nhất của GitHub là master
. Những nhánh khác được giao cho các nhà phát triển toàn quyền quản lý nên không cần bất kỳ chính sách phức tạp nào cả.
Chính sách của GitHub-flow
- Mã nguồn trên
master
lúc nào cũng có thể phát hành. - Khi thực hiện công việc mới, cần tạo một nhánh mới từ
master
và làm việc trên đó. - Các thay đổi được commit trên nhánh local, sau đó định kỳ push lên nhánh origin tương ứng.
- Tạo pull request khi cần phản hồi hay sự trợ giúp, hoặc khi các thay đổi trong mã nguồn đã sẵn sàng merge.
- Sau khi kiểm tra mã nguồn của người khác và chấp nhận pull request thì merge vào
master
. - Mã nguồn trên nhánh
master
sau khi thay đổi có thể phát hành ngay lập tức, và nhất định phải phát hành.
GitHub-flow có được những chính sách như trên chính vì đây là mô hình phát hành thường trực (luôn sẵn sàng để phát hành). Tuy nhiên, Scott Chacon cũng thừa nhận các lợi thế của việc phát hành định kỳ và nói thêm rằng: “Nếu nhóm làm việc trên dự án dài hạn và cần các công đoạn bảo trì như hotfix thì Git-flow là lựa chọn thích hợp. GitHub-flow phù hợp với nhóm cần luôn luôn sẵn sàng để phát hành.”
Tóm lại, GitHub-flow phù hợp với:
- Phát hành liên tục
GitLab-flow
GitLab đồng ý rằng GitHub-flow là quy trình chuẩn dành cho các nhóm phát triển liên tục, tuy nhiên đồng thời cũng chỉ ra rằng GitHub chưa đưa ra câu trả lời cho nhiều vấn đề như triển khai (deploy), môi trường (environment), phát hành (release) và hợp nhất (merge) mã nguồn. Dựa trên nền tảng GitHub-flow, GitLab đã đưa ra các hướng dẫn bổ sung để áp dụng quy trình làm việc này tùy theo những hoàn cảnh khác nhau, bao gồm:
- Cách giải quyết các vấn đề về hợp nhất trong trường hợp thời gian triển khai là cố định
- Cách tổ chức các nhánh theo tình hình server
- Cách khắc phục tình huống cần quản lý phiên bản phát hành riêng biệt với nhánh
master
Ngoài ra còn có cách sử dụng rebase để gom nhóm các commit, cách giảm thiểu số lượng merge commit, cách viết commit message, chính sách tách nhánh tính năng và nhiều câu trả lời cho những vấn đề đa dạng khác. Thêm vào đó, GitLab thúc đẩy việc theo dõi (tracking) dựa trên các issue.
“Việc thay đổi mã nguồn có mục đích rõ ràng, vậy nên hãy tạo issue và quản lý bằng issue.” Đây là nguyên tắc khuyến khích công khai lý do thay đổi một cách minh bạch. GitLab cho rằng các nhà phát triển làm việc trên cùng một repository cần biết về lịch sử thay đổi mã nguồn. Ngoài ra, việc quản lý bằng các issue khiến cho vòng đời của nhánh trở nên rõ ràng và dễ hiểu hơn, từ đó thuận tiện hơn cho công việc bảo trì.
Quy trình dành cho các ứng dụng web deploy định kỳ
Git-flow và GitHub-flow sở hữu những đặc điểm khác nhau nên điều kiện sử dụng cũng khác nhau. Nếu xem xét kỹ, bạn còn có thể biết được tình huống khiến hai luồng công việc này lộ ra khuyết điểm. Bạn sẽ lựa chọn chiến lược nào khi gặp phải các vấn đề dưới đây?
- Ứng dụng web thay đổi liên tục
- Cần quản lý phiên bản một cách không cần thiết
- Hệ thống phát hành định kỳ
- Thực hiện cùng một lúc nhiều dự án
Cả nvie – người đã giới thiệu Git-flow hay và Chacon – người tạo ra GitHub-flow đều khuyến khích người dùng lựa chọn quy trình làm việc phù hợp với tình hình của nhóm. GitLab cũng vậy. Bạn không thể mặc một chiếc áo không vừa với mình, vậy nên khi gặp những tình huống trên, bạn nên tiếp cận như thế này.

Tôi đã tổ chức lại quy trình làm việc cho phù hợp với ứng dụng web được phát hành định kỳ, bao gồm ba thay đổi chính:
Đầu tiên, tôi mượn quy trình được tạo ra dành cho phát hành định kỳ – Git-flow. Tuy nhiên, để mỗi nhánh có một môi trường kiểm thử (test environment) khác nhau, tôi đã bỏ nhánh develop
. Trường hợp các phiên bản phát hành đã được định sẵn, bạn có thể giữ nguyên nhánh develop
như Git-flow, còn đối với các ứng dụng web xem trọng khả năng phát triển đồng thời thì việc sử dụng nhánh develop
sẽ gây ra khá nhiều hạn chế.
Ảnh hưởng của nhánh develop
đến nhánh release
Mặc dù nhánh release
thường sẽ tồn tại, nhưng nếu nhánh release
chưa được phát hành vẫn tồn tại thì ta sẽ không thể quay lại thời điểm phát hành trước đó và không thể giải quyết được vấn đề code freezing.
Nếu bạn gặp tình huống mã nguồn đã lên tới nhánh release
nhưng cần quay lại nhánh feature
để phát triển tiếp thì bạn cần revert đến tận nhánh develop
. Lúc này, nếu có cả những nhánh release
khác thì những nhánh này cũng có thể bị ảnh hưởng, tùy vào thứ tự chúng được tạo ra.
Thứ hai, tôi chỉ duy trì một nhánh master
duy nhất, theo ý tưởng của GitHub-flow. Thêm vào đó, tất cả các nhánh đều được tạo ra từ master
. Nếu duy trì từ hai nhánh chính trở lên như Git-flow thì các chính sách sẽ trở nên phức tạp quá mức cần thiết và khả năng phát sinh vấn đề overhead sẽ càng lớn hơn. Tôi đồng ý với GitHub-flow về chiến lược nhánh master
.
Thứ ba, tôi quản lý các nhánh bằng các issue như GitLab-flow đề nghị. Nếu có nhiều dự án khác nhau được triển khai cùng một lúc thì chỉ với các commit message, bạn sẽ khó có thể biết được một nhánh nào đó tại sao lại được tạo ra hay còn cần thiết nữa hay không. Để tránh điều này, tôi tạo nhánh feature
dựa trên issue.
Cấu tạo các nhánh
Tên nhánh | Phân loại | Mô tả | Ghi chú |
feature | Nhánh phụ trợ | – Nhánh phát triển theo tính năng – Sau khi phát triển xong sẽ được merge vào release – Tạo và quản lý nhánh bằng một Issue List | |
release | Nhánh phụ trợ | – Nhánh test, tổng hợp code từ các nhánh feature để phát hành– Sau khi test xong sẽ được merge vào master – Được tạo ra để giải quyết vấn đề code freezing | – Có thể có nhiều lớp – Không được có nhánh con |
hotfix | Nhánh phụ trợ | Nhánh được tạo ra để giải quyết những lỗi khẩn cấp trong quá trình triển khai | |
master | Nhánh chính | – Nhánh cơ bản – Luôn duy trì trạng thái sẵn sàng phát hành | – Nhánh được bảo vệ – Chỉ nhân viên cấp trên Maintainer có quyền quản lý |
Trong bảng trên, tôi đã tóm tắt những nội dung ở phần trước. Nhánh feature
được tạo theo từng dự án và các nhà phát triển có thể tùy ý tạo thêm nhánh con của feature
nếu cần. Các tính năng riêng lẻ sau khi được phát triển sẽ được merge vào nhánh release
tương ứng để kiểm tra một cách toàn diện.
Lý do cần nhánh release
Khi mới bắt đầu phát triển, chúng ta hẳn đã ước tính một cách đại khái về lịch trình phát hành, tuy nhiên có khả năng lớn là lịch trình này sẽ thay đổi tùy theo tình hình thực tế. Thời điểm tạo nhánh feature
, bạn không thể dự tính chính xác thời gian mã nguồn trên nhánh này sẽ hoạt động trên server. Chính vì vậy, ta cần có nhánh riêng tổng hợp mã nguồn để test bên cạnh nhánh phát triển tính năng.
Nhánh release
tương ứng với thời điểm phát hành sau khi kết thúc quá trình kiểm thử sẽ tạo merge request vào nhánh master
. master
luôn duy trì trạng thái sẵn sàng phát hành. Khi chuyển mã nguồn từ nhánh release
sang master
, ta cần giải quyết tất cả conflict ở nhánh release
. Chúng ta không giải quyết conflict ở nhánh master
. Vì vậy, khi mã nguồn ở nhánh master
được phát hành, ta phải mang tất cả các thay đổi mới ở master
sang release
.
Chiến lược phân nhánh trên phù hợp với:
- Phát hành định kỳ
- Ứng dụng có dự án dài hạn
- Ứng dụng web không cần quản lý phiên bản
Lời khuyên từ nhóm Bitbucket của Atlassian: Tin tưởng merge và đơn giản hóa nhánh
Lược dịch bài viết của tác giả Nicola Paolucci, một chuyên gia Git ở Atlassian, về cách lựa chọn chiến lược phân nhánh hiệu quả.
Chiến lược phân nhánh đã đi từ đơn giản và sơ khai đến phức tạp, kiên cố và mang tính phòng thủ. Tổ chức của bạn cần sự phức tạp và bảo mật ở mức độ nào? Bài viết này sẽ nói về vấn đề thỏa hiệp giữa tính nhanh nhẹn và sự kiên cố, với một số chỉ dẫn giúp bạn lựa chọn được chiến lược git phù hợp với tổ chức của mình và những bài học chúng tôi đã học được ở Atlassian.
Tóm tắt:
- Hãy tin tưởng merge
- Tránh xa những mô hình phân nhánh phức tạp
- Đơn giản hóa và san phẳng cấu trúc nhánh nhiều nhất có thể
Tin tưởng merge
Bước đầu tiên trong việc lựa chọn mô hình phân nhánh tối ưu chính là tin tưởng merge. Tôi muốn đề cập điều này với những nhóm mới áp dụng git. Nếu bạn đã quen thuộc và không còn nghi ngờ gì về merge nữa, hãy bỏ qua đoạn này.
Mặc dù trong những hệ thống phát triển cũ, bạn có thể phải tránh merge bằng mọi cách, hoặc thuê một nhóm đặc biệt để quản lý việc này, nhưng thật ra merge không phải một sự kiện đặc biệt nào, nhờ có những đặc điểm dưới đây:
- Git khuyến khích sử dụng nhánh có vòng đời ngắn và merge thường xuyên, vì việc sử dụng các nhánh rất rẻ và nhanh chóng.
- Merge được tiến hành cục bộ (ở local) nên kết quả thường sẽ hiển thị ngay lập tức.
- Git biết nhánh cha của các nhánh và chỉ merge những commit cần thiết thay vì toàn bộ cây, nhờ đó hạn chế conflict trên mã nguồn.
- Bạn có thể có nhiều chiến lược merge, tùy theo nhu cầu của mình. Việc hợp nhất mã nguồn và phân nhánh rất dễ dàng, nên thậm chí nhiều nhóm lại đối diện với vấn đề sử dụng quá đà. Hãy duy trì một sự cân bằng và tránh lạm dụng.
Tránh những chiến lược phân nhánh quá mức
Nếu xem xét các mô hình phân nhánh trước và sau khi git ra đời, bạn sẽ thấy chiến lược phân nhánh đã đi từ đơn giản đến phức tạp. Sự phức tạp có thể trở thành vấn đề. Trong phần này, tôi sẽ chỉ ra hai chiến lược phân nhánh quá đà phổ biến.
Nhánh đơn phức tạp
Trong quá khứ, một nhánh đơn đã thống trị tất cả. Các nhóm phát triển phần mềm đã làm việc với chỉ một nhánh đơn, gọi là trunk
, trong suốt nhiều năm, trong mọi điều kiện. Tất cả các công việc đã hoàn thành hay chưa hoàn thành, các bản dựng (build) thành công hay lỗi đều nằm trong cùng một nhánh! Quy trình này đã từng được áp dụng và ngày nay vẫn còn được áp dụng. Tuy nhiên, nó đã không còn là quy trình hiệu quả nhất. Chiến lược đơn nhánh hoạt động như sau:

trunk
Các nhóm mới áp dụng git hoặc muốn duy trì quy trình làm việc cũ có thể sẽ muốn tái hiện cấu trúc này. Bạn vẫn có thể tạo ra cấu trúc tương tự như thế này, mặc dù bây giờ trunk
được gọi là master
trong git.

rebase
Tất cả mọi người làm việc trên nhánh master
, không ai tạo nhánh mới và mỗi nhà phát triển sử dụng rebase để cập nhật những thay đổi mới nhất trên mã nguồn. Phương pháp này rất giống với mô hình phân nhánh trunk-only
kiểu cũ.
Một số nhóm hiện đại đã phân cấp các nhánh thành một vài cấp độ, tuy nhiên họ vẫn áp dụng một số quy tắc gộp (squash) commit để nhánh master
trông giống hệt như hình trên. Vậy họ sẽ thiếu mất điều gì? Chính là bối cảnh và nguồn gốc.
Nhược điểm chính của chiến lược đơn nhánh là là khi nhánh master
gặp sự cố, công việc của tất cả mọi người sẽ phải ngừng lại. Khi sử dụng git, các nhà phát triển có được toàn bộ lịch sử của dự án nên việc quay trở lại trước thời điểm sự cố phát sinh và tiếp tục làm việc bình thường cho đến khi vấn đề được giải quyết là rất dễ dàng, tuy nhiên các nhà phát triển lại phải làm việc trên nhánh local của họ.
Phân nhánh quá nhiều lớp
Trái ngược với chiến lược nhánh đơn phức tạp là chiến lược chuẩn hóa các quy trình một cách quá đà và chia mã nguồn thành quá nhiều nhánh, nhưng lại khiến mọi thứ phức tạp hơn thay vì đơn giản hóa chúng.

Trong ví dụ này, nhóm tạo ra một nhánh riêng cho mỗi user story, từ mỗi nhánh user story lại tạo ra nhánh khác cho các issue và từ mỗi nhánh issue lại tiếp tục tạo nhánh mới cho các subtask. Sau đó, tất cả các nhánh này lại lần lượt được hợp nhất vào nhánh cha rồi cuối cùng mới đến nhánh master
.
Mặc dù git giúp bạn phân nhánh và hợp nhất mã nguồn một cách dễ dàng, nhưng chắc hẳn việc phức tạp hóa quy trình sẽ gây ảnh hưởng không nhỏ đến tốc độ làm việc của nhóm.
Đơn giản hóa và làm phẳng phân lớp nhánh nếu có thể
Cho đến bây giờ, chúng ta đều đã hiểu là cần tránh sự phức tạp quá đà. Vậy quy trình làm việc kết hợp và tối ưu hóa cấu trúc kiên cố cổ điển và tính linh hoạt là như thế nào? Bạn có thể cân nhắc dựa trên tình hình của tổ chức mình, qua hai câu hỏi sau:
- Dự án của tôi cần tối thiểu bao nhiêu nhánh dài hạn?
- Liệu tôi có cần duy trì các phiên bản cũ của phần mềm không?
Thông thường, bạn nên tổ chức cấu trúc nhánh phẳng hết mức có thể.
Tiêu chuẩn tạo nhánh: Mỗi user story một nhánh hay mỗi Jira issue một nhánh?
Mức độ chi tiết của các nhánh là một yếu tố quan trọng. Nếu chúng quá thô và cần nhiều người làm việc cùng nhau, bạn có thể gặp phải những vấn đề với chiến lược nhánh đơn được đề cập ở trên. Còn nếu bạn tạo ra quá nhiều nhánh, quy trình đánh giá và bức tranh tổng thể của bạn có thể trở nên khó hiểu và phức tạp.
Chúng tôi nhận thấy rằng mức độ chi tiết tốt nhất cho quy mô của nhánh là Một nhánh cho một issue.
Quy trình đúng đắn #1: Một nhánh chính ổn định, tận dụng các nhánh phụ nhưng giữ cho chúng đơn giản
Bạn có thể xây dựng một chiến lược nhánh đơn giản cho dự án cần phát hành liên tục. Trọng tâm của chiến lược này là mã nguồn trên nhánh master
phải luôn trong tình trạng sẵn sàng để phát hành. Mỗi commit tới master
đều được gắn thẻ một lần phát hành. Đây là một chiến lược phù hợp với các dự án như ứng dụng web hay SaaS – những phần mềm không cần quản lý lịch sử phiên bản.
Quy trình đúng đắn #2: Giữ cho các nhánh đơn giản nhưng vẫn duy trì phiên bản cũ
Chắc chắn chỉ một nhánh master là không đủ khi bạn cần duy trì và quản lý nhiều phiên bản cũ của ứng dụng. Giải pháp trong trường hợp này là có nhiều nhánh phát hành dài hạn cho mỗi phiên bản mà nhóm bạn cần duy trì. Các thay đổi luôn luôn được đưa từ các nhánh cũ sang các nhánh gần nhất và không bao giờ theo hướng ngược lại. Bạn có thể xem thêm lý do cho điều này trong bài viết Bản chất của quy trình làm việc trên nhánh trên blog của Atlassian.

Nhánh tích hợp bổ sung khi cần thiết
Trong trường hợp có nhiều tính năng phát hành trong cùng một thời điểm nhưng cần thực hiện riêng từng tính năng, bạn cần cô lập các nhánh tính năng và làm việc trên từng nhánh. Lúc này, bạn có thể tạo một nhánh tích hợp để tổng hợp các nhánh tính năng trước khi hợp nhất vào master
. Mã nguồn trên nhánh tích hợp này cần cập nhật thường xuyên những thay đổi trên nhánh master
để đơn giản hóa lần merge cuối cùng.
Ví dụ, nhóm ở BitBucket Server tạo nhánh tích hợp bổ sung 1-2 lần trong mỗi vòng phát hành (khoảng 6 tuần một lần). Những nhánh bổ sung này là cần thiết trong trường hợp nhóm có nhiều luồng công việc và cần giữ chúng cô lập một cách tương đối.
Không có câu trả lời đúng nào cả
Khi cân nhắc chiến lược phân nhánh phù hợp với tổ chức, bạn cần nắm rõ các điều kiện và tình hình của mình. Nếu không có chiến lược sẵn có nào phù hợp với tình hình của bạn, hãy tạo ra một quy trình làm việc mới cho riêng mình. Bạn không cần phải lãng phí tài nguyên để làm theo một khuôn khổ có sẵn. Quy trình dành cho ứng dụng web phát hành định kỳ ở trên chính là quy trình được sử dụng thực tế ở công ty tôi, và do chính tôi tổ chức.
The original articles:
The translated article above belongs to Hwahae, the author Nicola Paolucci and Atlassian. Metacoders commits not to use this content for any commercial purpose.
1 thought on “Xây dựng chiến lược phân nhánh mã nguồn với lời khuyên từ các chuyên gia”
Comments are closed.