以下に俺の認識を書く。プログラム分からない人でも読めなくもない感じに書く。でも読んだところで何か得られるというものでもない。時間の無駄。

書くにあたって特に調べ直したわけではないから、時に正しくない。Wikiのリンクを張っている場所があるけど、極力内容を読まずにリンク張ってます。なんとなく。

◆バグ

プログラム(ソースコード、設計)の間違い。根源的には人的なミス。バグによってエラーや不具合が引き起こされる。ソフトウェアのみならずハードウェアの設計に考慮漏れがあったりしたらそれもバグ。

「バグる」っていう言葉あるけど、バグという語が示す意味からして不適切な言い方です。バグはプログラムに埋め込まれた人的なミスだから、随時発生してくる類のものではない。「ゲームがバグってる」はプログラムの状態を指しているから有効だが「ゲームがバグった」は厳密には変な表現。バグは初めからそこにある。

バグは、特定の条件を整えたときに再現可能な不具合を引き起こす。再現不能であればバグが原因ではない。

システムからバグを取り除くことはデバグ。バグを埋め込んでしまうことはエンバグ。特に、エンバグによりそれまで動いていたものが動かなくなってしまうことをデグレと呼ぶ。degradeでデグレ。「デグった」とか言う。

◆エラー

広義の「間違い」がエラー。バグとは意味被りしない。

プログラムにバグがあるとき、エラーが発生して壊れることがある。あるいは、電子機器の物理的な設計が間違っていたり雷がおっこちて電気ビリビリしたり磁気がグワングワンしたときもソフトウェアにエラーが発生することがある。

▼ランタイムエラー

RuntimeError。Runが実行、Timeが時だから、ランタイムエラーは即ち実行している時のエラーを意味する。

もっと言えば「プログラムを実行する専用のプログラム」もランタイムって呼んだりしていて、そこで発生するエラーだからランタイムエラー。

ランタイムシステムについて厳密に説明できることが残っているけども、簡略のために割愛する。

プログラムが実行されている時のエラーにランタイムエラーという固有名詞が付いているわけだけど、それって何か変じゃないだろうか。プログラムが動いていないときにエラーが発生することってなくない?あんの?

▼コンパイルエラー

ドラマとかアニメで見るプログラマーって、眼鏡をかけながらなんだか黒い背景のテキストエディタに向かってキーボードガタガタいわして英語とか記号とかを入力しているじゃない。

それは何をしてるのかっていうと、ソースコードというものを書いていたりする。ソースコードはプログラムであるが、人間にとって書きやすく読みやすい状態のプログラムでしかない。その人間に読みよいプログラムは、どこかのタイミングでパソコンが解釈可能なデジタルデータの形のプログラム(マシンコード)にしてやらねばならない。

その変換は(モノによるけど)コンパイルと呼称されていて、コンパイルしてくれるプログラムはコンパイラと呼ばれている。そのコンパイラに自分で書いたソースコードを食わせる段で書き間違いとかあればそれはコンパイラくんから「解釈できねぇよクソが」と罵られるのは必然。それがコンパイルエラー。実行時じゃない時のエラー。

▼構文エラー

コンパイルも決して軽い処理ではないんで、コンパイルにかける前にソースコードの書きっぷりはチェックしておいてほしい。特に昔はコンパイルに途方もない時間がかかってたから、単純な書き間違いがダルいタイムロスを引き起こしていた。

だから静的コード解析すればいい。構文の解析だけして変換はしないプログラムがテキストエディタかなんかに組み込まれていれば、コンパイラにコードを食わせる前に間違いがわかるんで嬉しい。静的コード解析にかけた結果としてコードに間違いがあれば構文エラーになる。

静的コード解析も軽い処理とは言えないけど。

▼よだん:インタプリタ

コンパイラが使われないプログラムもあったりして、インタプリタというプログラムにソースコードを食わせて実行する。

実際のところ、インタプリタ扱いされているプログラミング言語にもコンパイラって作れるんじゃねぇのかな。でも例えば「入力から実行するプログラムを随時生成して動く」みたいな実装があったらインタプリタでしか実現し得ないかもしれない。クワインとか。テープを読んでテープを生み出しながら動くみたいな。LISPとかそんな感じだったっけ。どうだったっけ。

インタプリタはソースコード解釈のプログラムとランタイムを両方持っていて、実行時にソースコードを解釈しつつ解釈できたコードから実行していく。

大まかに以下のケースで認識すればいい。

  • AOT(Ahead-Of-Time、事前)
    • ソースコードをコンパイラに食わせて出力したマシンコードを、実行したいときにランタイムに食わせて動かす。
  • JIT(Just-In-Time、実行時)
    • ソースコードを、実行したいときにコンパイラに食わせつつ同じ流れでランタイムにかけて動かす。
  • インタプリタ
    • ソースコードを、インタプリタに食わせて解釈しながら実行する。

それぞれの方式にメリデメがあって、実際のところは最適化のために派生形が生み出され続けている。「このプログラミング言語はAOT」みたいには一概に言えないケースも多い。実行戦略(最適化戦略)は今も世界中ですげぇ工夫が色々と生み出されている。

◆不具合

プログラムを動かした感じ、思った通りじゃないこと。不具合はエラーを内包する。エラーは思った通りの動きとは言えないので不具合。

足し算するプログラムを書いて1 + 1 = 3みたいになっちゃったら不具合。望まぬ結果になったら不具合で、望まぬ結果の中のひとつにエラーがある。

▼障害

不具合によりシステムが不能となったとき、そのシステムには障害が発生していると言える。障害を解消するために技術者が泣きながら働くことを障害対応という。

◆例外

Exception。「それ以上の処理が進められなくなる状況」を例外と呼ぶ。テメーが中学校で習ったであろう算数の禁忌であるゼロ除算を、プログラム上で実行しようとすると例外が発生する。

ゼロ除算で絶対に例外が発生するってことでもない。処理系によっては工夫で回避していたりもする。ゼロ除算の結果を数値でないもの(NaN)として扱うとか。

例外が起こるとプログラムの動作が壊れたりするんで、それはエラー。

ぬるぽ」「ガッ」のぬるぽはJavaというプログラム言語の「NullPointerException」に由来を持つ。

▼例外処理

例外が放置されるとプログラムは壊れて、その実行は概ね強制終了となる。

JavaScriptというプログラミング言語はイベントループっていうアレをアレしてるんで、壊れるにしても局所的に壊れる。本処理を継続させやすい。

強制終了はビックリするので発生させたかない。そこで例外処理という考え方が発生する。完全に致命的な例外は復帰できないが、救える例外も多い。例えばゼロ除算だとか。

具体的に言えばtry-catchと呼ばれる機構が用意されている処理系があったりして、プログラムの指定した範囲内で発生した例外をキャッチして代わりに特定の処理を実行させることもできる。

もっと具体的に言えば、ゼロ除算の例外をcatchして「処理できない入力がなされましたよ。」みたいなエラーメッセージをユーザーに見せてもいい。

例外が発生したとき、開発者は修正のためにその例外の発生個所を特定できなければならない。そのために、例外にはスタックトレースという「コードの道のり(語弊あり)」の記録が残されることが多い。そのスタックトレースの収集には計算コストが多少なりかかるのであまり例外を発生させるもんじゃない。

・安易に例外処理してはいけない

例外処理は極力避けるべきである。とはいえ例外処理はゼロにはできないから、より少なくなるように留意せねばならない。複雑怪奇な例外処理を記述するとそれ自体が不具合を起こしかねない。

なぜ例外処理を減らすべきかというと、壊れてほしいときに壊れなくなるから。例外処理すると、その握りつぶされた例外の発生は使う側からして検知不能となる。「壊れてないのになんでか上手く動かない」になってしまう。壊れないプログラムを目指そうとすると全体が歪むから、壊れるときは壊れるプログラムにするべし。例外処理を細かい粒度で乱用するのは臭い物に蓋をするのと同じであるから、本来片付けるべき臭いものは見えておいたほうがいい。

例外はジャンプ、すなわちGOTOである。補足する場所が散り散りになっていると読み手からして全体の動きが追えなくなってくる。

▼なぜ例外が起こるのか

大まかにみっつ。語弊あるけど。

  • 明示的に例外を投げた
    • あるいは、どういう状況においても必ず例外を発生させるコードを書いた。
  • 物理的な障害が発生した
    • プログラムの実行中にパソコンをハンマーでドついて中のケーブルが断線したら内部通信が継続できなくなって例外が発生する。
  • 予期せぬ入力がされた

ゲームというプログラムに対してボタンをポチポチすることと思うんだけど、それも入力。パソコンに対するマウス操作とかキーボード操作とか、スマホに対するタッチも入力。それら入力が予期せぬ感じになったら時に例外が起こる。除算をするプログラムでゼロを入力して壊れるのも予期せぬ入力。

▼予期せぬじゃねぇよ予期しろ

そもそもよ。実際にゼロ除算するタイミングでは除数と被除数が分かってるじゃない。だから割り算するまえに値のチェックをするべきだし、ゼロで割ろうとしてたらそこで能動的に「ダメだよ」ってプログラムを安全に終わらせてあげたほうがいい。例外は軽くないのだから。

(理解できなくていいけど)例外が発生しうる関数とは別にTry関数が整備されているようなこともある。「文字列を数値に変換する関数」があったとして、入力された文字列が数値に変換できなかったときは例外になりがち。でもTryの関数により変換できずとも例外を起こさないルートを用意してくれていたりする。

例えばC#(.NET)であれば以下の感じ。

つまり「復帰できなくもない」と「復帰できない」があるんで、そこを厳密に違うものとして捉えているプログラミング言語もある。例えばRustという言語にはExceptionという語彙は登場せず、「Result<T,E>」と「panic(マクロ)」が存在している。素朴なゼロ除算は復帰不能でpanicだが、parse系はResultで表現されることが多い。LISPとかHaskellにも例外って表現は出てこない。興味あるなら調べるべし。

◆系

(いろいろ無視して簡単に表現すると)プログラムの実行をテストする際に

  • 正常系
    • 予期した入力がされた
    • 物理的に障害が発生しなかった
  • 異常系
    • 予期せぬ入力がされた
    • 物理的に障害が発生した

のふたつを考えることになる。正常系では正常終了することを確認し、異常系では安全に異常終了することを確認する。

でも「予期せぬ入力」は「予期せぬ入力が入力される」ということを予期できることも多く、予期できた時点でそのテストケースは「準正常系」と呼ばれたりもする。異常系は

  • インストールに失敗してる
  • インターネットが繋がってない
  • 通信がタイムアウトした

とか、そういうやつ。

異常系は、発生するけど発生してはならない。頻発してはいけない。だから、例えばWebシステムで例外が発生したら「なんか異常だよ」って通知が自動で飛ぶようにして開発者を叩き起こす必要がある。準正常系はまぁ起こることではあるから通知とかすることもない。

異常系は異常終了させる。正常系は正常終了させる。準正常系も正常終了させる。よくわからんだろうけど、準正常系を異常終了させると設計が変になったりする。

◆結

そんな感じ。

わかるかな。わっかんねぇだろうなぁ(松鶴家千とせ)

イェーイ!(松鶴家千とせ)