Skip to content

Instantly share code, notes, and snippets.

@yimajo
Created November 21, 2021 12:33
Show Gist options
  • Save yimajo/95e1e84fc4a03fb24be56dd1c55af027 to your computer and use it in GitHub Desktop.
Save yimajo/95e1e84fc4a03fb24be56dd1c55af027 to your computer and use it in GitHub Desktop.
Effective Swift本の目次と目的を考える

目的

Effective Swift本があるとしたなら、どんな目的で誰のための本?

  • 何のため
    • 言語の正しい理解と、簡潔で明瞭で正確なソフトウェアの設計に役立つ内容にしたい
  • どうやる
    • Effective〜な流儀に従って書籍スタイルは逆引きまとめスタイル
    • 言語仕様が常に進化しているSwiftの動向をしっかり押さえながら、それらの最新の機能をどのように使うべきかについて実践的な観点から逆引きにしてその技法をまとめる
    • 本当はどうあれば効果的?
      • より効率的で可読性が低下しないようなコードを書くためのガイドラインになればいい
        • よりよいデザインを考え、よくある問題を避け、自分の期待通りに動くように
        • プログラムを他者が理解しやすく、保守しやすく、移植性をあたえ、拡張しやすくするため
  • 誰のため
    • 初中級以上のSwiftプログラマに必須と思われる技法までをカバー

目次

  • はじめに
  • Swiftに慣れよう
    • 可能ならいつでもletを使おう
    • varを使う必要がある場合はデメリットを局所的にしていこう
    • できるだけイミュータブルなオブジェクトを使う
    • 可能ならselfを省略しよう
    • 必ずしもクロージャで[weak self]が必要ないことも理解しよう
    • guardを恐れずに使うためにguardの条件を明確にしよう
    • strongSelfなどせずselfをシャドーイングしても問題はない
  • オブジェクトの生成と消滅
    • 依存する処理を直接結び付けるよりも依存の注入を選ぶ
  • class
    • overrideされたくない静的メソッドならclass funcではなくstatic funcにする
  • enum
    • Int定数の代わりにenumを使う
  • struct
    • 値のやりとりをする場合にstructの利用を検討しよう
    • 状態を保持しその監視の役割としたstructを使わないようにしよう
  • 非同期処理
    • 可能であればwithCheckedContinuation(function:_:)でクロージャベースの非同期処理を置き換える
    • 必要があればwithUnchecked~を利用する
    • シングルトンを利用する場合にactorを検討する
    • メインスレッドから呼び出されて困るメソッドにはglobalActorを検討する
    • DispatchQueueよりもTask APIの利用を選ぶ
    • 副作用の非同期処理中にawait MainActor.run {}を乱用しない
      • 場当たり的なTask.detachedが文脈をぶったぎって見える
  • デザインと宣言
    • 外部公開しないプロパティはprivate宣言する
    • 配列を返すメソッドではnilを返すのではなく空配列を返す
    • オプショナルにする必要のないパラメータはオプショナルにしない
    • インタフェースはCommandとQueryの区別を意識する
    • 可能であればインスタンスメソッドよりインスタンスのプロパティを使う
    • プロパティによるgetterの計算量がO(1)より複雑になる場合はメソッドを検討する
    • 一般的な使用方法を単純化する場合には、関数にデフォルトのパラメータを利用する
    • デフォルトのパラメータを利用する際にクロージャならOptionalを使わず空のクロージャを利用する
    • 遅延評価の使用を検討する
    • initで他の型を引数とするメソッドをなるべく利用しない
    • 成功失敗の情報を表現するためにResult型を使う
  • 命名
    • 基本的にはswift.orgのAPI Guidelinesを見る
      • 引数をうまく区別できない場合は、すべてのラベルを省略する
        • min(number1, number2), zip(sequence1, sequence2)
      • 引数を区別する必要がある場合はラベルを省略せず表現する
        • func move(from start: Point, to end: Point)
      • 値を保持する型変換のためのイニシャライザは最初の引数のラベルを省略してよい
        • Int64(someUInt32)
      • 絞り込むような場合は型をラベルを省略せずその絞りこみ方法をラベルとする
        • extension UInt32 { init(truncating source: UInt64) }
        • extension UInt32 { init(saturating valueToApproximate: UInt64) }
      • 最初の引数が前置詞を含む場合にはラベルにそれを示す
        • x.removeBoxes(havingLength: 12)
      • 複数の引数が1つの前置詞に対して必要な場合は前置詞をメソッドに含める
        • a.move(toX: b, y: c) => a.moveTo(x: b, y: c)
        • a.fade(fromRed: b, green: c, blue: d) => a.fadeFrom(red: b, green: c, blue: d)
      • フレーズとして正しくても引数が行う意味をラベルで伝える必要がある
        • view.dismiss(false) => view.dismiss(animated: false)
      • Factoryメソッドの命名は"make"で始める
        • let iterator = x.makeIterator()
      • mutatingなメソッドでは"from"で始める
        • y.fromUnizon(z)
      • 能力を表すプロトコルはサフィックスにable,ible, ingのいずれかつける
        • Equatable, ProgressReporting
      • プロトコルでも役割を説明する場合には名詞でいい
        • Collection
        • 能力を表すプロトコルのみが例外であって大抵は名詞
    • 独自の省略された命名を作り出さない
    • Objective-Cからアクセスしないのであればenumによるnamespaceを検討する
      • 名前の衝突を避けるためにプレフィックス名を使う必要なんてない
  • 開発
    • デバッグオプションを活用する
    • 開発時にassertを利用したコードを書いて異常系の対応を後回しにする
    • preconditionを利用して事前条件を明確にする
    • fatalErrorを利用して異常実行に関する情報量を増やす
  • SwiftUI
    • (at)Publishedを利用するとViewが更新されることを理解しよう
    • Generic型を使ってUITableViewCellやUICollectionViewCellをSwiftUI.Viewにする
    • SwiftUI.Viewのinitで時間のかかる処理を書かない
    • SwiftUI.Viewのプレビューが常にできるようにinitで依存の注入ができるようにする
    • プレビューのためにCore Dataのデータが関係する場合はオンメモリに書き込める
    • プレビューの高速化のためにプレビュー時Run Scriptを実行しないよう"Run script only when installing"にチェックする
  • その他
    • private extensionを使ってextensionをまとめてprivateにする
    • ループ時にforではなく高階関数のmap, filter, reduceを検討し副作用を実行しないことを表現しよう
    • 高階関数内で複雑なコードを書いてしまうと可読性が落ちることを理解しよう
    • 後片付けを確実にやるためにあらかじめdeferキーワードを利用する
    • Selfを使う
    • NSPredicateでプロパティの変数名を利用する場合は#keyPathを利用し変更時に気がつけるようにする
    • Bindする必要がある戻り値にはタプルを使うと漏れがないことを知っておく
    • テストコードを階層化して情報量を増やす
    • コンパイラの警告に注意を払おう
    • 機能開発を外注する際のことを考えてEmmbeded Moduleに分割する
    • UUIDをString型とする前にUUID型を検討しよう
    • 可能であればuuidという変数名は避ける
    • IDの型としてtypealiasを検討する
    • IDの型として専用の型を検討する
      • IDをIntとしたってクライアントアプリ側では意味がない
    • 型の命名などはClean Architecture本を参考にすべきではない
      • クリーンアーキテクチャ本の型の命名は他にある一般的なプログラミングパラダイムを無視してる
        • クリーンアーキテクチャ本のViewModelはViewに表示するObjectを示す
    • 公式のドキュメントの場所
  • おわりに

参考

@yimajo
Copy link
Author

yimajo commented Nov 21, 2021

assertとpreconditionとfatalErrorはかなり雰囲気でそのときの判断で使ってるので文章化するのが難しい。

用途

  • 現在やってることに集中するために
    • 現在対応してないことは実行時に異常に気付けるようにする
  • 異常時の情報量の追加
    • 単に!するのではない
      • XCTFailに似ている
    • どういう勘違いなのかを明確にし調べられるようにする
      • どういう勘違いをしているかがわかるような、本質的な改善ができるような修正のためのメッセージ

使い分け

  • fatalError

    • 使いたくなるとき
      • 異常すぎる事態でUIの何かからどうにか復帰できるようなことは困難。
      • UIでどうにかするにしても「再インストール」促すしかない気がする
    • 実例
      • fatalError("このタイミングでローカルDBが見つからないとか異常すぎ")
  • precondition

  • assert

    • 使いたくなるとき
      • 開発時は作りかけの処理の結果が異常である場合もあり、そういうときに異常に気がつきたい
      • guardでreturnしても良いかもしれないんだけど、それがまだ確認できない間はassertでチェックして落とす
        • guardしてreturnしてしまうと、正常動作してしまうように見える時がある
    • 実例
      • assert(赤字による合計値 < 0 ,"赤字処理したからマイナスになるよう作ってて、もしそうじゃないことあるなら気が付きたい")
      • assertionFailure("このcaseに来るのは異常すぎ。ドキュメント見てもわからんので自分に知らないことがある")
  • 使い分け

    • assertをfatalErrorで代用
      • fatalErrorで代用しないのは、fatalErrorはリリース時に無理やりなんとかその条件/経路にたどりつける場合に使いたい
        • assertはそもそもなくていいので後で消すことを前提にしてる
          • assertが残ってても無視されるから残ってもいいけど無いことが前提
    • preconditionをfatalErrorで代用
      • precondition(directoryURL.isFileURL)guard directoryURL.isFileURL else { fatalError() }でできる
        • 本来そのメソッドではそれが条件として作成者が作ってあることを明示したい
          • guardが処理されてもいいけど想定されてるんだからチェックするのは無駄
            • 最適化されてないときはそんなチェックしないでいいようになる

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment