Closed13

学びメモ 2025/03

ebi_yuebi_yu

チューリング完全性について

1. 初心者向けの説明

チューリング完全性とは何か: チューリング完全性とは、理論上あらゆる計算(アルゴリズム)を実行できるだけの能力を持つことを指す概念です。イギリスの数学者アラン・チューリングが提唱した「チューリングマシン」という仮想計算機になぞらえており、チューリング完全なシステムはどんなチューリングマシンでもシミュレートできる(=どんな計算問題でも解ける)とみなされます。簡単に言えば、「この言語(またはシステム)で書けるプログラムは原理的に制限がなく、コンピュータで実行可能なあらゆる処理を表現できる」という意味になります。

https://ja.wikipedia.org/wiki/チューリング完全

具体例

現代のほとんどすべてのプログラミング言語はチューリング完全です。例えば PythonやJavaScript、Java、C++など一般的な言語はどれも、条件分岐やループ等の構造を使ってあらゆる計算手順を記述できます。実際、見た目がどんなに異なる言語であっても、チューリング完全である限り表現できる計算の範囲は同じであり、原理的には互いにシミュレート(再現)可能です。一方で、最近話題のノーコードツール(コードを書かずにアプリ開発等を行うツール)の中には、あらかじめ決められた処理しかできず自由な繰り返しや計算ができないものもあります。そのようなツールは計算能力に制限があるためチューリング完全ではありません。例えば、典型的な**表計算ソフトの数式(スプレッドシートの関数)**はループ(繰り返し処理)を記述できないのでチューリング完全ではありません(※ただし近年Excelに追加されたLAMBDA関数により、Excelの数式言語自体がチューリング完全になったという話題もあります)。

なぜ重要か

チューリング完全であることは、その言語やシステムが汎用的な計算機としての力を持つことを意味します。プログラミングにおいてこれが重要なのは、開発者が「その言語で表現できない処理があるせいでやりたいことができない」といった制約に悩まされずに済むからです。チューリング完全な言語であれば、工夫次第でどんなロジックでも実装できます(理論上は計算可能な限り)。逆にチューリング完全でないツールでは、実現できる処理の種類に 限界 があり、複雑なアプリケーションや汎用的なアルゴリズムを作ろうとすると行き詰まってしまう可能性があります。実際、「プログラミング言語」と呼ばれるものの多くはチューリング完全であり、もしチューリング完全でなければ真の意味で汎用のプログラミングには使えないとさえ言われます。そのためチューリング完全性は、初心者にとっては「そのツールで何でも作れるかどうか」の目安となる重要なポイントなのです。

詳細

2. エンジニア向けの説明

計算理論とチューリングマシン: コンピュータサイエンスの計算理論の文脈では、チューリング完全性は「万能チューリングマシンと同等の計算能力を持つこと」と定義されます。チューリングマシンとは無限長のテープ(記憶)と読み書きヘッドを備え、あらかじめ定めた遷移規則に従って計算を行う抽象機械モデルです。非常にシンプルなモデルですが、このテープ上の読み書きと状態遷移だけで任意の計算が可能であることが示されています。要するに、チューリングマシンは現在考えうる最も強力な計算モデルの一つであり、現実のコンピュータで実行できるアルゴリズムはすべてチューリングマシン上でも実行できます。チューリング完全なシステムは、このチューリングマシンが解ける問題をすべて解けるだけの計算力を備えているということになります。実用上は「有限メモリしか持たない現実のコンピュータにおいて、そのメモリ制限を無視すれば実質的に同じ計算ができる」という意味合いです。

https://zenn.dev/cybozu_ept/articles/github-actions-is-turing-complete#チューリング完全性とは

チューリング完全となる条件

プログラミング言語や計算モデルがチューリング完全となるためには、いくつかの基本的な構造要素が必要です。一般に命令型(Imperative)言語であれば少なくとも次の2点が求められます:

  • 条件分岐と繰り返し(条件によって処理をループさせたりジャンプさせたりする仕組み) – 例えばifやwhile、再帰呼び出しなど。これにより計算の長さ(ステップ数)を動的に伸ばすことができます。
  • 可変なメモリ領域(読み書き可能なデータ記憶) – 変数、配列、テープなど形態は問いませんが、計算途中の値を保存し、更新できる領域が必要です。特に重要なのはそのメモリが原理上無制限(必要に応じていくらでも増やせる)であることです。

これらが揃うと、プログラムは任意の長さの計算や任意サイズのデータ処理を記述できるようになり、結果としてチューリングマシンで実行できるあらゆる計算をシミュレートできるようになります。ちなみに関数型言語の場合はループの代わりに再帰的な関数適用やラムダ計算によって同等の表現力を持たせることができます。いずれにせよ、「計算を途中でいくらでも繰り返し展開できる仕組み」と「理論上無限に使えるメモリ」の二つがチューリング完全性の鍵となります。

具体的な例(チューリング完全なシステム)

抽象計算モデルの例では、前述のチューリングマシンそのものはもちろん、アルフォンス・チャーチのラムダ計算やポストの計算機械(Post体系)、またμ-Recursive関数などがすべてチューリング完全であると知られています。これらは計算論の研究用に考案されたモデルですが、実際には現代のほとんどすべての汎用プログラミング言語もチューリング完全です。例えば、命令数わずか8つしかない極簡単な言語Brainfuckや、関数定義すら持たない初期のBASICであっても、ループとメモリさえあればチューリング完全性を持ちます。実際にBrainfuckやLazy Kといった極限まで機能を絞った言語がチューリング完全であることが示されています。一方でPythonやJavaScriptなど現代的な高級言語は言うまでもなくチューリング完全であり、それらの言語が動作するコンピュータ自体も(メモリが許す限り)チューリングマシンと同等の計算能力を持っています。要素の少ない言語でも高級な言語でも、条件分岐・繰り返し・可変メモリといった基本要件を満たしていれば計算能力の上限は同じ(チューリング機械相当)なのです。

図: さまざまな計算モデルの階層。内側から「Combinational logic(組み合わせ回路)」→「Finite-state machine(有限状態機械)」→「Pushdown automaton(プッシュダウン・オートマトン)」→「Turing Machine(チューリングマシン)」の順に包含関係が描かれており、チューリングマシンが最も強力なモデルであることを示している。例えば正規表現が扱う正則言語は有限状態機械で受理され、**文法(コンテキストフリー言語)**はプッシュダウン・オートマトンで受理されますが、これらの計算モデルはチューリングマシンより表現力が低いため、それぞれチューリング完全ではありません。一般的に計算モデルの表現力はこの図のような階層構造になっており、チューリングマシンが頂点に位置します。チューリング完全な言語は、この頂点であるチューリングマシンと同じ領域(最外郭の緑色部分)に属することになります。

チューリング完全ではない例

世の中のすべてのシステムがチューリング完全というわけではありません。計算能力を限定した言語も存在し、そのような言語では実行できる処理に上限があります。典型的な例が正規表現(regex)や限定的なSQLクエリです。正規表現は文字列パターン照合のための言語ですが、理論的には有限オートマトン(有限状態機械)というモデルで表現されるため、無限にネストしたり再帰的な計算を行うことができず、チューリング完全ではありません。また標準的なSQLも、基本的には有限個のテーブルデータに対する問合せを記述するための宣言的言語であり、再帰的なクエリや無限ループを持たない限りチューリング完全の条件を満たしません。事実、SQL-99で再帰的な共通テーブル式(CTE)が導入されるまでは、SQLのクエリ部分はチューリング不完全でしたが、再帰SQLやストアドプロシージャなどの拡張によって現在ではチューリング完全と言えるレベルまで表現力が強化されています。このように一部の言語は意図的に計算能力を制限することで、無限ループが発生しないようにしたり(例:全ての計算が停止することを保証する全域関数型言語)、特定分野に特化して扱いやすくすることがあります。ただしその分、解ける問題の範囲が狭まるため、用途が限定的になります。「チューリング完全でないがゆえに実用上の制約が生じ、最終的には機能拡張されてチューリング完全になる」というケースも少なくありません(前述のSQLの例などが典型です)。

3. プログラミングとAIの関連性

AIのコード生成とチューリング完全性: 近年の高度なAI(特に大規模言語モデル)は、プログラミング言語のコードを生成することができます。例えばChatGPTにPythonやJavaScriptのコードを書かせるといったことが可能ですが、これらの一般的なプログラミング言語はすべてチューリング完全であり、したがってAIが生成したコードは原理的にどんな計算タスクでも実行し得る力を持っています。言い換えると、AIはコードを書くことで自らチューリング完全な計算を間接的に行えるのです。これは非常にパワフルで、複雑な問題に対しても適切なプログラムさえ組めれば解を計算できることを意味します。例えばAIが与えられたデータを分析して答えを出す際に、内部でPythonコードを組み立てて実行するようなケースでは、そのPythonの持つ表現力(チューリング完全性)のおかげで制約のない柔軟な処理が可能になります。

プロンプトエンジニアリングとチューリング完全性の関係: プロンプトエンジニアリングとは、AIに望む出力を得るために入力文(プロンプト)の工夫を凝らすことですが、これは見方を変えれば自然言語を用いたAIのプログラミングとも言えます。高度な使い方では、AIに段階的な推論をさせたり、一度の回答で完了しない処理を対話を通じて継続させることで、まるでループや関数呼び出しを行っているかのように振る舞わせることもできます。例えば大きな問題を何度もAIに解かせ、その都度部分結果をプロンプトに含めて次の処理をさせる、という対話型のループを作ることも可能です。実際の研究でも、プロンプトによって言語モデルにチューリングマシンの動作をシミュレートさせられることが示されています。これはモデルの出力を再度入力としてフィードバックする仕組みを使い、AIに仮想的な「メモリ」を与えることで無限に近い長さの計算を実現する手法です。このようにプロンプト設計次第で、AIは内部にループや条件分岐を持たなくとも段階的に任意の計算(アルゴリズム)を実行できる可能性があり、言語モデルとプロンプトの組み合わせ自体を一種のチューリング完全なシステムのように扱うことができます。

チューリング完全性がAI開発やプログラミングで重要な理由: プログラミングにおいてチューリング完全性が重要なのと同様に、AIの開発・活用においても汎用的な計算能力の保証が重要です。 AI分野のタスク(機械学習アルゴリズムの訓練、データ処理、シミュレーションなど)は非常に多様で複雑な計算を含みますが、それらを実装するためには底層でチューリング完全なシステムの支えが必要です。例えば大規模データの解析では繰り返し処理や条件分岐が不可欠ですし、機械学習のモデルも最終的にはチューリング完全なプログラミング言語で記述されています。AIアプリケーションを開発する際、もし使用する言語やプラットフォームがチューリング完全でなければ、途中で実現できない処理が出てきてしまい、十分な機能を持つAIを構築できなくなるでしょう。逆にチューリング完全であれば、新しい要求にも柔軟に対応できます。さらに、近年の生成AIは自らコードを書いたり、他のツールと連携したりすることで足りない機能を補うことができますが、これも根底にあるプログラミング言語や計算モデルがチューリング完全だからこそ可能なアプローチです。要するに、チューリング完全性は汎用性と将来性の土台なのです。プログラミング言語でもAIシステムでも、それがチューリング完全であることによって「理論上どんな計算でもこなせる」という安心感が得られます。そしてそのことが、私たちがソフトウェアやAIに問題解決を託す上で非常に重要だと言えるでしょう。

ebi_yuebi_yu

TypeScriptで学ぶクリーンアーキテクチャ

1. クリーンアーキテクチャの基本概念

● 関心の分離と依存関係の方向性:

クリーンアーキテクチャはソフトウェアの構造を複数のレイヤーに分割し、「関心事の分離」を徹底する設計手法です。レイヤー間の 依存関係の方向 が重要で、内側の高レベルな部分(ビジネスロジック)に向かってのみ依存 させます。これにより、外側のUIやデータベースなどの詳細に中心のビジネスロジックが影響を受けにくくなります。

● 依存関係逆転の原則(DIP: Dependency Inversion Principle):

DIPとは「高水準のモジュール(抽象)が低水準のモジュール(具体)に依存してはならず、どちらも抽象に依存すべき」という原則です。クリーンアーキテクチャではこの考えを適用し、上位レイヤー(ビジネスルール側)が下位レイヤー(フレームワークやDBなど具体的実装)に直接依存しないようにします。具体的には、抽象(インターフェース)を介して依存関係を逆転 させ、下位レイヤーが上位レイヤーのインターフェースを実装する形にします。こうすることで、外部の詳細(DBやフレームワーク)の変更が内部のビジネスロジックに及ばず、システムの保守性が向上します。

2. クリーンアーキテクチャのレイヤー構造

クリーンアーキテクチャは同心円状のレイヤー構造を持ち、内側から外側へ以下の4層に分かれます(名前は文脈により異なりますが、本記事ではエンティティ、ユースケース、インターフェースアダプター、インフラと呼びます)。

エンティティ層(Domain/Entities):

アプリケーションの最も中心にある層で、ビジネスルールやドメインモデルを表現します。この層のコンポーネントは他のどの層にも依存しません。データ構造やドメインオブジェクトの振る舞いを定義し、UIやDBといった外部要素の影響を受けないため、変更が発生しても他の層へ影響しにくく、単体テストもしやすい特徴があります。例えばエンティティクラスとして、タスク管理アプリならTaskクラスを定義し、タイトルや説明文などのプロパティとその操作(ビジネスルール上の制約)を持たせます。

ユースケース層(Application/Use Cases):

アプリケーション固有のビジネスロジックや操作の流れを実装する層です。エンティティを使用して具体的な機能(ユースケース)を提供し、外部から与えられた入力を処理して出力を生成します。ユースケースは内部のエンティティには依存しますが、外部(DBやUIなど)の詳細には依存しません。これによりユースケース自体のテストが容易になり、ビジネスロジックの変更が他の層に波及しにくくなっています。例えば「タスクを作成する」「タスク一覧を取得する」といった操作ごとにユースケースクラスを作り、エンティティとリポジトリを組み合わせて処理を実現します。

インターフェース層(Interface Adapters):

ユースケースやエンティティと、外部のフレームワーク・データベースとの 橋渡し をする層です。内部で扱うドメインオブジェクトと外部システム(例えばDBの行データやRESTのJSON)の間で、データ形式の変換やフォーマット調整 を行います。例えば、データベースから取得したレコードをTaskエンティティに変換したり、逆にエンティティをJSONにシリアライズしてAPIレスポンスの形に整える処理を担います。この層にはPresenter/ControllerやGateway/Repositoryなどが含まれ、外部の変更(DBスキーマ変更やUI変更)が直接内部のビジネスロジックに影響を与えないよう設計されています。ユースケースから見ると、この層に定義したインターフェース(後述のPortなど)を介してデータのやり取りを行います。

インフラ層(Frameworks & Drivers / Infrastructure):

最も外側に位置する層で、フレームワークやデータベース、UIなどの具体的な実装を含む部分です。Webフレームワークのコントローラやルーティング、ORMやデータベース接続、外部APIとの通信ドライバなど、システムの周辺的な仕組みがここに配置されます。インフラ層のコンポーネントは上位のインターフェースを実装し、具体的な処理(例えば実際のDBへのクエリ発行など)を行います。クリーンアーキテクチャでは、インフラの詳細を変更しても内側のユースケースやエンティティに影響を与えないため、例えばデータベースを変更したりフレームワークを入れ替える場合でも、影響範囲をこの層とインターフェース層に閉じ込められます。

※このように、内側の層ほど抽象度が高く安定したビジネスロジックであり、外側の層ほど具体的な仕組みや技術的詳細になります。依存関係は内側に向かう のみとすることで、「内側(方針)が外側(仕組み)に依存しない」構造を実現します。

詳細

3. 依存関係の管理とTypeScriptでの実装

● TypeScriptのインターフェースを活用した依存関係の管理:

上記DIPを実現するために、TypeScriptでは インターフェース(interface) を活用します。ユースケース側では外部依存(データ永続化や外部サービス呼び出しなど)に直接触れないように、ポート(Port) と呼ばれるインターフェースを定義します。例えば、タスク管理のユースケースがデータベースに依存しないように、TaskRepositoryやTaskPortといったインターフェースを用意し、ユースケースはそれに対してプログラミング します。これによりユースケースは「データ保存のできる何か」には依存しますが、その具体実装(SQLなのかファイルなのか)は知らなくて済みます。依存関係は抽象に向かうため、具体的な実装側(インフラ層)がこのインターフェースを実装し、後述のDIコンテナ等で注入します。

● ユースケースとリポジトリの関係:

ユースケースからデータベース操作を行う際の悪い例として、ユースケース内で直接DB接続やORMリポジトリを生成・呼び出ししてしまうケースがあります。例えば以下のように、ユースケース内で直接newしてDBのリポジトリを使うと、ユースケース層がインフラ詳細(DB)に依存してしまいます:

// 悪い例: ユースケースが直接DBリポジトリに依存している
execute(title: string, description: string, pool: any) {
  const task = new Task(title, description)
  const taskRepository = new TaskRepository(pool)  // インフラに依存
  return taskRepository.persist(task)
}

このようにデータベースのコネクションやORMに直接依存する設計は、クリーンアーキテクチャのルール違反 となります。解決策として、ユースケースはリポジトリのインターフェース(抽象)に依存 し、実体はインフラ層で用意するようにします。具体的には、例えばITaskRepositoryというインターフェースを定義し、ユースケースはコンストラクタでそれを受け取る形に変更します。TypeScriptであれば次のような実装になります:

// リポジトリのインターフェース定義(ユースケース側で定義)
interface ITaskRepository {
  persist(task: Task): Promise<void>;
  find(id: number): Promise<Task | null>;
  // ...必要なメソッド定義...
}

// ユースケースでインターフェースを受け取る
class CreateTaskUseCase {
  constructor(private taskRepo: ITaskRepository) {}  // 抽象に依存

  async execute(title: string, description: string) {
    const task = new Task(title, description)
    await this.taskRepo.persist(task)  // 抽象メソッド呼び出し
  }
}

ユースケースはITaskRepositoryという**抽象(インターフェース)**のみに依存し、具体的な保存処理は知りません。このITaskRepositoryを実装したクラス(例えばMySQLTaskRepositoryなど)をインフラ層に作成し、アプリ起動時にユースケースに注入することで、依存関係の逆転を実現します。上述の例でも、TypeScriptのインターフェースによりコンパイル時に契約が保証されるため、安全に依存関係を管理できます。

● 実装例(クラスとインターフェースの使用):

上記の設計をコードで表すと、インターフェース継承と依存性注入 の形になります。例えば、TaskUseCaseクラスのコンストラクタでTaskPortインターフェース(リポジトリの抽象)を受け取り、それを用いてタスク取得などの処理を行う実装が考えられます。このようにすることで、ユースケース(TaskUseCase)は具体的なデータアクセス手段を意識せず、与えられた抽象(TaskPort)経由で操作を行います。実装クラス側(例えばTaskRepositoryImpl)はTaskPortインターフェースを実装し、インフラ層でデータベース操作の詳細を持ちます。TypeScriptのクラスとインターフェース機能により、「内側のレイヤーは外側の詳細を知らず、抽象だけ知っている」 状態を作り出せるわけです。

4. DIコンテナの活用

● NestJSを活用した依存性の注入:

TypeScript/Node.jsのフレームワークであるNestJSは、標準で強力なDI(Dependency Injection)コンテナを備えており、クリーンアーキテクチャ実践に役立ちます。NestJSでは、クラスにデコレータを付与してプロバイダ(サービスやリポジトリ)として登録し、必要な箇所で自動的にインスタンスが注入されます。モジュールごとに依存関係を整理するため、構造が明確になり大規模プロジェクトでも管理しやすい利点があります。例えば、ユースケースサービスに対してリポジトリ実装をNestJSのprovidersでバインドしておけば、コントローラからユースケースサービスを呼ぶ際に適切な実装が注入されます。

● DIを用いたテスト容易性:

依存性注入を活用すると、テストが格段にしやすくなります。NestJSではテスト時にDIコンテナにモック(テスト用のダミー実装)を登録することで、ユースケースやサービスの挙動を外部要因に左右されず検証できます。実際、DIによりオブジェクトは自前で依存物を生成しないため、代わりにテスト用のフェイクやモックを注入できます。その結果、ユニットテストでは対象クラスだけに注力でき、副作用のない堅牢なテストが可能です。例えばNestJSのテストモジュール機能を使えば、あるサービスが依存するリポジトリをモック実装に差し替えて注入し、ビジネスロジック部分のみを検証できます。DIコンテナを活用することで、疎結合な設計とテスト容易性 の双方を実現できるのです。

5. クリーンアーキテクチャのメリット

クリーンアーキテクチャを導入することで、システム設計に以下のようなメリットが生まれます。

フレームワーク非依存

システムの動作が特定のフレームワークに縛られません。フレームワークはあくまで詳細の実装に過ぎず、いつでも交換可能な「ツール」として扱われます。例えば、ExpressからNestJSへの移行や、ReactからVueへのフロントエンド変更も、ビジネスロジック部分を変更せずに対応しやすくなります。

テスト容易性

ビジネスルールのロジックはUIやDBと無関係に分離されているため、ユニットテストが書きやすくなります。UIやデータベースの代わりにモックを用意して、ユースケースの振る舞いを単独で検証可能です。これにより、変更時に回帰バグがないか素早く確認でき、リファクタリングも安心して行えます。

変更に強い構造

レイヤー間の依存が内側に限定されているおかげで、技術要素の変化や要件変更に柔軟に対応できる保守性・拡張性の高い設計となります。例えばデータベースをSQLiteからPostgreSQLに差し替えたり、UIフレームワークを変更する場合でも、影響はインフラ層など限られた範囲に留まり、中心のビジネスロジックへの修正は不要で済む可能性が高いです。結果としてシステム全体が変更に強くなり、将来的な機能追加も容易になります。

UI/データベース非依存

ビジネスルールは外部のUIやデータベースの細部に影響されません。UIはシステムの他の部分を変更せずとも改修・交換でき、データベースについてもビジネスロジック層はその存在を意識しないため、極端に言えばDBなしでもビジネスロジックのテストが可能です。

外部システムからの独立

ビジネスロジックは外界のシステム(例えば認証基盤や他サービスのAPI)について何も知らない状態を保てます。外部サービスの変更やサードパーティライブラリのアップデートがあっても、影響はインターフェース層・インフラ層で吸収でき、ドメインやユースケースには波及しません。この独立性により、安心して外部サービスやライブラリの変更を取り込めます。


以上のように、クリーンアーキテクチャはフレームワークやデータソースに左右されない柔軟でテストしやすい構造をもたらします。その結果、システムが長期間にわたって拡張・変更しやすい状態を維持できる点が大きな利点です。

6. 実践のポイント

クリーンアーキテクチャを現場で適用する際には、以下のポイントに留意すると効果的です。

ドメインモデルの優先

システムで表現すべき業務上の概念やルールをドメインモデルとして最優先で設計しましょう。例えばエンティティ(ドメインオブジェクト)はデータベースのテーブル設計に引きずられず、業務の言葉で属性や振る舞いを定義します。ドメイン知識がコード上に散逸しないよう、この層に閉じ込めます。こうすることで、ビジネスロジックが常にシステムの中心に据えられ、技術的な変更にも影響されにくくなります(※ドメイン駆動設計(DDD)の考え方も参考になります)。

外部依存の抽象化

データベースや外部API、フレームワークといった外部要素への依存は必ず抽象化して受け渡すようにします。具体的にはインターフェースや抽象クラスで境界面(インターフェース層)を定義し、それを経由して外部システムとやり取りします。これにより「外側の変更が直接内側に影響しない」状態を作ります。例えば、ファイルストレージからクラウドストレージに変更する場合でも、ストレージ操作用のインターフェースを実装し直すだけで、ユースケースから見ると同じメソッド呼び出しで済みます。常に依存の方向が抽象に向くよう意識しましょう。

データフローの意識

各レイヤー間をデータがどのように流れるかを明確に設計します。特に、データの入力と出力の変換はインターフェースアダプター層で完結させることがポイントです。ユースケース層では外部のデータ形式を意識せずに済むように、リクエストDTOからドメインモデルへの変換、ドメインモデルからレスポンスDTOへの変換などを適切に橋渡しします。こうしたデータフローの境界を明確にすることで、レイヤー間の役割がはっきりし、将来の拡張時にも「どこを修正すべきか」が分かりやすくなります。


以上の点を踏まえ、実装時には**「今書いているコードはどの層の責務か」を常に自問すると良いでしょう。例えば、ビジネスルールに直接関係ないログ出力や認証チェックのコードがユースケース層に紛れ込んでいないか、UIフレームワーク特有のオブジェクトがドメイン層に漏れていないか、といった観点でコードを見直す習慣をつけます。クリーンアーキテクチャでは各層の責務が明確に分かれているため、逆に言えば責務外の処理が混入すると設計が崩れる**恐れがあるからです。

7. よくある間違いとその解決策

クリーンアーキテクチャを適用しようとする際に陥りがちなミスや、依存関係が破綻しやすいポイントを押さえておきましょう。

手段が目的化してしまう:

クリーンアーキテクチャという名前や同心円の図解だけが独り歩きし、「とにかくレイヤーを分ければ良い」「形だけ真似しよう」としてしまうことがあります。この場合、本来の目的である関心の分離や保守性向上がおろそかになり、かえって開発効率が下がってしまうこともあります。対策: クリーンアーキテクチャは銀の弾丸ではないことを認識し、プロジェクトの規模や要求に応じて適用範囲を見極めましょう。「何のためにこの構造にするのか(目的は何か)」をチームで共有し、単にレイヤーを作ること自体が目的化しないように注意します。

過度な抽象化・インターフェースの乱立:

DIPを意識するあまり、必要性の低い部分までインターフェースを作りすぎたり、レイヤーを細分化しすぎてしまうケースがあります。抽象化しすぎるとコードが複雑化し、開発効率や可読性が落ちて本末転倒になりかねません。対策: 抽象化はあくまで「変更に備えるための手段」であることを意識します。頻繁に変更が起こり得る箇所や外部依存が大きい箇所を重点的に抽象化し、その他は過度に分離しすぎないようにバランスを取ります。インターフェース導入の目的は依存方向の制御であり、数を増やすこと自体ではない点にも留意しましょう。

依存関係のルール違反:

レイヤー間の依存関係の制約を破ってしまう典型例も挙げられます。例えば、ドメイン層のエンティティが直接DBのエンティティ(ORMモデル)に依存してしまったり、ユースケース層でWebフレームワークのリクエストオブジェクトを参照してしまうなどです。特にユースケース層とインフラ層の境界は破綻しやすく、前述したようにユースケース内でDBコネクションを扱ってしまうミスがありがちです。対策: 依存ルールに反する箇所を定期的にレビューで洗い出し、インターフェース経由の呼び出しにリファクタします。ユースケースが何か外部要素に触れている場合はすぐにポートを定義しなおし、インフラ層に処理を移すべきサインだと考えましょう。チーム内で依存関係のチェックリスト(「ユースケースから〇〇を直接呼んでいないか?」等)を用意するのも有効です。

ドメイン貧血症(Anemic Domain Model):

クリーンアーキテクチャとDDDを混同して誤解した結果、ドメインモデルに何もロジックがなくデータの入れ物になってしまうケースがあります。全てのビジネスロジックをユースケースに寄せすぎると、エンティティはただのDTOと化しドメイン層の意義が薄れます。この状態だと変更箇所がユースケースに集中してしまい、場合によってはユースケースが肥大化します。対策: エンティティが担うべきドメインルール(不変条件や関連オブジェクトとの整合性維持など)はエンティティ内部に持たせます。ユースケースはフローの調整役に留め、個々のビジネスルールは適切にドメイン層でカプセル化しましょう。例えば、「タスクのステータス更新はTaskエンティティ自身のメソッドで行い、ユースケースはそのメソッドを呼ぶだけ」にするといった設計です。こうすることで各層の責務が明確になり、テストもしやすくなります。

8. 学習リソース

最後に、クリーンアーキテクチャをさらに深く学ぶための参考リソースを紹介します。

書籍『Clean Architecture 達人に学ぶソフトウェアの構造と設計』(Robert C. Martin 著, 日本語訳)
クリーンアーキテクチャの提唱者である「アンクルボブ」ことRobert C. Martin氏の著書です。アーキテクチャの歴史的背景からSOLID原則、具体的な設計指針まで体系的に解説されており、様々な種類のシステムに応用できる普遍的な知見が得られます。クリーンアーキテクチャの思想を包括的に理解するのに最適な一冊です。

原著記事「The Clean Architecture」およびその日本語翻訳ブログ
Robert C. Martin氏が自身のブログで発表したクリーンアーキテクチャ解説記事(英語)と、その有志による日本語翻訳記事があります。書籍ほど長くありませんが、同心円のレイヤー図とともに設計の要点がまとまっているため、まずはこちらを読むと全体像を掴みやすいでしょう。日本語翻訳は「クリーンアーキテクチャ The Clean Architecture 翻訳」というタイトルで公開されています。

サンプル実装リポジトリ

実際のコードでクリーンアーキテクチャを体験するには、GitHub上のサンプルリポジトリを読むのが近道です。例えば、quick-xp/taskhelp-api はNode.js (Express) + TypeScriptでToDoアプリAPIをクリーンアーキテクチャで実装した練習用プロジェクトで、ディレクトリ構成やコードが参考になります(SpaceMarket社ブログで紹介)。また、海外の例ですが FilipeMata/clean_architecture_typescript_example ではクリーンアーキテクチャの各層がフォルダで分けられており、TypeScriptでの実装方法を学べます。これらのリポジトリをクローンして手元でテストを動かしたり、リファクタリングしてみると理解が深まるでしょう。

その他の関連書籍

クリーンアーキテクチャ以外にもオニオンアーキテクチャやヘキサゴナルアーキテクチャといった類似の設計手法があります。それらを比較しつつ理解したい場合は『実践ドメイン駆動設計』(DDD)や『レガシーコード改善ガイド』なども併せて読むと、クリーンアーキテクチャの位置づけがより明確になります。特にDDDはクリーンアーキテクチャと組み合わせて使われることも多い概念なので、ドメインモデルの設計に興味があればチェックしてみてください。

ebi_yuebi_yu

(React)useEffect + useState と useMemo の違い

  • useEffectは引数を含まない結果を返した後に、引数を含む結果を返すので、レンダリングに使っていた場合、レンダリングが二回起きる
  • useMemoではレンダリングは一回。
  • 非同期でデータ取得するようなside effectの処理(いつ起きるかわからない処理)にuseEffetctを使う

https://stackoverflow.com/questions/56028913/usememo-vs-useeffect-usestate

ebi_yuebi_yu
特徴 useEffect useMemo
実行タイミング レンダリング完了後に実行(画面表示後) レンダリングに実行(画面表示前)
主な用途 非同期処理などの副作用(side effect) 値や計算結果をキャッシュする
レンダリングの影響 state更新で再レンダリングが起きる 再レンダリングは起きない(キャッシュ使用)
使用例 API呼び出し、イベント登録、タイマー設定など、非同期的な副作用 計算処理や関数の結果をキャッシュし、同期的な処理を最適化する場合

📌 useEffectの動作と特徴

  • レンダリングが完了した後に実行される
  • APIなど非同期処理に向いている
  • 非同期処理が完了するとstateを更新し、再レンダリングが発生する
useEffect(() => {
  fetchData().then(data => {
    setData(data); // ← state更新で再レンダリング
  });
}, []);
  • 最初はデータが無い状態で一度レンダリング
  • データ取得後に再度レンダリングが発生するので、計2回のレンダリングが起きる

📌 useMemoの動作と特徴

  • レンダリングの最中に実行され、結果をキャッシュする
  • 同期的な重い処理(計算など)を最適化するために使用
  • 依存する値が変更されるまで再計算されない
const memoizedValue = useMemo(() => {
  return expensiveCalculation(value);
}, [value]);
  • 値が変化しない限りキャッシュした値を再利用する
  • 再レンダリングを起こさない

🚩 まとめ(覚え方)

  • useEffect:画面表示後に実行したい非同期処理に使う
  • useMemo:同期処理の計算結果を再利用し、効率化したい時に使う
ebi_yuebi_yu

SQLのUNIONと関連する記法

SQLのUNIONは、複数のSELECTの結果を統合する演算子です。特定の条件を満たすデータを結合し、リストを作成する際に便利です。また、固定値を追加したり、データを加工したりするための関連SQL記法も存在します。

UNIONの基本動作

UNIONは、複数のSELECTクエリの結果を統合し、重複を排除します。

SELECT USER_ID FROM USERS_TABLE
UNION
SELECT USER_ID FROM ACTIVE_USERS_TABLE;

ポイント:

  • UNION重複を自動的に削除 します。
  • SELECTのカラム数とデータ型は一致させる必要があります。
  • 統合されたデータの並び順を制御するには ORDER BY を使用します。

UNION ALL を使うと、重複を削除せずにそのまま統合するため、高速に処理できます。

SELECT USER_ID FROM USERS_TABLE
UNION ALL
SELECT USER_ID FROM ACTIVE_USERS_TABLE;

UNIONで固定値を追加する

特定のユーザーをリストに 必ず含める 場合、固定値を追加できます。

SELECT USER_ID FROM USERS_TABLE
UNION
SELECT 'guest' AS USER_ID
UNION
SELECT 'admin' AS USER_ID;

このクエリの実行結果:

USER_ID
--------
user1
user2
admin
guest

用途:

  • すべてのリストに「ゲスト」や「管理者」を含める。
  • ユーザーリストのデフォルト値として使用。

AS を使ったエイリアス(別名)設定

AS を使うことで、カラム名を変更できます。

SELECT 'guest' AS USER_ID;

このSQLでは、固定値 'guest'USER_ID というカラム名で扱えます。

CASE を使った条件分岐

CASE を使用すると、条件に応じて異なる値を返せます。

SELECT 
    CASE WHEN ROLE = '1' THEN 'Admin'
         WHEN ROLE = '2' THEN 'Editor'
         ELSE 'Viewer' 
    END AS USER_ROLE
FROM USER_ROLES;

COALESCE を使ったNULL処理

データの値がNULLの場合にデフォルト値を設定するには COALESCE を使用します。

SELECT COALESCE(USER_NAME, 'Unknown') AS USER_NAME FROM USERS_TABLE;

このSQLでは、USER_NAME がNULLの場合に 'Unknown' というデフォルト値を返します。

用途:

  • ユーザー名が未登録の場合に「Unknown」を表示。
  • NULLを含むデータを処理しやすくする。

まとめ

UNION は、異なるテーブルのデータを統合する際に便利な演算子です。

  • UNION重複を削除 して統合。
  • UNION ALL重複を保持 したまま統合。
  • 固定値を追加 することで、リストに必ず特定の値を含める。
  • CASECOALESCE を使うと、データを柔軟に加工可能。
ebi_yuebi_yu

自動テストのAAAパターン

  • Action(準備),Act(実行Assert(確認)を明確にセクション分けしようねという手法
  • それぞれのセクションでコメントアウトを入れておくとよい
  • テストルールの展開時によいかも
test('商品追加時の合計金額が正しく計算されること', () => {
  // Arrange(準備)
  const cart = new ShoppingCart();
  const testItems = [
    { item: 'apple', price: 100 },
    { item: 'banana', price: 150 }
  ];
  const expectedTotal = 250;

  // Act(実行)
  testItems.forEach(({ item, price }) => {
    cart.addItem(item, price);
  });
  const actualTotal = cart.getTotal();

  // Assert(確認)
  expect(actualTotal).toBe(expectedTotal);
});

https://tech.anycloud.co.jp/articles/test-aaa/

ebi_yuebi_yu

Intl.DateTimeFormat

  • moment.jsやday.jsを使わなくても 日付を整形できるJavaScriptの組み込みAPI
  • インスタンスの生成がメモリを食うらしいので、使いまわしたほうが良い
const jsFormatDate = new Intl.DateTimeFormat('ja-JP', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
    timeZone: 'Asia/Tokyo',
}).format(new Date());
console.log(jsFormatDate); // 2025/03/13 16:23:32

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat

https://zenn.dev/herp_inc/articles/js-intl-date-time-format-performance

ebi_yuebi_yu

PostgreSQLの文字列結合・NULL処理まとめ

関数/演算子 目的 NULLの扱い 特徴・使い分け
COALESCE(A, B) NULLを別の値に置き換える 最初の非NULL値を返す NULL対策。空文字やデフォルトの設定に便利
CONCAT(A, B) 文字列を連結する NULLは空文字として扱う 安全に連結できる(NULLを無視ではなく空文字化)
CONCAT_WS('区切', A, B) 区切り付きで連結 NULLは完全に無視 区切り文字つきの連結。NULLはスキップされる

使用例

-- 姓と名を空白で連結(NULL安全)
CONCAT_WS(' ', last_name, first_name)'山田 太郎'

-- NULLなら空文字にして連結
CONCAT(COALESCE(last_name, ''), COALESCE(first_name, ''))'山田太郎'

文字列結合を使った検索処理(concat と LIKE の組み合わせ)

文字列結合を使った検索処理(concat と LIKE の組み合わせ)
SQLの concat 関数を使うことで、複数のカラムを1つの文字列として連結し、LIKE 演算子を使って部分一致検索を行うことができます

# first_nameカラムとlast_nameカラムを結合した結果をLIKEで検索
SELECT * FROM users
WHERE CONCAT(first_name, ' ', last_name) LIKE '%山田%';
ebi_yuebi_yu

Vue 3 nextTick: 完全ガイド

nextTickとは

nextTickは、Vue.jsのリアクティブな状態変更後のDOM更新サイクルを制御するための強力なユーティリティ関数です。Vue.jsの内部メカニズムにおいて、状態の変更は即座にDOMに反映されるのではなく、効率的な更新のためにバッチ処理されます。

技術的な背景

  • 非同期更新: Vue.jsは性能最適化のため、状態変更を即座にDOMに反映させません
  • バッチ処理: 複数の状態変更を一括して処理し、不要な再レンダリングを防ぎます
  • マイクロタスクキュー: nextTickは内部的にPromiseやMutationObserverを使用して次のDOM更新サイクルを待機します

実践的なユースケース

1. DOM更新後の要素操作

<template>
  <div>
    <input v-if="isEditing" ref="inputRef" type="text" />
    <button @click="startEditing">編集開始</button>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick } from 'vue'

const inputRef = ref<HTMLInputElement | null>(null)
const isEditing = ref(false)

function startEditing() {
  isEditing.value = true
  
  nextTick(() => {
    // DOM更新後に実行されるため、inputRefは存在する
    inputRef.value?.focus()
  })
}
</script>

なぜnextTickが必要か

  • 直接的な問題:

    1. v-ifによる条件付きレンダリングは非同期
    2. 入力フィールドのDOM生成と同期が必要
  • 使用しない場合のリスク:

    function startEditingWithoutNextTick() {
      isEditing.value = true
      inputRef.value?.focus() // ❌ inputRefはnullになる可能性が高い
    }
    

2. サードパーティライブラリとの統合

<template>
  <div>
    <canvas ref="chartContainer"></canvas>
    <button @click="updateChart">チャート更新</button>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import Chart from 'chart.js/auto'

const chartContainer = ref<HTMLCanvasElement | null>(null)
const chartData = ref([1, 2, 3, 4, 5])
let chart: any = null

onMounted(() => {
  if (chartContainer.value) {
    chart = new Chart(chartContainer.value, {
      type: 'line',
      data: {
        labels: ['A', 'B', 'C', 'D', 'E'],
        datasets: [{
          label: 'データ',
          data: chartData.value,
          borderColor: 'rgb(75, 192, 192)',
        }]
      }
    })
  }
})

function updateChart() {
  chartData.value = [5, 4, 3, 2, 1]
  
  nextTick(() => {
    if (chart) {
      chart.data.datasets[0].data = chartData.value
      chart.update()
    }
  })
}
</script>

なぜnextTickが必要か

  • 描画のタイミング問題:

    1. データ更新後、チャートライブラリは即座に更新できない
    2. Vue.jsの仮想DOMが更新されるまで待つ必要がある
  • 使用しない場合のリスク:

    function updateChartWithoutNextTick() {
      chartData.value = [5, 4, 3, 2, 1]
      chart.data.datasets[0].data = chartData.value
      chart.update() // ❌ データと表示の同期が取れない可能性がある
    }
    

3. 複雑な状態更新後の処理

<template>
  <div>
    <div v-if="isLoading">読み込み中...</div>
    <ul v-else>
      <li v-for="(item, index) in items" :key="index" class="item">
        {{ item }}
      </li>
    </ul>
    <button @click="fetchAndProcessItems">アイテム取得</button>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick } from 'vue'

const items = ref<string[]>([])
const isLoading = ref(false)

async function fetchItems() {
  // 実際のAPIコールを想定
  return new Promise<string[]>((resolve) => {
    setTimeout(() => {
      resolve(['アイテム1', 'アイテム2', 'アイテム3'])
    }, 1000)
  })
}

async function fetchAndProcessItems() {
  isLoading.value = true
  
  try {
    const newItems = await fetchItems()
    items.value = newItems
    
    await nextTick()
    
    // DOM更新完了後、アニメーション適用
    document.querySelectorAll('.item').forEach((el, index) => {
      setTimeout(() => {
        el.classList.add('fade-in')
      }, index * 100)
    })
  } finally {
    isLoading.value = false
  }
}
</script>

<style>
.item {
  opacity: 0;
  transition: opacity 0.5s ease;
}
.fade-in {
  opacity: 1;
}
</style>

なぜnextTickが必要か

  • レンダリングサイクルの非同期性:

    1. アイテムリストの更新は非同期
    2. DOMに新しい要素が追加されるまで待つ必要がある
    3. CSSアニメーションを正確に適用するためには、要素が確実に存在している必要がある
  • 使用しない場合のリスク:

    async function fetchItemsWithoutNextTick() {
      const newItems = await fetchItems()
      items.value = newItems
      
      document.querySelectorAll('.item').forEach(el => {
        // ❌ DOMがまだ更新されていない可能性があり、
        // 要素が存在しないか、古い要素に対して操作を行う可能性がある
        el.classList.add('fade-in')
      })
    }
    

注意点と制限事項

  • 過度なnextTickの使用は非効率的になる可能性があります
  • アプリケーション全体のパフォーマンスに影響を与える可能性があるため、必要な場合にのみ使用しましょう
  • nextTickは現在のコンポーネントのDOM更新サイクルを保証しますが、Suspenseを使用した非同期コンポーネントの完全な読み込みを保証するものではありません。

まとめ

nextTickは、Vue.jsのリアクティブシステムにおける非同期性を適切に管理するための重要なユーティリティです。状態変更後の確実な操作を保証し、複雑な更新シナリオを効果的に処理します。

  1. 非同期性の理解: Vue.jsのリアクティブシステムは状態変更を即座に反映しない
  2. タイミングの制御: DOM更新のタイミングを正確に制御する必要がある
  3. 状態と描画の同期: リアクティブな状態変更後のDOM操作にはnextTickが不可欠
ebi_yuebi_yu

PostgreSQL COLLATE(コレート)入門解説(初心者向け)

PostgreSQL では「COLLATE(コレート)」を使うことで、文字の並び順や比較の方法(例:大文字・小文字の違いをどう扱うか)を変更できます。日本語の「あいうえお」順に並べたり、英語の大文字と小文字を区別せずに比較したいときなどに便利です。

出典:公式ドキュメント(PostgreSQL v16)https://www.postgresql.org/docs/current/collation.html


✅ COLLATEってなに?

COLLATE(照合順序)は、文字列を比較したり並べ替えたりする際のルールです。

例えば:

  • 「Taro」と「taro」を同じと見なすかどうか?
  • 「あ」「い」「う」などを正しい日本語の並び順にしたい

こういった「どう比較・並び替えするか」を決めるのが COLLATE です。

そして、この COLLATE の中で使われるのが「ロケール(locale)」という設定です。


✅ ロケール(locale)とは?

ロケールとは、国や言語に応じたルールの集合のことです。PostgreSQL においては、以下の2つが重要です:

  • LC_COLLATE:文字列の並び順(ORDER BY の順番など)
  • LC_CTYPE:文字の種類(大文字・小文字、正規表現の動作など)

たとえば:

  • ja_JP.utf8:日本語のルール(あ→い→う... の順で並べる)
  • en_US.utf8:英語のルール(A→B→C... の順)
  • C:バイナリ比較(文字コード順で厳密に比較)

このロケールによって、COLLATE の挙動が決まるわけです。


✅ 基本の書き方

SELECT * FROM テーブル名
WHERE カラム名 COLLATE 照合順序 = '比較したい文字列';

🔸 COLLATE の使い方(PostgreSQL編)

1. 通常の比較 vs COLLATE を使った比較

-- 通常の比較(デフォルトの照合順序で比較)
SELECT * FROM users WHERE username = 'Taro';

-- COLLATE "C" を使った比較(大文字・小文字を厳密に区別)
SELECT * FROM users WHERE username COLLATE "C" = 'Taro';

✅ どう違うの?

  • username = 'Taro' は、データベース作成時に設定されたロケールのルールで比較されます。ロケールによっては大文字と小文字を区別しないこともあります。
  • COLLATE "C" は、大文字と小文字、記号もすべて区別する「バイナリ比較」です。

🔍 例:

SELECT 'Taro' = 'taro';              -- ロケールによって true になることもある
SELECT 'Taro' COLLATE "C" = 'taro'; -- 常に false

2. 並べ替え(ORDER BY)で使う

SELECT name FROM users
ORDER BY name COLLATE "ja_JP.utf8";

➡️ 日本語の「五十音順(あいうえお順)」で並べ替えたいときに有効です。


3. テーブル作成時に COLLATE を指定

CREATE TABLE users (
  name TEXT COLLATE "ja_JP.utf8"
);

➡️ これで name カラムは常に日本語の照合順序(ja_JP)で比較されます。


4. 大文字・小文字を無視した比較(ILIKE)

SELECT * FROM users WHERE username ILIKE 'taro';

➡️ PostgreSQL 独自の ILIKE は、「大文字・小文字を無視」して文字列を比較できます。
初心者にはまずこちらを使うのが簡単でおすすめです。


🔧 ロケール(照合順序)の切り替え方法

ここからは、照合順序の元となるロケールを切り替える方法を解説します。

🔍 注意:PostgreSQL では「データベースを作成するとき」にロケールを決めます。あとから変更はできません。

ステップ1:使えるロケールを確認する

Linux/macOS ターミナルで:

locale -a

出力例:

C
C.UTF-8
en_US.utf8
ja_JP.utf8

ja_JP.utf8 がない場合は、OS にロケールを追加してください。

ステップ2:新しいロケールでデータベースを作成

CREATE DATABASE mydb
  LC_COLLATE = 'ja_JP.utf8'
  LC_CTYPE = 'ja_JP.utf8'
  ENCODING = 'UTF8'
  TEMPLATE = template0;

各パラメータの意味

  • LC_COLLATE:文字列の並び順(ORDER BY)に影響
  • LC_CTYPE:文字の分類(大文字・小文字、正規表現)に影響
  • template0:ロケール変更を許可するベーステンプレート(重要)

ステップ3:データベースの現在のロケール確認

SELECT datname, datcollate, datctype
FROM pg_database
WHERE datname = 'mydb';

➡️ 上記で正しく設定されているか確認できます。

ステップ4:既存データベースのロケールを変更したい場合は?

直接変更できないため、以下の手順で対応します:

  1. pg_dump でデータをエクスポート
  2. 新しいロケール指定でデータベースを作成
  3. pg_restore または psql でインポート

📝 まとめ

やりたいこと 方法
大文字小文字を区別して比較 COLLATE "C" を使う
大文字小文字を無視したい ILIKE を使う
日本語のあいうえお順で並べる COLLATE "ja_JP.utf8" を使う
ロケールを変えたデータベースを使いたい CREATE DATABASE でロケール指定

COLLATE を正しく使えば、日本語や英語の並び順、文字列の一致チェックが正確になり、より使いやすいアプリケーションが作れます。

初心者のうちは ILIKE を活用して、大文字・小文字を気にせず検索し、必要に応じて COLLATE の知識を深めていくのがおすすめです。


💡 詳しくは PostgreSQL公式ドキュメント:
https://www.postgresql.org/docs/current/collation.html

このスクラップは6ヶ月前にクローズされました