はじめに
私はとあるITベンダーで、データサイエンティストとして日々業務に携わっています。データサイエンティストと聞くと、機械学習や生成AIを扱う印象を持たれることが多いかもしれませんが、私はどちらかというと最適化エンジニアに分類されます。ここで「最適化エンジニア」とは、「数理最適化の技術を用いて顧客の問題を解決する職種」を指すものとします。
この業界では「データサイエンティストはコードが汚い」とよく耳にします。私自身も時折、コードの可読性や保守性に悩まされることがあります。
そこで本記事では、特に数理最適化エンジニアに向けて、どうすればコードを綺麗にできるのか、その理由は何かについて、私なりの考えを述べたいと思います。
総論
この記事のテーマはシンプルです。タイトル通り、「最適化エンジニアはテストコードを書こう」です。
最適化エンジニアは、データサイエンティストの延長線上にある職種であるためか、汚いコードを書きがちな傾向が続いているように感じます。特に、テストコードを書かない人が多いようです。本記事では、最適化エンジニアがテストコードを書くべき理由を(私見ではありますが)整理していきます。
最適化エンジニア(∈データサイエンティスト)はコードが汚い
データサイエンティストのコードが汚いという話題は各所で議論されており(例: https://ainow.ai/2021/08/31/257391/ )、その理由も多岐にわたります。ざっくりいうと「データサイエンティストの本業は研究や検証であり、システム開発ではない」というのが主な理由でしょう。
最適化エンジニアもその延長線上にあるためか、「汚いコードを書く癖」が直りにくい傾向があります。しかし、敬虔なエンジニア諸氏にはご理解いただけると思いますが、汚いコードは世界共通で撲滅すべきです。汚い、すなわち可読性/保守性/変更容易性に乏しいコードを残すことは、技術的負債を残すことになり、将来その返済にコストがかかることは既に各所で述べられてきた通りです1。
テストコードはコードを綺麗にする第一歩
では、どうすればコードを綺麗にできるのでしょうか?
名著、『リファクタリング』に以下の一節があります。
リファクタリングは価値のあるツールですが、単体で成り立つものではありません。なんらかの誤りは必ずあり、それらを指摘してくれるテストスイートが、適切なリファクタリングには必要です。
リファクタリング(≒コードを綺麗にすること)にはテストスイート(≒自動で実行できるテストコード)が必要だと述べています。
また、こちらも名著、『レガシーコード改善ガイド』には
テストコードのないプログラムはレガシーコードだ!
なんて帯にでかでかと書いているくらいです。
他にもコードの品質を上げる上でのテストコードの重要性を説く書籍・記事は枚挙にいとまがありません。
最適化エンジニアはテストコードを書こう
ではなぜ、データサイエンティストという大きなくくりではなく、最適化エンジニアがテストコードを書くべきなのでしょうか? 自分が「最適化エンジニア」だから、というのもありますが、ここではそれ以外で3つの理由を挙げます。1つ目は他人の受け売りですが、2,3つ目は個人的な意見と思っています。
理由1: 最適化プロジェクトは変更が頻繁に起こる
元大阪大学教授の梅谷先生は、こちらの記事で、
数理最適化は,下図に示すように,(1)最適化問題のモデリング,(2)アルゴリズムによる求解,(3)結果の分析・検証,(4)最適化問題とアルゴリズムの再検討という一連の手続きからなります.
(図は省略)
そもそも,現場の計画担当者が最適化問題のモデリングに必要な情報を自ら整理できるならばコンサルタントは不要なはずであり,むしろ,暫定的な計画案を提示することは現場の計画担当者からモデリングに必要な情報を引き出すための手段であると割り切ることが望ましいです.そのようなわけで,初めに示した(1)-(4)の手続きが1回で終わることはまれで,妥当な計画案が得られるまで繰返し再検討が必要になります.
と述べています。実際、最適化のプロジェクトに従事した経験からすると、お客さんからのヒアリングをもとに最適化モデルを組んで解を持って行っても「なんか違う」と言われることが多いと感じます。そんなわけで、最適化のモデル含め、コードベース全体が何度も何度も書き換える必要があるわけですね。
そんなとき、変更前の仕様が維持されているか、変更された部分が正しく反映されているかを確認する手段として、テストコードは非常に有用です。いわば、自動で動くリグレッションテストになるわけですね。
理由2: 最適化は比較的テストしやすい分野
機械学習のように、学習に使う入力データによって出力が変わるシステムのテストは難しいです2。テストを書く上でのアンチパターンとして、確率や状況によってテスト結果が変わるテストのことを fragile(壊れやすい)テストと呼びますが、まさにそれにあたります。
対して最適化の結果は、(ソルバーの機嫌にもよりますが)確定的な出力となります。少なくとも、正しく動作していれば制約条件を満たした解は出力されます(そうでなければ実行不可能です)ので、「どうあってほしいか」をテストコードとして記述するのは、機械学習ほど難しくないのではないのでしょうか。
上記の文章を読んで、「ある目的関数値が X以上になることはどうテストするんだ」という意見があるかと思います。そのようなことが発生するのであれば、それは制約条件として明示すべきことの可能性があります。「この状況ではこういう解が出るはず」というのを、テストで明示し、そのテストをパスするような実装(制約に含む or 前処理でなんとかする etc...)をしましょう。
理由3: モデルの仕様を「動くもの」として把握できる
最適化モデルは複数の制約条件が絡み合って解が導かれます。人間は複数のことを同時に考えることが苦手ですので、他のエンジニアや後続の開発者が「これモデルの仕様書ね。読めば理解できるから」と仕様書を渡されたとしても、なかなか理解に時間がかかるものです。
そこで、テストコードがあれば、具体的な動作を通じてモデルの意図を把握することが容易になります。
結論
以上、最適化エンジニアがテストコードを書くべき理由について述べてきました。結局のところ、最も重要なことは「他の誰かが読んでもわかりやすいコードにすること」だと思います。そのためにテストコードは有効なツールだと考えています。
補足
以下、想定される質問と、それに対する私なりの回答をまとめました。
「テストを書いてる暇なんてないんやが」
- テストを書く習慣のある人とない人でテストコストの見積もりが大きく異なる。
- 理由は、テストありきの実装は比較的簡単に質の高いテストを書けるのに対し、テスト無視の実装にテストを書くのは至難の技だから。
と述べられております。「(設計がテストしやすいようにできてないので、テストを書くのに時間がかかる。なので)テストを書く暇がない」となっているのかなと思います。
私の提案は、「TDD(テスト駆動開発)を実践し、慣れよう」です。上述の記事でも述べられていますが、テストしやすい設計のためにはテストファーストを実践することが肝要です。TDDはテストファーストの実践方法の1つです。またそれだけでなく、本記事で述べたように「自動で動くリグレッションテスト」も実現できます。
みんなTDDやりましょう(もちろんすべての状況に効く『銀の弾丸』ではないですが)。
「テストの網羅性はどうやって担保するの?」
最適化を用いたシステムのテストにおいて、この質問に対する答えは私も模索中です。前述のように人間は複数のことを同時に考えられないので、「こういうデータが来たらこの制約とあの制約がコンフリクトして実行不可能になるな」というのは、頭の中だけで網羅できるものではないと思います。システムで実際に使用するようなデータを何パターンか用意して、それぞれで実行してみるしかないのかなと...。
皆様は、現場でどのようなテストを実施しておりますか?
-
Martin Fowler氏によれば、内部品質の低いコードベースは、短期的には内部品質の高いものより早く開発できるが、1カ月で逆転する、とのことです ↩
-
機械学習システムのテスト手法として、『AIソフトウェアのテスト――答のない答え合わせ [4つの手法]』でいくつか述べられています。 ↩