天天看點

Feature Toggle 實踐總結

Git Flow 模型

Feature Toggle 實踐總結

很多團隊的開發大都是這種

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 實踐總結

之後

Feature Toggle 實踐總結

優點

以下優點是基于主幹開發的 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
}           

效果

因項目是内部項目不能截圖,效果和上面舉個是一樣的

參考