🫶

世のため人のためにコードを書くとテスタブルになる

2024/01/28に公開

「テスタブルJavascript」を読んで学んだことを書いていきたい。洋書を翻訳したような書き方になっている点はご愛嬌。私は影響されやすい性格なのだ。

テスタブルなコードの概念

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write.”

Clean Code by Robert Martin

https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is

「プログラマーは書く時間の10倍読むことに時間を費やす。読みやすさを追求すると、より書きやすいコードにも繋がってくるから、読みやすいコードは遥かに重要だ。」

そんなふうにマーティン先生も言っているように、読みやすいコードは、重要なのだと思う。

読みやすいコードを書ければ、テスタブルになる。

また、テストがしやすいと、自信を持って関数に修正や機能追加を行うことができるため、チームメンバーの健全な精神を保つことにつながるという。

テスタブルなコードか、、書けている自信がないな、、

私が本書から学んだことは、「テスタブルとは人のために書かれたコードである」 ということだ。

そう直接明言されているわけではないが、コードの疎結合性やシンプルさを担保するための方法が繰り返し言及されていたのでそう感じた。

良い、すごく良いぞ。

良いコードは未来の開発者、ひいてはプロダクトを利用するユーザーのためになる。

お城を立てるためにレンガを積み上げているという逸話ほどではないが、自分がこれからコードを書く時、本著で学んだことを意識することで、小さいコードながら、すごい大きな何かに貢献することができるのだから。

人のために書かれたコード

人のためと言っても他人のためだけではなく、将来の自分が見て、メソッドがどのような振る舞いをするのかを短時間で理解できるコードが、人のために書かれたコードであると言える。

短いエンジニア人生だ、できるだけク◯コードを読む時間は無くしたいだろう。

クエリ系の関数名(fetchBooks, getAuthors)なのにモデルに変更を加えていたり、コマンド系の関数名(updateBookInfo, createAuthor)なのにDBからデータを取得していたりすると、コードの読み手が追わなければならないスコープが広がり、脳のリソースが過度に消費される。

私がそのようなコードに対峙したら行う防衛反応は大抵、「逃走」か「放置」だ。

fetchBooks(id: number)に2を渡したら、なぜかidが3の本が返ってくる。updateBookInfo(params: TBookInfo)に本を更新する情報を渡したらなぜか著者の年齢が更新された。(やれやれだぜ)

デバッグ時にこんな振る舞いをされたらたまったものじゃない。
だから、関数名は必ず「何に対して」「何をするか」がわかるものにするのだなと改めて実感した。

そう、「世のため人のためにコードを書こう」というメンタルモデルを形成するところから良いプログラマーへの道が切り開ける。そう思った。

じゃあ、「人が読みやすいコード = テスタブルコード」なのか?

半分Yesで半分Noだ。「人」の属性が分からない。「プリンシパル」が読みやすいコードと「ジュニア」はたまた「未経験」が読みやすいコードとでは、レベルが変わってくるだろう。

正直、「テスタブルJavascript」を読んでいて、答えは分からなかった。

自分がジュニアの時に読めばジュニアなりの「テスタブル」が見つかるし、プリンシパルの時(なれるかは知らんが)に読めばプリンシパルなりの「テスタブル」が見つかる。

「テスタブル」ってなんなんだ、、?と思った時にその都度見て、「ああ、これだった、、(感嘆)」になれば良いだろう。

テスタブルコードの条件

人が読みやすいコードを書く方法がいくつかあるらしい。

その前に、自分(ジュニアレベル)にとって読みやすいコードとは何か?を考えてみた。

条件分岐は1つ以内だと嬉しいし、関数内で呼び出している外部ライブラリは少ないほど辛みが減る。

「テスタブルJavascript」には下記のようにまとまっていた。

- シンプルであること

シンプルである

シンプルと一重に言っても、様々な工夫を凝らした結果シンプルになるみたいで、いくつか工夫が存在していた。

  • コード量が少ない
  • 結合度
  • 凝集度
  • 循環的複雑度
  • 外部依存が少ない
  • 副作用がない
  • Facadeとファンアウト

コードが少なければ少ないほど、有効範囲が狭まるため、保守がしやすい。そりゃそうだ。コードは0に近い方が良い。見る量が減るからだ。

とはいえ、コードが0のソフトウェアなんて存在しない。ノーコードだって、ノーコードにするためのコードが書かれている。エンドユーザーが書くコードが0なだけだ。

だから、関数に持たせる役割をできるだけ小さくするんだ。

小さいと何が良いって、再利用性が高まるんだ。

関数を小さくしていくと、関数Aから呼び出す外部の関数の数が増えそう。ここでいう「外部依存が少ない」とは、外部ライブラリへの依存、つまり、自分が自信を持ってテストをかけない範囲のことを指している。

テスタブルなコードを書く目的は、自信を持って前に進むため、いわゆる、既存の実装に自信を持って変更を加えるメンタルをつけるためにある。

そのため、副作用が無く、循環的複雑度の低い関数を書くことが喜ばれるんだと思う。

テスト方法

良いテストを書くために、最小単位が何かを自問自答してくれる誰かを自分に宿す。その誰かは歴史上の偉人(老子やNo.2 ベストジーニストでもなんでも)の聖人さによる。
本当に最小になってる?スコープは小さい?
ここでも複数の角度から良いテストを書く方法があった。多すぎないか?という気持ちが口から出そうになるのを抑える。

  • ポジティブテスト
  • ネガティブテスト
  • ユニットテスト
  • 結合テスト
  • パフォーマンステスト
  • 負荷テスト

テスト対象を最小に抑えるために、関数から外部ライブラリのインスタンスを作成している箇所にはモックやスタブ、あるいはテストダブルを使う。

モックはCQRSでいうC(コマンド)を、スタブはQ(クエリ)をテストするために用いられる。

どこまで網羅すれば良いテストと言えるのか。永遠の命題だろう。

かの有名な著書「テスト駆動開発」では、Fail Fastの精神で、失敗するテストから書くのをおすすめしている。

失敗するコードを書き、Test suitesがグリーンになるのをみて、重複箇所を消すためのリファクタリングを行う。

"You spend much more time reading code than writing code." by Bayrhammer Klaus
https://medium.com/swlh/why-fail-first-approach-like-test-driven-development-tdd-is-overlooked-f6edbc7ff608

ポジティブケースとネガティブケースを洗い出すのは大変だ。

だから、関数の有効範囲(スコープ)および役割は小さくするんだ。

小さくするのさ、それが全てだ

https://www.youtube.com/watch?v=Ap6vIMCDh4k

CQRS

先程から頻出しているCQRSについて触れておく。
コマンドと問い合わせは分離させた方がいい、という意味で、疎結合な関数を作成する際に非常に役にたつ。
Query: データがDBにあるかDB管理システムへ問い合わせる。管理システムは、「ありましたよ〜」と結果とともに返却してくれる。非常に素直で良いやつだ。
Command: データ更新のパラメータを管理システムに渡し、データを更新させる。管理システム、あいつはしごできだね。

コマンドの場合、モックを用意し、問い合わせの場合はスタブを用意する。

CQRSをNestjsで学ぶ

コマンドとクエリを分離させる。
一般的なAPIにはリクエストを受け付ける層と受け付けたデータを加工、利用する層とで分かれており、コントローラーとサービス層がそれに当たる。
小中規模のプロジェクトであればそれで大丈夫だが、大規模になると、FAT CONTROLLERになって保守性が下がり、開発効率が下がることにつながってしまう。

コマンドとクエリの責務を分離することにより、以下のメリットを受けることができる。

  • Separation of concerns. The model separates the read and write operations into separate models.
  • Scalability. The read and write operations can be scaled independently.
  • Flexibility. The model allows for the use of different data stores for read and write operations.
  • Performance. The model allows for the use of different data stores optimized for read and write operations.

https://docs.nestjs.com/recipes/cqrs

まとめ

本著を読み進めることで、リーダブルコードやCyberAgentの新卒向け資料等で出て来た、「良いコードを書くための手法」のコアに触れることができた気がする。
もちろん、良いコードを書くことは難しい。そもそも概念を定義するなんて畏れ多い。
だが、人のためを思ってコードを書く、それくらいは明日から実践できることなんじゃないかな。
愛を持ってコードを書こうと決めた日だった。

参考文献

https://www.amazon.co.jp/リーダブルコード-―より良いコードを書くためのシンプルで実践的なテクニック-Theory-practice-Boswell/dp/4873115655

https://www.oreilly.co.jp/books/9784873116358/

https://medium.com/swlh/why-fail-first-approach-like-test-driven-development-tdd-is-overlooked-f6edbc7ff608

https://note.com/cyberz_cto/n/n26f535d6c575

https://www.amazon.co.jp/テスト駆動開発-Kent-Beck/dp/4274217884/ref=sr_1_1?adgrpid=139211758767&gclid=CjwKCAiA8NKtBhBtEiwAq5aX2Kfm2Bn2Tkioemz5Qa2ujOx4Ic4L3UDc4FOWrSv7QDhhurHnbsrZUhoCC3IQAvD_BwE&hvadid=651693312418&hvdev=c&hvlocphy=1009307&hvnetw=g&hvqmt=e&hvrand=2543800304746635860&hvtargid=kwd-612958987109&hydadcr=26499_11748701&jp-ad-ap=0&keywords=テスト駆動開発&qid=1706403260&sr=8-1

Discussion