日々常々

ふつうのプログラマがあたりまえにしたいこと。

「検算」と「選択」

AIと開発者の付き合い方について現時点で思ってることのメモ。

AIのパワフルさは今更いうまでもない。 そしてこの手の話には必ず「AIがあれば人間の開発者は不要」みたいな話がついてくる。 ある面では正しいが、そういう主張をする人は自分の答えを持っている(これ自体は悪いことではない)し、面倒なのでそういう主張からは距離を置いている。

将来的に「現時点で開発者の仕事と思われているもの」の多くがなくなるのはそうだと思う。 遠い将来ではなく、今この瞬間もなくなっている(本当に消滅したということではなく「なくなったと言って良い状態」になっていること)ことだろう。 そういう仕事がなくなるとかいう方向の話は別でやるとして。

現時点での開発者(およびAIを使用する多くの仕事)の仕事はなんなのか。 それを言葉にするなら「検算」と「選択」になるかなと言うのが本稿の趣旨。

AIに答えを求めるとそれっぽい答えが出てくる。 この答えが妥当なのかを調べる必要があるのがAIを使っていくなかで必要なことで、「コンピューターは正確だが柔軟性がない」というひと昔前のイメージの真逆である。 正確な振る舞いはコンピューターが得意とするところなので、「答えが合っているか」をコンピューターに調べさせるのは当然の活動。ハーネスとか言ってるのがこの辺なんだろかってふわっと思ってる。

検証を無邪気にAIに任せてるのがしばしば行われるが、これがイマイチなのは「正確でない答えを正確でない方法で検証しても正確でないまま」という単純な話になるので効果は限定的である。 「正確でない人間の仕事を正確でない人間がチェックする」というのはダブルチェックなどで行われることであるが、限界があるのは知っての通り。

つまり、AIの仕事の結果を検証する方法は正確である必要がある。「正確な方法を使った検証」を実行するのはAIでよい。この辺りの構造を認識したり設計したりできるかがAIを使うものに必要なスキルである。が、そういう組み合わせとかもAIができるようになっている。

では人間がやるべき仕事ってないのでは?となるのだが、ここで「検算」と「選択」という言葉を用いる。

検算は別の方法で確かめること。 その方法を選択するのが人間の仕事で、選ばれる方法は「(その答えに関わる)人間が扱えるもの」でなくてはいけない。 関わる人間が理解不能なロジックでしか検算できないものは「検算不能」として捨てるのが、人間の仕事じゃないかなって。 「検算」は「伝えたい相手に伝わる言葉での説明」である。伝えたい相手はステークホルダーと呼ばれる人かもしれないし、対象プロダクトをメンテしていく開発者かもしれない。

AIはしばしば人類には理解不能な答えを出してくる。 それはひとめ「間違っている」と映ることもあるが、よくよく確かめると正しいこともなくはない。 唯一無二の答えならそれを選ぶしかないが、多くの場合、答えは一つではない。 ゆえにいくら正しくとも「選ばない選択」をするのが、人間の仕事じゃないかなって。

人間に合った答えを選び、人間が扱える方法を選んで検算する。 人間に合っていない答えは選ばない、人間が扱えない方法での検算は採用しない。

って感じ。

昔からある構造

AI関連で色々と「今までとは違う!」って話はでるのだけど「構造は変わってないなぁ」と思うことはよくある。 今回の話も技術差のある現場では昔からよくある話だったりする。

技術差があると技術上位者による「他のメンバーが理解できないが正しい答え」はしばしば持ち込まれるが、それをそのままにするとスケールしなかったり、オーパーツ化して痛い目にあったりする。そこで「メンバーのスキルをあげる」のと「メンバーがわかる(あるいは将来的にわかるようになる)レベルの答えを選択する」などの方法を組み合わせて対応してきた。AIでも同様のことが必要ってだけの話。

なので別に新しい考え方でもなんでもない。

のだけど、「メンバーがわかるようになる」は答えを出したのが「言うても同じコンテキストに同席した人間」条件下だったのである程度期待してもよかった(酷なこともあるが)。しかしながらAIとなると「人間にAIと同じレベルになれ」を要求するのは流石に無理がある領域もある。その辺は差分かもしれない。

プロの投手で160km/hのボールを投げられる人がいて。同じプロの投手でトレーニングにより投げられる人がいるのを期待するのは自然。 でもピッチングマシーンが300km/hのボールを投げるからといって、人に投げられるようになるのを期待するのは違うよねって。

おまけ

「検算」は 京都総合法律事務所のメルマガ2026年4月号 から編集後記から。

決定の遅延と添木の設計

設計してますか?

ぶっちゃけ「設計 is 何」ってよくわからないんだけど、設計はしてます。 で、その設計なんだけど、今の何かを満たすものだけでなく未来に備える系があるなと思った。 私の中では「決定の遅延」と「添木」と呼んでるものです。

決定の遅延

決定を遅延させるために行う設計があります。

現実はいろんなことが起こり、未来がどうなるかはわかりません。 少し待てば情報が増えることがわかっていることはあります。でもどのような情報が来るかは分かりません。

「すべての情報が揃ってから動けばいい」という選択もあり、これはこれで一理あります。 ですがそのようにしていると期限に間に合わないこともしばしばあります。 時間は融通が効かないので、他で融通を効かせる必要があります。

そういう時に「決定を遅延させる設計」を使うことがあります。 決まっている部分を進めて、決まっていない部分を後回しにするという当たり前のような手段ですが、往々にして決まっていることと決まっていないことはないまぜになっています。

そういう時にいろんな整理をします。

  • 決まっていることと決まっていないことを分ける
  • 決まっていない度合いで濃淡をつける
  • 決まる時に取られうる選択肢を限定する
  • ...

かたい部分はハードパーツって呼んだりするもののこともある。

(直接ここで書いているものとイコールではないが。)

あの手この手で決め難いところを柔らかく包み込んで、決定を遅らせることが可能なようにする。 単なる先送りではなく、重要な部分を決めるための時間を稼ぎ、決まった時に憂いなく取り組めるようにする準備を整える。 そんな感じ。

添木

プロジェクトの期間中に決定が訪れるものであり、それが待つ価値のあるものであれば、可能な限り遅延させられるようにして進めます。 それはそれとして決定が訪れないものもそれなりにあります。

「将来こうなるかもしれない」と考えるのは重要ではあるものの、なっていないことを前提に作るのはYAGNIと言われるアンチパターンです。(作るコストがAIで劇的に下がってるので「作っちゃえばいいじゃん」って話もありはしますが、本稿の趣旨ではないのでそれはそれとしてください。) でも考えるのはいいことなんです。 前提にした対応はしない、でも方向づけはする。 そういう設計を「添木」と呼んでいます。

具体例はうまく伝わるかわかんないのですが、構造や名前は添木の側面を持ちます。

たとえばパッケージ構造はクラスの増え方を方向づけします。 新規プロダクトで序盤に用意したどこからか借りてきたパッケージ構造が邪魔くさく感じたことはないでしょうか。 それはおそらく借りてくる元のコンテキストでは「その方向に進んでほしくない」というものです。意図的であれ結果的であれ。 そのコンテキストを踏襲するなら「邪魔くさい」と感じたのは何かしらアーキテクチャのルールに違反しているからでしょう。

たとえば名前は他の名前に影響を与えます。 「添木」という言葉があれば「鉢」とか「枝」とかが連想されるものです。 システム開発での名前選びは、今後の他の名前に何かしらの制約を与えます。具体例?触ったことあるシステムのクラス名とかを思い返して、新しいクラスを追加しようとしたらそれに引っ張られますよね。それです。

「添木」は存在を認識されないなど、伝わらないこともしばしばです。 実際の植物でも添木を無視して成長してることもままあるものですし、まぁ仕方ない。 「そうはならんやろ」と思うような方向に成長することも稀によくある。

添木で物足りないなら周りをしっかり囲いましょう。 ニュアンスや文化で伝えるのではなく、規約やルールでしっかり固めましょう。 どちらがいいかなーというのもまちまちだと思うんだけど、AIを前提にすると添木は主張が弱すぎて役に立たない気もしなくはない。

speakerdeck.com

ルールや規約の話を出してしまったので「添木」がその系に読み取れなくもない気はするけど、あくまで「成長に方向性を与える」という設計パターンです。 「"今は決めない"と決めたもの」でも「成長するならこっち方向かなー」という意図を込める。そんな設計ってのもあるよなぁと。伝わるかなー。

補足: 仮止め

私の中で「仮止め」と呼んでいる設計パターンもあるので触れておく。

「仮止め」は家具を組み立てる時とかにネジを軽く締めておく、キツく締める前にやっておくあれです。 永続的ではなく一時的にその状態で固定して全体のバランスを調整したり、場合によってはやめたりするのだけど、とにかくそこを固定しないと他のことを考えようにも空中分解してしまうようなことはそれなりにあります。 「一旦これで決めて次に進めよう」という感じで決定はしているので、「決定の遅延」とは区別しています。

なおADRなどの軽量ドキュメントによる「取り消し可能な決定」も「仮止め」に近いけれど、そのままにする可能性があるのが「取り消し可能な決定」です。「仮止め」はその場所で固定するにしても「ちゃんと締める」は必須になる点が違ったりする。

しめ

どんな設計でも動けばいい。それはそう。動くのはいいこと。動かないのは話にならない。 とは言えコンピューターの発達に伴い、「動く」を満たした上で取れる選択肢も非常に広くなっている。 「こういう作りをしないとそもそも動くと言えない」のような制約は縮小し、超富豪的プログラミングも十分成立してしまう今日この頃。 そんな制約が少ない中での設計の考え方ってのもあるわけで。

決められるなら決めてしまえばいいことも多いのだけど、決めない方がいいこともそれなりにありはします。 そういう時に「決定を遅延」させられるようにし、さらに決めないまま方向性を主張する「添木」を配置する。 そんな感じの考え方をすることがなくはないなぁ、という話でした。

「いちばんめ」が大事でないならfindFirstよりfindAnyを使う

Javaの Stream の話です。

Stream は複数のものを扱いますが、結果が1つ(もしくは0 or 1個)だけ欲しい場合がよくあります。 こういう時に使用できるのが findFirstfindAny です。

jshell> Stream.of("hoge", "fuga", "piyo").findFirst()
$1 ==> Optional[hoge]

jshell> Stream.of("hoge", "fuga", "piyo").findAny()
$2 ==> Optional[hoge]

結果は同じで、先頭の "hoge" が返っています。findなんで Optional で包まれています。

実際の使用シーンはコレクションをIDとかで filter して、1件に絞られている場合とか、filter の条件を満たすものならどれでもいい場合です。 filterせずに findFirst() するのはあまりない気がする。そんなものは最初から Optional にしとけって感じだし。

こういう時に findFirst() が使われがちです。AIも findFirst() を生成してきます。ですがーー

「いちばんめ」に強い意志がないならば findAny() を選ぶ のがいいです。

実装している時は軽い気持ちで選ぶでしょうが、何か不具合があった時にこういうコードを見ると「これfirstでないとダメなのかな?」とか「このStreamの順序ってどうなってるんだっけ?」とか考えなきゃいけなくなります。まじめんどい。

脱線:2件以上あったらまずいときの話

「1件しか入っていないから findFirst() でいいや」としている場合。 2件以上入ってたら変なことになるけど目を瞑って findFirst() している場合。前者も大概だけど、後者のほうが罪深いです。

実は複数件入っているとかに起因する不具合がそれなりに見ます。わかりづらいです。 取得できて処理が続行できるので、不正な状態になっていることが握りつぶされている。2件目以降は消え去るので、本番でしか起こらないとかだとほんと調査しづらい。

もし「1件であること」が大事なのなら findFirst() を使ってはいけないです。もちろん findAny() もだめです。 ただ findAny() を選択している場合は「複数あったらどれが返ってもいいやつなんだよな」という確認が入っている感じがします。 FirstとAnyの違いに意識を向けられる場合は複数件にも意識が向く感じ。完璧じゃないけど結構な精度があるとは思う。 思考停止や「 findFirst() はグチグチ言われるから findAny() にする」とかだと効果ないですけどね。

やるなら一度 toList() とかでコレクションにして、サイズチェックしてから1件取り出しです。 ものによっては迂闊に toList() したら思った以上の件数があってOOMEとかも起こったりするので、 .limit(2).toList() とかですかね。

めんどくさいのはわかるけど、障害のほうがめんどくさいですし、お金もかかります。せっかくJavaつかってんだから実装で防げるものは防ぐのがいいです。

reduce で2件目以降がきたら例外にするとかもできます。ちゃっぴーに書いてもらったらこんな感じ。

T value = stream.reduce((a, b) -> {
    throw new IllegalStateException("More than one element");
}).orElseThrow(() -> new IllegalStateException("No element"));

これをいろんなとこにコピペするのはNGです。やるなら共通のユーティリティとかにしましょう。

……脱線のほうが長くね?

おまけ:StreamのAPI仕様

最後に一応、Javadocも見ておきましょう。サイドにメソッド一覧とか出るようになって使いやすくなりましたね。って今日気づきました。普段IDEでみるからね。。。

安定した結果が必要な場合は、かわりにfindFirst()を使用してください。

こんな書き方をされると「安定してる findFirst() の方を使った方が良さそう」と思っても仕方ないと思います。

でももし安定した結果が大事なのであれば、ストリームが順序を持つものであることを保証しなければなりません。 Stream自身に isOrdered() みたいな判定メソッドはないため、メソッド内でListから生成されたStreamであることが明らかであり、今後もそれが維持されることが前提になります。これは思っている以上に難しいことです。 そういうことにとらわれずにやるとすれば sorted() を読んで並び替えてから findFirst() でしょうか。 sorted() で実行時例外が出ないといいですねー。

おまけ:Codexさん

あなたがいま書いてきたコードですけど。

人間なら「わかってんのになんでやらんの」と言いたくなっちゃいますが、AIにそういう説教するのは「お金払って話を聞いてもらって相槌を打ってもらう」というアレですから、アレ。