スパゲティプログラム
スパゲティプログラム(spaghetti program)またはスパゲティコード(spaghetti code)とは、コンピュータプログラムの状態を指すための表現(俗語)であり、命令の実行順が複雑に入り組んでいたり、遠く離れた関連性の薄そうなコード間で共通の変数が使われるなど、処理の <<流れ>> や <<構造>> が把握しにくい見通しの悪い状態になっているプログラムのことである。
名称の由来は、皿に盛られたスパゲッティのように実行される箇所の線が絡み合っていることから。「パスタプログラム」とも呼ばれる。
スパゲティプログラムというのは、端的には、命令の実行の順番がプログラムの記述の順番どおりになっておらず、まさにスパゲティがこんがらがったような状態になったプログラムである。
スパゲティプログラムは、プログラムのテストを行ったり、プログラムをトレース(Tracing (software) つまり1命令や1行を実行させ、各命令・各行ごとの状態が正常かどうかひとつひとつ確認すること)すら困難になる。デバッグすら困難になり、プログラムのスムーズな開発や完成を妨げる。またソフトウェアを改良したり拡張することも困難にする。
スパゲティプログラムは、特に1980年代ころ、まだ構造化プログラミングという知識・手法がプログラマたちに十分に普及しておらず、構造化プログラミングが可能なC言語系統のプログラミング言語(C言語,C++,Java) ではない言語が使われてしまっていた状況でしばしば発生した。
現代では、プログラミング(コーディング)というものは、組織で業務として行う場合でも、たとえ個人的な趣味で行う場合でも、構造化プログラミングを行うべきだとされている。というのは、たとえ個人が自分ひとりのために書くプログラムでも、書いてから数ヶ月や数年もすれば、書いた時の記憶はほとんど、後から積み増しされた日々の記憶の山の中に埋もれてしまい、書いた当時に自分が何を考えて書いたか思い出せなくなり、まるで「赤の他人が書いたよう」に見える状態で読まなければならなくなるからである。日々多くの文字を読み多くの体験をしつづけている普通の人ならば、特にコードをしばしば書く人ならば、数ヶ月の間に大量のコードを読み書きすることになり、「数ヶ月前、数年前の自分」が書いたコードやそれを書いた時の理屈は、大量の記憶の中に埋もれてしまっており、すぐには思い出せず、「まるで他人が書いたコードのよう」で理解しがたいのである。だからプログラム(コード)というものは、チームで共同作業で書く場合でも、個人が個人的趣味で書く場合でも、誰が読んでも直感的に理解しやすいように、整然と、原則上から下へと素直に順番に実行されるように書くべきだとされている。またしっかりブロック化し、変数のスコープ(有効範囲)を常に意識し、たとえどんなに巨大なプログラムのどの行でも、もし変数の役割がやや不明な行があれば、せめてそのブロックの冒頭に戻れば変数の宣言も明確に書かれていて(さらにもし宣言だけでは変数の役割・機能が曖昧なら、その役割もコメント文などで明記されていて)、誰でも理解しやすいように(未来の「他人のような自分」でも理解できるように)、コードの中を何度も上下(右往左往)しなくても容易に変数の役割・機能も理解できるようにコードは書くべきだとされている。
そういった、現代では当たり前になっている手法を実践できていないような、低品質なプログラムがスパゲティプログラムなのである。
単にバイト数が比較的大きいだけのプログラム、やや冗長な記述を含んだプログラム、ソースコード解析を意図的に困難にしたプログラム(リバースエンジニアリングの防止)などは、「スパゲティプログラム」には分類されない。
たとえばウェブサイトからダウンロードしたJavaScriptファイルでスペースや改行なしで変数名も極端に短く、可読性を欠いたようなコードが配布されていることがあるが、これはダウンロードの高速化を目的としたミニファイ(圧縮)処理やソースコードを解析しづらくする難読化を行うプログラムからの生成ファイルであり、元のソースコードがそのようになっているわけではないのでこれをスパゲティコードと呼ぶことはない。これらのコードをスパゲティコードと混同すべきではない。
スパゲッティプログラムを作りだす原因としてよく挙げられるのが、goto文の濫用である。(構造化以前の)BASICなどの言語にあるgoto文は、無条件に指定した行番号の位置までジャンプする。これは関数やループなどの制御構文を利用した制御に比べ、処理の(素直な、上から下への)流れを混乱させる。離れた位置に書かれた行へ飛ばせるので、もちろんソースコードの可読性も低下させ、バグ含みのコードを書いてしまう原因にもなる。構造化が重要だと認識されるようになって以降のプログラミング言語では、例外処理や多重ループを抜けるための制御文を用意し、基本的には悪しきgoto文は使用しないようになった。
特に 1980年代のBASICは各行に「行番号」があり、その行番号をジャンプ先として指定してジャンプするという酷いものだった。(構造化されたプログラミング言語と違い、呼び出し元に戻りもしなかった。ジャンプ先のブロックに、またいい加減に(思いつきで)Goto文を記述して戻り先を指定するような、だから呼び出し元にまともに戻れないような、現代では「ありえない」「あってはならない」ような低品質の命令文であり、その結果スパゲティプログラムが氾濫し、人々を悩まし、構造化以前のBASICは酷評されるようになり、BASIC自体が廃れてしまった。現在生き残っているのは、Goto文は悪だと判断してGoto文は廃止し、新たに開発された、全く別系統の、行番号やGOTO文を廃した「構造化BASIC」である。)
なお次のような場合、goto文を使うことが容認される場合や使わざるを得ない場合もあった。
C言語等でエラーを処理する場合、goto文を使うことでエラー処理を書きやすくなることがある。
現代のプログラミングでは、できるだけグローバル変数の使用は控え、原則、出来る限りローカル変数だけで動作するようにプログラムは書くべきだとされている。グローバル変数を使えば使うほどプログラムは混乱してゆく。グローバル変数を使えば使うほど、変数の宣言文と、変数が実際に使われる文の距離(行数)が離れ、ソースコードのある画面内を見ただけでは変数の役割・機能が不明になり、変数の統一的な役割・機能を理解しないまま、各ブロックで異なった「場当たり」的な「チグハグ」な役割・機能をグローバル変数に設定するようなコーディングが行われる率が高くなってゆき、バグ発生の確率が増し、混乱が増してゆく。グローバル変数を使うと、あちらのブロックとこちらのブロックで、同一の変数の役割・機能がズレてしまっているような奇妙なプログラムが(物理的に、文字上)書けてしまい、しかも記述されている場所が離れているのでひと目で見ることができず、人間の頭脳ではその間違いに直感的に気づくことができない。だからグローバル変数は基本的には悪しきもので、基本的には使用するべきではなく、減らせるだけ減らすべきだ、使うのは他にその機能を実現する手法が無いような特別な場合だけ、とされている。
プログラミング言語によってはスパゲティプログラムを作り出してしまう事態を最初から防ぐために、グローバル変数が最初から言語仕様に無いものもすらある。
オブジェクト指向を取り入れたプログラミング言語においても、多重継承を機能追加のためにむやみに濫用し、クラス間の関係が複雑になりすぎてしまうことでスパゲティ化が起こることがある。このため(メンバの名前の衝突や菱形継承を避けるなどの目的もあり)、Delphi、Java、C#などの言語では多重継承が禁止され、機能の追加にはオブジェクトコンポジションを利用することが推奨されている。
現在ではスパゲティプログラムは絶対に書いてはいけないとされている。現在ではメモリ量はふんだんにあり、可読性が重要で、メンテナンスやコードの改変が容易で、システムが安定して動作することが重要だということが理解されているからである。
だが特に1980年代ころまでの、まだ構造化されていないプログラミング言語が横行していていた時代で、構造化前のプログラミング言語しか動かないマシンで限られたメモリにプログラムをおさめなければならなかった状況下では、しかたなくGoto文が使われることがあった。数バイトでも削減したいような、追い詰められた開発環境では、無理やり詰め込むため、現代から見れば「あり得ない」「許容されない」ような、粗野なプログラミング手法ですら使われた。
一部のシステムでは、CPUのバグを突くようなトリッキーなプログラミング手法がしかたなく選ばれていたものがあり、ソースコードやアセンブリコードを普通の読み方で読んだだけではとても理解できないようなジャンプが起き、まさにプログラムのスパゲティ化を招いた。例えばファミコンやApple IIなどのゲームにおいては、65C02の仕様書に記載されていない未定義命令(Undocumented instruction、本来使ってはいけない)を利用することが常套化していた。特にファミコン後期においては、競合機や次世代機の登場によってファミコンのゲームに対する性能への要求が強くなり、また視覚的キャラクタに割り当てられるデータ量・メモリ量の増大によってプログラムのほうに割り当てられるバイト数が減ってしまい、本来してはいけないはずのコーディング手法が、ますます横行するようになってしまった。たとえばファミコン用ゲーム『ファイナルファンタジーIII』の飛行艇の高速移動のプログラミングに関しては、既に退職した当時のメインプログラマのナーシャ・ジベリ以外誰も理解できず、そのため当時の人気ゲームでありながら後継機への移植が難航したという逸話がある。
1980年代のメモリ量が限られていた時代には、プログラムのバイト数を圧縮する技を競い合う誌上コンテンストなどが開催されていて、そこではスパゲティプログラムが許容された。例えば、雑誌MSX・FANの投稿プログラムコーナー『ファンダム』で事実上のスパゲティプログラムが許容されてしまっていた。特に「1画面プログラム」すなわち行番号を含めた40×24文字のショートプログラムを多数採用・掲載しており、行から行へと、標準的ではない手法でジャンプをさせるようなコードが横行した。
たとえば次のようなものである。
行内の分岐に関わるIF文の使用を極力避けてしまう (if文を終了するには行を終了するしか方法が無かった為)
と記述するのに対し、1画面プログラム採用作品では
という記述が用いられていた。
中には「マシン語ソースをアスキーコード制御文字列を含まない範囲の8ビット値にシフトさせ、そのキャラクターコード群とデコーダのみ記述する」という可読性が全く無いプログラムが採用される事もあった。
「*」をコントローラーで左右に動かすプログラムを一般的な記法で書いたもの。
条件分岐させ水平座標、Xが 0未満、28を超えないように加減算する。
以下はよく用いられていた圧縮方法で書かれたソース。
メイン部分を1行にまとめるために、条件分岐を省いている。
水平座標の変数 Xのはみ出しをチェックする関係式の評価を数値として扱い、1個の算術代入文としている。
しかしMSX BASICでは式の評価が 真ならば-1、偽ならば0 が返る為、一見すると変数 Xへの加算と減算が逆に見えてしまう。
また、五・七・五・七・七の三十一バイト(みそひとバイト) でプログラムを記述するという曲芸的ジャンル「アセンブラ短歌」を考案した人も昔はおり、こうした作品でも31バイトの中でプログラムを完結させるためにスパゲティ状のジャンプを内部で行うものが多々あった。
スパゲティプログラムは保守・機能追加を妨げるので、できることなら修正することが望ましい。しかし実務で使われているシステムは「スパゲティプログラムを修正した場合のメリットとデメリット」「修正せず、そのまま放置する場合のメリットとデメリット」を天秤にかけて、「とりあえずうまく動作しているプログラムは、滅多なことでは修正しない」ということが広く行われている。
実務で実際に日々使用されているシステムでは、安定が非常に重要であり、うっかりシステムの安定を損ねるとシステムに依存した業務がすっかり停止してしまい、利用者たちに多大な迷惑をかけ、おまけに金銭的な実損(しばしば数百万円〜数億円といった桁の損害)が発生し、損失を補償しなければならない事態に陥るからである。
またスパゲッティプログラムを修正するとしても、一部の修正だけだと、かえって整合性のとれないコードを追加して結果としてバグを追加してしまったり、「寝たバグを起こす」ことをしてしまう可能性が高いからである。時間や予算・人材が許す場合でも、この傾向は見られた。スパゲティプログラムを修正するとしても、しばしば小手先の作業だけでは困難で、しばしば相当に大掛かりな作業になる。
後にテストファーストの方法論が確立され、プログラム本体の完成と同時期にテストプログラムも作成されるようになると、プログラム変更の危険性は相対的に低くなり、このような状態のプログラムは積極的に修正することが奨励されるようになった(リファクタリング)。
なおあまりに酷い状態に陥っているスパゲティプログラムは、修正するのではなく、思い切って放棄してしまって、新たにゼロから整然と構造化したプログラムを書いたほうがよほど早い、ということもある。