スパゲティプログラム

スパゲティプログラムまたはスパゲティコードとは、コンピュータプログラムの状態を指すための表現であり、命令の実行順が複雑に入り組んでいたり、遠く離れた関連性の薄そうなコード間で共通の変数が使われていたりするなど、処理の流れや構造が把握しにくい見通しの悪い状態になっているプログラムのことである。スパゲッティプログラム、スパゲッティコードとも表記される。

名称の由来は、皿に盛られたスパゲッティのように実行される箇所の線が絡み合っていることから。「パスタプログラム」とも呼ばれる。

1. 概要
-----

命令型プログラミングでは、プログラムはコンピュータに対する手順書であり、プログラムに書かれた通りの順でコンピュータに対する指示が次々に出され、それをコンピュータが解釈することで動作していく。中間表現と仮想機械を利用する形態もあるが、最終的には機械語としてコンピュータが直接解釈可能な命令に変換される。プログラムの状態を変化させながら、プログラムに書かれた通りに文を次々に実行していく。基本的には上から下に辿る順でプログラム行が1文ずつ実行されていくが、行番号や何らかのラベルを指定して遠く離れた場所にジャンプする命令や、サブルーチン呼び出しと復帰など、遠く離れた場所にジャンプして別のコードを実行した後、また元の位置に戻る、といった命令もある。スパゲティプログラムというのは、規律のない不用意なジャンプの多用によって命令実行の順番が複雑に入り組んでいたり、プログラムの状態を管理するための変数が遠く離れた場所で読み書きされていたりと、まさにスパゲティがこんがらがったような状態になったプログラムのことである。

スパゲティプログラムは、プログラムのテストを実施したり、内部動作解析やデバッグのためにプログラムをステップ実行によりトレース1命令や1行だけを実行させ、命令ごとあるいは行ごとの状態が正常かどうかひとつひとつ確認すること。}}したりすることが困難になる。結果として、プログラムのスムーズな開発や完成を妨げる。またソフトウェアを改良したり拡張したりすることも困難にする。

スパゲティプログラムは、date=2023-09、まだ構造化プログラミングという知識・手法がプログラマたちに十分に普及しておらず、また仕様上構造化プログラミングが不可能なBASICのようなプログラミング言語が使われてしまっていた状況でしばしば発生した。

たとえ構造化プログラミングをサポートする現代的な環境であっても、変数名などが不適切で分かりづらいソースコード大規模プロジェクトでは[[命名規則 1つの変数に複数の意味・役割を持たせて使いまわしすると、変数名も不適切・あいまいになりやすく、コードの可読性やメンテナンス性が低下する。}}や、複雑な条件分岐、あるいは処理内容および機能のまとまりに応じて適切に分割されていない長大なサブルーチンおよび巨大なクラスなどによって、可読性やメンテナンス性の欠如したスパゲティプログラムは容易に発生しうる。

ソースコードの可読性という概念は、読み手の技量や知識にも左右される相対的な指標であるため、初心者にとって難解なコードが必ずしもスパゲティプログラムとは言えない。しかし、むやみに技巧を凝らしたコードや、処理速度などの最適化のために可読性・汎用性・拡張性などを犠牲にしたコードは、一見では何をしたいのか理解できず、経験を積んだプログラマであっても内容の理解に時間を要したり誤解してしまったりすることがある。OSやAPI・ライブラリの不具合を回避するためにアプリケーションソフトウェア側で仕方なく必要とされるようなコードは、一見では無意味に思えるものもある。また、たとえ自分自身が書いたコードであっても、後から読み返すときには詳細を忘れてしまっていることも多い。そのようなとき、仕様書やソースコード中のコメントに十分な説明がなかったり、あるいはコードの修正によって仕様書やコメントが実装と乖離していたりすると、たちまち解読不能なスパゲティコードと化してしまう。

サブルーチンやクラスを最初に実装したときは、整然とした読みやすいコードになっていたとしても、機能追加や仕様変更に対応するためにコードを修正し、状態を管理・保持するための変数や条件分岐などが増えていくにつれて、徐々に読みやすさが失なわれ、設計も陳腐化していき、気付いたときにはスパゲティコードになっていることもある。

そういった、現代では当たり前になっている手法を実践できていないような、低品質なプログラムがスパゲティプログラムである。

1.1. スパゲティプログラムではないもの
---------------------

単にプログラム規模に見合っていてソースファイルの数が多かったりバイナリのサイズが相応に大きかったりするだけのプログラムや、リバースエンジニアリング防止のためにソースコード解析を意図的に困難にしたプログラムなどは、「スパゲティプログラム」には分類されない。

たとえば動的なウェブページの表示に必要とされるJavaScriptで書かれたプログラムの中には、コメントもなく、スペースや改行なしで変数名なども極端に短く、可読性を欠いたようなコードとなっているものもあるが、これはファイルのダウンロードおよびウェブブラウザによるスクリプト解釈の高速化を目的としたミニファイ処理や、ソースコードを解析しづらくする難読化を行うプログラミングツールによって自動生成されたコードであり、変換元のソースコードがそのようになっているわけではない。人間が読むことを目的としていない、自動生成されたコードをスパゲティコードと呼ぶことはない。

2. スパゲティプログラムの要因
----------------

2.1. goto文の濫用
-------------

スパゲティプログラムを作り出す原因としてよく挙げられるのが、goto文の濫用である。BASICなどの言語にあるgoto文は、機械語やアセンブリ言語のアドレス指定ジャンプ命令に近い特性を持ち、無条件に指定した行番号の位置までジャンプする。これはサブルーチンやループなどの制御構文を利用した制御に比べ、処理の流れを混乱させる。離れた位置に書かれた行へ飛ばせるので、もちろんソースコードの可読性も低下させ、不具合や欠陥を含んだコードを書いてしまう原因にもなる。

特に構造化されていないBASICは、各行に「行番号」があり、その行番号をジャンプ先として指定してジャンプするという原始的な機能だった。BASICにおいて、goto文はプログラム中のあらゆるところで登場し、プログラムがif文で条件分岐する際、goto文を使ってジャンプするということもよくあった。return文を持つサブルーチン機能もあるものの、GOSUBでサブルーチンに飛ぶためにはやはり行番号を指定する必要があり、goto文でサブルーチンの途中に飛ぶような危険なコードも記述可能だった。結果として、低品質なスパゲティプログラムが氾濫し、人々を悩まし、構造化以前のBASICは酷評されるようになり、BASIC自体が廃れてしまった。のちに全く別系統の、行番号やGOTO文を廃して構造化プログラミングを可能とした「構造化BASIC」が登場した。構造化BASICの子孫は、Visual Basic for Applications  やVisual Basic .NET  といった形で生き残っており、2023年現在も実用途で使われ続けている。

構造化プログラミングを可能とする構文機能を備えたPascalやC言語では、一応goto文もサポートされていたものの、BASICのような無条件のジャンプ命令ではなく、サブルーチンを飛び越える大域ジャンプには使えないものだったただしCには大域ジャンプを可能とするsetjmpとlongjmpも用意されていた。}}。

さらにC++やJavaといった後発言語では、例外処理や多重ループを抜けるためのラベル付きbreak文を用意し、基本的には悪しきgoto文は使用しないようになった。C#にもgoto文は用意されているが、多重ループの脱出にgoto文を用いることは推奨されておらず、メソッドとして抽出してreturn文を用いることが推奨されている。

なお次のような場合、goto文を使うことが容認される場合や使わざるを得ない場合もあった。

C言語等でエラー発生時の後始末を記述する場合、goto文を使うことでエラー処理をまとめて書きやすくなることがあるC++やObject Pascalにはデストラクタがあり、C#やJavaではusing文。クラスのメンバー変数に関しても、同じインスタンスのメンバー関数であればどこからでもアクセスできるため、使い方によってはグローバル変数と似たような問題を抱えることになる。JavaやC#には、C/C++のような名前空間スコープに直接定義できるグローバル変数はないが、クラスには静的フィールドを定義することができる。静的フィールドはグローバル変数とほぼ同様の性質を持ち、自クラス外への公開性に関するアクセス制限をかけることはできるものの、グローバル変数と同様の問題点を持つ。

Singleton パターンは、静的ローカル変数や静的フィールドを使って実装されることが多いが、やはりグローバル変数と同様の問題を抱えることになるため、むやみに多用すべきではない。

C/C++では、グローバル変数やクラスの静的メンバー変数の初期化順序は、1つの翻訳単位内では定義した順すなわち上から下に初期化されるが、異なる翻訳単位の間の初期化順序は不定である。そのため、翻訳単位を超えたグローバル変数や静的メンバー変数の初期化順序に依存したコードを書くと未定義動作を引き起こす。これもまたスパゲティコードの一種である。

ローカル変数のみを使用するサブルーチンは入出力が明確になり、部品化・再利用・テストもしやすくなるが、グローバル変数のようなサブルーチンをまたいでアクセス可能な変数を使用するサブルーチンは入出力が不明瞭になり、部品化・再利用・テストをしにくくなる。

なお、ローカル変数であっても、ポインタによるアドレス渡しや参照渡しによって、複数のサブルーチンから参照されうる。引数として渡された複数のポインタや参照が、それぞれ別の変数を参照していることを仮定する仕様のサブルーチンに、同じ変数へのポインタを渡すと、予期しない危険な動作を引き起こすこともある。クラスのメンバー変数をメンバー関数の引数としてアドレス渡しや参照渡しする場合も、同様の問題が発生しうる。コピー代入演算子やムーブ代入演算子では、自己代入に備えなければならない。ポインタや参照はメモリ上のオブジェクトの実体にエイリアスを与えるものであり、前述のような注意点に配慮せず不用意に使うと、グローバル変数のように思わぬタイミングで内容が書き換わってしまうこともあり、未定義動作やスパゲティコードの原因になることがある。

2.2. 継承の濫用
----------

オブジェクト指向を取り入れたプログラミング言語においても、継承を機能追加のために濫用し、クラス間の関係が複雑になりすぎてしまうことでスパゲティ化が起こることがある。特に多重継承はメンバーの名前の衝突や菱形継承などの問題を抱えているため、多重継承はアンチパターンとされた。C++では仮想継承を使うことで菱形継承問題を回避できるが、オブジェクトサイズが肥大化するなどの別の問題もある。Delphi、Java、C#などの言語ではインターフェイスの多重継承のみが許可され、実装の多重継承は禁止された。多重継承だけでなく単一継承による差分プログラミングも、拡張性や互換性を意識してスーパークラスを慎重に設計する必要があり、むやみに使うとプログラムの複雑化やメンテナンス性の低下を招く。継承はgoto文と同じくらいプログラムを分かりにくくする要因であるという意見もある。『Effective C++』や『Effective Java』のような書籍では、機能の追加には継承よりもオブジェクトコンポジションを利用することがベストプラクティスとして推奨されている。

2.3. 不適切なマルチスレッドプログラミング
-----------------------

並行処理や並列処理のために複数のスレッドを使用するマルチスレッドプログラミングでは、処理の流れが1つではなく、同時に並行動作する複数のスレッドが互いに協調し合う必要があり、不用意にスレッドを使うとスパゲティ化を招きやすい。データ競合による未定義動作や競合状態による意図しない動作が発生しないよう、スレッドセーフを意識して慎重にプログラミングする必要がある。排他制御の作法を誤ると性能低下やデッドロックのような問題も発生する。特にマルチスレッドのバグはタイミングによって異常が発生したりしなかったりすることもあるため、シングルスレッドのプログラミングよりもデバッグの難易度が高い。

マルチスレッドプログラミングを簡略化し、コードの信頼性を向上するために、構造化並行性  と呼ばれる概念についても議論されている。

2.4. コールバックの多用
--------------

コールバックを多用するプログラム、特にイベント駆動型プログラミングもスパゲティコードを招きやすい。例えばGUIアプリケーションは常にユーザー操作に対する応答ができるようになっていることが重要であり、イベントループを持ちユーザー応答をつかさどるメインスレッドでネットワーク通信やストレージI/Oなどの長時間かかる可能性のある処理をその場で同期的に実行するとアプリケーションが応答停止することがある。そのため、いったん他のスレッドやプロセスに実際の処理を任せるようにリクエストを発行した後、処理結果をコールバック関数の引数などの形で非同期的に受け取るようなイベント通知スタイルを採用する必要があるが、その結果を受けて次に実行する処理をさらにコールバック関数で記述して……といったように、非同期処理のコードは一連の流れを把握しづらいスパゲティスタックとなりやすい。この問題を緩和するために、Future パターンに対応したライブラリや、それを発展させたasync/await構文をサポートする言語なども登場している。

2.5. 動的結合の濫用
------------

プログラムのカスタマイズポイントを提供するコールバック関数、オブジェクト指向のポリモーフィズムを実現する仮想関数、ダック・タイピングに使われるリフレクションのような動的結合は、アルゴリズムの再利用性向上のために有用な機能だが、実行時でなければ実体の特定ができず、統合開発環境の機能を使っても呼び出し構造や依存関係を直接追跡できないため、濫用するとスパゲティコードを招きやすい。関数オーバーロードや演算子オーバーロード、C++のテンプレートのような静的結合であっても、テンプレートメタプログラミングなどで濫用するとスパゲティコードを招きやすくなることもある。

3. しかたなくスパゲティプログラムにしてしまった事例
---------------------------

現在ではスパゲティプログラムは絶対に書いてはいけないとされている。現在ではメモリ量はふんだんにあり、可読性が重要で、メンテナンスやコードの改変が容易で、システムが安定して動作することが重要だということが理解されているからである。

だが特に1980年代ころまでの、まだ構造化されていないプログラミング言語が横行していていた時代で、構造化前のプログラミング言語しか動かないマシンで限られたメモリにプログラムをおさめなければならなかった状況下では、しかたなくGoto文が使われることがあった。数バイトでも削減したいような、追い詰められた開発環境では、無理やり詰め込むため、現代から見れば「あり得ない」「許容されない」ような、粗野なプログラミング手法ですら使われた。

一部のシステムでは、CPUのバグを突くようなトリッキーなプログラミング手法がしかたなく選ばれていたものがあり、ソースコードやアセンブリコードを普通の読み方で読んだだけではとても理解できないようなジャンプが起き、まさにプログラムのスパゲティ化を招いた。例えばファミコンやApple IIなどのゲームにおいては、65C02の仕様書に記載されていない未定義命令を利用することが常套化していた。特にファミコン後期においては、競合機や次世代機の登場によってファミコンのゲームに対する性能への要求が強くなり、また視覚的キャラクタに割り当てられるデータ量・メモリ量の増大によってプログラムのほうに割り当てられるバイト数が減ってしまい、本来してはいけないはずのコーディング手法が、ますます横行するようになってしまった。date=2023-09

4. スパゲティプログラムが許容、放置された具体例
-------------------------

1980年代のメモリ量が限られていた時代には、プログラムのバイト数を圧縮する技を競い合う誌上コンテンストなどが開催されていて、そこではスパゲティプログラムが許容された。例えば、雑誌MSX・FANの投稿プログラムコーナー『ファンダム』で事実上のスパゲティプログラムが許容されてしまっていた。特に「1画面プログラム」すなわち行番号を含めた40×24文字のショートプログラムを多数採用・掲載しており、行から行へと、標準的ではない手法でジャンプをさせるようなコードが横行した。

たとえば次のようなものである。

行内の分岐に関わるIF文の使用を極力避けてしまう 
NEXTの対象変数を書かないたとえばBASICにおけるトリガー入力待機ルーチンは通常 10 IF STRIG=0 THEN GOTO 10 と記述するのに対し、1画面プログラム採用作品では 10 FORI=0TO1:I=-STRIG:NEXT という記述が用いられていた。

中には「マシン語ソースをアスキーコード制御文字列を含まない範囲の8ビット値にシフトさせ、そのキャラクターコード群とデコーダのみ記述する」という可読性が全く無いプログラムが採用される事もあった。

別の例「*」をコントローラーで左右に動かすプログラムを一般的な記法で書いたもの。
条件分岐させ水平座標、Xが 0未満、28を超えないように加減算する。 10 CLS 20 LET X=14 30 LOCATE X,10:PRINT " "; 40 LET S=STICK 50 IF S=3 THEN X=X+1: IF X>28 THEN X=28 60 IF S=7 THEN X=X-1: IF X<0 THEN X=0 70 LOCATE X,10:PRINT "*"; 80 GOTO 30

以下はよく用いられていた圧縮方法で書かれたソース。 1 CLS:X=14 2 LOCATE X,10:PRINT" ";:S=STICK:X=X-*+*:LOCATE X,10:PRINT"*";:GOTO 2

メイン部分を1行にまとめるために、条件分岐を省いている。
水平座標の変数 Xのはみ出しをチェックする関係式の評価を数値として扱い、1個の算術代入文としている。
しかしMSX BASICでは式の評価が 真ならば-1、偽ならば0 が返る為、一見すると変数 Xへの加算と減算が逆に見えてしまう。

その他また、五・七・五・七・七の三十一バイト でプログラムを記述するという曲芸的ジャンル「アセンブラ短歌」を考案した人も昔はおり、こうした作品でも31バイトの中でプログラムを完結させるためにスパゲティ状のジャンプを内部で行うものが多々あった。

5. スパゲティプログラムを修正する方法
--------------------

スパゲティプログラムは保守や機能追加を妨げるので、できることなら修正することが望ましい。しかし実務で使われているシステムは「スパゲティプログラムを修正した場合のメリットとデメリット」「修正せず、そのまま放置する場合のメリットとデメリット」を天秤にかけて、「とりあえずうまく動作しているプログラムは、滅多なことでは修正しない」ということが広く行われている。

各種の汎用オペレーティングシステム、ソフトウェア開発ツール、金融機関の基幹システム、産業用機械の制御ソフトウェア、業務用アプリケーションなど、実務で日々使用されているシステムでは安定性が非常に重要であり、不用意にコードを修正してうっかりシステムの安定性を損ねるとシステムに依存した業務が停止してしまい、ユーザーに多大な迷惑をかけるだけでなく、不具合によって発生した金銭的な損失に対して補償しなければならなかったり、果てはユーザーを失なったりする事態に陥るからである。

またスパゲティプログラムを修正するとしても、十分な解析やテストをせずにうかつに修正してしまうと、かえって既存機能や動作の互換性を損なったり、別のバグを追加してしまったり、修正されていたはずのバグを復活「寝たバグを起こす」「寝ているバグを起こす」とも形容される。}}させてしまったりする可能性が高いからである。時間や予算・人材が許す場合でも、この傾向は見られた。スパゲティプログラムを修正するとしても、コードが入り組んでいて解読や分割・分離が難しいことから、しばしば小手先の作業だけでは困難で、相当に大掛かりな作業になることが多い。

後にテストファーストの方法論が確立され、プログラム本体の完成と同時期にテストプログラムも作成されるようになると、プログラム変更の危険性は相対的に低くなり、不適切な状態のプログラムは積極的に修正することが奨励されるようになった。

なおあまりに酷い状態に陥っているスパゲティプログラムは、修正するのではなく、思い切って放棄してしまって、新たにゼロから整然と構造化したプログラムを書いたほうがよほど早い、ということもある。

6. 脚注
-----

6.1. 注釈
-------

6.2. 出典
-------

7. 関連項目
-------

技術的な債務
構造化プログラミング
リファクタリング 
IOCCC
MSX・FAN
アンチパターン