コードを書き方は何かしらの判断基準や理由を持って決定されます。今回は1つの題材を元にSwiftという多様な表現が可能な言語で、どういう状況で、どういう書き方ができ、なぜそう書くのかについて考えてみました。
͋ͳͨͲ͏ॻ͖ͳͥͦ͏ॻ͘ͷ͔ʁUSZ4XJGU1SF5BMLT
View Slide
ͷࣗࣾαʔϏε։ൃձࣾͷJ04ΤϯδχΞЋTIJ[!TU[OTIJ[ ͣ͠!TIJ[TU[O TIJ[։ൃܦݧݴޠ4XJGU,PUMJO+BWBTDSJQU1)1$+BWB
Ͳ͏ͬͯίʔυͷॻ͖ํΛܾΊ͍ͯ·͔͢ʁ
໌֬ͳཧ༝ීஈͷ׳शܾఆࢼߦࡨޡʜஅ
ʁ
ϩάΛૹΔ YYYYQSPUPDPM
struct AnalyticsEvent {var name: Stringvar metadata: [String: Any] = [:]init(name: String, metadata: [String: Any] = [:]) {self.name = nameself.metadata = metadata}}class AnalyticsManager {private let engine: AnalyticsEngineinit(engine: AnalyticsEngine) {self.engine = engine}func log(_ event: AnalyticsEvent) {engine.send(name: event.name, metadata: event.metadata)}}protocol AnalyticsEngine: class {func send(name: String, metadata: [String: Any])}struct Book {let name: String}final class Library {private var library: [Book] = []func add(_ book: Book) -> Book {library.append(book)return book}}
final class BookDetailViewController: UIViewController {func addBook(book: Book) {library.add(book)let event = AnalyticsEvent(name: "bookOdded")analytics.log(event)}}wλΠϓϛεΛ͢ΔՄೳੑ͕͋Δwमਖ਼͕ෳՕॴʹೖΔfinal class BookListViewController: UIViewController {func addBook(book: Book) {library.add(book)let event = AnalyticsEvent(name: "bookAdded")analytics.log(event)}}
FOVN
enum AnalyticsEvent {case bookAdded(Book)var name: String {switch self {case .bookAdded:return "bookAdded"}}var metadata: [String: Any] {switch self {case .bookAdded(let book):return ["book": book.name]}}}final class BookListViewController: UIViewController {func addBook(book: Book) {library.add(book)analytics.log(.bookAdded(book))}}final class BookDetailViewController: UIViewController {func addBook(book: Book) {library.add(book)analytics.log(.bookAdded(book))}}ɾλΠϓϛε͕ͳ͘ͳΔɾίϯύΠϧ࣌ʹνΣοΫ͕Ͱ͖Δ
enum AnalyticsEvent {case loginScreenViewedcase loginAttemptedcase loginFailed(reason: LoginFailureReason)case loginSucceededcase bookListViewedcase bookAdded(Book)case bookSelected(Book)case bookDeleted(Book)var name: String {switch self {case .loginScreenViewed, .loginAttempted,.loginSucceeded, .bookListViewed:return String(describing: self)case .loginFailed:return "loginFailed"case .bookAdded:return "bookAdded"case .bookSelected:return "bookSelected"case .bookDeleted:return "bookDeleted"}}var metadata: [String: Any] {switch self {case .loginScreenViewed, .loginAttempted,.loginSucceeded, .bookListViewed:return [:]case .loginFailed(let reason):return ["reason" : String(describing: reason)]case .bookAdded(let book):return ["book" : book.name]case .bookSelected(let index):return ["book" : book.name]case .bookDeleted(let book):return ["book" : book.name]}}}w4XJUDIจ͕େྔʹͳΔwએݴͱมͷఆ͕͔ٛΕΔͲΜͲΜ૿͍͑ͯ͘ɻɻɻ
Ϟδϡʔϧͷׂ͕Ͱ͖ͳ͍Πϕϯτ͕૿͑ΔenumʹcaseΛՃenumͷextensionͷमਖ਼YYYYQSPUPDPM
struct
struct AnalyticsEvent {let name: Stringlet metadata: [String : Any]private init(name: String, metadata: [String: Any] = [:]) {self.name = nameself.metadata = metadata}}• ҰՕॴͰએݴͰ͖Δ• ݺͼग़͠ํenumͷ࣌ͱมΘΒͳ͍final class BookListViewController: UIViewController {func addBook(book: Book) {library.add(book)analytics.log(.bookList_BookAdded(book))}}extension AnalyticsEvent {static func bookList_BookAdded(_ book: Book) -> AnalyticsEvent {return AnalyticsEvent(name: "bookList_BookAdded",metadata: ["book" : book.name])}}
Ϟδϡʔϧͷׂ͕Ͱ͖Δextensionʹ͢Δ͜ͱͰଞʹӨڹΛ༩͑ͳ͍YYYYQSPUPDPM
w໊લͷিಥfinal class BookListViewController: UIViewController {func addBook(book: Book) {library.add(book)analytics.log(.bookList_BookAdded(book))}}extension AnalyticsEvent {static func bookList_BookAdded(_ book: Book) -> AnalyticsEvent {return AnalyticsEvent(name: "bookList_BookAdded",metadata: ["book" : book.name])}static func bookDetail_BookAdded(_ book: Book) -> AnalyticsEvent {return AnalyticsEvent(name: “bookDetail_BookAdded",metadata: ["book" : book.name])}}final class BookDetailViewController: UIViewController {func addBook(book: Book) {library.add(book)analytics.log(.bookDetail_BookAdded(book))}}
ݺͼग़͠ଆͰॳظԽͷํ๏ΛTUSVDUͷ߹ʹ߹ΘͤΔඞཁ͕͋Δextension AnalyticsEvent {static func bookDetail_BookRead(_ book: Book, count: Int) -> AnalyticsEvent {return AnalyticsEvent(name: “bookDetail_BookAdded",metadata: ["book": book.name, “read_count”: count])}}
init(book: Book, count: Int) {self.init(name: "bookRead",metadata: ["book" : book.name, "read_count":count])}init(a: String) {self.init(name: a)}init(b: String) {self.init(name: b)}init(c: String) {self.init(name: c)}init(d: String) {self.init(name: d)}…DVTUPNJOJUΛఆٛͰ͖Δ͕ʜͲΜͲΜ૿͍͑ͯ͘ɻɻɻ
protocol
• ৽͍͠Πϕϯτͷ࡞͕༰қ(caseextensionͷՃඞཁͳ͠)• ಠࣗͷΠχγϟϥΠβΛఆٛͰ͖ݺͼग़͠ଆͷෛ୲Λ૿͞ͳ͍• enumstructclass͑Δ• Xcodeͷࣗಈิͳ͠protocol AnalyticsEvent {var name: String { get }var metadata: [String: Any] { get }}struct BookReadErrorAnalyticsEvent: AnalyticsEvent {var name: String {return "BookReadError"}var metadata: [String: Any] {return ["code": reason.code,"description": reason.description]struct Reason {let code: Intlet description: String}private let reason: Reasoninit(reason: Reason) {self.reason = reason}}
final class ProductionAnalyticsEngine: AnalyticsEngine {func log(_ event: AnalyticsEvent) {let name = "Production-\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}}final class DevAnalyticsEngine: AnalyticsEngine {func log(_ event: AnalyticsEvent) {let name = "Dev-\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}}final class StagingAnalyticsEngine: AnalyticsEngine {func log(_ event: AnalyticsEvent) {let name = "Staging-\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}}final class TestAnalyticsEngine: AnalyticsEngine {func log(_ event: AnalyticsEvent) {let name = "Test-\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}}ಉ͡Α͏ͳܕͷఆ͕ٛ૿͍͑ͯ͘
• ܕΛఆٛ͢Δඞཁ͕ͳ͘ͳΔ• ΧελϚΠζ͕ࣗ༝ʹͳΔstruct AnalyticsEngine {let log: (Event) -> Voidinit(log: @escaping (Event) -> Void) {self.log = log}}final class BookListViewController: UIViewController {private let engine: AnalyticsEngine}let prefix = "Production-"let bookListEngine = AnalyticsEngine { event inlet name = "\(prefix)\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}let collection = Library()let vc = BookListViewController(library: collection, engine: bookListEngine)let bookDetailEngine = AnalyticsEngine { event inlet name = "\(prefix)\(event.name)"AnalyticsAPI.send(name: name, metadata: event.metadata)}
struct MultipleAnalyticsEngine {let log: ([Event]) -> Voidinit(log: @escaping ([Event]) -> Void) {self.log = log}}let multiEngine = MultipleAnalyticsEngine { events inevents.forEach {AnalyticsAPI.send(name: $0.name, metadata: $0.metadata)}}let book = Book(name: "ΧϥϚʔκϑͷܑఋ")multiEngine.log([BookListAnalyticsEvent.bookAdded(book),BookDetailAnalyticsEvent.bookAdded(book)])struct MultipleAnalyticsEngine {let log: ([AnalyticsEvent]) -> Voidinit(log: @escaping ([AnalyticsEvent]) -> Void) {self.log = log}}let book = Book(name: "ΧϥϚʔκϑͷܑఋ")multiEngine.log([BookListAnalyticsEvent.bookAdded(book),BookListAnalyticsEvent.bookAdded(book)])ຊBookDetailAnalyticsEvent.bookAdded(book)])ΛૹΓ͔ͨͬͨɻɻɻ
ͦͦ
protocol AnalyticsEvent {var name: String { get }var metadata: [String: Any] { get }}"OBMZUJDTʹΑͬͯ࠶มͷඞཁ͕ग़ͯ͘Δ
·ͱΊ
-FU`TUSZBOEFSSPS-FU`TFOKPZUSZTXJGU
͋Γ͕ͱ͏͍͟͝·ͨ͠ʁ
ࢀরϦϯΫIUUQTXXXTXJGUCZTVOEFMMDPNQPTUTCVJMEJOHBOFOVNCBTFEBOBMZUJDTTZTUFNJOTXJGUIUUQTNBUUEJFQIPVTFDPNXIFOOPUUPVTFBOFOVNIUUQTEBWFEFMPOHDPNCMPHNJTVTJOHFOVNTIUUQTXXXZPVUVCFDPNXBUDI W1OR+J+7D1