Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fas...
Search
Tomohiro Imaizumi
September 12, 2022
Technology
3
4.4k
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development
iOSDC Japan 2022 Day2 Track Aにて発表した「Feature Flagを使った開発で高速でストレスフリーなデリバリーを実現する」の発表資料です。
Tomohiro Imaizumi
September 12, 2022
Tweet
Share
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
VisionFrameworkで実現する - プライバシーに配慮した「顔ぼかし」機能 / Face blurring with Vision Framework
imaizume
0
280
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
imaizume
1
860
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.3k
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
14
7.7k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.8k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
6
4.2k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
6
5.2k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8.5k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
5.1k
Other Decks in Technology
See All in Technology
ECS_EKS以外の選択肢_ROSA入門_.pdf
masakiokuda
1
130
AI に「学ばせ、調べさせ、作らせる」。Auth0 開発を加速させる7つの実践的アプローチ
scova0731
0
260
人工知能のための哲学塾 ニューロフィロソフィ篇 第零夜 「ニューロフィロソフィとは何か?」
miyayou
0
440
業務の煩悩を祓うAI活用術108選 / AI 108 Usages
smartbank
9
22k
迷わない!AI×MCP連携のリファレンスアーキテクチャ完全ガイド
cdataj
0
500
わが10年の叡智をぶつけたカオスなクラウドインフラが、なくなるということ。
sogaoh
PRO
1
550
RALGO : AIを組織に組み込む方法 -アルゴリズム中心組織設計- #RSGT2026 / RALGO: How to Integrate AI into an Organization – Algorithm-Centric Organizational Design
kyonmm
PRO
3
1.3k
Bill One 開発エンジニア 紹介資料
sansan33
PRO
4
17k
国井さんにPurview の話を聞く会
sophiakunii
1
370
Models vs Bounded Contexts for Domain Modularizati...
ewolff
0
180
AWS re:Invent 2025 を振り返る
kazzpapa3
2
110
技術選定、下から見るか?横から見るか?
masakiokuda
0
190
Featured
See All Featured
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
2
79
How to Think Like a Performance Engineer
csswizardry
28
2.4k
Technical Leadership for Architectural Decision Making
baasie
0
210
Odyssey Design
rkendrick25
PRO
0
460
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
54
49k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
210
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
130
Darren the Foodie - Storyboard
khoart
PRO
2
2.1k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
80
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
0
89
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
54k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
8.5k
Transcript
5PNPIJSP*NBJ[VNJ!JNBJ[VNF 3FUUZגࣜձࣾ 'FBUVSF'MBHΛͬͨ։ൃͰ ߴ͔ͭετϨεϑϦʔͳ σϦόϦʔΛ࣮ݱ͢Δ J04%$+BQBO
5PNPIJSP*NBJ[VNJ w *%!JNBJ[VNF w ॴଐ3FUUZ ג ΞϓϦνʔϜ w ڵຯϚΠΫϩϋοΫ4JSJγϣʔτΧοτ w
৯ຊͷڷྉཧ͝άϧϝ͖ ൃදऀ
νʔϜ։ൃͰͷϒϥϯνӡ༻ 'FBUVSF'MBHͱ 'FBUVSF'MBHͷར༻ํ๏ 'FBUVSF'MBHͷར ࣗલ࣮74֎෦αʔϏε
J04Ͱͷ࣮ύλʔϯ 'FBUVSF'MBHͷ5*14 'FBUVSF'MBHͷҙͱ՝ ࣍
νʔϜ։ൃͰͷϒϥϯνӡ༻
νʔϜ։ൃͷಛ w ৽ػೳ։ൃ w 6*มߋ w όάमਖ਼ w ϦϑΝΫλϦϯά w
ϥΠϒϥϦߋ৽ w FUD ෳϒϥϯνͰฒߦ։ൃ
ϝΠϯϒϥϯν ϦϦʔε͢Δίʔυ Λյͯ͠ͳΒͳ͍
Γ͕ͪͳϒϥϯνӡ༻
ઐ༻ͷ։ൃϒϥϯν ௨শϒϥϯν ΛΔ
ϒϥϯνࣜͷϒϥϯνӡ༻ ։ൃελʔτ࣌ main
ϒϥϯνࣜͷϒϥϯνӡ༻ มߋूͷͨΊͷϒϥϯνΛ࡞ parent
ϒϥϯνࣜͷϒϥϯνӡ༻ ݸผػೳΛ։ൃ͢ΔͨΊͷϒϥϯν ࢠϒϥϯν Λ࡞ parent featureA featureB
ϒϥϯνʹΑΓ Өڹൣғͷʹޭ ͔͠͠
ϒϥϯνࣜͷϒϥϯνӡ༻ ͋Δఔ։ൃ͕ਐΜͩঢ়ଶ parent featureA featureB
ϒϥϯνࣜͷϒϥϯνӡ༻ ϝΠϯ্ͷมߋΛϒϥϯνऔΓࠐΉඞཁ͕͋Δ
ϒϥϯνࣜͷϒϥϯνӡ༻ ͞Βʹࢠϒϥϯνൖڝ߹ղফͤ͞Δඞཁ͕͋Δ
ϒϥϯνࣜͷϒϥϯνӡ༻ શࠩͷϚʔδڝ߹ղফͰॳΊͯϦϦʔεՄೳ
ϒϥϯνࣜͷ՝ ϒϥϯνण໋ʹൺྫ͠ڝ߹ղফίετ͕૿͑Δ w णϒϥϯν΄ͲϝΠϯͱͷࠩڝ߹ൃੜ͕֬૿Ճ w ಛʹ9.-ϑΝΠϧ Storyboard, Xib, xcodeproj ղফ͕͍͠ͷͰආ͚͍ͨ
w සൟͳϩʔΧϧϚʔδͰղফՄೳɺ͕ͩ࡞ۀίετߴΕ͕ͪ
ϒϥϯνࣜͷ՝ ΞδϟΠϧ։ൃͱ૬ੑ͕ѱ͍ w ಈ͘ίʔυΛҡ࣋ͭͭ͠ɺ༏ઌɾείʔϓมߋͷରԠ͕ඞཁ w ͔͠͠ w ڝ߹͕ൃੜ͢Δ⾣ͦͷ··Ͱʮਖ਼͘͠ಈ͔ͳ͍ʯ w ։ൃఀࢭɾ࠶։ͷίετ͕େ͖͍⾣։ൃ༏ઌมߋ͕ͮ͠Β͍
w ϒϥϯνͷׂϚʔδ͍͠⾣։ൃείʔϓมߋ͕ͮ͠Β͍ w ڥมԽ͕ܹ͍͠։ൃͰରԠ͕͍͠ 包括的なドキュメントよりも動くソフトウェアを、 ...(中略)... 計画に従うことよりも変化への対応を価値とする (アジャイルソフトウェア開発宣言より)
ϒϥϯνࣜͷ՝ NFSHFSFWFSUͷӨڹൣғ͕େ͖͍ɾݟ௨ͮ͠Β͍ w ڊେ1VMM3FRVFTUӨڹൣғの把握が難しい • 子ブランチに対するレビューが限界 w revert͢Δ߹ʹ͕ࠩڊେʹ w λΠϛϯάʹΑͬͯrevertࣗମ͕ෆՄೳ
ϒϥϯνࣜͷ՝ ण໋ʹൺྫ͠ڝ߹ղফίετ͕૿͑Δ ΞδϟΠϧ։ൃͱ૬ੑ͕ѱ͍ NFSHFSFWFSUͷӨڹൣғ͕େ͖͍ɾݟ௨ͮ͠Β͍ ετϨεϑϧͳै࡞ۀ ͳσϦόϦʔ
͜ΕΒͷΛղܾ͢Δ ϒϥϯνӡ༻
τϥϯΫϕʔε ։ൃ
τϥϯΫϕʔε։ൃͱ w ৗʹϝΠϯϒϥϯν͔Β։࢝Ϛʔδ͢Δ։ൃख๏ w ϝΠϯϒϥϯνʹมߋ͕ू͞Εɺϒϥϯνؒͷ͕ࠩੜ͡ʹ͍͘ w (JU)VC'MPXʹ΄΅͍ۙ トランク ベース開発とは、
開発者が細かく頻繁なアップデートをコア「トランク」 または main ブランチにマージするバージョン管理手法です。 "UMBTTJBOެࣜαΠτʮܧଓతσϦόϦʔʯΑΓ
3FUUZͰ ϒϥϯν͔ΒτϥϯΫϕʔε w ͔ͭͯେ͖ͳࠩΛҰʹϦϦʔε͢Δɺ ௨শʮϏοάόϯϦϦʔεʯ͕ओྲྀͩͬͨ w ͔͠͠ϨϏϡʔσϦόϦʔ͕Լ w ϦϦʔεޙʹ͕ଟ͘ൃੜ w
ʮখ͘͞։ൃɾখ͘͞ϦϦʔεʯ͢ΔͨΊɺ શࣾͰτϥϯΫϕʔε։ൃΛਪਐ
ͨͩ͠ ͦͷ··ϒϥϯνΛϚʔδ͢Δͱ ϦϦʔε༻ίʔυʹӨڹΛ༩͑ͯ͠·͏
τϥϯΫϕʔε։ൃͱ ηοτͰඞཁͳςΫχοΫ
'FBUVSF'MBH
"UMBTTJBOެࣜαΠτʮ'FBUVSF'MBHTʯΑΓ 'FBUVSFGMBHT BMTPDPNNPOMZLOPXOBTGFBUVSFUPHHMFT JT BTPGUXBSFFOHJOFFSJOHUFDIOJRVF UIBUUVSOTTFMFDUGVODUJPOBMJUZPOBOEPGGEVSJOHSVOUJNF XJUIPVUEFQMPZJOHOFXDPEF 'FBUVSF'MBHͱ
'FBUVSF'MBH Ұൠʹ'FBUVSF5PHHMFͱ ɺ ৽͍͠ίʔυΛσϓϩΠ͢Δ͜ͱͳ͘ɺ ࣮ߦ࣌ʹબͨ͠ػೳΛΦϯ·ͨΦϑʹ͢Δ ιϑτΣΞΤϯδχΞϦϯάख๏Ͱ͋Δɻ
'FBUVSF'MBHͷ֓ཁ ϑϥάͱͳΔมΛఆٛ ରͷػೳදग़Λ͙Α͏ʹذΛ࡞ ؔ࿈ࠩϝΠϯϒϥϯνϚʔδ ஈ֊Ͱసͤ͞ϦϦʔε
͕ͳ͚ΕϑϥάͱذΛআ // 1. リリースまではfalseにしておく let isNewFeatureAvailable = false // 2. 分岐で機能表出を防ぐ if isNewFeatureAvailable { // 3. 本番では呼ばれない 本番では showNewFeature() } // 4. 反転してリリース let isNewFeatureAvailable = true if isNewFeatureAvailable { showNewFeature() } // 5. フラグと分岐を削除 showNewFeature()
ࢀߟ'FBUVSF'MBHͷྨ NBSUJOGPXMFSDPNΑΓ w 3FMFBTF5PHHMF։ൃதͷػೳΛϝΠϯϒϥϯνͰར༻Մೳʹɺ͍ͭͰ ຊ൪σϓϩΠՄೳʹ͢ΔͨΊͷϑϥάɻ w &YQFSJNFOU5PHHMF"#ςετͰৼΓ͚ΔͨΊͷϑϥάɻ w 0QT5PHHMFγεςϜͷಈ࡞Λ੍ޚ͢ΔͨΊͷϑϥάɻஈ֊తϦϦʔε w
1FSNJTTJPO5PHHMFಛఆϢʔβʔʹઌߦͯ͠ར༻Մೳʹ͢ΔͨΊͷϑϥάɻ Ћ൛ͷػೳϓϨϛΞϜձһ͚ػೳ ຊൃදͰ3FMFBTF5PHHMFʹߜͬͯղઆ &YQFSJNFOUBM5PHHMFʹ͍ͭͯJ04%$+BQBO 5BLFTIJ*IBSB͞Μͷʮ'FBUVSF'MBHΛదʹྨ͢Δ͜ͱͰ"#ςετͷӡ༻ίετΛԼ͛ΔʯͰղઆ͋Γ
'FBUVSF'MBH τϥϯΫϕʔε։ൃ ͷར
'FBUVSF'MBHͷར ϒϥϯνण໋͕͘ظ։ൃͰڝ߹͠ʹ͍͘ w Ϛʔδઌͱͷࠩৗʹখ͍͞ w ैසɾڝ߹ൃੜ͕֬গͳ͍ w ςΫχοΫ࣍ୈͰ9.-ϑΝΠϧYDPEFQSPKͷڝ߹ճආՄೳ 'FBUVSF'MBH0''
'FBUVSF'MBH0/
'FBUVSF'MBHͷར ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍ w ڝ߹͕ൃੜ͠ʹ͍͘ਖ਼͘͠ಈ͘ίʔυ͕ҡ࣋͞ΕΔ w ։ൃ్தͰϑϥά0''ͷ··ϦϦʔεՄೳ w ༏ઌมߋͷӨڹΛड͚ʹ͍͘ w Ԙ௮͚ظ͕ؒͯ͘ैɾڝ߹ղফίετ্͕͕Βͳ͍
w ϚʔδͱϦϦʔε͕͔ΕΔϦϦʔεൣғɾλΠϛϯάΛॊೈʹมߋՄೳ ࠩ͠ࠐΈͰผͷ։ൃϦϦʔε ϝΠϯϒϥϯν͔Β࠶։ ։ൃ్தͰϚʔδ
'FBUVSF'MBHͷར NFSHFSFWFSU͕͕ࠩ͘͢͠খ͍͞ w ϑϥάͷ࠶సͰSFWFSUՄɾࠩখ͍͞ w ࢀরՕॴΛݟΕӨڹൣғΛѲ͍͢͠ w ෆ҆ͳΒҰఆظؒϑϥάΛ͢ͷՄ //
再反転すればrevert完了 let isNewFeatureAvailable = false if isNewFeatureAvailable { showNewFeature() }
'FBUVSF'MBHͷར ϒϥϯνण໋͕͘ظ։ൃͰڝ߹͠ʹ͍͘ ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍ NFSHFSFWFSU͕͕ࠩ͘͢͠খ͍͞ ैڝ߹͕ͳ͘ετϨεϑϦʔ ߴͳσϦόϦʔΛ࣮ݱ
ϒϥϯν74τϥϯΫϕʔε 'FBUVSF'MBH ϒϥϯν ൺֱ߲ τϥϯΫϕʔε 'FBUVSF'MBH ଟ͍ ϩʔΧϧϚʔδڝ߹ൃੜ গͳ͍
ߴ͍ தஅɾ࠶։ίετ ͍ 13ͱಉ͡ߦ SFWFSU࣌ͷࠩ dߦ ͍͠ɾίετ͕ߴ͍ είʔϓɾλΠϛϯάมߋ ༰қ
J04Ͱͷ࣮ύλʔϯ
if enabledNewFeature { present(viewController: NewViewController()) } else { present(viewController: OldViewController())
} w QSFTFOUɾQVTIՕॴͰذ w ذָ͕Ͱཧతͳύλʔϯ 7JFX$POUSPMMFS if enabledNewFeature { navigationController?.pushViewController( NewViewController(), animated: true ) } else { navigationController?.pushViewController( OldViewController(), animated: true ) }
HStack(spacing: 0) { if enabledNewFeature { NewView(newViewModel: .init()) } else
{ OldView(oldViewModel: .init()) }.padding(.bottom, 16) w දࣔՕॴʹذΛ࣮Մೳ w ذָ͕Ͱཧతͳύλʔϯ 4XJGU6*
// XibやStoryboardを使ったレイアウト @IBOutlet private var oldViewHeight: NSLayoutConstraint! @IBOutlet private var
newViewHeight: NSLayoutConstraint! if enabledNewFeature { newViewHeight.priority = .defaultHigh oldViewHeight.priority = .defaultLow } else { newViewHeight.priority = .defaultLow oldViewHeight.priority = .defaultHigh } // コードのみでのレイアウト let viewToAdd: UIView = enabledNewFeature ? newView : oldView view.addSubview(viewToAdd) w /4$POTUSBJOU-BZPVUͰͷذ ʹ'FBUVSF'MBHΛ͚Δ w ίʔυͰͷϨΠΞτͳΒ BEE4VC7JFXΛ'FBUVSF'MBHͰ ذͯ͠0, w ϨΠΞτ݅ʹԠͨ͡બΛ "VUP-BZPVU
func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) ->
UITableViewCell { if familiarCategoryFeatureEnabled { return tableView.dequeueReusableCell( withIdentifier: "NewCell", for: indexPath ) as! NewCell } else { return tableView.dequeueReusableCell( withIdentifier: "OldCell", for: indexPath ) as? OldCell } w EFRVFVF3FVTBCMF$FMMͷ JEFOUJ fi FSΛม͑Εྑ͍ w ྆ํͷDFMMΛEFRVFVFͭͭ͠ IFJHIUΛʹઃఆ͢Δͷ͋Γ 6*5BCMF7JFX$FMM
func setupViews() { if isNewFeatureEnabled { let response = fetchNewResponse()
titleView.text = response.title newView.value = response.newField newView.isHidden = false oldView.isHidden = true } else { let response = fetchOldResponse() titleView.text = response.title oldView.isHidden = response.oldField newView.isHidden = true oldView.isHidden = false } } func fetchNewResponse() -> NewResponse { return NewResponse(title: ..., newField: ...) } func fetchOldResponse() -> OldResponse { .init(title: ..., oldField: ...) } 6*,JU w ৽چͷܕʹରԠ͢ΔϑΟʔϧυ Λఆٛ w ӨڹΛड͚Δ7JFXΛ w දࣔঢ়ଶɾ7JFXͷΈཱͯΛ ذͤͤ͞Δ σʔλܕ͕มΘΔ߹
class ViewModel: ObservableObject { @Published var newItem: NewItem @Published var
oldItem: OldItem } 4XJGU6* w ৽چͷܕʹରԠ͢ΔϑΟʔϧυ Λఆٛ w ӨڹΛड͚Δ7JFXΛ w දࣔঢ়ଶɾ7JFXͷΈཱͯΛ ذͤͤ͞Δ σʔλܕ͕มΘΔ߹ if isNewFeatureEnabled { Text(viewModel.newItem.value) } else { Text(viewModel.oldItem.value) }
ࣗલ࣮74֎෦αʔϏε
/* OTHER_LDFLAGSにコンパイルフラグを渡すことで ビルド時にフラグの値を指定可能 */ #if FEATURE_FLAG_NEW_FEATURE let isNewFeatureAvailable = true
#else let isNewFeatureAvailable = false #endif ϑϥάఆͷఆٛͱࢀর w άϩʔόϧͳఆΛ༻ҙ w Ϗϧυ࣌ͷมͰ֎෦Γସ͑ Ͱ͖ΔΑ͏ʹ͓ͯ͘͠ w ͋ͱදग़ՕॴͰࢀর͢Δ͚ͩ ࣗલ࣮Ͱͷ 'FBUVSF'MBHTTXJGU // 使用箇所での分岐に使用するだけ if isNewFeatureAvailable { present(newViewController, animated: true) } else { present(viewController, animated: true) } 7JFX$POUSPMMFSTXJGU
# FeatureFlags = HONYA,MORAKE と入ってくるので # それぞれプレフィックスに FEATURE_FLAG_ を付与して渡す。 activate_feature_flags
= (options[:activate_feature_flags] || '').split(",") xcodebuild_args = activate_feature_flags .map { |k| "-D FEATURE_FLAG_#{k.shellescape}" }.join(' ') build_app( workspace: "MyApp.xcworkspace", scheme: "MyApp", export_options: { xcargs: xcodebuild_args.length > 0 ? "OTHER_SWIFT_FLAGS='$(inherited) #{xcodebuild_args}'" : "" ) 'BTUMBOFͷઃఆ w ҾͰ'FBUVSF'MBH༻ͷίϯύ ΠϧϑϥάΛड͚औΔ w ଞͷҾͱ۠ผͰ͖ΔΑ͏ʹɺ ϓϨϑΟοΫεΛ͚Δͷ ࣗલ࣮Ͱͷ 'BTU fi MF $ fastlane build_my_app activate_feature_flags:HONYA,MORAKE > FEATURE_FLAG_HONYAとFEATURE_FLAG_MORAKEがtrueになる
֎෦αʔϏεͷൺֱද جຊతػೳੑʹେࠩͳ͘'JSFCBTF͕͓खࠒ ൺֱ߲ 'JSFCBTF 3FNPUF$PO fi H -BVODI%BSLMZ TQMJUJP
'MBHTIJQ ಛ ଞͷ'JSFCBTF αʔϏεͱͷ࿈ܞ ϦΞϧλΠϜੑ NTҎͰ৴ ܭଌࢹͷ ػೳ͖ ࣗಈϩʔϧόοΫ ஈ֊తϦϦʔε ྉۚ ແྉ NPOUId ໊·Ͱແྉ NPOUId GFUDIճ੍ݶ ࣌ؒҎճ·Ͱ ެࣜͰ໌ݴͤͣ ͳ͠ ͳ͠
w σϑΥϧτͰλʔήοτΞϓ Ϧɾ04ɾࠃͳͲΛࢦఆՄೳ w ҙϢʔβʔϓϩύςΟʹՃ͑ɺ ϥϯμϜͳׂΓৼΓϕʔε ͷذՄ w औಘ੍ݶͱΩϟογϡͷͨΊɺ සൟͳมߋʹҙ
'JSFCBTF3FNPUF$POGJHͷ݅ࢦఆ
w ύϥϝʔλ໊ɾσʔλܕɾσϑ ΥϧτΛࢦఆ w ذ݅Λࢦఆ͢Δ w "QQ%FMFHBUFͰύϥϝʔλΛ GFUDIͯ͠ར༻͢Δ 3FNPUF$POGJHͰͷϑϥάఆٛ
import Firebase let remoteConfig = RemoteConfig.remoteConfig() let isNewFeatureEnabled = remoteConfig["newFeatureEnabled"].boolValue 'FBUVSF'MBHTTXJGU
͍͚ ϦϦʔε SFWFSU λΠϛϯάͱذ݅࣍ୈ ࣗલ࣮ w ΞϓϦϦϦʔεػೳϦϦʔε w ذ͕݅੩త
֎෦αʔϏε αʔόʔ੍ޚ w ΞϓϦϦϦʔεͱػೳϦϦʔεͷ λΠϛϯάΛ͚͍ͨ w ذ݅Λಈతɾৄࡉʹม͍͑ͨ
ࣗલ࣮74֎෦αʔϏε ϦϦʔεɾλʔήςΟϯάͰ੍͕͋ΔͳΒ֎෦αʔϏεΛݕ౼ ൺֱ߲ ࣗલ࣮ ΫϥΠΞϯτͷΈ ֎෦αʔϏε ϑϥάసλΠϛϯά Ϗϧυ࣌ ҙλΠϛϯά
ϑϥάཧมߋॴ ΫϥΠΞϯτͷίʔυ αʔόʔ(6* λʔήςΟϯάɾ࣌ݶࣜ ͍͠ Մೳ ґଘ ͳ͠ 4%,͕ඞཁ Ձ֨ ແྉ ༗ྉͷ߹͋Γ
'FBUVSF'MBHͷ5*14
'FBUVSF'MBH࠷ऴखஈ ৗʹখ͘͞Ϛʔδ͢ΔઃܭɾྗΛ w ಈ࡞ʹӨڹ͠ͳ͍ࠩૣ͘Ϛʔδ͢Δ͖Ͱ'FBUVSF'MBHෆཁ w ྫίϯϙʔωϯτఆٛͷΈͷࠩ w ྫϑϩϯτ͔Β౸ୡෆՄೳͳൣғͰͷ݁߹ w Ϣʔβʔ͕ࢀরՄೳʹͳΔಋઢͰॳΊͯ'FBUVSF'MBHΛݕ౼͢Δ
> Only if you can't do small releases or UI last should you employ release toggles. (小さなリリースや UI を最後に実行できない場合にのみ、リリース トグルを使用するべきである。) martinfowler.com より
։ൃ్தͰͷಋೖɾมߋ͋Γ ϦϦʔεͷείʔϓɾλΠϛϯά͕มΘͬͨΒ w ॳͷείʔϓͷҰ෦ΛઌߦతʹϦϦʔε͍ͨ͠ w 'FBUVSF'MBHΛׂ͢Δ͜ͱͰείʔϓׂ͕Մೳ w ʮҙࢥܾఆϩδοΫ͔ΒҙࢥܾఆϙΠϯτΛΓ͢ʯ NBSUJOGPXMFSDPNΑΓ
w ޙ͔Βͷ'FBUVSF'MBHಋೖ͋Γ
w 'FBUVSF'MBHͷࢀরɺ ʮҙࢥܾఆϩδοΫϙΠϯτʯ w ϦϦʔεͷείʔϓ͕݅ม͑ ͮΒ͍ w ؒϨΠϠʔΛڬΉ͜ͱͰґଘ ͕ബ͘ͳΓ྆ऀΛՄೳʹ w
ґଘؔٯసͰ%*Մೳʹ ҙࢥܾఆϙΠϯτͱϩδοΫͷ // Feature Flagが意思決定ロジック=意思決定ポイントに let isNewFeatureEnabled: Bool = true if isNewFeatureEnabled { showView() editView() } // 意思決定ロジック(Feature Flagを入力にする) func canShowView(featureFlags: [String: Bool]) -> Bool { featureFlags["newFeature"] == true && conditionForShowView } func canEditView(featureFlags: [String: Bool]) -> Bool { featureFlags["newFeature"] == true && conditionForEditView } // 意思決定ポイント let isNewFeatureEnabled: Bool = true if canShowView(["newFeature": isNewFeatureEnabled]) { showView() } if canEditView(["newFeature": isNewFeatureEnabled]) { editView() }
আ࣌ͷϛεࢭ؍͔Β w ϒϩοΫͰ·ͱ·͍ͬͯΔ߹ɺ ফ͠࿙Εޡআࢭ͍͢͠ w %3:͕ྑ͍ͱݶΒͳ͍ ذۃྗϒϩοΫʹ·ͱΊΔ // 各行で削除が必要なため誤削除・漏れに注意が必要
view.addSubview(isNewFeatureEnabled ? newView : oldView) newView.isHidden = isNewFeatureEnabled oldView.isHidden = !isNewFeatureEnabled // 削除対象がブロックなので範囲が明確 if isNewFeatureEnabled { view.addSubview(newView) newView.isHidden = false oldView.isHidden = true } else { view.addSubview(oldView) newView.isHidden = true oldView.isHidden = false }
'FBUVSF'MBHར༻࣌ͷҙ
'FBUVSF'MBHͷҙ ϑϥάͷཧʹҙ w ޡͬͨస͕ى͜Βͳ͍Α͏ʹҙ͕ඞཁ w ಛJ04৹ࠪʹΑΓSFWFSUʹ͕͔͔࣌ؒΔ w 2"6*ςετͰͷ֬ೝΛ w /JHIUMZ#VJMEͰࠒ͔ΒࣾνΣοΫ͢Δͷ༗ޮ
'FBUVSF'MBHͷҙ ෆཁϑϥάͷআૣΊʹ w ޡͬͨίʔυΛࢀর͢ΔϦεΫ͕͋ΔͷͰૣΊʹফ͢ w ʮϑϥάআ·Ͱ͕ϦϦʔεʯ w ࠓޙআͷࣗಈԽΛظFH(JU)VCͷऔΓΈ
'FBUVSF'MBHͷҙ 1VMM3FRVFTUૣࠞͥ͘Δ w τϥϯΫϕʔεͰࡉ͔͘1VMM3FRVFTUΛग़͢ w ͨͩ͠ϨϏϡʔͪͷ13͕૿͑ΔͷΞϯνύλʔϯ w ͪͷ1VMM3FRVFTUΛݮΒ͠ίʔυΛ࠷৽ʹ w ϖΞɾϞϒϓϩ͢Δ
w ϨϏϡʔ༻ͷ࣌ؒΛઃఆ
'FBUVSF'MBHͷҙ ར༻͕͍͠έʔε w ෳࡶͳ4UPSZCPBSE9JCϨΠΞτ w ϒϥϯνͰਐΊΔ͔࡞Γ͠Λݕ౼͢Δ w ৽چͷܕͷ͕ࠩେ͖͍ w ৽چܕͷϑΟʔϧυΛ༻ҙͯ͠ذࠔͳ߹
w நԽϨΠϠʔΛઃ͚Δํ๏͋Γ w มߋՕॴ͕ΫϥΠΞϯτଆʹͳ͍ w "1*όʔδϣϯΛม͑ΔPSαʔόʔʹϑϥάΛಋೖ
·ͱΊ
ࠓ͔Β'FBUVSF'MBHΛೖΕͯΈΑ͏ ߴ͔ͭετϨεϑϦʔͳσϦόϦʔ͕࣮ݱ w ฒߦظ։ൃͰैڝ߹͕গͳ͘ετϨεϑϦʔ w ༏ઌɾείʔϓมߋʹڧͯ͘ΞδϟΠϧ w ·ͣϩʔΧϧͷϑϥά͔Β࢝ΊΑ͏ w ޡͬͨసɾআʹҙɾؾ͚ΔΛ
w ৗʹখ͘͞ɾߴʹϚʔδ͍ͯ͜͠͏
'FBUVSF'MBHΛͬͨ։ൃͰ ߴ͔ͭετϨεϑϦʔͳσϦόϦʔΛ
ࢀߟจݙ63- wΞδϟΠϧιϑτΣΞ։ൃએݴ 🔗IUUQTBHJMFNBOJGFTUPPSHJTPKBNBOJGFTUPIUNM wτϥϯΫϕʔε։ൃc"UMBTTJBO 🔗IUUQTXXXBUMBTTJBODPNKBDPOUJOVPVTEFMJWFSZDPOUJOVPVTJOUFHSBUJPOUSVOLCBTFEEFWFMPQNFOU w'FBUVSF5PHHMFT BLB'FBUVSF'MBHT 🔗IUUQTNBSUJOGPXMFSDPNBSUJDMFTGFBUVSFUPHHMFTIUNM w'FBUVSF'MBHΛదʹྨ͢Δ͜ͱͰ"#ςετͷӡ༻ίετΛԼ͛ΔJ04%$5BLFTIJ*IBSB
"#&." 🔗IUUQTTQFBLFSEFDLDPNOPODIBMBOUCUFTVUPGBMTFZVOZPOHLPTVUPXPYJBHFSV w'FBUVSF'MBHT 5PHHMFT $POUSPMTr5IF)VCGPS'FBUVSF'MBH%SJWFO%FWFMPQNFOUc'FBUVSF'MBHT 🔗IUUQTGFBUVSF fl BHTJP