これは何?
上記のアドベントカレンダー1日目の記事です。
最近私は関数型言語であるHaskellにハマっており、生成AIと相性の良い概念ではないかと考えています。
関数型言語を触ったことの無い方に対して、初心者目線で関数型言語の良さをアピールできたら嬉しいです。
Cline、vibe codingの台頭
2025年の年明けすぐに業界に激震が走った。
Clineの登場である。
私を含め、たくさんのエンジニアがGitHub CopilotやChatGPTなどの生成AIツールをコーディングに活用していたが、それはあくまで人間の補助としての使い方であった。
Clineは生成AIに命令して放っておくだけで完全自動で成果物ができてしまうというのが圧倒的であった。
人々は生成AIを使ってなんとなくものが作れるということに狂喜し、世は正にvibe coding時代!
vibe codingが流行るが自分はvibe codingの恩恵を受けきれずにいた
ツヨツヨエンジニア達がvibe codingに熱狂する中、自分はなんとなく乗り切れていない感じがしていた。
Clineに賭けて失う金銭的コストに日和ってしまっていた
まずは、価格面だ。
Clineを使うにはかなりお金がかかる。
しかもお金をかけてもゴミができあがる可能性もある。
私はかなりの貧乏性なのもあり、Clineをちょこちょこ試すことしかできず、使い倒すことはできなかった。
価格面の問題は意外と早く解決された
Clineに比べて若干火力が足りないものの、GitHub Copilot ChatにもAGENT MODEが追加され、GitHub Copilotのプラン内で試せるようになった。
(本格的な価格面の問題の解消は、Claude CodeのMax Planとccusageの登場だと思うが)
生成AIの作成したコードを素早くレビューできなかった
生成AIは人間と比較して、大量のコードをものすごい速度で生成できる。
コードレビューに時間がかかることで、生成AIを使っても生産性が大きく上がらず、私自信がボトルネックになっているのを感じていた。
また、動かないコードが混じっていたり、意図が不明なコードが生成されることもあり、生成AIの作成したコードをレビューすることにストレスを感じるようになった。
TDDと出会い、生成AIの作成したコードレビュー負担が減った
そんな折、twitterで強そうな人たちからTDDという言葉を聞くことが増えたように感じた。
そこで、Kent Beck氏の提唱したTDD(テスト駆動開発)と出会った。
この書籍を読み、1つだけ自動テストを先に書いてからそれを通すコードを書き、リファクタリングをするTDDというスタイルを取り入れて、生成AIを使えば、
- テストが正しい前提で、テストが通っていれば生成AIの作成したコードが最低限意図通りに動くのを確認できる
- TDDのサイクルで開発することでAIのコードをレビューする単位を小さくできる
という2つのメリットを享受できるとわかった。
テストを生成AIと一緒に書いて、それを通す最初の実装部分を生成AIに書いてもらい、その後必要ならばリファクタリングをするというスタイルで開発をすると一度に大量のレビューをする機会も減り、生成AIもテストを通すことをゴールにコードを生成するので大きく間違えにくいというメリットが得られると感じた。
自分でもQiitaBashのイベントでTDDのメリットをLTしたり、Kent BeckからTDDのテストを生成AIに書かせるかどうかは、お前がいろいろ試して考えるんだという激励の言葉をいただいたのは良い思い出である。
TDDのために良いテストが書きたくなり、設計に興味がでてくる
TDDを知った年にKent Beckに会えた私はすっかりTDD信者になり、テストがないシステムに対しても後からテストを足しつつ、TDDできる環境にしようと試みるようになった。
TDDの場合、古典学派的なふるまいベースのテストを書くのが良いのはわかったのだが、自分の環境で自動テストを追加しようとすると統合テストやE2Eテストだらけになってしまいそうな予感がした。
単体テストのしやすさからプロダクション・コードの質を評価することは確かにできるのですが、その評価は一方向にしか向いていません。つまり、高い精度で検出できるのはプロダクション・コードの質の悪さだけになります。(単体テストの考え方/使い方より)
単体テストの数を増やし、時間のかかる統合テストやE2Eテストはなるべく少なくすることで自動テストにかかる時間を短くするのが良いとされているため、できるだけ単体テストが多くなるようなコードを書く必要がある。
この頃t-wadaさんの開発生産性カンファレンスでの発表を聞き、Is High Quality Software Worth the Costで言われている損益分岐点が生成AIの登場で早まった話を聞いたこともあり、良い設計に興味を持つようになった。
ドメインロジックを凝集し、単体テストを書きやすくする
しかし、どうすればふるまいベースの自動テストを書きつつ、単体テストの数を増やせるような設計になるのかという疑問が私の中に残った。
そんな中で良いコード/悪いコードで学ぶ設計入門を読んでいるとドメインモデルの完全性という言葉がでてきた。
これを深堀していくうちに、単体テストの考え方/使い方の著者である、Vladimir Khorikov氏の以下のブログにたどり着いた。
このブログにより、
- ドメインモデルの完全性とはアプリケーションドメインロジックがドメインモデルに全て含まれていること
- ドメインモデルが純粋とは、ドメインモデルがプロセス外依存を持たないこと
- 完全で純粋なドメインモデルを作るとパフォーマンスが悪くなること
- このトリレンマを解決する際には、プロセス外依存をコントローラ層に切り出し、ドメインモデルの完全性を犠牲にするのが妥当な選択であること
を学んだ。
ドメイン純粋かつ、なるべくドメインモデルが完全になるようなコードつまり、プロセス外依存がなく、ドメインロジックを一箇所に集まったコードを書くのが良いということだ。
ドメインロジックが一箇所に集まっており、プロセス外依存(副作用)がなければそのふるまいテストは単体テストで実施できる。
副作用を起こす部分を分離し、単体テストを書きやすくするための関数型プログラミング
このあたりで、単体テストの考え方/使い方にテストを重要な部分に集中させるために、関数型プログラミングの話を紹介していたのを思い出した。
関数型プログラミング とは、数学的関数を用いたプログラミングのことです。
そして、この数学的関数(mathematical function)は 純粋関数(pure function)とも呼ばれ、隠れた入力や出力がない関数(もしくはメソッド)のことになります。つまり、数学的関数のすべての入力と出力はメソッド名、引数、戻り値の型で構成される メソッド・シグネチャ に明示されることになります。そのため、数学的関数は受け取った入力値が同じであれば何度呼び出されても同じ結果を返すことになります。(単体テストの考え方/使い方より)
もちろん、関数型プログラミングを使うことで、全く副作用のないコードを書くことができるわけではない。
ビジネスロジックを扱うコードと副作用を起こすコードを分離することが関数プログラミングの目標としていることである。
関数型プログラミングで実装されたコードは副作用を起こす部分が明確に分離されており、純粋関数の部分は特にテストが書きやすくなる。
このため、TDDで作成するテストコードのうち、単体テストを増やすという目標を達成するには純粋関数でコードが記述されているほうがベターであるとわかった。
関数型プログラミングの視点で生成AIの書いたコードをレビューできるようになりたいこと、自分が10月から育休に入るため、技術的な投資をする時間があることから、時間をとって関数型プログラミングにコミットすることにした。
じゃあ、なぜ、Haskellを選んだの?
2日目に記載しているので是非。