この記事は クラスター Advent Calendar 2023 19日目の記事です。 昨日は ChameleonO2 さんの「何か」でした。公開楽しみですね。
クラスター株式会社でソフトウェアエンジニアとして働いている id:Sixeight です。
クラスターではトランクベース開発を実現するためにフィーチャフラグを使っています。 フィーチャフラグを使うことでたとえ開発が途中であっても、変更は完全に動作する状態でトランクに取り込まれます。
今回はフィーチャフラグを使って開発するときに意識しているささやかなTIPSを共有します。
TIPS1: 元のコードはそのままにする
フィーチャフラグで分岐を追加するときに、気を利かせて安易にコードの重複を減らそうとしてはいけません。 たとえコードが重複することになったとしても、変更前のコードは出来るだけそのままの形で残るようにしましょう。
なぜならフィーチャフラグを入れたことで元のコードを壊してしまっては元も子もないからです。 フィーチャフラグをオフの場合には挙動が変わらないことを担保するもっとも簡単は方法はコードに手を入れないことです。
以下はものすごく簡単な例ですが、オリジナルのコードがもっと複雑だったと仮定すると、挙動の変更を追いかける負荷は good 側のコードの方が低いのではないでしょうか。
original
total := 0 total += 1 fmt.Printf("%d", total)
bad
total := 0 if featureFlag.IsHoge { total += 2 } else { total += 1 } fmt.Printf("%d", total)
good
if featureFlag.IsHoge { total := 0 total += 2 fmt.Printf("%d", total) } else { total := 0 total += 1 fmt.Printf("%d", total) }
TIPS2: 消すときのことを考えておく
フィーチャフラグによる分岐は最初から消されることが確定したコードです。消すときのことを考えておくに超したことはありません。
例えば、以下のようにフィーチャフラグが有効なときに特定の処理をやめたい場合、否定の条件にしたくなるかもしれません。 しかし、フィーチャフラグを使う多くの場合は処理を足す、つまり肯定の条件になることが多いです。
bad
if !featureFlag.IsA { for _, digit := range digits { result = result * 10 + digit } }
この1つだけなら間違わないとは思うのですが、沢山の分岐を消すことになると間違えてしまうかもしれません。 消すときのことを考えるとは、以下のように集中してコードを読まなくても消せるようにするということです。
good
if featureFlag.IsA { // DO NOTHING } else { for _, digit := range digits { result = result * 10 + digit } }
追加するときに少し苦労したとしても、消すときのことを考えておいた方が事故も減って結局は自分が助かることになるでしょう。
TIPS3: 依存の表現するフラグを用意する
リリースを細かく分割することは良いプラクティスです。ただし、それぞれのリリースが依存している場合はフィーチャフラグでの分岐が難しくなるケースが発生します。 確実に前段のフィーチャフラグが有効で、かつ対象のフィーチャフラグも有効であることを担保するために、専用のフィーチャフラグを用意すると認知負荷を減らすことが出来ます。
bad
if featureFlag.IsA && featureFlag.IsB { // ... }
good
// 定義 IsCombinedAB := IsA && IsB // 実装 if featureFlag.IsCombinedAB { // ... }
TIPS4: どちらもテストする
ユニットテストを書くときに、フィーチャフラグが有効なケースと、無効なケースのどちらにもテストを追加して、常にCIでどちらのケースもテストするようにしましょう。 オンケースで新しく追加したコードが動いていることを確認することは当然ながら、オフケースで既存のコードが壊れていないかも常に確認しておけると自信を持って開発途中の状態でもリリースできるようになります。
つどつど似たようなコードを書くのはミスの元なので、フィーチャフラグのオンオフケースを漏れなく書けるようなヘルパーを用意して、仕組みとしてどちらのテストも書かざるを得ない状況を作りましょう。
例
featureFlag.TestHelper(t, featureFlag.IsA func(t *testing.T) { // on case }, func(t *testing.T) { // off case }, }
合わせてCIでは全てのテストケースをフィーチャフラグの様子を切り替えて実行するようにしておくと、そもそもヘルパーを使い忘れるとテストが落ちて気づけるようになります。
TIPS5: すぐに消す
役目を終えたフィーチャフラグとオフケースのコードは出来るだけ早く消しましょう。
フラグがそのままだと分岐が増えているので複雑度が上がってしまっていて、素早い開発を阻害することになりかねません。 これまでのTIPSを守っていれば簡単に消せるはずですのでシュッと消してしまいましょう。
フィーチャラグを追加するチケットを切るときに、一緒に削除するチケットも切ってしまうのがもっとも確実だと思います。
開発の速度を落とさないためのフィーチャフラグが足を引っ張ってしまったら元も子もないのでこれが一番大切です。
明日は kyokomi さんの「オフィスアワーをやってみた話」です。