じぶん対策

日々学んだことをアウトプットして備忘録にしています。

「t-wadaさんが後世に残したい、実録レガシーコード改善」を聞いて得た気づきメモ

はじめに

個人的なエンジニアとしての強みとして、テストやレガシーコード改善を武器にしていきたいと考えて日々プログラミングと向き合っています。
そんな中、t-wadaさんが後世に残したい、実録レガシーコード改善というイベントの再放送に参加し、自身のテストに関する解像度がさらに上がりました。
また、テストの考え方を人に伝える上でとてもいい表現をされていたので、そのあたりをメモ的に残しておきます。
あくまで個人的に印象に残った部分をピックアップしているので、全体の流れや実際のコード例についてはぜひ元のスライドをご覧ください。

対象イベント

発表スライドはこちらです。

概要

実際のスマートスピーカースキルの開発を通して、非エンジニアが書いたコードを引き継ぎ、テストを用意しながらレガシーコードを改善していくという内容です。

ソフトウェア開発の3本柱

  • Version Control
  • Testing
  • Automation

この中の優先度について、Version Control > Automation > Testingという順番であるという話がありました。
理由は「早期に用意することでより多くのレバレッジが効く」からだそうです。テストを専門にしているt_wadaさんがテストより自動化を優先するというのが意外で印象に残っています。
とにかく手作業を減らすことで、単純作業の時間を安定して削減することができ、かつチーム開発の場合はさらに全員が利益を得られるので、より効果が高まります。

自動テスト

自動テストは安全にコードを変更するための前線基地となります。
最初は網羅性は不要で、能天気な正常系(Happy Path)のテストを書くことから始めるといいとのことでした。
この辺りは、自分自身レガシーなコードに対してどんなテストを用意するべきか悩むことも多かったので、新しい気づきが多かったです。
特に、リファクタリングへの耐性を高めるために、実装から間合いを取ったテストを書く、という表現はわかりやすく、他人に伝わりやすいなと感じました。

ここから個人的な考えをまとめておきます。
自分の書くコードではクラスごとにユニットテストを用意していることが多いです。

これは、API単位のテストのような実装から間合いを取ったテストと比べるとリファクタリングへの耐性は劣りますが、網羅のしやすさ、つまりはメンテナンスコストが抑えられると考えています。
ただ、テストがないレガシーなコードの場合はそもそもテストが書きづらかったり、テストを書くなかでより良い設計に気づくことが多いため、ある程度実装から間合いを取ったテストにしておくと、安全に、より良い設計に近づけるのかなと思いました。

私が自分で書くコードの場合はある程度自分やチームの中で設計の方針が共有できており、大幅なインターフェースの変更が少ないため、クラス単位のテストのメリットが大きくなるなと考えています。
反対に、慣れていない技術を使用しているものなど、より良い正解を模索しながらインターフェースを変更していく段階では、実装から間合いを取ったテストの方が良いのかなと思いました。
また、テストには開発者のためのテストと、品質を担保するためのテストに分かれると思っていて、ユニットテストは開発者のためのテストとしての役割が大きいと考えています。

以下、t_wadaさんのスライドでもレガシーコード改善ガイドから引用されている言葉ですが、テストを書く場合にはコードを変更が必要となり、例外は少ないです。

コードを変更するためにはテストを整備する必要がある。多くの場合、テストを整備するためには、コードを変更する必要がある。

テストとリファクタリング

スライドでは、テストを整備する準備ができたらTDDのサイクルを回しつつ、機能追加等を進めていくんですが、途中で設計への不安に気づく場面が出てきます。

リファクタリングしないと内部の質は上がらないのに、リファクタリングを行うという発想がそもそも無くなっちゃっている。

テストを書いて変更を安全に行うことはできるようになったものの、リファクタリングを行っていないため、コードの設計は改善されず、ただ単にテストコードが増えていくだけになっています。

テストでは品質は上がらないですよ。テストはあくまでも品質をあげるきっかけ。品質を上げるのはプログラミングです。これは大昔からそう。

この言葉はとても印象に残りました。自分の中でもテストがあるコードは漠然と品質が高いんじゃないかと思っていたんですが、よくよく考えると、コード自体に鎧を着せるようなイメージで、コードの中身自体は変わっていないんだから、そりゃ品質は変わらないよなあと思いました。

テストはリファクタリングのために必要ですが、テストだけで終わっていては品質は変わらないというのは肝に銘じておきたいです。

このあと、スライドではリファクタリングに話題が移っていくんですが、引用されている言葉が実際の場面によく当てはまるので、こちらもメモしておきます。

「あとでクリーンにすればいいよ。先に市場に出さなければ!」 開発者はそうやっていつもごまかす。だが、あとでクリーンにすることはない。 市場からのプレッシャーは止まらないからだ。「先に市場に出さなければ」ということは、後ろに競合他社が大勢いるということである。競合他社に追い抜かれないためには、これからも走り続けるしかない。
その結果、開発者はモードを切り替えることができない。次の機能、また次の機能、またまた次の機能を追加することになり、コードをクリーンにすることまで手が回らない。
そして、崩壊が始める。生産性がゼロに近づいていく。

いろんなプロダクトの話を聞いてもよくある話ですね。コードは基本的には書いた瞬間から将来の変更の際の生産性を下げてしまうので、常にリファクタリングによってそのコストを抑えていく必要があります。
これを日常的にできなかった場合は、プロダクトが崩壊するか、どこかで膨大な工数リファクタリングが必要となります。
リファクタリングに限らず、ソフトウェアに関する諸々の問題は早期に発見して早期に対応するのが最も効果がかからないというのは、よく言われています。
早期発見、かつ安全な変更のためにテストを書き、リファクタリングも日常的に行うことが大切です。

設計の改善

スライドでは、リファクタリングをExtractという手法を用いて進めていきます。

比較されるのはSproutと呼ばれる手法です。

  • Extract(書籍: リファクタリング)
    • 既存のコードにテストを書いて保護しながら、新しいコードを抽出していく
  • Sprout(書籍: レガシーコード改善ガイド)
    • 既存のコードにテストを書くことはあきらめるとしても、せめて新しく書くコードだけはテストを書きながら開発する

具体的な設計の改善は以下のような手順で進んでいきますが、ここでは割愛したいと思います。

  1. 詳細への依存を減らす
  2. 状態遷移もモデル自身が判断できるようにする
  3. 結合度をさらに減らし、抽象度の高い関数のみを公開する

結果として、さまざまな設計の原理原則を適用していくと、最終的にはクリーンアーキテクチャのような依存の方向性が整理されたコードに近づいていく、という点はクリーンアーキテクチャを他人に説明する際にもきちんと伝えないといけないなと思いました。

所感

今回のイベントを通して、テストや設計に対する考え方がさらに深まりました。
また、これらの思想を他人に伝える際の語彙がかなり増えたので、今後に活かせる発表でした。
参考文献として挙げられている書籍の大半はすでに読んでいるんですが、エンジニアになりたてのころに読んでちゃんと理解できていないものもありそうなので、改めて読み直すことで新しい気づきを得られそうです。