on
オブジェクト指向を「用語の定義」として捉え直す ― 私的OOP解釈
⚠️ 免責: この記事は生成AIにアイデアの添削, 文章の作成を手伝ってもらい, akaがレビューしてます.
どうもakaです.
前の記事で「OOP, TDD, DDDについてはまた別記事で作ろうと思います.」と書いていたので, OOPとDDDについて書きました. ただし, 教科書的なOOP解説ではなく, 私がOOPについて考えてきたことの言語化, つまり 私的解釈 です.
想定読者はOOP経験者です. DDD/TDDに触れたことがあるとより楽しめると思います. この記事のゴールは, 「オブジェクト=用語」という軸でOOPを説明できるようにする こと. 読み終わったとき, 「OOPってそういう見方もできるのか」と思ってもらえたら嬉しいです. では, 以下本文です.
OOPとの出会い ~ aka.encounter(oop) ~
私は良いプログラムの書き方に興味があり, getter/setter, デザインパターン, そこからOOPという概念に触れた. 「getterとsetterを書きましょう」「Strategyパターンはこう使います」――そういった話から入り, 「それらはOOPの1パターンである」という主張を見て, OOPとは何なのだろうと興味を持ち始めたのだ. (ちなみに今ではgetter/setterがアンチパターンになりうることは広く知られていると思いたい.) 実際にOOPを学んでいくうちに, こうした「パターンを覚えて適用する」文化はOOPの解釈をむしろ悪化させていることに気づいた. パターンを覚えて適用するだけではOOPではないのだ. そしてOOPの思想に触れ, DDDを学び, ようやく私なりの解釈が持てるようになった. この記事では, OOPやDDDにまつわる概念・実装・ノウハウを踏まえて, 私なりの解釈を述べていく.
プログラミングとは用語を定義することである ~ aka.define(programming) ~
いきなり結論めいたことを言う.
OOPとかDDDとか色々あるけど, 結局プログラミングって用語を定義することだ.
何を当たり前のことを言っているんだと思うだろう. プログラミング 言語 なんだからそりゃそうだろう, と. しかし, その"言語"としての性質 ―― 用語を定義し, 用語を使って世界を記述するという行為 ―― を意識する機会は意外と少ないのではないだろうか.
現実世界では, 言葉の意味は曖昧で, 文脈によって揺れる. でもプログラムの中では, 定義した用語が, その定義通りに振る舞う. (もちろん, そう振る舞うように実装するのは自分だけど.) 自分が名前をつけたものが, 自分が決めた通りに動く世界. この性質がプログラミングの面白さの根底にあると思っている.
具体的に見てみよう.
// ECサイトの注文管理システムにおいて
class Order {
private final List<OrderLine> lines;
private final Money totalAmount;
public void confirm() { ... }
public void cancel() { ... }
}
この Order は「注文」というラベルを貼っただけではない. 「このシステムにおいて"注文"とは, 明細を持ち, 合計金額を持ち, 確定でき, キャンセルできるものである」と 定義 している. どんなプロパティを持つか, どんな振る舞いをするかのすべてが, 「注文」という用語の定義を構成する. そして, この定義の通りに動く.
このように, プログラミングにおける用語の定義にはいくつかの種類がある.
- クラス/型 は用語の定義だ. 名詞にあたる. 「ECサイトにおいて"注文"とは何か」を定義している.
- メソッド も用語の定義だ. 動詞・関係・ルールにあたる. 「"確定する"とはどういう行為か」を定義している.
- インターフェース は用語の契約だ. 「この用語はこういう振る舞いができることを保証する」という約束を定義している.
- そして 名前(命名) は設計そのものだ. 何を用語として切り出し, 何と名づけるか. それが設計判断の中核にある.
私にとっての「オブジェクト」 ~ aka.recognize(object) ~
人間の認識をコードに固定したもの
では, OOPの文脈でこれを考えてみる.
オブジェクトとは, 人間の認識をコード上の用語として固定したもの だと私は捉えている.
ここで重要なのは, 「実世界をそのまま写している」のではないということだ. OOPが写しているのは 実世界を認識した結果 だ. 人間が現実を見て, 「ここに境界がある」「これは一つのまとまりだ」と認識した, その認識の構造をコードに落としている.
「OOPは実世界の写像」という説明は, おそらく教育上の比喩として始まったものが, いつしか原理として定着してしまったんだと思う. 比喩としては有効だ. 初学者が「車クラス」「犬クラス」で概念を掴むのは悪くない. でもそのまま「実世界を忠実にモデリングすればよい」と思い込むと, 設計がおかしくなる.
たとえば, ペットサロンの予約管理システムを作るとする.
// ペットサロン予約システムにおける「犬」の定義
class Dog {
private final Name name;
private final Owner owner;
private final Breed breed;
}
この Dog は生物学的な犬の完全なモデルではない. 「ペットサロンの予約システムにとって犬とは何か」―― 名前があり, 飼い主がいて, 犬種がある. それがこのシステムにおける「犬」という用語の定義だ. 内臓の構造も遺伝子情報も bark() もない. それはこのシステムの関心事ではないから.
つまり, オブジェクトは実在物のコピーではなく, 目的のために切り出した認識単位 だ.
例:「ユーザーが車を動かす」を"用語"として書く ~ aka.example(car) ~
もう少し具体的に, 「用語を定義する」感覚を体験してもらいたい.
「ユーザーが車を動かす」―― これをコードにするとき, 私たちは何をしているのか.
まず, 用語を定義する
車とは何か. レースゲームにおいて, 車とは, 「加速できて, ブレーキできて, ハンドルを切れるもの」だ. まずはこの契約をインターフェースで定義する.
// レースゲームにおける「車」の契約
interface Car {
void accelerate();
void brake();
void steer(Direction direction);
}
車検の有効期限も, 保険の契約内容も, 所有者の名義も定義しない. レースゲームにとってそれらは関心事ではないからだ. スポーツカーでもゴーカートでも, 加速とブレーキとステアリングができれば「車」だ.
プレイヤーとは何か. このゲームにおいて, プレイヤーとは, 車を操作してレースを走るものである.
// レースゲームにおける「プレイヤー」の定義
class Player {
private final PlayerId id;
private Car car;
public void drive(Direction direction) {
car.accelerate();
car.steer(direction);
}
}
操作するとは何か. drive? control? operate? ここで何を選ぶかが, 用語の定義そのものだ. レースゲームなので drive にした. カーシェアリングサービスなら reserve かもしれない. 自動運転シミュレータなら navigate かもしれない.
用語間の関係が成立すること
ここで大事なのは, 車の内部構造ではない. プレイヤーが車を操作できる という, 用語間の関係が成立していることだ.
Player は Car の内部実装を知らなくていい. 加速時に物理演算がどう動いているか, ステアリングの内部で何が起きているかを知る必要はない. 必要なのは, 「車は accelerate と brake と steer ができる」という用語の契約だけだ.
これは人間と車の関係そのものだ. 人間は車のエンジンの仕組みを知らなくても, 車を動かせる. 必要なのはハンドルとアクセルとブレーキの使い方だけだ. この「詳細を知らずに使える」という性質 ―― いわゆる 抽象化 の効能が, 用語を定義するだけで自然に現れてくる.
「振る舞い」とは何か ~ aka.define(behavior) ~
命令ではなく, 意味のある行為
ここまでの話で, 「用語を定義する」「用語間の関係が成立する」と書いてきた. では, 用語同士の関係を成立させているものは何か. それが 振る舞い(behavior) だ.
振る舞いは「命令」ではない. 「この値をセットしろ」は命令だ. 「注文を確定する」は意味のある行為だ. この違いは小さいようで, 設計に大きく影響する.
振る舞いがあるからこそ, 不変条件やルールや制約を オブジェクトの内側に閉じ込められる.
// 図書館の貸出管理システムにおける「貸出」
class Loan {
private final Book book;
private final Member member;
private final LocalDate dueDate;
private boolean returned;
// 「返却する」という意味のある行為
public void returnBook() {
if (returned) {
throw new AlreadyReturnedException();
}
// 延滞チェック, 罰金計算などもここに閉じる
this.returned = true;
}
}
returned = true を直接セットするのではなく, returnBook() という振る舞いを通すことで, 「二重返却はできない」「延滞チェックを必ず行う」というルールが用語の定義の中に閉じ込められる.
振る舞い中心で設計すると, 主語が変わる. データが主語なら「この値を変更する」になる. 意味が主語なら「この行為を実行する」になる. コードを読んだときに「このオブジェクトは何ができるか」が見えてくるか, 「このオブジェクトはどんなデータを持っているか」が見えてくるか. この差は大きい.
ここで登場するAlan Kayの「メッセージ」 ~ aka.realize(message) ~
ここで, Alan Kayの「メッセージ」の話をしたい.
Alan Kayは「OOPにとって大事なのはメッセージングだ」と言った. 長い間, 私はこれを「オブジェクト間の通信プロトコルの話だろう」と捉えていた. だからピンと来なかった.
でも, この記事を書くために「振る舞い」について改めて考えていたとき, ふと繋がった.
Kayの「メッセージ」を私の言葉で言い換えると, こうなる. 相手の内部を知らずに, 意味のある関わり方を委ねること.
order.confirm() と書くとき, 私たちは注文の内部実装を知らない. 知らないけれど, 「確定する」という意味のある行為を委ねている. これは単なるメソッド呼び出しではなく, 意味のあるやり取りだ.
しかも, メッセージは「命令」だけではない. 私なりに整理すると, こんなバリエーションがある.
- 何かをしてくれ:
order.confirm(),order.cancel() - 何かを教えてくれ:
order.totalAmount() - 判断してくれ:
order.canBeCancelled() - 手順に従ってくれ:
iterator.hasNext()→iterator.next()
深入りはしない. Kayの思想を正確に解釈できている自信はないし, 私はSmalltalkをちゃんと書いたこともない. ただ, 私が「振る舞い」と呼んでいたものが, Kayの「メッセージ」に該当していた ということに, この考察をしている中で気づいた. 使っていた言葉は違ったけれど, やっていたことは同じだったのかもしれない.
なのに, なぜOOPは歪んだのか ~ aka.analyze(distortion) ~
OOPは「用語を定義する力」を持つ強力なパラダイムだ. しかし, OOPには 何のための用語かを区別する制約がなかった.
用語を定義する力はある. でも, 「その用語はドメインのためか? DBのためか? フレームワークのためか?」を区別する仕組みがOOP自体にはなかった. 力はあるが, 向きがない. だから歪んだ.
歪みを生む要因
分業による「意味」の分断. 画面チーム, APIチーム, DBチームで分業すると, それぞれが自分の都合で用語を定義し始める. 同じ「注文」でも, 画面側の OrderForm, API側の OrderRequest, DB側の order_table がそれぞれ独立して存在し, ドメインとしての「注文とは何か」が誰のものでもなくなる.
ORM・JavaBeans規約による"プロパティ"中心化. ORMは「テーブルの列=クラスのプロパティ」を前提にする. JavaBeans規約はgetter/setterの存在を要求する. フレームワークに従っているだけで, 自然と「プロパティの集合」としてクラスが設計される. 用語の定義ではなく, データ構造の写像になる.
短期最適の「とりあえずデータ+サービス」. 納期が迫ると, 「とりあえずデータクラスを作って, ロジックはサービスクラスに書こう」となりがちだ. 短期的にはこれで動く. でも, 用語としての振る舞いがオブジェクトから剥がれて, 貧血ドメインモデルが出来上がる.
パターンの神格化. デザインパターンは本来「この問題にはこういう構造が有効」という思考の補助輪だ. でもいつしか「正解の型」になった. 「ここはStrategyパターンで」「ここはObserverで」と, パターンありきで設計が始まるようになった.
setter文化を私の言葉で説明する ~ aka.explain(setter) ~
用語の崩壊
ここまでの話を踏まえて, setter文化の問題を言語化してみる.
setterは 「意味のある行為」ではなく「内部状態の直接操作」 になりやすい. そこに問題がある.
// メールアドレスを「セットする」
user.setEmail("new@example.com");
// メールアドレスを「変更する」
user.changeEmail("new@example.com");
setEmail は内部の email フィールドを上書きする操作だ. 一方, changeEmail は「メールアドレスを変更する」という意味のある行為だ. 命名を変えただけじゃないかと思うかもしれない. でも, changeEmail の中には「変更前のメールに通知を送る」「変更履歴を残す」「バリデーションを行う」といったルールが自然に入り込む余地がある. setEmail にそういう期待は生まれにくい.
setterが増えると, 用語の中心が「振る舞い」から「データ」へ戻ってしまう. オブジェクトが「何ができるか」ではなく「何を持っているか」で定義されるようになる. これは, 用語の崩壊だ.
setterが流行ったのは, OOPが理解されたからではなく, "理解しなくても使える形"にされたからだ. getter/setterは, OOPの力を使うための最低限の定型だった. 定型があれば考えなくていい. フレームワークが要求し, IDEが生成し, みんなが使う. 用語を定義するという本質を理解しなくても, とりあえずコードは書ける.
ただし, setterが悪なのではない
個人的にはsetterは嫌いだ. オブジェクトの不変性が壊れるから. ただ, ここで注意を入れておくと, setterそのものが絶対悪かと言われると, そうとも言い切れない. どこで使うかの問題 だ.
- DTO(Data Transfer Object): API境界でデータを受け渡すための入れ物. setterがあって構わない.
- ViewModel: 画面表示用のデータ構造. setterがあっても問題ない.
- ORM Entity: フレームワークの制約でsetterが必要な場合もある.
これらは「用語を定義する」場所ではない. データの運搬・表示のための構造だ. 問題は, ドメインモデル ―― 本来「用語を定義する」場所 ―― にまでsetter文化が侵食すること だ. 境界を区別すればよい. すべてのクラスが用語の定義である必要はない.
DDDは, OOPの"向き"を取り戻すための再規定 ~ aka.connect(ddd) ~
力と向き
ここでDDDの話をしたい. OOPとDDDの関係を, 私はこう理解している.
OOPは用語を定義する「能力」を与え, DDDは「どの用語を, 誰のために定義すべきか」という問いを定めた.
OOPは「用語を定義し, その用語に振る舞いを持たせ, 用語同士の関係で世界を記述する」という強力な能力を与えた. しかし, 何を用語として定義すべきかについてはガイドを持たなかった. だから歪んだ.
DDDは, その能力の向き先を ドメイン(業務領域) に定めた. 「ユビキタス言語」という考え方は, まさにチームが共有する用語を定義し, コードに反映させるプラクティスだ.
OOP: 用語を定義する「力」
↓ (向きが定まらない → 歪む)
DDD: 用語の向きを「ドメイン」に定める
↓
結果: ドメインの認識が, 適切な用語としてコードに固定される
DDDがなくてもOOPは使える. でもDDDがあることで, OOPの力が本来発揮されるべき方向に整えられる. 私がDDDに惹かれた理由もここにある.
DDDとの差別化
ここで「"プログラミングは用語を定義すること"って, DDDのユビキタス言語と同じことを言っているのでは?」と思った人もいるかもしれない.
確かに重なる部分はある. だが, DDDは「ドメイン専門家とコードが同じ言葉を使うべき」という 実践的方法論 だ. 私がここで言いたいのはもう一段手前の話で, プログラミングという行為そのものが用語を定義する行為である という認識の話だ. DDDはその認識の上に, 「では誰の用語を採用すべきか」「文脈(Bounded Context)でモデルをどう切るか」という実践的回答を与えたもの, と私は位置づけている.
TDD→BDDも同じ構造
ちなみに, TDD(テスト駆動開発)→ BDD(振る舞い駆動開発)の変遷も, OOP→DDDと同じ「力 → 向き」の構造だと思っている.
- TDD は「テストを先に書く」という力を与えた
- BDD は「何の振る舞いをテストすべきか」という向きを定めた
OOPやTDDは"力"を与えた技術であり, DDDやBDDはその力の"向き"を定義した思想だ. 力だけでは歪む. 向きがあって初めて, 力が正しく発揮される.
私的まとめ ~ aka.summarize(oop) ~
ここまでの話をまとめる.
OOPは「実在の写像」ではない.
私にとってOOPとは:
人間の認識を"用語"として定義し, 用語同士の関係(振る舞い)で意味を成立させ, 詳細を隠して使えるようにする技術.
そして, OOPの歪みとは:
用語が崩れ, データ操作が主語になること.
setterが蔓延するのも, 貧血ドメインモデルが生まれるのも, パターンだけが一人歩きするのも, 根っこは同じだ. 「用語を定義する」という本質から離れ, 「データを操作する」が主語になったとき, OOPの力は失われる.
逆に, 用語を丁寧に定義し, 振る舞いを中心に設計し, 用語間の関係で世界を記述できれば ―― OOPは今でも強力な道具だと思う. そしてそれは多分, DDDがやろうとしていることそのものだ.
補足 ~ aka.appendix(oop) ~
ここからは補足. メインの主張から少し離れて, 周辺の話をいくつか.
教科書的OOP四原則について
カプセル化, 継承, ポリモーフィズム, 抽象化. OOPといえばこの4つが語られることが多い.
これらは重要な概念だが, 私の解釈では 手段 であって 本質 ではない. 本質は「用語を定義すること」であり, 四原則はその定義をうまくやるための道具だ.
- カプセル化: 用語の内部定義を隠し, 外部には振る舞いだけを見せる → 用語の境界を守る手段
- 継承: 既存の用語の定義を引き継いで拡張する → 用語の再利用の手段
- ポリモーフィズム: 異なる用語を同じインターフェースで扱う → 用語の抽象化の手段
- 抽象化: 詳細を隠して本質を取り出す → 用語を適切な粒度で定義する手段
そしてこれらを支えるのが インターフェース だ. インターフェースは「この用語はこういう振る舞いができることを保証する」という 契約の定義 であり, 用語同士が関係を結ぶための共通言語になる.
// 「支払い手段」という契約の定義
interface PaymentMethod {
PaymentResult pay(Money amount);
}
// クレジットカードは「支払い手段」である
class CreditCard implements PaymentMethod {
public PaymentResult pay(Money amount) { ... }
}
// 銀行振込も「支払い手段」である
class BankTransfer implements PaymentMethod {
public PaymentResult pay(Money amount) { ... }
}
PaymentMethod というインターフェースは, 「支払い手段とは, 金額を受け取って支払い結果を返せるものである」という契約を定義している. Order は CreditCard か BankTransfer かを知る必要がない. 「支払い手段」という用語の契約だけを信頼して, pay() を呼べばいい. ポリモーフィズムもカプセル化も, このインターフェースという契約があって初めて成立する.
逆に言えば, これらの手段だけを学んでも, 「何を用語として定義すべきか」がわからなければ, OOPの力を活かせない.
なぜ「用語を付けること」が強いのか
「プログラミングとは用語を定義すること」が正しいとして, なぜそれが強力なのか. 4つの観点で整理する.
1. 認知の圧縮
人間の短期記憶には限界がある. 複雑な概念に名前を付けることで, その詳細を一旦忘れて, 名前だけで思考を進められる. OrderConfirmationService という名前があれば, その中身を覚えていなくても「注文確定に関する処理」として扱える.
2. 共有の基盤
チームで開発するとき, 用語が定義されていれば, 同じ言葉で同じ概念を指せる. 「あの処理」「あれ」ではなく, 明確な名前で会話できる. DDDのユビキタス言語はまさにこの価値を最大化するプラクティスだ.
3. 推論の土台
適切に定義された用語は, 推論を可能にする. 「Order には confirm() がある. では cancel() もあるべきでは?」「OrderLine があるなら, Order は複数の明細を持つのだな」と, 用語の構造から次の設計が推論できる.
4. 変更耐性
用語が適切に定義されていれば, 変更の影響範囲が予測しやすくなる. 「注文確定のロジックを変えたい」→「Order#confirm() を見ればいい」と, 用語が変更のガイドになる.
チームと用語
チーム開発における用語について少しだけ.
良い設計はチームによって異なる. あるチームにとって最適な用語体系が, 別のチームでも最適とは限らない. しかし, 一つだけ言えることがある.
業界で一般的に使われている語彙に寄せること は, チームの持続性への投資だ.
独自の用語体系は, そのチームの文脈を深く理解した人にとっては効率的かもしれない. でも, 新しいメンバーが入ったとき, メンバーが抜けたとき, その用語体系が属人的であるほどチームは脆くなる. 業界の共通語彙をベースにすることで, チームの入れ替わりに対する耐性が上がる.
パラダイムを超えた原理
最後に, ここまでOOPを軸に語ってきたが, 「プログラミングとは用語を定義すること」はOOP固有の原理ではない.
関数型プログラミングでも, 関数名や型名は用語の定義だ. Haskellの型クラスも, Rustのトレイトも, 「この概念はこういう性質を持つ」という定義を書いている. どのパラダイムであっても, プログラマーは用語を定義している.
だから「"用語を定義する"って, OOPじゃなくても同じでは?」と思うかもしれない. その通りだ. 関数型だろうと手続き型だろうと, プログラマーは関数名や型名という形で用語を定義している. 「用語を定義する」はパラダイムを超えた原理だ. だから「良い設計」はパラダイムの優劣では決まらない. そのチームが共有できる用語体系と, 解くべき問題の制約の交差点で決まる. ここでいう「制約」にはパフォーマンス要件やデバッグ容易性も含まれる. それらは用語の切り方そのものを変えるからだ. たとえば, 並行処理が重要なシステムでは, 状態を持つオブジェクトよりも不変データと純粋関数の方が状態競合が起きにくい. すると, 同じ概念でも用語の定義の仕方が変わってくる.
// OOP的: 注文オブジェクトが状態を持ち, 自ら変化する
class Order {
private OrderStatus status;
public void confirm() {
this.status = OrderStatus.CONFIRMED;
}
}
// 関数型的: 注文は不変. 確定するとは新しい注文を生成すること
Order confirm(Order order) {
return new Order(order.lines(), OrderStatus.CONFIRMED);
}
同じ「注文を確定する」でも, OOP的には「注文が自らの状態を変える」と定義し, 関数型的には「確定済みの新しい注文を生成する」と定義する. 並行処理の制約が強ければ後者の方が安全だし, ドメインの表現力を重視するなら前者の方が自然かもしれない. 制約が変われば, 最適な用語体系もパラダイムも変わる. だが, どちらのアプローチでもプログラマーがやっていることは同じだ. 「このシステムにおいて"注文を確定する"とは何か」を用語として定義している. 切り方が違うだけで, 行為は同じなのだ.
あくまで個人的な感覚だけど, チームがどういう思考に慣れているかでパラダイムの「しっくり感」は変わると思う. 数学的な抽象化が得意なメンバーが多ければ関数型のアプローチが自然にハマるし, 現実のモノや関係性でモデルを組み立てるのが得意なチームならOOP的な考え方の方がスッと入る. もちろん今どきの言語は両方使えるから, 白黒つける話でもないのだけど.
ではOOP固有の特徴は何か.
OOPは, 用語が「能動的」であることを構文レベルでデフォルトにしたパラダイムだ.
オブジェクトは自分自身の振る舞いを持つ. 他のオブジェクトとの関係を自ら成立させる. 用語がただの定義ではなく, 主体として動く.
// 関数型的: 用語(データ)と操作が分離
confirm(order, paymentInfo);
// OOP的: 用語(オブジェクト)が自ら振る舞う
order.confirm(paymentInfo);
もちろん, Haskellの型クラスやRustのトレイトでも同様の表現力は持てる. しかしOOPでは, 用語が振る舞いを内包することが最も自然な記述形式になっている. この「デフォルトの思考様式」の違いが, パラダイムの違いだと思う.
どちらが良いという話ではない. ただ, OOPは「用語に主体性を持たせる」という独自のアプローチで, 人間の認識をコードに写している.
締め ~ aka.end(oop) ~
以上, 私的OOP解釈でした.
繰り返すが, これは体系的な理論ではなく, 私が考えてきたことの言語化だ. 「当たり前じゃん」と思った人もいるかもしれない. でも, その「当たり前」をちゃんと言語化することに意味があると思っている.
私がプログラミングに惹かれる理由は前の記事で書いた. 今回はその先, 「何を表現しているのか」を掘った形になる.
ではまた.
疑念: AIが進化する時代にOOPやその解釈を学ぶ必要はあるのか? ~ aka.doubt(future) ~
正直に言うと, AIを日常的に使うようになってから, 「この記事は今更必要ないかもしれない」と何度も思った. 今のAIは強い. コードの設計も, 命名も, かなりのレベルでやってくれる. 「用語を丁寧に定義しましょう」という話ですら, AIの進化に飲み込まれるんじゃないかという感覚は, 書いている本人が一番持っている.
一方で, AIに指示を出すとき, 曖昧な言葉で伝えれば曖昧な結果が返ってくる. 「いい感じにして」では「いい感じ」のコードは出てこない. それはプロンプトが用語定義そのものだからだ, という見方もできる.
あるいは, AIが生成したコードがsetterだらけの貧血モデルだったとき, 「これはおかしい」と気づけるかどうか. その判断には, この記事で書いたような視点が要るのかもしれない.
ただ, これも「だからAI時代にこそ本質が大事なんです」と綺麗にまとめたいわけではない. 正直, わからない. 5年後にこの記事を読み返したとき, 「やっぱり必要だった」と思うのか, 「完全に自動化されたな」と思うのか. 今の私にはどちらとも言えない. ただ, 少なくとも今この瞬間, 用語を定義する行為が完全に無意味になったとは, まだ思えていない.