スパゲッティコード(スパゲッティコード、ヌードルコード)— これは、論理ブロックが秩序なく絡み合った、混乱した混沌としたプログラム構造です。TIOBE Index(2024)の調査によると、スパゲッティコードのレベルが高いプロジェクトでは、新機能の実装に2.5倍の時間が必要です。この用語は、goto文がプログラムの任意のポイント間をジャンプできるようにしていた初期のプログラミング時代に生まれ、読み取り不可能な構造を作り出しました。
重要なポイント
スパゲッティコード — これは、コードの構造がスパゲッティの皿に似ていることを表す比喩です。個々の麺(論理ブロック)が絡み合い、くっつき、互いに分離できません。このようなコードでは、レイヤー、モジュール、またはコンポーネントを分離することは不可能です — すべてが一つの大きな塊に混ざっています。
単に不注意な可能性がある悪いコードとは異なり、スパゲッティコードは根本的なアーキテクチャ上の問題です。優れた変数名で完全にフォーマットされたコードでも、アーキテクチャが混沌としていればスパゲッティコードになり得ます。問題はプログラム構造のレベルにあり、書き方のスタイルではありません。
IEEE(2022)によると、大規模プロジェクトにおける全エラーの約35%は、開発者の論理エラーではなく、コード構造の絡まりによって引き起こされています。開発者はタスクを誤解したからではなく、スパゲッティコード内の実行フローを追跡できなかったために間違いを犯します。
悪いコードが単一の関数やファイルの規模での貧弱なコードであるのに対し、スパゲッティコードはアプリケーション全体の規模での貧弱なアーキテクチャです。ヌードルは個別に適切に書かれた関数で構成されていますが、それらの相互作用は混沌として予測不可能です。
“スパゲッティコード”という用語は、goto文への批判とともに1970年代に登場しました。初期のプログラミング言語(BASIC、FORTRAN、COBOL)では、gotoが実行フローを制御する主要な方法でした。プログラムは番号付きの行のシーケンスであり、gotoはそれらのいずれかにジャンプすることを可能にしました。これにより、解きほぐすことが不可能なジャンプの“もつれ”が生まれました。
1968年、エドガー・ダイクストラは彼の有名な書簡“Go To Statement Considered Harmful”を発表し、構造化プログラミング時代の幕開けとなりました。ダイクストラは、任意のアルゴリズムがgotoなしで、順次、分岐(if)、ループ(while)の3つの構造のみを使用して実装できることを証明しました。これは現代のプログラミングの基礎となりました。
構造化プログラミングは問題を完全には排除しませんでした。スパゲッティコードは新しいレベルに移行しました — 物理的なgotoの代わりに、開発者は論理的な“goto”を作り始めました。グローバル変数、JavaScriptのコールバック地獄、複雑な呼び出しチェーン、コンポーネント間の暗黙的な依存関係などです。問題は残り、形だけが変わりました。
コールバック地獄、深くネストされたPromise、エラー処理のないasync/await、誰がいつトリガーするのか誰も理解できないイベント — これらはすべてスパゲッティコードの現代的な変種です。アンチパターンは健在で繁栄していますが、今はgoto文を使用していないだけです。
レイヤーの欠如 — 最初で主要な兆候。スパゲッティコードでは、ビジネスロジック、データベース操作、HTMLマークアップ、ネットワーク通信がすべて1つのファイルまたは1つのメソッドに混在しています。データベースクエリを変更すると、UI表示が壊れる可能性があります。これらのレイヤーのコードが分離されていないからです。
グローバル変数とシングルトン — 2番目の明らかな兆候。アプリケーションの状態がグローバルオブジェクトに保存されると、実行フローが予測不可能になります。任意の関数がグローバル状態を変更でき、それがどこでいつ発生したかを追跡することは事実上不可能です。
神クラスと神関数 — 3番目の兆候。ビジネスロジック、表示、データ操作を処理する2000行以上のクラス — これは典型的なスパゲッティコードです。10個のパラメーターを受け取り、5つの異なることを行う関数 — これもスパゲッティです。
| 兆候 | 説明 | 例 |
|---|---|---|
| レイヤーの混在 | UIコード内のSQLクエリ | データベースに直接書き込むコントローラー |
| グローバル変数 | どこからでもアクセス可能な状態 | すべてのクラスのstatic SessionManager |
| 神クラス | 1つのクラスがすべてを行う | 3000行のOrderManager |
| 長いメソッド | 分割のない関数 | 5つの責任を持つ200行のメソッド |
| コールバック地獄 | 無限にネストされたコールバック | JavaScriptでの6レベルのネスト |
もし15個のモックオブジェクトを作成せずに関数の単体テストを書けないなら — それはスパゲッティコードです。単一のモジュールをテストするためにアプリケーション全体のインフラを起動する必要があるなら — それはスパゲッティコードです。テスト不能性は、絡まったアーキテクチャの客観的な指標です。
アーキテクチャ設計の欠如 — 最も一般的な原因です。チームが計画なしでコードを書き始め、アーキテクチャを“その場しのぎ”で選択すると、結果は必然的にスパゲッティになります。新しい機能は、論理的に属する場所ではなく、“今都合の良い”場所に追加されます。
進化的開発 — 2番目の原因。プロジェクトは小さなスクリプトとして始まり、機能が追加され、アプリケーションになり、そしてモノリスになります。その間、アーキテクチャは見直されません。100行のコードで機能していたものが、100,000行では災害になります。
SOLID原則の違反 — 3番目の原因。特に単一責任の原則(S)と依存関係逆転の原則(D)です。クラスがすべてに責任を持ち、依存関係が硬直的で、モジュールが密結合している場合 — スパゲッティコードになります。
納期とホットフィックス文化 — スパゲッティコードの触媒です。“昨日必要だった”場合、開発者はアーキテクチャを考えずに最初の利用可能な場所にコードを挿入します。このようなホットフィックスが10個あると — アプリケーションのアーキテクチャは破壊されます。
主な結果 — コードベースの制御不能。開発者はアプリケーションが全体としてどのように機能するかを理解しなくなります。ある場所の変更が、一見無関係な別の場所を壊します。各パッチが2つの新しいバグを生み出します。チームは“変更への恐れ”の状態に入ります。
チームの生産性は指数関数的に低下します。Microsoft Research(2023)は、スパゲッティコードで新機能を追加する時間がコードベースサイズに対して2次的に増加することを示しました。クリーンアーキテクチャの場合、この増加は線形です。その差は50,000行以上のコードで重要になります。
セキュリティ — もう一つの犠牲者。スパゲッティコードでは、未処理の例外、誤った入力検証、データ漏洩を見逃しやすくなります。複雑なアーキテクチャのプロジェクトでのセキュリティ監査は事実上不可能です — ユーザー入力が使用されているすべての場所を見つけることは非現実的です。
離職率はスパゲッティコードのあるプロジェクトで平均以上です。経験豊富な開発者は“ヌードル”での作業を望まず去っていきます。新しい従業員はコードを理解できず、最初の数ヶ月で離職します。プロジェクトは専門知識を失い、コード品質をさらに悪化させます — 悪循環です。
第一に — レイヤーの分離から始めます。コードを3つのレベルに分割します。プレゼンテーション(UI、コントローラー)、ビジネスロジック(サービス、ユースケース)、データアクセス(リポジトリ、DAO)です。部分的な分離だけでも構造が即座に改善され、コードがテスト可能になります。
第二に — 依存性注入を実装します。コンストラクターやパラメーターを介して依存関係を渡すことで、依存関係の直接作成を置き換えます。これによりコンポーネント間の硬直した接続が断ち切れ、各モジュールを分離してテストできます。
第三に — 神クラスと神関数を抽出します。それらを単一責任を持つ小さなクラスとメソッドに分割します。複雑なサブシステムを簡素化するためにFacadeパターンを使用します。覚えておいてください:20行のクラスは2000行のクラスよりも明確です。
// スパゲッティ — すべてを一つのメソッドに
function handleRequest(req, res) {
const db = new Database("mysql://...");
const user = db.query("SELECT * FROM users WHERE id =" + req.params.id);
let html = "";
html += ""
+ user.name + "";
html += "Balance: "
+ user.balance + "";
html += "";
res.send(html);
}
// クリーンアーキテクチャ — 分離されたレイヤー
class UserController {
constructor(userService) {
this.userService = userService;
}
async getUser(req, res) {
const user = await this.userService.findById(req.params.id);
res.json(new UserResponse(user));
}
}
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async findById(id) {
return await this.userRepository.findById(id);
}
}
一度にコードベース全体を書き換えようとしないでください — それは確実な失敗です。1つのモジュールを選び、現在の動作をキャプチャする特性テストを作成し、その後にのみリファクタリングします。徐々に、モジュールごとに、スパゲッティを解きほぐしていきます。
アーキテクチャ計画 — 予防の基礎。開発を開始する前に、アーキテクチャスタイルを承認します:MVC、MVVM、Clean Architecture、VIPERなど。選択を正当化するADR(Architecture Decision Record)を書きます。コードレビューでアーキテクチャの遵守を要求します。
依存関係逆転の原則(DIP)— スパゲッティコードに対する強力なツール。高レベルモジュールは低レベルモジュールに依存すべきではありません。両方とも抽象化に依存すべきです。依存性注入はこの原則の実用的な実装です。
テスト — 最善の予防策。コードの前にテストを書く(TDD)と、必然的に疎結合のコンポーネントを設計します。テスト可能なコードは適切に構造化されたコードです。テスト不可能なコードはほとんどの場合スパゲッティコードです。
SonarQube — 循環的複雑度、継承の深さ、メソッドサイズを追跡します。JDepend(Java)— パッケージ間の依存関係を測定します。PhpMetrics — PHPプロジェクトの保守性指標を提供します。CI/CDでメトリクスを監視し — 事後対応ではなく、ヌードルの発生を予防します。
よくある質問
はい、段階的なリファクタリングが推奨されます。Strangler Figメソッドを使用して — アプリケーションを停止せずに古いコンポーネントを新しいものに徐々に置き換えます。データレイヤーまたはビジネスロジックを分離することから始めます。機能を失わないように、変更前に古いコードをテストでカバーします。
スパゲッティコード — アプリケーションの全レイヤーが混沌と絡み合ったものです。ラザニアコードは厳格な多層アーキテクチャですが、各レイヤーが非常に分離されているため、それらの間のデータ転送が官僚的になります。どちらのアンチパターンも有害ですが、スパゲッティコードはコードを予測不可能にするため、より危険です。
依存関係を見てください:モジュールがアプリケーションのすべてのレイヤーからモジュールをインポートしている場合 — それは疑わしいです。メソッドのサイズに注意してください — 30行を超えると通常は悪いです。関数がUI作業、ビジネスロジック、データを混在させているかどうかを確認してください。もしそうなら — それはスパゲッティコードです。
Robert MartinのClean ArchitectureとHexagonal Architecture(Ports & Adapters)— 2つの最良のアプローチです。どちらもレイヤー分離、フレームワークからのビジネスロジックの独立性、テスト可能性を保証します。モバイル開発には — Repositoryパターンを用いたMVVM。
部分的に。循環的複雑度(McCabe)、モジュール結合度、継承ツリーの深さ(DIT)などのメトリクスは、潜在的なスパゲッティコードを示します。SonarQube、CodeClimate、PhpMetricsはこれらのメトリクスを自動的に計算します。ただし、完全な診断には人間によるアーキテクチャ分析が必要です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。