それをEnumるなんて とんでもない! - Swift Enum antipattern

704056da9a4c4e075ad14479beaebab7?s=47 takasek
November 29, 2016

それをEnumるなんて とんでもない! - Swift Enum antipattern

AKIBA.swift 第7回 - connpass
https://classmethod.connpass.com/event/44812/
での発表資料です。

704056da9a4c4e075ad14479beaebab7?s=128

takasek

November 29, 2016
Tweet

Transcript

  1. by. ▶ SLIDE START 1

  2. ͠Β΂Δ: takasek GitHubͰ͸ SwiftyͳϚΠΫϩϥΠϒϥϦ ࡞ͬͯ·͢ ▶Notifwift NSNotificationͷuserInfoΛ Swiftyʹѻ͏ ▶ActionClosurable target/actionΛ

    Ϋϩʔδϟه๏ʹม׵ 2
  3. SwiftͷEnum͸ڧྗ 4 switchט·ͤΔͱ໢ཏతʹॻ͚Δ 4 ϝιουΛ࣋ͨͤΒΕΔ 4 Protocol΁ͷద߹΋ࢥ͍ͷ·· 4 associated valuesศར

    4 Optional΋ਖ਼ମ͸Enum! 4 rawValueͱͷ૬ޓม׵΋Ͱ͖Δ 3
  4. ΨϯΨϯ ͔͓ͭ͏ͥ! 4

  5. ⚠ɹ 5

  6. EnumʹཔΓ͗ͯ͢ ຊདྷ࢖͏΂͖Ͱͳ͍ܗͰ EnumΛ࢖ͬͯ·ͤΜ͔! 6

  7. ͱΜͰ΋ͳ͍Enumͷ ΉΕ͕ ͋ΒΘΕͨʂ ɹ ผΧςΰϦͷcase͕ࠞͬͨ͟Enum ɹ ҟͳΔίϯςΩετͰ࢖͍ճ͞ΕΔEnum ɹ άϧʔϐϯάͷͨΊ͚ͩͷEnum 7

  8. ͱΜͰ΋ͳ͍Enumͷ ΉΕ͕ ͋ΒΘΕͨʂ ▶ ผΧςΰϦͷcase͕ࠞͬͨ͟Enum ɹ ҟͳΔίϯςΩετͰ࢖͍ճ͞ΕΔEnum ɹ άϧʔϐϯάͷͨΊ͚ͩͷEnum 8

  9. ▶ ผΧςΰϦͷcase͕ ɹࠞͬͨ͟Enumɹᶃ !γνϡΤʔγϣϯ! 4 ΅͏͚Μͷ͠ΐCellΛબͿ 4 API௨৴Ͱfetch͢Δ 4 struct

    SaveData 4 ௨৴ঢ়گ͸EnumͰ؅ཧ 4 enum LoadingState 9
  10. final class ΅͏͚Μͷ͠ΐListViewModel { enum LoadingState { case idle case

    proceeding case succeeded case error } let loadingState = Variable<LoadingState>(.idle) let saveData = Variable<SaveData?>(nil) func fetchSaveData(at index: Int) { SaveDataAPI(of: index).fetch { [weak self] (data: SaveData?) in if let data = data { self?.saveData.value = data self?.loadingState.value = .succeeded } else { self?.loadingState.value = .error } } } 10
  11. !ɹ 11

  12. final class ΅͏͚Μͷ͠ΐListViewModel { enum LoadingState { case idle case

    proceeding // !͍ͭ͜Β͸͔֬ʹʮ௨৴ঢ়ଶʯ͚ͩͲ… case succeeded case error // !͍ͭ͜Β͸ʮ௨৴݁ՌʯΛදͯ͠Δ } let loadingState = Variable<LoadingState>(.idle) let saveData = Variable<SaveData?>(nil) //ɹ ! SaveDataͱLoadingState.error ͸ //ɹɹɹͲͬͪ΋ʮ௨৴݁Ռʯͱݴ͏ҙຯͰ໾ׂ͕ॏෳ 12
  13. ͬͪ͜ͷઃܭͷ΄͏͕ਖ਼͍͠ final class ΅͏͚Μͷ͠ΐListViewModel { enum LoadingState { case idle

    case proceeding //!௨৴ঢ়ଶ͚ͩΛࣔ͢Enumʹ͠·͠ΐ͏ //"Ή͠Ζ Bool ͰࡁΉ } let loadingState = Variable<LoadingState>(.idle) let saveData = Variable<Result<Error, SaveData>?>(nil) //!௨৴ޙ͸ɺ݁Ռ(Error or SaveData)͕ඞͣೖΔ 13
  14. 14

  15. ▶ ผΧςΰϦͷcase͕ࠞͬͨ͟Enumɹᶄ !γνϡΤʔγϣϯ! 4 ΩϟϥΛ࡞੒ 4 struct Character 4 Ωϟϥ͸

    ͠ΐ͗͘ΐ͏ Λ࣋ͭ 4 enum Job 15
  16. struct Character { enum Job: String { case Ώ͏͠Ό =

    "hero" case ͤΜ͠ = "soldier" case ·΄͏͔͍ͭ = "wizard" case ͦ͏Γΐ = "priest" case undefined } let job: Job ... init(json: JSON) { ... job = Job(json["job"].stringValue) ?? .undefined ... } 16
  17. !ɹ 17

  18. enum Job: String { case Ώ͏͠Ό = "hero" case ͤΜ͠

    = "soldier" case Ϳͱ͏͔ = "fighter" case ·΄͏͔͍ͭ = "wizard" case ͦ͏Γΐ = "priest" case undefined // ! ཻ౓͕͓͔͍͠ } let job: Job // ! Required ͜͏͡Όͳͯ͘ɺ 18
  19. enum Job: String { case Ώ͏͠Ό = "hero" case ͤΜ͠

    = "soldier" case Ϳͱ͏͔ = "fighter" case ·΄͏͔͍ͭ = "wizard" case ͦ͏Γΐ = "priest" //case undefined ͳͲͳ͍ } let job: Job? // ! Optional ͜͏Ͱ͠ΐʂ 19
  20. ▶ ผΧςΰϦͷcase͕ࠞͬͨ͟Enumɹᶄ ΋ͬͱݴ͑͹ɺ let job: Job // ! Optionalʹͨ͘͠ͳ͍ͳΒ init?(json:

    JSON) { guard job = Job(json["job"].stringValue) else { // Enum͕࡞Εͳ͍৔߹ɺ // failable initializerʹͯ͠ܕ͝ͱ௵ͦ͏ return nil } self.job = job ... } 20
  21. ࢓༷ͱͯ͠ ʮΩϟϥʹ ͠ΐ͗͘ΐ͏ ͕͋ΔʯͳΒ ܕઃܭͱͯ͠΋ඞͣ ʮ ͠ΐ͗͘ΐ͏ ͕͋Δʯ΂͖ 21

  22. ʮ ͠ΐ͗͘ΐ͏ ͕ͳ͍ʯܕઃܭΛڐ͢ͱ ίʔυΛॻ্͘Ͱ΋ৗʹ ʮ ͠ΐ͗͘ΐ͏ ͕ͳ͍ʯέʔεΛ ߟྀ͠ͳ͖Ό͍͚ͳ͘ͳΔ 22

  23. ग़య: ܕ҆શੑͱ͸Կ͔ | ϓϩάϥϛϯά | POSTD http://postd.cc/what-is-type-safety/ 23

  24. Swift͸ܕ҆શݴޠ 24

  25. ͔ͤͬ͘ͳΒ ܕγεςϜͷԸܙΛ ड͚ΒΕΔ ΑΓྑ͍ܕઃܭΛ! 25

  26. 26

  27. ͱΜͰ΋ͳ͍Enumͷ ΉΕ͕ ͋ΒΘΕͨʂ ɹ ผΧςΰϦͷcase͕ࠞͬͨ͟Enum ▶ ҟͳΔίϯςΩετͰ࢖͍ճ͞ΕΔEnum ɹ άϧʔϐϯάͷͨΊ͚ͩͷEnum 27

  28. !γνϡΤʔγϣϯ! 4 ͯΜ͠ΐ͘͠·͢ 4 ͨͩ͠ɺΏ͏͠Όʹ͸ ɹɹͯΜ͠ΐ͘Ͱ͖·ͤΜ 28

  29. final class μʔϚͷਆ఼ViewModel { /// TableViewCellͷrowͱׂͯ͠Γ౰ͯΔ഑ྻ let options: [Character.Job] =

    [ //.Ώ͏͠Ό, !స৬ෆՄೳͳͷͰলུ .ͤΜ͠, .Ϳͱ͏͔, .·΄͏͔͍ͭ, .ͦ͏Γΐ ] let targetCharacter: Character func select(_ selection: Character.Job) { switch selection { case .Ώ͏͠Ό: return //Ώ͏͠Όʹ͸స৬Ͱ͖ͳ͍ default: changeJob( of: targetCharacter, to: selection) } } ... } 29
  30. !ɹ 30

  31. switch selection { case .Ώ͏͠Ό: return //Ώ͏͠Όʹ͸స৬Ͱ͖ͳ͍ default: changeJob(of: targetCharacter,

    to: selection) } ! ࠓճ͸ૣظreturnͰࡁΉύλʔϯ͔΋͠Εͳ͍͚Ͳɺ ৔߹ʹΑͬͯ͸ɺ࠷ѱॻ͚Δॲཧ͕ͳͯ͘ fatalError() ͠ͳ͖Ό͍͚ͳ͍΍ͭͩ 31
  32. Կނ બ୒ࢶʹͳ͍ Ώ͏͠Όͷ͜ͱΛ ؾʹ͠ͳ͖Ό ͳΒΜͷͩ 32

  33. Ώ͏͠Ό΁ͷస৬caseɺ ࣮ӡ༻্͸͋Γ͑ͳ͍͚ΕͲɺ ϩδοΫ্͸ଘࡏͯ͠͠·͏͔Β ࢓ํͳ͍… 33

  34. Enum͔Β .Ώ͏͠Ό Λ֎͢͜ͱͳΜͯ Ͱ͖ͳ͍͠… 34

  35. ຊ౰ʹʁ 35

  36. ʮΩϟϥͷ ͠ΐ͗͘ΐ͏ʯͱ ʮస৬Ͱ͖Δ ͠ΐ͗͘ΐ͏ʯ͸ ίϯςΩετ͕ҧ͏ 36

  37. ίϯςΩετ͕ҧ͏ͳΒɺ ͦΕ͸ผͷEnumͰ؅ཧ͢Ε͹͍͍ ͭ·Γɺ͜͏Ͱ͸ͳ͘… final class μʔϚͷਆ఼ViewModel { let options: [Character.Job]

    func select(_ selection: Character.Job) { switch selection { case .Ώ͏͠Ό: return //Ώ͏͠Όʹ͸స৬Ͱ͖ͳ͍ default: changeJob(of: targetCharacter, to: selection) } } ... } 37
  38. ίϯςΩετ͕ҧ͏ͳΒɺ ͦΕ͸ผͷEnumͰ؅ཧ͢Ε͹͍͍ ͜͏ʂ final class μʔϚͷਆ఼ViewModel { enum ChangableJob {

    //స৬ͱ͍͏ίϯςΩετͰͷΈ࢖͏ʂ case ͤΜ͠, Ϳͱ͏͔, ·΄͏͔͍ͭ, ͦ͏Γΐ } let options: [ChangableJob] func select(_ selection: ChangableJob) { //બ୒ࢶʹ͋Δ͠ΐ͗͘ΐ͏͔͠౉ͬͯ͜ͳ͍ //΋͏ɺΏ͏͠Όͷ͜ͱ͸ߟ͑ͳͯ͘ྑ͍…ʂ changeJob(of: targetCharacter, to: selection) } ... } 38
  39. ίϯςΩετڥքΛҙࣝͯ͠ ద੾ʹEnumͷద༻ൣғΛ෼அ͢Ε͹ ࢓༷มߋɾ௥Ճ͕ൃੜͨ͠৔߹ʹ΋ ӨڹΛہॴԽͰ͖·͢! 39

  40. 40

  41. ͱΜͰ΋ͳ͍Enumͷ ΉΕ͕ ͋ΒΘΕͨʂ ɹ ผΧςΰϦͷcase͕ࠞͬͨ͟Enum ɹ ҟͳΔίϯςΩετͰ࢖͍ճ͞ΕΔEnum ▶ άϧʔϐϯάͷͨΊ͚ͩͷEnum 41

  42. !γνϡΤʔγϣϯ! ɹɹɹɹɹɹͦ͏ͼͷछผΛબΜͰ͔Βɹ⾣ɹͦ͏ͼΛબͼ·͢ 42

  43. struct Equipment: Item { enum Kind { case Ϳ͖ case

    ΑΖ͍ case ͨͯ case ͔Ϳͱ } let kind: Kind ... } 43
  44. final class ͦ͏ͼListViewModel { let character: Character //ର৅Ωϟϥ let equipments:

    [Equipment] //TableViewʹදࣔ͢ΔΞΠςϜͷϦετ init(character: Character, for kind: Equipment.Kind) { self.equipments = character.items //Ωϟϥͷ͍࣋ͬͯΔΞΠςϜͷதͰɺ .flatMap { $0 as? Equipment } // ͦ͏ͼͰ͋Γɺ .filter { $0.kind == kind } // ࢦఆ͞ΕͨछผͱҰக͢Δ΋ͷΛTableViewʹදࣔ self.character = character } func select(equipment: Equipment) { //ͦ͏ͼͷछྨʹΑͬͯɺ΍Δ͜ͱ͕มΘΔ switch equipment.kind { case .Ϳ͖: performProcess(asWeapon: equipment) case .ΑΖ͍: performProcess(asArmor: equipment) case .ͨͯ: performProcess(asShield: equipment) case .͔Ϳͱ: performProcess(asHelmet: equipment) } } private func performProcess(asWeapon weapon: Equipment) { guard case .Ϳ͖ = weapon.kind else { return } ... } ... } 44
  45. !ɹ 45

  46. ͜ͷίʔυɺ ݏͳ೏͍͕͠·͢Ͷ switch equipment.kind { case .Ϳ͖: performProcess(asWeapon: equipment) case

    .ΑΖ͍: ... } private func performProcess( asWeapon weapon: Equipment) { guard case .Ϳ͖ = weapon.kind else { return } ... 46
  47. Կނ ͢ͰʹͿ͖ͩͱ ಛఆͰ͖ͯΔΞΠςϜʹ ౎౓νΣοΫೖΕͳ͖Ό ͳΒΜͷͩ 47

  48. Enumͷར఺ ͻͱͭͷܕͰ ෳ਺ͷέʔεΛ వΊͯදݱͰ͖Δ 48

  49. Enumͷܽ఺ ෳ਺ͷέʔε͕ ͻͱͭͷܕʹ వΊΒΕͯ͠·͏ 49

  50. …͍ͬͯ͏͔ ʮͦ͏ͼʯͷछผͬͯผʹ ྻڍ͢Δඞཁͳ͘ͳ͍ʁ 50

  51. !͜ͷը໘Ͱ͸ ྻڍ͕ඞཁ͚ͩͲɺ ͦͷίϯςΩετʹ റΒΕΔඞཁ͕ ͳ͍ͬͯ͜ͱ͸ɺ طʹઆ໌ͨ͠ͱ͓Γ 51

  52. ͳΒɺ͜ΕͰ͍͍Μ͡Όͳ͍ʁ struct Weapon: Item, Equipable { ... } struct Armor:

    Item, Equipable { ... } struct Shield: Item, Equipable { ... } struct Helmet: Item, Equipable { ... } 52
  53. final class ͦ͏ͼListViewModel<E: Item, Equipable> { let character: Character //ର৅Ωϟϥ

    let equipments: [E] //TableViewʹදࣔ͢ΔΞΠςϜͷϦετ init(character: Character) { self.equipments = character.items //Ωϟϥͷ͍࣋ͬͯΔΞΠςϜͷதͰɺ .flatMap { $0 as? E } // ͦͷͦ͏ͼछผͷ΋ͷΛTableViewʹදࣔ self.character = character } func select(equipment: E) { switch equipment { case let e as Weapon: performProcess(weapon: e) case let e as Armor: performProcess(armor: e) case let e as Shield: performProcess(shield: e) case let e as Helmet: performProcess(helmet: e) default: () } } // ܕϨϕϧͰ Ϳ͖ ͩͱಛఆͰ͖͍ͯΔʂʂ private func performProcess(weapon: Weapon) { ... } } 53
  54. ܕ҆શ! 54

  55. ʮࣅͨΑ͏ͳ΋ͷΛ άϧʔϐϯά͢Δͱ͖ʹ ͱΓ͋͑ͣEnumʯ 55

  56. …͢Δલʹɺ ͪΐͬͱߟ͑ͯΈ·ͤΜ͔ ը໘ͷදࣔཁૉͱ͔ɺAPIͷ޲͖ઌͱ͔ɺ ΍Γ͕ͪͰ͢ΑͶ… 56

  57. 57

  58. ࠷ޙʹɺ EnumΛ࢖͏΂͖͔νΣοΫγʔτ 4 ͦͷάϧʔϓ͸switchจͰ໢ཏ͢Δඞཁ͕͋Δ͔ʁ 4 ͳ͍ͳΒɺprotocol + ݸผͷstructͰྑ͍ͷͰ ͸ʁ 4

    ͋Δ͍͸άϧʔϐϯάࣗମɺෆཁͳͷͰ͸ʁ 4 ΧςΰϦͷҧ͏case͕ࠞͬͯ͟ͳ͍͔ʁ 4 ͦͷEnum͸ɺίϯςΩετʹԊͬͨ΋ͷ͔ʁ 58
  59. 59

  60. !…ͱ͸͍͑ String enum͸ศརͰ͚͢ͲͶͬʂ enum ;͔ͬͭͷ͡Ύ΋Μ: String { case lv25 =

    "΄Γ͍Ώ͏ɹ͑͡ʹͭ͘͢ͲɹΒ͑͘͢͝ɹͱͩΑ" case lv20 = "·Δ͔ͭ͸ɹ΍ͭ͸Γ͔͍ͤɹ͍ͪͩͭͨɹͷͩΑ" case lv24 = "ΓΏ͏͓͏ɹ͓·͑͸΋͏͠ɹ͵Θ͔ͭͨɹ͔͹͔" case lv27 = "ͲΒ͑͘͸ɹͶͱ͛ʹͳͭͯɹͭ·Βͳ͍ɹ͋͏ͱ" case lv17 = "͑;͑;͸ɹͲΒ͑͘ΑΓ΋ɹ͓΋͠Ζ͍ɹ·͡͞" } passwordTextView.text = ;͔ͬͭͷ͡Ύ΋Μ.lv25.rawValue 60