Git Flow 模型

很多團隊的開發大都是這種
Git Flow
模型,在這稍作解釋,具體實踐可以參考這些位址
https://yq.aliyun.com/articles/68655
和
https://datasift.github.io/gitflow/IntroducingGitFlow.html
- 主要分支
- master 分支上的代碼随時可以部署到生産環境
- develop 作為每日建構的內建分支,到達穩定轉台時可以釋出并 merge 回 master
- 支援性分支
- feature 分支每個新特性都在獨立的 feature 分支上進行開發,并在開發結束後 merge 到 develop
- release 分支為每次釋出準備的 release candidate, 在這個分支上隻進行 bug fix, 并在完成後 merge 回 master 和 develop
- hotfix 分支使用者快速修複,在修複完成後 merge 回 master 和 develop
那麼,這麼做有什麼壞處呢?
- 規則太複雜,隻要規則複雜,人就一定會在規則上出問題
- 在開發過程中,因代碼隔離,隻在特定的分支上有有效的回報,但是在全量代碼上并沒有有效的回報
- ...
接下來,看看下面的場景
場景
場景一
項目時間緊、功能多,每次上線完要開始新的疊代,如果線上出現 bug, 且 release 分支就是 master 分支,我們不可能将我們新開發的功能推到生産環境。那麼怎麼保證将線上的bug
修複了,還能将我們的代碼隐藏起來,隻在特定的情況下我們的新功能才能使用呢?
場景二
我們為客戶開發軟體,軟體中有個修改使用者資訊的功能,作為客戶 TA 不想讓一般人修改資訊,但總有那麼些人要修改,作為開發我們就需要設定一個功能開關,
在特定條件下這個修改使用者資訊的功能才能被看到。
場景N
根據上面的描述,項目組肯定用的不是 Git Flow 模型,那麼我們先來了解一下 Trunk-Based Development。然後再看看什麼是 Feature Toggle, Feature Toggle 能否解決上面描述的場景呢?
Trunk-Based Development
項目組業務是基于主幹開發的(Trunk-Based Development), 意思就是所有項目組成員在一個分支 master 上進行開發,同時;利用 CI/CD 確定 master 上的代碼
随時都是生産可用的,釋出時,從 master 上檢出 release 分支進行釋出。
Feature Toggle
The basic idea is to have a configuration file that defines a bunch of toggles for various features you have pending. The running application then uses these toggles in order to decide whether or not to show the new feature.
--
Martin Fowler
簡言之,Feature Toggle 就是通過在不更改或者修改少量代碼的情況下修改系統行為:
- 控制功能特性釋出
- 權限政策
- 測試政策
- 控制突發事件
舉個
在已有網頁的 url 中加入特定的辨別,重新整理浏覽器,在頁面的特定部分出現某一個功能。
如下,在 url 中加入
?switch=1
, 重新整理頁面就會出現下圖中的更改賬戶類型
之前
之後
優點
以下優點是基于主幹開發的 Feature Toggle 的優點
- 因基于主幹的開發,避免了分支合并代碼沖突的問題
- 每次送出都在主幹,疊代速度明顯有優勢
- 新功能的整個過程都持續內建
- 對生産環境基本沒有影響
缺點
- 未完成的功能可能會部署到線上,如果配置有誤可能将未完成的功能開啟,對公司早上損失
- 主幹上擔心送出代碼影響其他功能,影響開發進度
實踐
需求
在項目的聊天功能中,我們采用的是 Trunk-Based Development, 現在有個
常用話術
的功能,但是這個功能在下個疊代上線,同時我在這個疊代已經将下次上線的功能做完了[那是️可能的],現在有時間可以做
常用話術
這個功能了,為了做完可以讓 QA 測試,又不能影響已有的功能,那麼我就需要 Feature Toggle 這一神器了。
Coding
測試
// featureToggle.test.js
import { featureToggle } from './featureToggle'
describe('Test for featureToggle', () => {
afterEach(() => {
Object.defineProperty(window.location, 'href', {
writable: true,
value: '',
})
})
it('test should run', () => {
const universal = 42
expect(universal).toBe(42)
})
const testCase = [
{ url: 'http://localhost:8008/#/', expect: false, result: featureToggle('test'), },
{ url: 'http://localhost:8008/#/test?', expect: false, result: featureToggle('test'), },
{ url: 'http://localhost:8008/#/test?features=&', expect: false, result: featureToggle('test'), },
{ url: 'http://localhost:8008/#/test?features=test&', expect: true, result: featureToggle('test'), },
{ url: 'http://localhost:8008/#/test?features=test,123', expect: true, result: featureToggle('test'), },
{ url: 'http://localhost:8008/#/test?features=test,123', expect: true, result: featureToggle('123'), },
]
testCase.forEach((item => {
Object.defineProperty(window.location, 'href', {
writable: true,
value: item.url,
})
it(`use ${item.url} should return ${item.expect}`, () => {
expect(item.expect).toBe(item.result)
})
}))
})
實作
// faetureToggle.js
export const featureToggle = (feature) => {
const featureString = window.location.href.match(/.*features=(.*)/)
if (!featureString) {
return false
}
const features = featureString[1].split(',')
const result = features.includes(feature)
return result
}
效果
因項目是内部項目不能截圖,效果和上面舉個是一樣的