この記事について
2021年に初版が刊行された「Googleのソフトウェアエンジニアリング」という書籍があります。
以下Google本と呼びます。
Google本はあの超有名大企業Googleのソフトウェアエンジニア達がどのようにして上手くソフトウェアエンジニアリングをやっているかに関するノウハウを凝縮した書籍になっています。
全編通してとても良い本なのですが、如何せん鈍器かの如く分厚い見た目をしているため、読むのが憚られてしまうという欠点があります。
そこでこの記事では、リファクタリングという文脈に絞って、気軽に読めるようにまとめようと思います。
また、この記事はリファクタリングでシステムを安定化!日頃の取り組みや工夫を教えてください by カオナビ Advent Calendar 2022の記事となっています。
私について
ただの一般バックエンドエンジニアです。
普段はZennや個人ブログに記事を投稿していますので、よければご覧ください。以下の記事が一番人気です。
Qiitaでの投稿もアドベントカレンダーへの参加も初めてなのですが、見劣りしないような記事にしたいと思っていますのでよろしくお願いします。
ソフトウェアエンジニアリングとは
そもそもソフトウェアエンジニアリングとは何でしょうか。Google本ではプログラミングとソフトウェアエンジニアリングを明確に区別しており、その差は以下の3つです。
- 時間
- スケール
- トレードオフ
特に1つ目の時間に関して、Google本では以下の様に書かれています。
ソフトウェアエンジニアリングとは時間で積分したプログラミングである。
つまり、一瞬動けば良いコードを書く (プログラミング) のではなく持続可能なコードを書いて運用していくことがソフトウェアエンジニアリングの1要素であるということです。
逆に言えば、ある程度の時間動き続けることを要求されるようなコードでは、持続可能なコードを書いて運用していく (ソフトウェアエンジニアリング) ことが大事なわけです。
リファクタリングとは
その上で、リファクタリングとは何でしょうか。
Google本では訳注の部分に以下のような定義が書かれています。
コードの挙動を変えずに整理された内容に書き直すこと
要件が変わらないコードであればぐちゃぐちゃな実装でも良いかもしれません。しかし、それでは保守や新規機能の実装ができないため、持続可能なコードではないでしょう。
持続可能ではないコードを持続可能に戻す行為がリファクタリングと言えそうです。
リファクタリングする上で重要なこと
リファクタリングする上で重要なことは何でしょうか。コードの凝集度を上げたり結合度を下げたりすることでしょうか。
リファクタリングの定義に立ち返ると「コードの挙動を変えずに」という前提があることがわかります。前提が崩れてしまったら元も子もないですね。それを担保するにはどうしたら良いでしょうか。
つまり、最も重要なのは自動テストです。自動テストがあれば挙動が変わっていないかどうか判断できるからです。
そんなことはわかっているよと言われるかもしれませんが、それくらい広く知られている事実です。
そしてこの記事では、Google本で書かれている自動テストのノウハウをまとめていきます。
つまりこの記事は自動テストの記事だったんですね。自動テストについてのノウハウを共有することで、皆さんのリファクタリングに貢献するという流れです。
リファクタリングと自動テスト
ではリファクタリングをするという観点ではどの様な自動テストが書かれるべきでしょうか。
Google本では以下のような記述があります。
既存の挙動を保ちつつコードをリファクタリングする変更は、既存テストへの変更を (理想的には) 何も必要とすべきではない。
つまり、リファクタリングしても変更しなくて良いような自動テストこそが理想の自動テストであるわけです。
また、Google本には以下のような記述もあります。
リファクタリングの最中にテストの変更が必要な場合、その変更がシステムの挙動に影響しており純粋なリファクタリングではないか、もしくはそのテストが適切な抽象化レベルで書かれていなかったことを示している。
つまり、リファクタリングに伴って変更が必要なテストコードは適切ではないのです。
理想的な自動テストを書くためのノウハウ
ではリファクタリングしやすい理想的な自動テストを書くにはどうしたら良いのでしょうか。
これについてもGoogle本に書かれており、主に以下の2つです。
- 公開API経由でテストせよ
- 相互作用ではなく状態をテストせよ
それぞれについて詳細を述べます。
公開API経由でテストせよ
自動テストを書くとき、可能な限り公開API経由で行いましょう。ここで「公開API」とは「チームの外部に公開しているAPI」のことです。
何を「公開API」とするかは、ユニットテストの「ユニット」の範囲がどこなのか、という問題の核心ですが、Google本では「チームの外部に公開しているAPI」となっています。例えばチーム外にWEB APIを提供している場合、そのエンドポイント経由でしかテストしないということになります。これはかなり粒度としては大きく感じます。実際、Google本でも以下のような記述があります。
Googleでは、公開API経由のテストは実装詳細に対するテストより優れているという点を納得させるために、エンジニアを説得しなければならない場合があることをわかっている。エンジニアの気が進まないのは理解できる。(中略) しかし、システムに対して意味のある変更が起こった場合のみテストが失敗するよう担保するために行える対策として、最も重要なのは、公開APIに対するテストだ。
Googleがメリットとデメリットを取捨選択して、それでも公開API経由でのテストを選んでいることが見て取れると思います。ということは、公開API経由にせず、関数を直接呼んだりして単体テストを書こうとするならば、Googleが検討したベストプラクティスに勝るような特別な理由が必要で、熟考せずに書いて良いようなものではないのです。書いてしまうとリファクタリングをし辛くしてしまいます。
関数ごとに直接ホワイトボックステストを書くような状況もしばしば見られますが、そういったことは安易にしてはいけないのです。
相互作用ではなく状態をテストせよ
相互作用ではなく状態をテストするとはどういうことでしょうか。
例えばライブラリを使ってDB更新する関数についてテストするとき、「その関数内でDB更新するライブラリが呼ばれたか」をテストしたくなります。これが相互作用をテストするということです。
一方で、その関数を実行した後「DBを確認し、実際に更新されているか」をテストすることもできます。これが状態をテストするということです。
DB更新する際にどのライブラリを使うかは重要ではありません。リファクタリングでその関数が使うライブラリが変わったとしても、特に問題にはならないはずです。実際に関心があるのは「その関数を実行したら、DBが正しい状態になるのか」ですから。すなわち、状態をテストする方がリファクタリングしやすくなり、かつ実態に即してもいるわけです。
まとめ
この記事では「Googleのソフトウェアエンジニアリング」という本をリファクタリングという観点で切り取りました。そして、以下のような主張をしました。
- リファクタリングする上で最も重要なのは理想的な自動テストを書くことである。
- 理想的な自動テストを書く上で重要なのは以下の2点である。
- 公開API経由でテストする。
- 相互作用ではなく状態をテストする。
みなさんのテストライフ、ひいてはリファクタリングライフが少しでも幸せになれば幸いです。