こんにちは、株式会社iimonでエンジニアをしている遠藤です。
本記事はアドベントカレンダー8日目の記事となります。
昨日に引き続きアドベントカレンダーを担当させていただきます。
はじめに
今回は既存のプロジェクトに型チェックを効果的に導入するための考え方について書いていこうと思います。
というのも、既存のPythonプロジェクトに型チェッカツール(mypy)が導入された際に、どのコードにどこまで明示的に型アノテーションをつけるのが良いのか考えたことがありました。既存のすべてのコードに型をつけきるには大きなコストがかかります。また、型アノテーションを詳細にすると、コードが明確化する分、コードへの型アノテーションの追加や型チェッカツールのエラー解消などにより多くのコストがかかるようになります。
そこで、最近読んだ「ロバストPython」で参考になった記載を自分なりにまとめてみました。
3つの戦略
まず、ロバストPythonでは「全てのコードに型アノテーションをつける必要はない」と述べられています。型アノテーションを厳格に指定しすぎると、コードの表現力を制約する場合があり、またあくまで目標は「保守しやすいコードベース」であるためです。そのため、まずは型アノテーションで得られるメリットとコストを比較しながらどのコードからどのように導入していくかの戦略を立てて行くことになります。特に大規模な既存プロジェクトへの型アノテーションの導入はよりコストが大きくなるのでこういった戦略の検討が必要になってきます。
そこで、導入コストが高いと感じる場合には、以下の3つの戦略を検討します。
- ペンポイントの特定
- 対象コードの戦略的な選択
- ツールの活用
ペンポイントの特定
まず、現在のプロセスで時間やコストがかかっている箇所を特定します。
例えば、以下のような箇所です。
- テストで失敗する箇所
- 顧客からバグの指摘を受けた箇所
- 開発者がコードベースの特定に苦労している箇所
そして、根本原因が型アノテーションによって解決するコードでないかを確認します。
例えば、以下のような原因の場合です。
- Noneに関連するエラー
- 無効な属性アクセス
- 型変換に関連するエラー
こういった大きなコストを軽減できれば、結果的に型アノテーションの追加がコストの削減につながります。
対象コードの戦略的な選択
優先的に型アノテーションをつけるコードを選択します。
ロバストPythonでは以下のような戦略が紹介されていました。
新規コードのみ型アノテーション
上記2つのルールに沿って型アノテーションを追加する戦略です。 長期的に書き変わらないコードは比較的に安定しており、型アノテーションをつけてもあまり効果のないコードだと思われるので、特に2を行うことで効果的な箇所に優先的に型アノテーションをつけることができます。
ボトムアップ型アノテーション
他の部分を構築する基礎として使われているコアライブラリやユーティリティに型アノテーションを追加する戦略です。 この基礎の上に作られているすべてのコードが型チェックのメリットを受けることができるので、こういった部分に型アノテーションを追加することで、広い範囲で型チェックの効果が期待できます。利益を生み出す部分への型アノテーション
システムの中でも価値創出に最も大きな影響を与えるビジネスロジックに型アノテーションを追加する戦略です。ビジネスロジックに型アノテーションを追加すれば、コードベースのコア部分を保護することができます。また、長寿命になることが多いコードなので、長く持続するメリットを得ることができます。よく書き換えられる部分への型アノテーション
他の部分よりも頻繁に書き換えられるコードに型アノテーションを追加する戦略です。
最も多くコミットされたファイル、長期にわたって書き換えられた行数が最も多いファイル、多くの人がコミットしたファイルなどに型アノテーションを追加します。
ロバストコードの意味は「エラーを持ち込む機会を減らすこと」にあるようで、こういった書き換えによってエラーが起きるリスクの高いコードへの型アノテーションの追加はより効果が期待できます。複雑な部分への型アノテーション
複雑なコードは理解するのに時間がかかります。 少しでも理解しやすいコードにするための方法として、リファクタリングやコメントの追加といった方法がありますが、型アノテーションの追加も方法の1つになります。型アノテーションを追加することで、どのデータ型が使われており、値をどのように扱えば良いかがわかりやすくなります。
ツールの活用
mypy
型アノテーション導入の最初の目標は「型チェッカが全くエラーを出さない状況に持ち込むこと」のようです。
このエラーへの対応としては、適切な型アノテーションで解決する、もしくは量が膨大な場合はエラーを無視するように設定をします。エラーを無視するように設定するのは、短期間ですべてのエラーを解消しきれない場合に大量のエラーを放置しておくと、新しいエラーとの見分けがつかなくなるためです。
mypyでは設定ファイルでコードベース全体もしくはモジュール単位で設定する方法と
#type: ignore
というコメントアウトをつける方法でエラーを無視することができます。けれども、これはあくまで型チェックを導入する作業を進めるためであり、いずれは無視するエラーの数は減らしていかないといけません。
MonkeyType
実行したコードの型アノテーションを自動で追加してくれるツールです。pipでインストールすることができます。
具体的な使い方については以下の記事が参考になりました。
Pytype
MonkeyTypeは、実行時に観察できたコードにしか型アノテーションがつけられないという問題があり、コードを実行するコストがかかったり、実行できない場合にはあまり役に立たないようです。Pytypeは静的解析によって型アノテーションを追加するため、コードを実行しなくてもデータ型を判断できます。こちらもpipでインストールできます。
Pytypeは単なる型アノテータではなく、型チェッカとしての機能も持っているようです。型アノテーションがなくてもコードの型をチェックすることができるため、コードベース全体に型アノテーションをつけずとも型チェックすることが可能です。そのため、導入時に低いコストで高いメリットを得ることができます。ただ、型アノテーションをつけることはコードを読みやすくすることでもあり、またPytypeがより正確なチェックを行えるようにするためにも必要になります。
まとめ
既存プロジェクトに型チェックを導入したとして、すべてのコードにすぐに型アノテーションを追加するのはやはりコストがかかり大変かと思います。優先的に型アノテーションを追加することでより大きなメリットを得られるコードとはどういったコードなのかや、実際にどのように戦略を立てて考えていけば良いのかについてとても勉強になりました。また、MonkeyTypeやPytypeなどツールについても知らないことが多かったので、今後実際に活用していけたらいいなと思います。
参照
最後に
この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いします。
次のアドベントカレンダーの記事は再び登場のhogeさんです!今回もどんな記事を書くのかとても楽しみです!