Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development

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

More Decks by Tomohiro Imaizumi

Other Decks in Technology

Transcript

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

  2. 5PNPIJSP*NBJ[VNJ w *%!JNBJ[VNF w ॴଐ3FUUZ ג ΞϓϦνʔϜ w ڵຯϚΠΫϩϋοΫ4JSJγϣʔτΧοτ w

    ৯೔ຊͷڷ౔ྉཧ͝౰஍άϧϝ޷͖ ൃදऀ 
  3. 

  4. 

  5.  νʔϜ։ൃͰͷϒϥϯνӡ༻  'FBUVSF'MBHͱ͸  'FBUVSF'MBHͷར༻ํ๏  'FBUVSF'MBHͷར఺  ࣗલ࣮૷74֎෦αʔϏε

     J04Ͱͷ࣮૷ύλʔϯ  'FBUVSF'MBHͷ5*14  'FBUVSF'MBHͷ஫ҙ఺ͱ՝୊ ໨࣍ 
  6. νʔϜ։ൃͰͷϒϥϯνӡ༻ 

  7. νʔϜ։ൃͷಛ௃ w ৽ػೳ։ൃ w 6*มߋ w όάमਖ਼ w ϦϑΝΫλϦϯά w

    ϥΠϒϥϦߋ৽ w FUD  ෳ਺ϒϥϯνͰฒߦ։ൃ
  8. ϝΠϯϒϥϯν ϦϦʔε͢Δίʔυ Λյͯ͠͸ͳΒͳ͍ 

  9. ΍Γ͕ͪͳϒϥϯνӡ༻ 

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

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

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

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

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

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

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

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

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

  19. ਌ϒϥϯνࣜͷ՝୊ ϒϥϯνण໋ʹൺྫ͠ڝ߹ղফίετ͕૿͑Δ w ௕णϒϥϯν΄ͲϝΠϯͱͷࠩ෼ڝ߹ൃੜ֬཰͕૿Ճ w ಛʹ9.-ϑΝΠϧ Storyboard, Xib, xcodeproj ͸ղফ͕೉͍͠ͷͰආ͚͍ͨ

    w සൟͳϩʔΧϧϚʔδͰղফՄೳɺ͕ͩ࡞ۀίετߴ๨Ε͕ͪ 
  20. ਌ϒϥϯνࣜͷ՝୊ ΞδϟΠϧ։ൃͱ૬ੑ͕ѱ͍ w ಈ͘ίʔυΛҡ࣋ͭͭ͠ɺ༏ઌ౓ɾείʔϓมߋ΁ͷରԠ͕ඞཁ w ͔͠͠ w ڝ߹͕ൃੜ͢Δ⾣ͦͷ··Ͱ͸ʮਖ਼͘͠ಈ͔ͳ͍ʯ w ։ൃఀࢭɾ࠶։ͷίετ͕େ͖͍⾣։ൃ༏ઌ౓มߋ͕ͮ͠Β͍

    w ਌ϒϥϯνͷ෼ׂϚʔδ͸೉͍͠⾣։ൃείʔϓมߋ͕ͮ͠Β͍ w ؀ڥมԽ͕ܹ͍͠։ൃͰ͸ରԠ͕೉͍͠  包括的なドキュメントよりも動くソフトウェアを、 ...(中略)... 計画に従うことよりも変化への対応を価値とする (アジャイルソフトウェア開発宣言より)
  21. ਌ϒϥϯνࣜͷ՝୊ NFSHFSFWFSUͷӨڹൣғ͕େ͖͍ɾݟ௨ͮ͠Β͍ w ڊେ1VMM3FRVFTU͸Өڹൣғの把握が難しい • 子ブランチに対するレビューが限界 w revert͢Δ৔߹ʹ΋ࠩ෼͕ڊେʹ w λΠϛϯάʹΑͬͯ͸revertࣗମ͕ෆՄೳ

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

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

  24. τϥϯΫϕʔε ։ൃ 

  25. τϥϯΫϕʔε։ൃͱ͸ w ৗʹϝΠϯϒϥϯν͔Β։࢝Ϛʔδ͢Δ։ൃख๏ w ϝΠϯϒϥϯνʹมߋ͕ू໿͞Εɺϒϥϯνؒͷࠩ෼͕ੜ͡ʹ͍͘ w (JU)VC'MPXʹ΄΅͍ۙ  トランク ベース開発とは、

    開発者が細かく頻繁なアップデートをコア「トランク」 または main ブランチにマージするバージョン管理手法です。 "UMBTTJBOެࣜαΠτʮܧଓతσϦόϦʔʯΑΓ
  26. 3FUUZͰ΋ ਌ϒϥϯν͔ΒτϥϯΫϕʔε΁ w ͔ͭͯେ͖ͳࠩ෼ΛҰ౓ʹϦϦʔε͢Δɺ ௨শʮϏοάόϯϦϦʔεʯ͕ओྲྀͩͬͨ w ͔͠͠ϨϏϡʔ΍σϦόϦʔ଎౓͕௿Լ w ϦϦʔεޙʹ΋໰୊͕ଟ͘ൃੜ w

    ʮখ͘͞։ൃɾখ͘͞ϦϦʔεʯ͢ΔͨΊɺ શࣾͰτϥϯΫϕʔε։ൃΛਪਐ 
  27. ͨͩ͠  ͦͷ··ϒϥϯνΛϚʔδ͢Δͱ ϦϦʔε༻ίʔυʹӨڹΛ༩͑ͯ͠·͏

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

  29. 'FBUVSF'MBH 

  30. "UMBTTJBOެࣜαΠτʮ'FBUVSF'MBHTʯΑΓ 'FBUVSFGMBHT BMTPDPNNPOMZLOPXOBTGFBUVSFUPHHMFT JT BTPGUXBSFFOHJOFFSJOHUFDIOJRVF UIBUUVSOTTFMFDUGVODUJPOBMJUZPOBOEPGGEVSJOHSVOUJNF  XJUIPVUEFQMPZJOHOFXDPEF  'FBUVSF'MBHͱ͸

    'FBUVSF'MBH Ұൠʹ'FBUVSF5PHHMFͱ΋ ͸ɺ ৽͍͠ίʔυΛσϓϩΠ͢Δ͜ͱͳ͘ɺ ࣮ߦ࣌ʹબ୒ͨ͠ػೳΛΦϯ·ͨ͸Φϑʹ͢Δ ιϑτ΢ΣΞΤϯδχΞϦϯάख๏Ͱ͋Δɻ
  31. 'FBUVSF'MBHͷ֓ཁ  ϑϥάͱͳΔม਺Λఆٛ  ର৅ͷػೳදग़Λ๷͙Α͏ʹ෼ذΛ࡞੒  ؔ࿈ࠩ෼͸ϝΠϯϒϥϯν΁Ϛʔδ  ׬੒ஈ֊Ͱ൓సͤ͞ϦϦʔε 

    ໰୊͕ͳ͚Ε͹ϑϥάͱ෼ذΛ࡟আ  // 1. リリースまではfalseにしておく let isNewFeatureAvailable = false // 2. 分岐で機能表出を防ぐ if isNewFeatureAvailable { // 3. 本番では呼ばれない 本番では showNewFeature() } // 4. 反転してリリース let isNewFeatureAvailable = true if isNewFeatureAvailable { showNewFeature() } // 5. フラグと分岐を削除 showNewFeature()
  32. ࢀߟ'FBUVSF'MBHͷ෼ྨ NBSUJOGPXMFSDPNΑΓ w 3FMFBTF5PHHMF։ൃதͷػೳΛϝΠϯϒϥϯνͰར༻Մೳʹɺ͍ͭͰ΋ ຊ൪σϓϩΠՄೳʹ͢ΔͨΊͷϑϥάɻ w &YQFSJNFOU5PHHMF"#ςετͰৼΓ෼͚ΔͨΊͷϑϥάɻ w 0QT5PHHMFγεςϜͷಈ࡞Λ੍ޚ͢ΔͨΊͷϑϥάɻஈ֊తϦϦʔε౳ w

    1FSNJTTJPO5PHHMFಛఆϢʔβʔʹઌߦͯ͠ར༻Մೳʹ͢ΔͨΊͷϑϥάɻ Ћ൛ͷػೳ΍ϓϨϛΞϜձһ޲͚ػೳ౳  ຊൃදͰ͸3FMFBTF5PHHMFʹߜͬͯղઆ &YQFSJNFOUBM5PHHMFʹ͍ͭͯ͸J04%$+BQBO಺ 5BLFTIJ*IBSB͞Μͷʮ'FBUVSF'MBHΛద੾ʹ෼ྨ͢Δ͜ͱͰ"#ςετͷӡ༻ίετΛԼ͛ΔʯͰ΋ղઆ͋Γ
  33. 'FBUVSF'MBH τϥϯΫϕʔε։ൃ ͷར఺ 

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

    'FBUVSF'MBH0/
  35. 'FBUVSF'MBHͷར఺ ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍ w ڝ߹͕ൃੜ͠ʹ͍͘ਖ਼͘͠ಈ͘ίʔυ͕ҡ࣋͞ΕΔ w ։ൃ్தͰ΋ϑϥά0''ͷ··ϦϦʔεՄೳ w ༏ઌ౓มߋͷӨڹΛड͚ʹ͍͘ w Ԙ௮͚ظ͕ؒ௕ͯ͘΋௥ैɾڝ߹ղফίετ্͕͕Βͳ͍

    w ϚʔδͱϦϦʔε͕෼͔ΕΔϦϦʔεൣғɾλΠϛϯάΛॊೈʹมߋՄೳ  ࠩ͠ࠐΈͰผͷ։ൃϦϦʔε ϝΠϯϒϥϯν͔Β࠶։ ։ൃ్தͰ΋Ϛʔδ
  36. 'FBUVSF'MBHͷར఺ NFSHFSFWFSU͕͠΍ࠩ͘͢෼͕খ͍͞ w ϑϥάͷ࠶൓సͰSFWFSUՄɾࠩ෼΋খ͍͞ w ࢀরՕॴΛݟΕ͹ӨڹൣғΛ೺Ѳ͠΍͍͢ w ෆ҆ͳΒҰఆظؒϑϥάΛ࢒͢ͷ΋Մ  //

    再反転すればrevert完了 let isNewFeatureAvailable = false if isNewFeatureAvailable { showNewFeature() }
  37. 'FBUVSF'MBHͷར఺  ϒϥϯνण໋͕୹͘௕ظ։ൃͰ΋ڝ߹͠ʹ͍͘  ΞδϟΠϧ։ൃͱ૬ੑ͕ྑ͍  NFSHFSFWFSU͕͠΍ࠩ͘͢෼͕খ͍͞  ௥ैڝ߹͕ͳ͘ετϨεϑϦʔ ߴ଎ͳσϦόϦʔΛ࣮ݱ

  38. ਌ϒϥϯν74τϥϯΫϕʔε 'FBUVSF'MBH  ਌ϒϥϯν ൺֱ߲໨ τϥϯΫϕʔε 'FBUVSF'MBH ଟ͍ ϩʔΧϧϚʔδڝ߹ൃੜ গͳ͍

    ߴ͍ தஅɾ࠶։ίετ ௿͍ ਌13ͱಉ͡ߦ਺ SFWFSU࣌ͷࠩ෼ d਺ߦ ೉͍͠ɾίετ͕ߴ͍ είʔϓɾλΠϛϯάมߋ ༰қ
  39. J04Ͱͷ࣮૷ύλʔϯ 

  40. 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 ) }
  41. HStack(spacing: 0) { if enabledNewFeature { NewView(newViewModel: .init()) } else

    { OldView(oldViewModel: .init()) }.padding(.bottom, 16) w දࣔՕॴʹ௚઀෼ذΛ࣮૷Մೳ w ෼ذָ͕Ͱཧ૝తͳύλʔϯ 4XJGU6* 
  42. // 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 
  43. 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 
  44. 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ͷ૊ΈཱͯΛ෼ ذͤͤ͞Δ σʔλܕ͕มΘΔ৔߹ 
  45. 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) }
  46. ࣗલ࣮૷74֎෦αʔϏε 

  47. /* 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
  48. # 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になる
  49. ֎෦αʔϏεͷൺֱද جຊతػೳੑʹ͸େࠩͳ͘'JSFCBTF͕͓खࠒ  ൺֱ߲໨ 'JSFCBTF 3FNPUF$PO fi H -BVODI%BSLMZ TQMJUJP

    'MBHTIJQ ಛ௃ ଞͷ'JSFCBTF αʔϏεͱͷ࿈ܞ ϦΞϧλΠϜੑ NTҎ಺Ͱ഑৴ ܭଌ΍؂ࢹͷ ػೳ෇͖ ࣗಈϩʔϧόοΫ ஈ֊తϦϦʔε ྉۚ ແྉ NPOUId ໊·Ͱແྉ NPOUId GFUDIճ਺੍ݶ ࣌ؒҎ಺ճ·Ͱ ެࣜͰ໌ݴͤͣ ͳ͠ ͳ͠
  50. w σϑΥϧτͰλʔήοτΞϓ Ϧɾ04ɾࠃͳͲΛࢦఆՄೳ w ೚ҙϢʔβʔϓϩύςΟʹՃ͑ɺ ϥϯμϜͳׂΓৼΓ΍೔෇ϕʔε ͷ෼ذ΋Մ w औಘ੍ݶͱΩϟογϡͷͨΊɺ සൟͳมߋʹ͸஫ҙ

    'JSFCBTF3FNPUF$POGJHͷ৚݅ࢦఆ 
  51. w ύϥϝʔλ໊ɾσʔλܕɾσϑ Υϧτ஋Λࢦఆ w ෼ذ৚݅Λࢦఆ͢Δ w "QQ%FMFHBUFͰύϥϝʔλΛ GFUDIͯ͠ར༻͢Δ 3FNPUF$POGJHͰͷϑϥάఆٛ 

    import Firebase let remoteConfig = RemoteConfig.remoteConfig() let isNewFeatureEnabled = remoteConfig["newFeatureEnabled"].boolValue 'FBUVSF'MBHTTXJGU
  52. ࢖͍෼͚ ϦϦʔε SFWFSU λΠϛϯάͱ෼ذ৚݅࣍ୈ ࣗલ࣮૷ w ΞϓϦϦϦʔεػೳϦϦʔε w ෼ذ৚͕݅੩త 

    ֎෦αʔϏε αʔόʔ੍ޚ  w ΞϓϦϦϦʔεͱػೳϦϦʔεͷ λΠϛϯάΛ෼͚͍ͨ w ෼ذ৚݅Λಈతɾৄࡉʹม͍͑ͨ
  53. ࣗલ࣮૷74֎෦αʔϏε ϦϦʔεɾλʔήςΟϯάͰ੍໿͕͋ΔͳΒ֎෦αʔϏεΛݕ౼  ൺֱ߲໨ ࣗલ࣮૷ ΫϥΠΞϯτͷΈ ֎෦αʔϏε ϑϥά൓సλΠϛϯά Ϗϧυ࣌ ೚ҙλΠϛϯά

    ϑϥά؅ཧมߋ৔ॴ ΫϥΠΞϯτͷίʔυ αʔόʔ(6* λʔήςΟϯάɾ࣌ݶࣜ ೉͍͠ Մೳ ґଘ ͳ͠ 4%,͕ඞཁ Ձ֨ ແྉ ༗ྉͷ৔߹͋Γ
  54. 'FBUVSF'MBHͷ5*14 

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

    w ޙ͔Βͷ'FBUVSF'MBHಋೖ΋͋Γ 
  57. 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() }
  58. ࡟আ࣌ͷϛε๷ࢭ؍఺͔Β 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 }
  59. 'FBUVSF'MBHར༻࣌ͷ஫ҙ఺ 

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

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

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

    w ϨϏϡʔ༻ͷ࣌ؒΛઃఆ 
  63. 'FBUVSF'MBHͷ஫ҙ఺ ར༻͕೉͍͠έʔε w ෳࡶͳ4UPSZCPBSE9JCϨΠΞ΢τ w ਌ϒϥϯνͰਐΊΔ͔࡞Γ௚͠Λݕ౼͢Δ w ৽چͷܕͷࠩ෼͕େ͖͍ w ৽چܕͷϑΟʔϧυΛ༻ҙͯ͠΋෼ذࠔ೉ͳ৔߹΋

    w ந৅ԽϨΠϠʔΛઃ͚Δํ๏΋͋Γ w มߋՕॴ͕ΫϥΠΞϯτଆʹͳ͍ w "1*όʔδϣϯΛม͑ΔPSαʔόʔʹϑϥάΛಋೖ 
  64. ·ͱΊ 

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

    w ৗʹখ͘͞ɾߴ଎ʹϚʔδ͍ͯ͜͠͏ 
  66. 'FBUVSF'MBHΛ࢖ͬͨ։ൃͰ ߴ଎͔ͭετϨεϑϦʔͳσϦόϦʔΛ

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