はじめに
CI/CD。
この2つ、片方だけを口にすることは、あまりない。CI と言えば CD が続くし、CD と言えば CI が前提にある。セットで初めて意味をなす。
エンジニアリングの世界には、こういう「対で語られる用語」がたくさんある。そしてよく見ると、対のあり方は一様じゃない。大きく2種類に分けられる。
1つは、セットで一つの仕組みを成す対。送る側がいれば受ける側がいる、という構造上の対。片方を理解すれば、もう片方の役割が予測できる。
もう1つは、似ているせいで混同される対。仕組みとしてセットなわけではなく、概念が近いために取り違えられる。こちらは対比して差分を押さえないと、正しく使い分けられない。
この記事では、対になる用語を11個、この2グループに分けて整理した。各ペアに Mermaid の図を付けたので、リファレンスとして辞書のように使ってもらってもいい。
用語の対応関係には独断が含まれる。「これは対じゃない」「こっちの方が対だ」があればコメントで教えてほしい。
対で動くペア ── 片方が分かれば、もう片方が読める
最初のグループは、セットで一つの仕組みを成すペア。片方の存在が、もう片方の存在を要求する。
発行 / 購読 ── Publisher / Subscriber(Pub/Sub)
メッセージを発行する側と、購読する側。
ポイントは、Publisher と Subscriber が直接お互いを参照しないこと。Publisher は「誰が受け取るか」を知らずにトピックへ発行し、Subscriber は「誰が送ったか」を知らずにトピックから受け取る。間にトピック(チャネル)が挟まることで、両者は疎結合になる。
この疎結合が実務で効くのは、主に次の3点。
- 送信側と受信側を独立に増減できる。Subscriber を追加しても Publisher のコードは変わらない
- 障害の分離。Subscriber が落ちていてもメッセージはトピックに滞留し、復旧後に処理できる(メッセージブローカーが永続化する場合)
- 非同期化。Publisher は配送完了を待たずに次の処理へ進める
Google Cloud Pub/Sub や Firebase Cloud Messaging、Kafka のトピックモデルは、いずれもこの構造の実装。直接呼び出し(RPC)との使い分けの基準は「送信側が受信側の完了を知る必要があるか」にある。
クライアント / サーバ ── Client / Server
要求する側と、応える側。
Web の最も基本的な対。Client は要求を出し、Server は応答を返す。
注意すべきは、Client / Server がマシンの属性ではなく、通信ごとの役割だということ。上の図の API サーバは、ブラウザに対しては Server だが、DB に問い合わせる瞬間は Client になっている。マイクロサービス構成では、ほぼすべてのサービスが Server であり同時に Client でもある。
だから「このサーバは Client を兼ねる」という言い方は正確には冗長で、正しくは「この通信において、どちらの役割か」を都度特定する。障害調査でタイムアウトの原因を追うとき、この視点がないと「どの通信の、どちら側で詰まっているか」を切り分けられない。
要求 / 応答 ── Request / Response
Client/Server が「誰が」なら、こちらは「何を」やり取りするか。
基本は1リクエストに1レスポンスの1対1対応。ただし、この対応が崩れるパターンを知っておくと設計の幅が広がる。
- fire-and-forget:レスポンスを待たない。ログ送信やメトリクス送出など、失敗を許容できる処理向き
- ストリーミング:1リクエストに複数レスポンス。gRPC の server streaming、LLM のトークン逐次返却が典型
- Server Push:リクエストなしにサーバから届く。SSE や WebSocket の push
崩すかどうかの判断基準は「Client が結果を待つ必要があるか」「結果が一度に確定するか、逐次生成されるか」の2軸。たとえば LLM の応答は逐次生成されるからストリーミングが合理的で、決済の確定は一度に確定するから素朴な 1対1 でいい。
同期 / 非同期 ── Sync / Async
待つか、待たないか。
Sync は結果が返るまでその場で止まる。Async は「あとで結果を受け取る」約束(Dart の Future、JavaScript の Promise)だけを先に受け取り、先へ進む。Go は goroutine とチャネルでこの対を表現していて、言語ごとに設計思想の違いが出る部分でもある。
よくある誤解は「Async にすれば速くなる」というもの。Async が短縮するのは待ち時間の使い方であって、処理そのものの所要時間ではない。I/O 待ち(ネットワーク、ディスク)が支配的な処理なら、待っている間に別の仕事ができるので全体のスループットは上がる。逆に CPU バウンドな処理を async にしても速くならない。ここを混同すると、効果のない async がコード全体に伝播して読みづらくなるだけの結果になる。
押し付け / 取りに行く ── Push / Poll
情報を送りつけるか、取りに行くか。
Push は変化があった瞬間にサーバから送る。WebSocket やモバイルのプッシュ通知が典型。Poll は Client が定期的に問い合わせる。cron で叩く定期バッチや、単純な setInterval での取得がこれにあたる。
トレードオフは明確で、次の3点で比較できる。
- 遅延:Push は即時。Poll はポーリング間隔ぶん最大で遅れる
- 実装コスト:Push は常時接続の管理・再接続・スケールが重い。Poll はただの HTTP リクエストで済む
- 無駄なトラフィック:Poll は「更新なし」の問い合わせを大量に発生させる。Push は変化時のみ
判断の起点は「許容できる遅延はどれくらいか」。数分の遅れが許されるダッシュボードなら Poll で十分。チャットの新着表示のように秒単位が要るなら Push を選ぶ。中間解として、Poll の接続を保留するロングポーリングもある。
確定 / 巻き戻し ── Commit / Rollback
確定するか、なかったことにするか。
トランザクションの対。一連の操作を「全部成功(Commit)」か「全部なし(Rollback)」のどちらかに倒す。この性質が原子性(atomicity)で、ACID の A にあたる。
この対が保証するのは「中途半端な状態を外に見せない」こと。典型例が送金処理。「A口座からの引き落とし」と「B口座への入金」は2つの UPDATE だが、片方だけ成功した状態は業務として存在してはいけない。だから2つを1トランザクションに束ね、途中で失敗したら Rollback で引き落とし前に戻す。
実務で意識すべきは、トランザクションの範囲設計。広く取りすぎるとロック競合でスループットが落ち、狭く取りすぎると不整合が漏れる。「どこからどこまでを不可分にするか」の線引きが、この対を使う上での本題になる。
対比で理解するペア ── 似ているから、混同される
次のグループは毛色が違う。セットで動くのではなく、概念が近いせいで取り違えられるペア。片方を理解するには、もう片方との差分を押さえるしかない。
認証 / 認可 ── Authentication / Authorization
エンジニアが最も混同する対の代表格。
Authentication(認証)は「あなたは誰か」を確かめる。ID とパスワード、OAuth、パスキーなど手段は問わず、本人性の確認がこれ。Authorization(認可)は「あなたは何をしてよいか」を決める。ログインは前者、管理者だけが削除ボタンを押せるのは後者。
混乱の元は、どちらも略すと Auth になること。だから業界では AuthN(認証)と AuthZ(認可)と書き分ける。
差分は HTTP ステータスコードにも現れている。401 Unauthorized は認証の失敗(誰だか分からない)、403 Forbidden は認可の失敗(誰かは分かるが権限がない)。名前と実態がねじれていて紛らわしいが、この対応を覚えておくと API のエラー設計で迷わない。
順序も固定されている。認証なしの認可はありえない。誰だか分からない相手に権限は割り当てられないから、必ず Authentication が先、Authorization が後。
並行 / 並列 ── Concurrency / Parallelism
日本語だと一文字違いで、なお混同しやすい。
Concurrency は「複数のことを同時に扱う構造」。Parallelism は「複数のことを同時に走らせる実行」。前者はプログラムの設計の話、後者はハードウェアの話で、レイヤーが違う。
Rob Pike の整理が分かりやすい。Concurrency は同時に多くを扱うこと(dealing with)、Parallelism は同時に多くをこなすこと(doing)。この区別から次が導ける。
- シングルコアでも Concurrency は成立する。タスクを細かく切り替えれば「同時に扱っている」ことになる
- Parallelism は物理的に複数の実行ユニット(コア)がないと成立しない
- Concurrency な設計は Parallelism の前提になるが、保証はしない
Go を書くなら避けて通れない対。go キーワードが与えるのは Concurrency(goroutine という「同時に扱う単位」)であって、それが実際に並列実行されるかは GOMAXPROCS とコア数、スケジューラが決める。「goroutine を増やしたのに速くならない」と悩んだら、まずこの区別に立ち返るといい。
コンパイル時 / 実行時 ── Compile-time / Runtime
エラーがいつ出るかの境界。
型エラーや構文エラーは Compile-time に出る。null 参照や配列の境界外アクセスは Runtime に出る。両者の決定的な違いは検出の確実性で、Compile-time エラーはビルドすれば必ず見つかるのに対し、Runtime エラーは該当するコードパスをその入力で通らない限り発覚しない。テストで拾えなければ本番で出る。
だから「エラーは早く出るほど安い」。修正コストは、コンパイル時 < テスト時 < 本番障害の順で跳ね上がる。Compile-time に倒せるものを Runtime に残さない、というのが型で縛る思想の核心。
TypeScript を書くときは特にこの境界を意識する必要がある。TypeScript の型は Compile-time にしか存在せず、トランスパイル後の JavaScript には残らない。つまり API レスポンスのような外部入力に対しては、型注釈だけでは何も守られない。Runtime のバリデーション(zod 等)を別途置くか、as で握り潰した型が実行時に不整合を起こすかの二択になる。境界(外部入力の入口)でバリデーションし、内側は型で守る、という役割分担が定石。
文 / 式 ── Statement / Expression
値を返さないか、返すか。
Statement は実行されるが、それ自体は値を持たない。Expression は評価されて値になる。判定基準はシンプルで、「変数に代入できるか」「関数の引数に渡せるか」を考えればいい。
if が文か式かは言語で分かれ、そこに言語の設計思想が出る。JavaScript の if は文なので const x = if (cond) {...} とは書けず、式版として三項演算子が別に用意されている。一方 Rust の if は式で、let x = if cond { 1 } else { 2 }; と書ける。Kotlin や Ruby もこちら側。
「すべてが式」に寄せた言語ほど、コードは値の変換の連なりとして読めるようになる。中間変数への再代入が減り、let の右辺に処理が収まる。関数型言語が Expression 寄りなのは、プログラムを「値から値への写像」と捉える思想の帰結で、偶然ではない。
許可リスト / 拒否リスト ── Allowlist / Denylist
デフォルトをどちらに倒すか。
Allowlist は「これだけ通す、あとは全部拒否」。Denylist は「これだけ拒否、あとは全部通す」。
セキュリティの定石は Allowlist、つまりデフォルト拒否(default deny)。理由は、想定外への挙動が逆だから。Denylist は「攻撃パターンを列挙する」方式なので、列挙漏れ=素通しになる。新種の攻撃、エンコーディングの変種、思いつかなかった入力は、すべて「リストにない」ので通ってしまう。Allowlist なら、想定外は「リストにない」ので落ちる。失敗したときに安全側に倒れる(fail-safe)のは Allowlist の方だ。
Denylist が選ばれるのは、正当な入力の全列挙が現実的でない場合。たとえば公開 Web サービスの IP 制限を Allowlist にはできない。「原則 Allowlist、列挙不能なときだけ Denylist」が判断の順序になる。
補足として、かつては WhiteList / BlackList と呼ばれていたが、色に善悪を結びつける含意を避けるため、Allowlist / Denylist への置き換えが進んだ。Chromium も Android もコードベースの用語を移行済み。機能そのままに名前だけ差し替えた事例として、命名の観点でも参照価値がある。
おわりに
対になる用語を11個、2つの軸で並べた。
整理してみると、同じ「対」でも性質が違うのがわかる。Pub/Sub や Request/Response は、セットで動いて初めて意味をなす対。Authentication/Authorization や Concurrency/Parallelism は、似ているせいで混同され、差を知って初めて使い分けられる対。
この分類は覚え方に直結する。前者は片方の役割からもう片方を導出できるので、構造(図)ごと覚えるのが効率的。後者は差分こそが本体なので、「何が違うか」を一文で言えるようにしておくのが実用的だ(例:AuthN は誰か、AuthZ は何をしてよいか)。
他にも対になる用語はたくさんある。Map/Reduce、Serialize/Deserialize、Mount/Unmount ── 「これも対だ」と思うペアがあれば、ぜひコメントで教えてほしい。