まちがいの、読みかた

よみもの+手を動かす時間:およそ30分

返事は、英語でやってくる

レッスン1で、エラーは失敗ではなく、誠実な読み手からの返事だと学びました。にわ語の返事は日本語でしたが、JavaScriptの返事は英語です。でも身がまえる必要はありません——返事の構造は、言語が変わっても同じだからです。

JavaScriptのエラーは、いつも決まった形をしています。「種類: 何に困ったか(どこで)」。英語の単語が読めなくても、この三つの部品に分解できれば、手がかりは取れます。

エラーには、よく出会う大きな族(ファミリー)が三つあります。これから一族ずつ、わざと起こして顔を覚えます。

一族目:ReferenceError——知らない名前

まずは実行して、返事を読んでください。

JavaScript
let namae = "にわ";
console.log(nmae);

Ctrl+Enter でも実行できます

ReferenceError: nmae is not defined という返事が来たはずです。三つの部品に分解してみます。

  • ReferenceError ——種類。「その名前を参照(reference)できない」族
  • nmae is not defined ——何に困ったか。「nmae は定義されて(defined)いません」
  • (2行目あたり) ——どこで

1行目で名づけたのは namae、2行目で使ったのは nmae。たった一字の打ちまちがいですが、レッスン1で見たとおり、コンピュータは察しません。「似てるから namae のことでしょ」とは読んでくれない代わりに、知らない名前に出会った場所を正確に教えてくれます。

この庭では、英語の返事の下に日本語の読み解きと次の一歩が自動で付きます。翻訳で置き換えないのは、英語の返事をじかに読む力も、ここで一緒に育てたいからです。

二族目:SyntaxError——文として読めない

二族目は、文のかたちそのものが壊れているときの返事です。

JavaScript
console.log("こんにちは);

Ctrl+Enter でも実行できます

SyntaxError、つまり構文(syntax)のエラー。引用符 " が閉じられていないので、コンピュータはどこまでが文字列なのか分からなくなりました。日本語でいえば「彼は『おはよう と言った」のような、カギかっこの閉じ忘れです。

この族だけ、特別な性質があります。一行も実行される前に起きることです。文として読めない手紙には、返事の書きようがありません——だから読む段階で、まず突き返されます。

三族目:TypeError——その値には、それはできない

三族目は、文としては読めるのに、頼まれたことがその値にはできないときの返事です。

JavaScript
let aisatsu = "こんにちは";
aisatsu();

Ctrl+Enter でも実行できます

aisatsu is not a function——「aisatsu は関数(function)ではありません」。名前のうしろに ( ) を付けるのは「これは手順のまとまりだから、実行して」という頼みごとです。でも aisatsu の中身は文字列で、文字列に「実行して」は頼めません。

三族の顔を覚えたら、英語はこの三つの言い回しだけで足ります。is not defined(定義されていない)、unexpected(予想していなかった)、is not a function(関数ではない)。

よりみち:なぜ「バグ(虫)」と呼ぶのか

1947年9月9日、ハーバード大学のリレー式計算機 Mark II が止まりました。原因はリレーに挟まった一匹の蛾。グレース・ホッパーらのチームは蛾をテープで作業日誌に貼り、「バグが実際に発見された最初の事例」と書き添えました。ただし「バグ=機械の不具合」という言い回し自体はずっと古く、エジソンも1878年の手紙で使っています。つまり日誌のあの一文は、「不具合(バグ)の原因が本物の虫だった」という技術者のジョークなのです。蛾の標本は、いまもスミソニアン協会に残っています。

エラーの行は、犯人の行とは限らない

ここで、知らないと長時間さまようことになる事実をひとつ。エラーが発覚した行と、まちがえた行は、別のことがあります。

JavaScript
let kai = 0;
while (kai < 3) {
  console.log(kai + " 回目");
  kai = kai + 1;
console.log("おしまい");

Ctrl+Enter でも実行できます

返事は Unexpected end of input——「まだ続きがあるはずなのに、終わってしまった」。まちがいは「4行目のあとに } を書き忘れたこと」なのに、発覚するのはいちばん最後です。

理由を考えると、納得がいきます。読み手は { を見た瞬間から、対になる } を待ちながら読み進めます。待ちつづけたままコードが尽きたとき、はじめて「閉じられていない」と確定する——だから閉じ忘れは、いつも後ろのほうで発覚します。

エラーの行番号は「発覚した場所」です。そこに犯人がいなければ、そこから上へさかのぼって読んでください。

直しかたの、三拍子

まちがいを直す作業には名前があって、デバッグと呼ばれます。プロも初心者も、やることは同じ三拍子です。

  1. 再現する——もう一度実行して、同じまちがいが必ず起きることを確かめる。毎回確実に起きるまちがいは、半分直ったようなものです
  2. 切り分ける——疑わしい範囲を狭める。行を減らして試す、console.log で途中の値をのぞく
  3. 修正する——直したら、また実行して確かめる。変えるのは一度にひとつだけ

「一度にひとつだけ」は、レッスン3で模様の数字をいじったときと同じ作法です。二つ同時に変えると、どちらが効いたのか分からなくなります。

修理工房

ここからは、手を動かします。壊れたプログラムが5本、運ばれてきました。返事を読み、行をさかのぼり、一本ずつ直してください。

修理1:名前のまちがい

実行すると、止まります。「好きな花は ひまわり です」と表示されるように直してください。

JavaScript
let hana = "ひまわり";
console.log("好きな花は " + hama + " です");

Ctrl+Enter でも実行できます

ヒント1(エラーの読みかた)

hama is not defined——「hama という名前を知らない」という返事です。でも、あなたは1行目で、よく似た名前をつけたはずです。

ヒント2(どの行を見るか)

1行目で名づけた名前と、2行目で使っている名前を、一字ずつ見比べてください。

こたえ

2行目の hamahana に直します。逆に1行目を hama にそろえても動きます——大事なのは、名づけた側と使う側が一字残らず一致していることです。

修理2:見えない異物

見た目はほとんど正しいのに、3行目で止まります。「合計 200 円」と表示されるように直してください。

JavaScript
let ringo = 120;
let banana = 80;
let goukei = ringo + banana;
console.log("合計 " + goukei + " 円");

Ctrl+Enter でも実行できます

ヒント1(エラーの読みかた)

Invalid or unexpected token——「読めない文字がまじっている」という返事です。日本語入力のまま記号を打つと、よく起きます。

ヒント2(どの行を見るか)

3行目の と、4行目の + を見比べてください。形が、ほんのすこし違います。

こたえ

3行目の (全角)を、半角の + に打ち直します。全角と半角は、人間の目には同じでも、コンピュータには完全に別の文字です。コードの記号と数字は、いつも半角で書きます。

修理3:呼べないものを呼ぶ

実行すると「ただいま」は表示されるのに、「おかえり」が出ません。両方表示されるように直してください。

JavaScript
console.log("ただいま");
console.lag("おかえり");

Ctrl+Enter でも実行できます

ヒント1(エラーの読みかた)

console.lag is not a function——「lag というものは関数ではない(そもそも、ない)」という返事です。そして「ただいま」が表示されたことも、手がかりです。1行目までは読めて、実行できた証拠だからです。

ヒント2(どの行を見るか)

2行目。loglag、一字違いです。

こたえ

2行目の laglog に直します。SyntaxError と違って、ReferenceError や TypeError は実行の途中で起きるので、止まる直前までの結果は画面に残ります。「どこまで動いたか」も、返事の一部として読めるのです。

修理4:終わらないカウントダウン

実行すると、2秒たって強制終了されます。出力をよく見てから、「のこり 5」から「のこり 1」まで数えて「はっしゃ」と表示するように直してください。

JavaScript
let nokori = 5;
while (nokori > 0) {
  console.log("のこり " + nokori);
}
console.log("はっしゃ");

Ctrl+Enter でも実行できます

ヒント1(読みかた)

今回はエラーではなく「時間切れ」です。出力を見ると「のこり 5」だけが延々と並んでいます。つまり nokori は、ずっと 5 のままです。

ヒント2(どの行を見るか)

くりかえしの { } の中に、nokori を減らす文がありません。減らないかぎり、nokori > 0 は永遠に成り立ちつづけます。

こたえ

{ } の中に nokori = nokori - 1; を足します。「0 になったら終わる」という条件は、書いただけでは訪れません。条件に近づいていく文が、くりかえしの中に必要です。

修理5:エラーの出ないまちがい

最後は、いちばん手ごわい修理です。このプログラムは「1から5までの合計」(15 のはず)を出したいのに、エラーをひとつも出さずに、違う答えを表示します。

JavaScript
let goukei = 0;
let kazu = 1;
while (kazu < 5) {
  goukei = goukei + kazu;
  kazu = kazu + 1;
}
console.log("1から5までの合計は " + goukei);

Ctrl+Enter でも実行できます

ヒント1(読みかた)

返事は来ません。手がかりは「15 のはず」と実際の出力とのずれだけです。表示された 10 という答えは、何と何を足すと出てくるでしょうか。

ヒント2(どの行を見るか)

10 = 1+2+3+4。つまり 5 だけが足されていません。3行目の条件 kazu < 5 は、kazu が 5 になった瞬間に、どうなるでしょう。

こたえ

3行目を kazu <= 5 に直します(<= は「より小さい、または等しい」)。「5 まで」のつもりが「5 未満」になっていました。この境界の1つずれは、プロも定期的にやらかす、世界でいちばん有名なまちがいのひとつです。

コンピュータが、教えられないこと

修理5には、返事がありませんでした。これは偶然ではありません。コンピュータが指摘できるのは、ことばとして成立しないまちがいだけだからです。

kazu < 5 は、文として完璧に正しい。コンピュータはそれを誠実に、書いてあるとおりに実行しました。「あなたが本当は 15 を求めていた」ことは、プログラムのどこにも書かれていません——意図と結果のずれは、あなたにしか見えないのです。

だから、エラーが出ないことは「正しい」ことの証明ではありません。書いた人が「こうなるはず」という予想を持って出力をたしかめる——その読みくらべだけが、最後の種類のまちがいを見つけます。

このレッスンで分かったこと

  • エラーは英語でも、構造は同じ。**「種類: 何に困ったか(どこで)」**の三部品に分解して読む
  • 三大族:ReferenceError(知らない名前)・SyntaxError(文として読めない)・TypeError(その値にはできない)
  • エラーの行番号は「発覚した場所」。犯人がいなければ上へさかのぼる。直すときは再現→切り分け→修正、変えるのは一度にひとつ
  • いちばん手ごわいのはエラーの出ないまちがい。意図とのずれは、あなたにしか見えない

まちがいを読めて、直せるようになったあなたに、足りない道具はもうありません。次のレッスンは、課題ではなく——作品です。