#はじめに
JavaScript(JSに限らずですが)でコードを書いていく時、誰しもがバグにこんにちはされてしまいます。
その時皆さんはどの様な原因の探り方をしているでしょうか?
バグ(エラー)により無駄な時間を過ごしてしまっている。
エラーの解決方法がわからず嫌になってしまう駆け出しや初学者の方達、今まで違うバグの対処をしていた方々(自分のなれているやり方の方が早いかも)の助けになれば幸いです。
まず最初に大きく2つ、この様なバグの解決方法があると思います。
*エラーとバグは違う意味ですが、こちらでは同じ様な意味で扱わせていただきます。
###プリントデバッグ
console.logなどで自分が使っている値が正しく動いているかを出力する。
その値が自分の意図していない値であるかを確認し、バグの原因を探る。
###コメントデバッグ
原因だと思われる箇所をコメントにし、バグが起こるかどうかを確認する。
それを繰り返して、どの部分がバグの原因なのかを探る。
debuggerステートメント
そして今回紹介するのが、こちらのdebuggerステートメントです。
もちろん、既に知っていらっしゃる方々もいると思いますがまだ知らない方達のために共有したいと思います。
使い方を簡単に説明すると
・コードの止めたいところにブレークポイントを置くことができる。
・他であるデバッグ実行の様なもの。
です。
他にも様々の方法がございます。
知りたい方はこちらをご参照ください。
JavaScriptのデバック方法
debuggerステートメントを知る
それでは実際に使ってみましょう。
サンプルのコードとして、与えた値の平均値を出力するコードを用意しました。
const avg = (...score) => {
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
};
const numList = [10,10,10,10,10];
const result = avg(numList);
console.log('平均:' + result);
簡単にコードの説明をします。
const avg = (...score) => {
};
avg という関数を定義します。引数には 「...」 をつけることで複数の値を際限なく(ないとは言っていない)まとめて受け取れる様にしています。
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
複数の引数をscoreで受け取り、reduceを使うことで合計をsumに入れています。
そしてその後scoreの長さでsumを割り、それをreturnしています。
const numList = [10,10,10,10,10];
const result = avg(numList);
console.log('平均:' + result);
numListには合計したい数値が配列によって入っています。
そしてその後avg関数を使いnumListの平均を求め、resultに格納。
結果のresultをconsole.logを使い、出力しています。
いかがでしょうか?
一見何も問題のない様に見えます。実際に実行してみましょう。
平均:NaN
NaN(Not a Number)、数値じゃないゼェと言われてしまっています。
ナンデデスカ、ナンデナンデスカ 大体の人がバグに遭遇すると、この様なおちんぱんに変身してしまいます。
あ、なるほど と,このくらいなら気づいてしまう人もいます。そういう人は優しくおちんぱんを見守ってください。
おちんぱんになってしまった人たち安心してください。
その時に降り立つ女神がdebuggerステートメントです。
実際に使ってみる。
最初にも書いた通り使い方は簡単で、プログラムを止めたい箇所に debugger; と入力するだけです。
そうするとそこでプログラムが止まってくれます。
文章だけだと何を言ってだ此奴は状態だと思うので実際に動かしてみましょう。
読むだけでもいいですが、実際に使ってみた方が理解できると思うので、ぜひ、お手元でもやってみでください。
const avg = (...score) => {
debugger;
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
};
const numList = [10,10,10,10,10];
const result = avg(numList);
console.log('平均:' + result);
二行目に**debugger;**と書き加えられていることがわかるでしょうか。
記入する箇所はどこでも良いのですが今回は関数の先頭ににおきました。
chromeで実行してみましょう。
(実行の仕方はhtmlファイルにscriptタグで紐付けそのhtmlファイルをChromeで開いています。)
右クリックを押して検証を押します。
開けたらページをリロードしましょう。(command+R)
debuggerの部分が青くなっています。
プログラムでは**result = avg(score)**で関数が呼び出され、そこから止まっているということです。
右側に何やら色々と書いてありますがそれはひとまず気にせず
右上の7つのマークからなる左から3つ目のボタンを押してみてください。 (↓●)
すると青い枠が一つ下の行に進んだのがわかると思います。
これは先ほど止まった一つ上の行から一行プログラムが進んだことになります。
もう一度同じボタンを押して今度はソースコードの一行目、scoreと書かれている引数の部分にカーソルを当ててみてください。
すると今度は何やら情報が出てきました。
これは実際に今、scoreに格納されている物を見ることができます。
**0:Array(5)と書かれている場所の詳細を見ているとこの様に表示されるはずです。
少しわかりにくいかもしれませんがこれはscore[0]**に渡した引数である5つの10が格納されてしまっています。(わかりにくいかもしれません。サンプルが悪かったです。ごめんなさい)
つまり
score[0][0] = 10,score[0][1] = 10....
と二次元配列になってしまっているのです。
scoreはscore[0]の一つしかないのでreduceで全部足そうにも一度しか回りません。
score[0]に格納されている配列の値がそのままsumに入る結果となりました。
なのでこの様な二次元配列になってしまったのです。
あくまでイメージですが,そのあとのreturnではこの様になっています。
return [10,10,10,10,10] / 1;
何だか計算できなそうですね。
これであらかたdebuggerを使い原因を見つけることができました。
いかがだったでしょうか?
最低限のことのみでしたがdebuggerの使い方がわかっていただけたでしょうか。
もしかしたらこれらを使わない方が速いと言う方もいらっしゃるかもしれませんが、一つのやり方として覚えていただけたら幸いです。
それではdebuggerの使い方はここまでとして、ここからは実際にこの問題を解決していきましょう。
実際に解決してみよう!
今回はデバッグ方法を知って欲しいというものでした。
なのでそもそもこの問題の原因がわかっている方、この問題には興味がない方もいらっしゃると思います。そのため、解決の部分は分けさせていただきました。
ここからは、どうしてこうになるんだろうと思った方々をすっきりできたらと思います。
先ほどまでの探索で、どうやら引数を受け取る際に二次元配列になってしまっているというのが原因なのではないかということに気づくことができました。
なのでその辺りを重点的に調べていきましょう。
結論から言ってしまいますが、引数の部分の ...scoreの**...(残余引数)**に原因があります。
こちらのコードを見てください。
const printArr = (...arr) => {
console.log(arr);
};
printArr();
こちらの関数は引数として渡された物をconsole.logで出力する関数です。
実際に使ってみましょう。
printArr(10,20,30);
[10,20,30]
ちゃんと表示されていますね。
次に何も渡さずに実行してみましょう。
printArr();
すると結果はこの様になります。
[]
おわかりいただけるでしょうか。
引数を渡さない場合はこの様に、空の配列が出力されます。
つまり残余引数を使うと配列が生成されているのです。
そしてその配列に、渡ってきた値をせっせと入れていっているのです。
これらを踏まえ、今回の問題に戻りましょう。
const avg = (...score) => {
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
};
const numList = [10,10,10,10,10];
const result = avg(numList);
console.log('平均:' + result);
この場合、numListと言う配列がavg関数に渡されます。その結果、配列numListは残余引数によって生成された、配列scoreに格納されます。結果、
sum[0] = numList;
となり、二次元配列が出来上がってしまいます。
原理が掴めてきたところで実際に解決していきましょう!
解決策としてパッと思いつくのは、引数を配列として受け取る様にする方法です。
const avg = (score) => {
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
};
const numList = [10,10,10,10,10];
const result = avg(numList);
console.log('平均:' + result);
この様にすれば配列がそのまま渡されちゃんと計算することができます。
しかしどうしても残余引数を使わなくてはいけないのかもしれません。
その場合**flat()**を使う方法があります。
const avg = (...score) => {
const flat = score.flat();
const sum = flat.reduce((acc,cur) =>{
return acc + cur;
});
return sum / flat.length;
};
flatとは文字通り配列を平らにすることができます。二次元の配列に使うと、一次元の配列になります。
これを利用し一度フラットにした配列を用意し、その配列で諸々の計算をおこなわせます。この様にすることで正しい値が出力されます。
flatでは、引数に値を指定することでフラットにする深さを設定することもできます。
全てをフラットにした場合は**flat(Infinity)**とすることで真っ平にすることもできるので試してみてください。
**score[0]**で引数を扱エバインじゃない?と思う方もいらっしゃるかもしれません。
しかしその場合、引数が配列でなかった時に先頭の値しかコントロールできないためまた別のバグを生んでしまう可能性があります。
追記
@rryu さんからコメントでご指摘いただきました。
@rryuさん
どちらかというと数値の引数郡を期待する関数に数値の配列を渡しているという呼び出し側が間違っている状態で、その間違った呼び出しを関数側でflat()して吸収するのは良くないと思います。
以下のようにすれば正しく呼び出すことができます。
const result = avg(...numList);
この残余引数ににている物はSpred構文と言って配列を展開してくれます。
これひとつで解決です。笑
const avg = (...score) => {
const sum = score.reduce((acc,cur) =>{
return acc + cur;
});
return sum / score.length;
};
const numList = [10,10,10,10,10];
const result = avg(...numList);
console.log('平均:' + result);
最終的にはこの様になります。
結局自分が、おちんぱん。
人類みんな、ホモサピエンス。
@rryu さん、ありがとうございました!