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を検討する
- 名前の衝突を避けるためにプレフィックス名を使う必要なんてない
- 基本的にはswift.orgのAPI Guidelinesを見る
- 開発
- デバッグオプションを活用する
- 開発時に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を示す
- クリーンアーキテクチャ本の
- クリーンアーキテクチャ本の型の命名は他にある一般的なプログラミングパラダイムを無視してる
- 公式のドキュメントの場所
- おわりに
- Effective Java
- Effective C++
- More Effective C++
- Effective Objective-C 2.0
assertとpreconditionとfatalErrorはかなり雰囲気でそのときの判断で使ってるので文章化するのが難しい。
用途
!
するのではない使い分け
fatalError
fatalError("このタイミングでローカルDBが見つからないとか異常すぎ")
precondition
assert
assert(赤字による合計値 < 0 ,"赤字処理したからマイナスになるよう作ってて、もしそうじゃないことあるなら気が付きたい")
assertionFailure("このcaseに来るのは異常すぎ。ドキュメント見てもわからんので自分に知らないことがある")
使い分け
precondition(directoryURL.isFileURL)
はguard directoryURL.isFileURL else { fatalError() }
でできる