Slide 1

Slide 1 text

5PNPIJSP*NBJ[VNJ!JNBJ[VNF 3FUUZגࣜձࣾ 'FBUVSF'MBHΛ࢖ͬͨ։ൃͰ ߴ଎͔ͭετϨεϑϦʔͳ σϦόϦʔΛ࣮ݱ͢Δ J04%$+BQBO

Slide 2

Slide 2 text

5PNPIJSP*NBJ[VNJ w *%!JNBJ[VNF w ॴଐ3FUUZ ג ΞϓϦνʔϜ w ڵຯϚΠΫϩϋοΫ4JSJγϣʔτΧοτ w ৯೔ຊͷڷ౔ྉཧ͝౰஍άϧϝ޷͖ ൃදऀ

Slide 3

Slide 3 text

Slide 4

Slide 4 text

Slide 5

Slide 5 text

νʔϜ։ൃͰͷϒϥϯνӡ༻ 'FBUVSF'MBHͱ͸ 'FBUVSF'MBHͷར༻ํ๏ 'FBUVSF'MBHͷར఺ ࣗલ࣮૷74֎෦αʔϏε J04Ͱͷ࣮૷ύλʔϯ 'FBUVSF'MBHͷ5*14 'FBUVSF'MBHͷ஫ҙ఺ͱ՝୊ ໨࣍

Slide 6

Slide 6 text

νʔϜ։ൃͰͷϒϥϯνӡ༻

Slide 7

Slide 7 text

νʔϜ։ൃͷಛ௃ w ৽ػೳ։ൃ w 6*มߋ w όάमਖ਼ w ϦϑΝΫλϦϯά w ϥΠϒϥϦߋ৽ w FUD ෳ਺ϒϥϯνͰฒߦ։ൃ

Slide 8

Slide 8 text

ϝΠϯϒϥϯν ϦϦʔε͢Δίʔυ Λյͯ͠͸ͳΒͳ͍

Slide 9

Slide 9 text

΍Γ͕ͪͳϒϥϯνӡ༻

Slide 10

Slide 10 text

ઐ༻ͷ։ൃϒϥϯν ௨শ਌ϒϥϯν Λ੾Δ

Slide 11

Slide 11 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ ։ൃελʔτ࣌఺ main

Slide 12

Slide 12 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ มߋू໿ͷͨΊͷ਌ϒϥϯνΛ࡞੒ parent

Slide 13

Slide 13 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ ݸผػೳΛ։ൃ͢ΔͨΊͷϒϥϯν ࢠϒϥϯν Λ࡞੒ parent featureA featureB

Slide 14

Slide 14 text

਌ϒϥϯνʹΑΓ Өڹൣғͷ෼཭ʹ͸੒ޭ ͔͠͠

Slide 15

Slide 15 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ ͋Δఔ౓։ൃ͕ਐΜͩঢ়ଶ parent featureA featureB

Slide 16

Slide 16 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ ϝΠϯ্ͷมߋΛ਌ϒϥϯν΁औΓࠐΉඞཁ͕͋Δ

Slide 17

Slide 17 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ ͞Βʹࢠϒϥϯν΁఻ൖڝ߹ղফͤ͞Δඞཁ͕͋Δ

Slide 18

Slide 18 text

਌ϒϥϯνࣜͷϒϥϯνӡ༻ શࠩ෼ͷϚʔδڝ߹ղফͰॳΊͯϦϦʔεՄೳ

Slide 19

Slide 19 text

਌ϒϥϯνࣜͷ՝୊ ϒϥϯνण໋ʹൺྫ͠ڝ߹ղফίετ͕૿͑Δ w ௕णϒϥϯν΄ͲϝΠϯͱͷࠩ෼ڝ߹ൃੜ֬཰͕૿Ճ w ಛʹ9.-ϑΝΠϧ Storyboard, Xib, xcodeproj ͸ղফ͕೉͍͠ͷͰආ͚͍ͨ w සൟͳϩʔΧϧϚʔδͰղফՄೳɺ͕ͩ࡞ۀίετߴ๨Ε͕ͪ

Slide 20

Slide 20 text

਌ϒϥϯνࣜͷ՝୊ ΞδϟΠϧ։ൃͱ૬ੑ͕ѱ͍ w ಈ͘ίʔυΛҡ࣋ͭͭ͠ɺ༏ઌ౓ɾείʔϓมߋ΁ͷରԠ͕ඞཁ w ͔͠͠ w ڝ߹͕ൃੜ͢Δ⾣ͦͷ··Ͱ͸ʮਖ਼͘͠ಈ͔ͳ͍ʯ w ։ൃఀࢭɾ࠶։ͷίετ͕େ͖͍⾣։ൃ༏ઌ౓มߋ͕ͮ͠Β͍ w ਌ϒϥϯνͷ෼ׂϚʔδ͸೉͍͠⾣։ൃείʔϓมߋ͕ͮ͠Β͍ w ؀ڥมԽ͕ܹ͍͠։ൃͰ͸ରԠ͕೉͍͠ 包括的なドキュメントよりも動くソフトウェアを、 ...(中略)... 計画に従うことよりも変化への対応を価値とする (アジャイルソフトウェア開発宣言より)

Slide 21

Slide 21 text

਌ϒϥϯνࣜͷ՝୊ NFSHFSFWFSUͷӨڹൣғ͕େ͖͍ɾݟ௨ͮ͠Β͍ w ڊେ1VMM3FRVFTU͸Өڹൣғの把握が難しい • 子ブランチに対するレビューが限界 w revert͢Δ৔߹ʹ΋ࠩ෼͕ڊେʹ w λΠϛϯάʹΑͬͯ͸revertࣗମ͕ෆՄೳ

Slide 22

Slide 22 text

਌ϒϥϯνࣜͷ՝୊ ण໋ʹൺྫ͠ڝ߹ղফίετ͕૿͑Δ ΞδϟΠϧ։ൃͱ૬ੑ͕ѱ͍ NFSHFSFWFSUͷӨڹൣғ͕େ͖͍ɾݟ௨ͮ͠Β͍ ετϨεϑϧͳ௥ै࡞ۀ ௿଎ͳσϦόϦʔ

Slide 23

Slide 23 text

͜ΕΒͷ໰୊Λղܾ͢Δ ϒϥϯνӡ༻

Slide 24

Slide 24 text

τϥϯΫϕʔε ։ൃ

Slide 25

Slide 25 text

τϥϯΫϕʔε։ൃͱ͸ w ৗʹϝΠϯϒϥϯν͔Β։࢝Ϛʔδ͢Δ։ൃख๏ w ϝΠϯϒϥϯνʹมߋ͕ू໿͞Εɺϒϥϯνؒͷࠩ෼͕ੜ͡ʹ͍͘ w (JU)VC'MPXʹ΄΅͍ۙ トランク ベース開発とは、 開発者が細かく頻繁なアップデートをコア「トランク」 または main ブランチにマージするバージョン管理手法です。 "UMBTTJBOެࣜαΠτʮܧଓతσϦόϦʔʯΑΓ

Slide 26

Slide 26 text

3FUUZͰ΋ ਌ϒϥϯν͔ΒτϥϯΫϕʔε΁ w ͔ͭͯେ͖ͳࠩ෼ΛҰ౓ʹϦϦʔε͢Δɺ ௨শʮϏοάόϯϦϦʔεʯ͕ओྲྀͩͬͨ w ͔͠͠ϨϏϡʔ΍σϦόϦʔ଎౓͕௿Լ w ϦϦʔεޙʹ΋໰୊͕ଟ͘ൃੜ w ʮখ͘͞։ൃɾখ͘͞ϦϦʔεʯ͢ΔͨΊɺ શࣾͰτϥϯΫϕʔε։ൃΛਪਐ

Slide 27

Slide 27 text

ͨͩ͠ ͦͷ··ϒϥϯνΛϚʔδ͢Δͱ ϦϦʔε༻ίʔυʹӨڹΛ༩͑ͯ͠·͏

Slide 28

Slide 28 text

τϥϯΫϕʔε։ൃͱ ηοτͰඞཁͳςΫχοΫ

Slide 29

Slide 29 text

'FBUVSF'MBH

Slide 30

Slide 30 text

"UMBTTJBOެࣜαΠτʮ'FBUVSF'MBHTʯΑΓ 'FBUVSFGMBHT BMTPDPNNPOMZLOPXOBTGFBUVSFUPHHMFT JT BTPGUXBSFFOHJOFFSJOHUFDIOJRVF UIBUUVSOTTFMFDUGVODUJPOBMJUZPOBOEPGGEVSJOHSVOUJNF XJUIPVUEFQMPZJOHOFXDPEF 'FBUVSF'MBHͱ͸ 'FBUVSF'MBH Ұൠʹ'FBUVSF5PHHMFͱ΋ ͸ɺ ৽͍͠ίʔυΛσϓϩΠ͢Δ͜ͱͳ͘ɺ ࣮ߦ࣌ʹબ୒ͨ͠ػೳΛΦϯ·ͨ͸Φϑʹ͢Δ ιϑτ΢ΣΞΤϯδχΞϦϯάख๏Ͱ͋Δɻ

Slide 31

Slide 31 text

'FBUVSF'MBHͷ֓ཁ ϑϥάͱͳΔม਺Λఆٛ ର৅ͷػೳදग़Λ๷͙Α͏ʹ෼ذΛ࡞੒ ؔ࿈ࠩ෼͸ϝΠϯϒϥϯν΁Ϛʔδ ׬੒ஈ֊Ͱ൓సͤ͞ϦϦʔε ໰୊͕ͳ͚Ε͹ϑϥάͱ෼ذΛ࡟আ // 1. リリースまではfalseにしておく let isNewFeatureAvailable = false // 2. 分岐で機能表出を防ぐ if isNewFeatureAvailable { // 3. 本番では呼ばれない 本番では showNewFeature() } // 4. 反転してリリース let isNewFeatureAvailable = true if isNewFeatureAvailable { showNewFeature() } // 5. フラグと分岐を削除 showNewFeature()

Slide 32

Slide 32 text

ࢀߟ'FBUVSF'MBHͷ෼ྨ NBSUJOGPXMFSDPNΑΓ w 3FMFBTF5PHHMF։ൃதͷػೳΛϝΠϯϒϥϯνͰར༻Մೳʹɺ͍ͭͰ΋ ຊ൪σϓϩΠՄೳʹ͢ΔͨΊͷϑϥάɻ w &YQFSJNFOU5PHHMF"#ςετͰৼΓ෼͚ΔͨΊͷϑϥάɻ w 0QT5PHHMFγεςϜͷಈ࡞Λ੍ޚ͢ΔͨΊͷϑϥάɻஈ֊తϦϦʔε౳ w 1FSNJTTJPO5PHHMFಛఆϢʔβʔʹઌߦͯ͠ར༻Մೳʹ͢ΔͨΊͷϑϥάɻ Ћ൛ͷػೳ΍ϓϨϛΞϜձһ޲͚ػೳ౳ ຊൃදͰ͸3FMFBTF5PHHMFʹߜͬͯղઆ &YQFSJNFOUBM5PHHMFʹ͍ͭͯ͸J04%$+BQBO಺ 5BLFTIJ*IBSB͞Μͷʮ'FBUVSF'MBHΛద੾ʹ෼ྨ͢Δ͜ͱͰ"#ςετͷӡ༻ίετΛԼ͛ΔʯͰ΋ղઆ͋Γ

Slide 33

Slide 33 text

'FBUVSF'MBH τϥϯΫϕʔε։ൃ ͷར఺

Slide 34

Slide 34 text

'FBUVSF'MBHͷར఺ ϒϥϯνण໋͕୹͘௕ظ։ൃͰ΋ڝ߹͠ʹ͍͘ w Ϛʔδઌͱͷࠩ෼͸ৗʹখ͍͞ w ௥ैස౓ɾڝ߹ൃੜ֬཰͕গͳ͍ w ςΫχοΫ࣍ୈͰ9.-ϑΝΠϧ΍YDPEFQSPKͷڝ߹΋ճආՄೳ 'FBUVSF'MBH0'' 'FBUVSF'MBH0/

Slide 35

Slide 35 text

'FBUVSF'MBHͷར఺ ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍ w ڝ߹͕ൃੜ͠ʹ͍͘ਖ਼͘͠ಈ͘ίʔυ͕ҡ࣋͞ΕΔ w ։ൃ్தͰ΋ϑϥά0''ͷ··ϦϦʔεՄೳ w ༏ઌ౓มߋͷӨڹΛड͚ʹ͍͘ w Ԙ௮͚ظ͕ؒ௕ͯ͘΋௥ैɾڝ߹ղফίετ্͕͕Βͳ͍ w ϚʔδͱϦϦʔε͕෼͔ΕΔϦϦʔεൣғɾλΠϛϯάΛॊೈʹมߋՄೳ ࠩ͠ࠐΈͰผͷ։ൃϦϦʔε ϝΠϯϒϥϯν͔Β࠶։ ։ൃ్தͰ΋Ϛʔδ

Slide 36

Slide 36 text

'FBUVSF'MBHͷར఺ NFSHFSFWFSU͕͠΍ࠩ͘͢෼͕খ͍͞ w ϑϥάͷ࠶൓సͰSFWFSUՄɾࠩ෼΋খ͍͞ w ࢀরՕॴΛݟΕ͹ӨڹൣғΛ೺Ѳ͠΍͍͢ w ෆ҆ͳΒҰఆظؒϑϥάΛ࢒͢ͷ΋Մ // 再反転すればrevert完了 let isNewFeatureAvailable = false if isNewFeatureAvailable { showNewFeature() }

Slide 37

Slide 37 text

'FBUVSF'MBHͷར఺ ϒϥϯνण໋͕୹͘௕ظ։ൃͰ΋ڝ߹͠ʹ͍͘ ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍ NFSHFSFWFSU͕͠΍ࠩ͘͢෼͕খ͍͞ ௥ैڝ߹͕ͳ͘ετϨεϑϦʔ ߴ଎ͳσϦόϦʔΛ࣮ݱ

Slide 38

Slide 38 text

਌ϒϥϯν74τϥϯΫϕʔε'FBUVSF'MBH ਌ϒϥϯν ൺֱ߲໨ τϥϯΫϕʔε'FBUVSF'MBH ଟ͍ ϩʔΧϧϚʔδڝ߹ൃੜ গͳ͍ ߴ͍ தஅɾ࠶։ίετ ௿͍ ਌13ͱಉ͡ߦ਺ SFWFSU࣌ͷࠩ෼ d਺ߦ ೉͍͠ɾίετ͕ߴ͍ είʔϓɾλΠϛϯάมߋ ༰қ

Slide 39

Slide 39 text

J04Ͱͷ࣮૷ύλʔϯ

Slide 40

Slide 40 text

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 ) }

Slide 41

Slide 41 text

HStack(spacing: 0) { if enabledNewFeature { NewView(newViewModel: .init()) } else { OldView(oldViewModel: .init()) }.padding(.bottom, 16) w දࣔՕॴʹ௚઀෼ذΛ࣮૷Մೳ w ෼ذָ͕Ͱཧ૝తͳύλʔϯ 4XJGU6*

Slide 42

Slide 42 text

// 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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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ͷ૊ΈཱͯΛ෼ ذͤͤ͞Δ σʔλܕ͕มΘΔ৔߹

Slide 45

Slide 45 text

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) }

Slide 46

Slide 46 text

ࣗલ࣮૷74֎෦αʔϏε

Slide 47

Slide 47 text

/* 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

Slide 48

Slide 48 text

# 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になる

Slide 49

Slide 49 text

֎෦αʔϏεͷൺֱද جຊతػೳੑʹ͸େࠩͳ͘'JSFCBTF͕͓खࠒ ൺֱ߲໨ 'JSFCBTF 3FNPUF$PO fi H -BVODI%BSLMZ TQMJUJP 'MBHTIJQ ಛ௃ ଞͷ'JSFCBTF αʔϏεͱͷ࿈ܞ ϦΞϧλΠϜੑ NTҎ಺Ͱ഑৴ ܭଌ΍؂ࢹͷ ػೳ෇͖ ࣗಈϩʔϧόοΫ ஈ֊తϦϦʔε ྉۚ ແྉ NPOUId ໊·Ͱແྉ NPOUId GFUDIճ਺੍ݶ ࣌ؒҎ಺ճ·Ͱ ެࣜͰ໌ݴͤͣ ͳ͠ ͳ͠

Slide 50

Slide 50 text

w σϑΥϧτͰλʔήοτΞϓ Ϧɾ04ɾࠃͳͲΛࢦఆՄೳ w ೚ҙϢʔβʔϓϩύςΟʹՃ͑ɺ ϥϯμϜͳׂΓৼΓ΍೔෇ϕʔε ͷ෼ذ΋Մ w औಘ੍ݶͱΩϟογϡͷͨΊɺ සൟͳมߋʹ͸஫ҙ 'JSFCBTF3FNPUF$POGJHͷ৚݅ࢦఆ

Slide 51

Slide 51 text

w ύϥϝʔλ໊ɾσʔλܕɾσϑ Υϧτ஋Λࢦఆ w ෼ذ৚݅Λࢦఆ͢Δ w "QQ%FMFHBUFͰύϥϝʔλΛ GFUDIͯ͠ར༻͢Δ 3FNPUF$POGJHͰͷϑϥάఆٛ import Firebase let remoteConfig = RemoteConfig.remoteConfig() let isNewFeatureEnabled = remoteConfig["newFeatureEnabled"].boolValue 'FBUVSF'MBHTTXJGU

Slide 52

Slide 52 text

࢖͍෼͚ ϦϦʔε SFWFSU λΠϛϯάͱ෼ذ৚݅࣍ୈ ࣗલ࣮૷ w ΞϓϦϦϦʔεػೳϦϦʔε w ෼ذ৚͕݅੩త ֎෦αʔϏε αʔόʔ੍ޚ w ΞϓϦϦϦʔεͱػೳϦϦʔεͷ λΠϛϯάΛ෼͚͍ͨ w ෼ذ৚݅Λಈతɾৄࡉʹม͍͑ͨ

Slide 53

Slide 53 text

ࣗલ࣮૷74֎෦αʔϏε ϦϦʔεɾλʔήςΟϯάͰ੍໿͕͋ΔͳΒ֎෦αʔϏεΛݕ౼ ൺֱ߲໨ ࣗલ࣮૷ ΫϥΠΞϯτͷΈ ֎෦αʔϏε ϑϥά൓సλΠϛϯά Ϗϧυ࣌ ೚ҙλΠϛϯά ϑϥά؅ཧมߋ৔ॴ ΫϥΠΞϯτͷίʔυ αʔόʔ(6* λʔήςΟϯάɾ࣌ݶࣜ ೉͍͠ Մೳ ґଘ ͳ͠ 4%,͕ඞཁ Ձ֨ ແྉ ༗ྉͷ৔߹͋Γ

Slide 54

Slide 54 text

'FBUVSF'MBHͷ5*14

Slide 55

Slide 55 text

'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 より

Slide 56

Slide 56 text

։ൃ్தͰͷಋೖɾมߋ΋͋Γ ϦϦʔεͷείʔϓɾλΠϛϯά͕มΘͬͨΒ w ౰ॳͷείʔϓͷҰ෦ΛઌߦతʹϦϦʔε͍ͨ͠ w 'FBUVSF'MBHΛ෼ׂ͢Δ͜ͱͰείʔϓ෼ׂ͕Մೳ w ʮҙࢥܾఆϩδοΫ͔ΒҙࢥܾఆϙΠϯτΛ੾Γ཭͢ʯ NBSUJOGPXMFSDPNΑΓ w ޙ͔Βͷ'FBUVSF'MBHಋೖ΋͋Γ

Slide 57

Slide 57 text

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() }

Slide 58

Slide 58 text

࡟আ࣌ͷϛε๷ࢭ؍఺͔Β 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 }

Slide 59

Slide 59 text

'FBUVSF'MBHར༻࣌ͷ஫ҙ఺

Slide 60

Slide 60 text

'FBUVSF'MBHͷ஫ҙ఺ ϑϥάͷ؅ཧʹ͸஫ҙ w ޡͬͨ൓స͕ى͜Βͳ͍Α͏ʹ஫ҙ͕ඞཁ w ಛJ04͸৹ࠪʹΑΓSFWFSUʹ΋͕͔͔࣌ؒΔ w 2"΍6*ςετ౳Ͱͷ֬ೝΛ w /JHIUMZ#VJMEͰ೔ࠒ͔Βࣾ಺νΣοΫ͢Δͷ΋༗ޮ

Slide 61

Slide 61 text

'FBUVSF'MBHͷ஫ҙ఺ ෆཁϑϥάͷ࡟আ͸ૣΊʹ w ޡͬͨίʔυΛࢀর͢ΔϦεΫ͕͋ΔͷͰૣΊʹফ͢ w ʮϑϥά࡟আ·Ͱ͕ϦϦʔεʯ w ࠓޙ͸࡟আͷࣗಈԽΛظ଴FH(JU)VCͷऔΓ૊Έ

Slide 62

Slide 62 text

'FBUVSF'MBHͷ஫ҙ఺ 1VMM3FRVFTU͸ૣࠞͥ͘Δ w τϥϯΫϕʔεͰࡉ͔͘1VMM3FRVFTUΛग़͢ w ͨͩ͠ϨϏϡʔ଴ͪͷ13͕૿͑Δͷ͸Ξϯνύλʔϯ w ଴ͪͷ1VMM3FRVFTUΛݮΒ͠ίʔυΛ࠷৽ʹ w ϖΞɾϞϒϓϩ͢Δ w ϨϏϡʔ༻ͷ࣌ؒΛઃఆ

Slide 63

Slide 63 text

'FBUVSF'MBHͷ஫ҙ఺ ར༻͕೉͍͠έʔε w ෳࡶͳ4UPSZCPBSE9JCϨΠΞ΢τ w ਌ϒϥϯνͰਐΊΔ͔࡞Γ௚͠Λݕ౼͢Δ w ৽چͷܕͷࠩ෼͕େ͖͍ w ৽چܕͷϑΟʔϧυΛ༻ҙͯ͠΋෼ذࠔ೉ͳ৔߹΋ w ந৅ԽϨΠϠʔΛઃ͚Δํ๏΋͋Γ w มߋՕॴ͕ΫϥΠΞϯτଆʹͳ͍ w "1*όʔδϣϯΛม͑ΔPSαʔόʔʹϑϥάΛಋೖ

Slide 64

Slide 64 text

·ͱΊ

Slide 65

Slide 65 text

ࠓ೔͔Β'FBUVSF'MBHΛೖΕͯΈΑ͏ ߴ଎͔ͭετϨεϑϦʔͳσϦόϦʔ͕࣮ݱ w ฒߦ௕ظ։ൃͰ΋௥ैڝ߹͕গͳ͘ετϨεϑϦʔ w ༏ઌ౓ɾείʔϓมߋʹڧͯ͘ΞδϟΠϧ w ·ͣ͸ϩʔΧϧͷϑϥά͔Β࢝ΊΑ͏ w ޡͬͨ൓సɾ࡟আʹ஫ҙɾؾ෇͚Δ޻෉Λ w ৗʹখ͘͞ɾߴ଎ʹϚʔδ͍ͯ͜͠͏

Slide 66

Slide 66 text

'FBUVSF'MBHΛ࢖ͬͨ։ൃͰ ߴ଎͔ͭετϨεϑϦʔͳσϦόϦʔΛ

Slide 67

Slide 67 text

ࢀߟจݙ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