📝
技術書読書ログ「ロバストPython」
感想
Pythonでの効果的な型ヒントや型チェックの方法が解説されていて、あまり使ってこなかった機能を知ることができて良かった。Python以外のコードを書く時にも使える内容も結構あった。
個人的に一番参考になったのが、どのユーザー定義型を使うかの指針のフローチャート。クラスを作る際は不変式を持たせるようにするという考えがあまりなかったので、今後は意識して使い分けたい。
静的解析ツールやテスト、アーキテクチャに関するツール・ライブラリも多く紹介されていて、試していきたいなと思った。
個人的に重要だと思ったところ
サマリ
- ロバストとは
- 保守性が高い
- 変化に対して耐久性が高くエラーを起こさない
- 将来の開発者が理解しやすく安心して開発できる
- ロバストなPythonコードを書くために重要なこと
- 型を使いこなす -> コードの意図を明確にするため
- ツールを活用する -> 正しい型が使われているか、ルールが守られているかをチェックするため
型を使いこなす
データ型の明示がロバストネスの大黒柱
- データ型に注意を払うだけで、将来の開発者に多くのことを伝えられ、メンテの負担を軽減できる
- 以下のデータ型を使いこなす
- Optional, Union, Literal, NewType, Final, TypeDict, UserList, UserString, ジェネリクス
- クラスと不変式を使ったほうが表現が強力になるが、これらは軽量で気軽に使えるメリットがある
ユーザー定義型
- ユーザー定義型で、コードベース内のドメイン固有のコンセプトを表現する
- データ型は汎用的なユースケースのために作られているので、ドメインを表現するには不十分
- ユーザー定義型には「Enum」「データクラス」「クラス」の3種類がある
- どのユーザー定義型を使うかの指針は、以下のフローチャートを参考にするといい
- 同種 : 同じデータ型の集合
- スカラー -> Enum
- コレクション -> 辞書
- 異種 : 異なるデータ型の集合
- 不変式なし -> データクラス
- 不変式あり -> クラス
- JSONやYAMLなどのデータのマッピング -> TypeDict
- 他で辞書として使わないなら、TypeDictにマッピングした後でデータクラスやクラスに変換すべき
- 同種 : 同じデータ型の集合
- Enum
- 静的で、離散的(連続していない)な値の集合
- 単純な値の制限が必要なだけならLiteralを使えばいい
- 実行時に変化する動的なキーバリューデータには向かないので、その場合は辞書を使う
- auto, flag, unique などの機能も使いこなすと表現が強力になる
- データクラス
- お互いに依存しないデータのグループ
- あるメンバによって他のメンバの値や振る舞いが制限される場合は、データクラスではコードの動作や正しさなどを論理的に判断しにくい
- 異種データの使い方を管理する辞書やnamedtupleが使われる箇所は、データクラスに置き換えるといい
- クラス
- 維持したい不変式がある場合にクラスを使う
- 不変式 -> 変化しない性質、コードを通して維持される概念
- 不変式を意識しないメソッドや、メンバを意識しないメソッドは、クラスには含めずにただの関数にすべき
- staticmethod, classmethod も不変式を意識しないメソッドになるので、あまり使わない
- コンストラクタで事前条件のアサートを行うとともに、メソッドや外部アクセスが不変式を破らないようにする
- 維持したい不変式がある場合にクラスを使う
ツールの活用
- 型チェッカー
- mypy(基本), Pyre, Pysa, Pyright/Pylance(リアルタイムでのデータ型検査)
- 自動で型アノテーションを追加
- MonkeyType, Pytype(リンタ・型チェッカでもある)
- 実行時型チェック、バリデータ
- pydanic
- 依存関係の可視化
- pidepgraph(パッケージ), pydeps(インポート), pyan3(関数呼び出し)
- サブシステムのプラグイン化
- stevedore
- その他の静的解析
- Pylint -> リンター、独自のプラグインを作ることができる
- McCabe -> 複雑度
- Bandit -> セキュリティ
- テスト
- behave(BDD, Gherkin), Hypothesis(プロパティベーステスト), mutmut(ミューテーションテスト)
その他
- アサーション、例外
- アサーションは開発中のエラー検知が目的で、開発者とのコミュニケーションのために使う
- 例外は、例外的なユースケース(ユーザーのミスや悪意の行動等)のときのみに使う
- 例外的ではないユースケースには、Optionalなどで表現する
- 利用者視点のインタフェース設計
- 将来の開発者が自然にインタフェースを利用できるようにユーザー定義型を設計する
- TDDやREADME駆動開発は利用者視点のインタフェース設計につながる
- 部分型(継承)は上位型を置換可能なときに限って使う、そうでないならコンポジション
- プロトコル
- 静的型チェックでも、構造的部分型(ダックタイピング)に対応できるようにする
- 型チェッカが、チェック対象クラスが定義した属性とメソッドだけで、プロトコルを満たしているかを判断してくれる
- 複雑なクラスの階層構造にも対応できる、複合プロトコルも可能
- 継承と使い分け
- 契約がデータ型の構造を定義するだけならプロトコル
- 上位型の契約が振る舞いにまで及ぶ(特定の条件下での操作方法などを定義している)なら継承
- 依存関係のコントロール
- ファンインが大きいエンティティは、依存グラフの葉に配置させ、安定化(頻繁に書き換えずに済むように)したい
- ファンアウトが大きいエンティティは、依存グラフの根に配置させることで、頻繁に書き換えられるようにする
- ビジネスロジックの大部分が含まれるはずの場所
Discussion