プログラムのバグって何が原因なんだろう
JavaScriptでバグを発見する簡単な方法を知りたい
基本的なバグの修正方法を知りたい
プログラムが書けるようになってくると避けては通れないのがバグとの付き合い方でしょう。初心者のうちは何が原因なのか?どうやって発見するのか?ということが分からなくて悩む人も少なくありません。
しかし、どのようにして学習を進めていけば良いのでしょうか?
こんにちは!ライターのマサトです。
この記事では、初心者でも今日からバグの原因・発見・修正方法について分かりやすく解説していきますので、ぜひ最後まで読んで理解を深めて頂ければ幸いです。
この記事はこんな人のために書きました
- バグの原因について知りたい方
- バグを発見する手段を学びたい方
- バグの修正方法について知りたい方
「バグ」とは?
バグは、基本的にプログラムの不具合を意味するのですが、現在では仕様の段階における間違いやセキュリティ的に脆弱な部分を指したりなどを総称して呼ぶことが多くなっています。
JavaScriptの場合は、HTMLなどのDOM要素を制御することが多いこともあって、想定していた動きと違う動作をしてしまうような現象も少なくありません。
そこで、本記事ではバグが引き起こされる基本的な原因から発見方法・修正手段について、初心者が知っておくべき基礎について解説をしていきます!
バグの原因
この章では、バグが起きる基本的な原因について見ていきましょう!主に、論理エラーの基本とエラー事例について学んでいきます。
論理エラーについて
プログラムのエラーは基本的に文法エラーと論理エラーの2種類に大きく分けることができます。
文法エラーはタイプミスなどによって構文を間違うケースがほとんどなので、実行する際にエラーが表示されるため大抵は簡単に解決できてしまいます。
問題は論理エラーが原因になっている場合です!
論理エラーというのは、プログラム上は問題なく正常に動作するのですが、実際には意図した通りに実行されていないケースです。
例えば次の例を見てください!
let result = 0; for(let i=0; i>10; i++) { result += i; } console.log(result);
実行結果
0
この例は、for文を使って1から9までの数字を足し算するという計算処理です。実行結果としては合計の45という数字が欲しいのですが、結果は0になってますよね?原因はfor文の条件式がi>10になっていることで、正しくはi<10と記述しなければいけません。
しかしながら、プログラム上は問題ないのでエラーが発生せずに実行されるわけです。
これは簡単な例ですが、複雑なプログラムになってくるとこのようなエラーがバグの原因となり、関連するソースコード全体に影響を及ぼしてしまうことになります。
よくあるエラーサンプル
論理エラーのようにエラーが発生しないケースが原因になりやすい事例をいくつかご紹介しておきます。
例えば、JavaScriptでDOMを制御する際にHTML要素を取得するわけですが、存在しない要素を取得してもそれ自体はエラーになりません。
const sample = document.getElementById('sample'); console.log(sample);
この例ではID属性値sampleを指定していますが、実際には存在してなくてもコンソールログにはエラーではなくnullが出力されます。この要素を利用して何らかのメソッドを実行する時に初めてエラーが出力されるのです。
配列やオブジェクトなどは、文字列や数値と同じように = を使ってコピーしてもあくまで参照渡しとなるだけで実際にはコピーされていません。
const obj = {}; const copy_obj = obj; obj.hello = () => console.log('Hello'); copy_obj.hello();
この例では、空のオブジェクトobjとそれを単純にコピーしたcopy_objがあります。元になっているオブジェクトにhello()というメソッドを作成してますが、実行しているのはコピーされたオブジェクトですよね?
しかし、これは正常に動作します。
あくまで参照渡しなので、元のオブジェクトを変更するとコピーされたオブジェクトも一緒に変更されてしまうわけです。
外部のJavaScriptファイルを読み込む時には、その順番が重要です。なぜなら、それぞれのファイルで定義されているオブジェクトや変数は読み込まれた順番に処理されるからです。
例えば上記の例で、main.jsファイルの中に定義されている変数はsample.jsから利用することはできません。その場合は、先にmain.jsを読み込んでからsample.jsを読み込まなければいけないわけです。
JavaScriptにはthisという特殊な値があり、実行される場所や用途に応じて中身の値が都度変更されます。
例えば、次の例を見てください!
const obj = { myfunc: function() { console.log(this); function sample() {console.log(this)} sample(); } } obj.myfunc();
実行結果
Object { myfunc: function } Window { postMessage: function, blur: function……}
この例では、オブジェクトのメソッドを定義しているのですが、そのメソッドの中に別の関数を定義しています。
実行すると、メソッド内でのthisと関数内でのthisの中身が出力されます。
ところが、それぞれのthisの中身は異なっており、メソッド内のthisは自身のオブジェクトを指しており、関数内のthisはwindowオブジェクトを指しています。
thisについてまだ不安のある方は、次の記事で基本から体系的にまとめているので合わせて参考にしてみてください!
バグの発見(チェック)
この章では、バグを未然に発見するための手法について見ていきましょう!もっとも基本的なconsoleオブジェクトの使い方やdev toolsの活用、DOMの監視について学んでいきます。
consoleオブジェクトの活用
JavaScriptにはデバッグを効率よく行えるようにconsoleオブジェクトが標準で提供されています。このメソッドを活用することでプログラムのバグを未然に防ぐことができます。
もっとも基本的なメソッドとしてはconsole.log()です。
const text = 'hello'; const array = [3,5,7,2,5,7]; const obj = {name:'taro', age:30, area: 'Tokyo'} console.log(text, array, obj);
上記の例のように、変数・配列・オブジェクトなどさまざまな値をコンソールログへ出力することができます。つまり、プログラムの実行過程においてconsole.log()を忍ばせておくことで、意図した通りに動作しているかを監視できるわけです。
また、複数の関数が実行されるようなケースにおいてはconsole.trace()を使ったスタックトレースもできます。
function test1() { function test2() { console.trace(); } test2(); } test1();
どこのどんな関数を経由してconsole.tarce()が実行されたのかログで記録されるので分かりやすいでしょう。
また、プログラムの処理に時間が掛かっていたりパフォーマンスが悪い時に、console.time()を使うと処理にかかる時間を計測することができます。
console.time('test'); let result = 0; for(let i=0; i<10000; i++) { result += i; } console.timeEnd('test');
time()からtimeEnd()までの囲んだ部分の処理を計測することが可能で、引数に文字列を設定することで複数箇所の計測も行えます。
これらのconsoleオブジェクトの基本的な使い方や活用方法については、次の記事でまとめているのでぜひ参考にしてみてください!
ブラウザのdevtoolsを使う
Chromeブラウザをはじめ、現在主流のブラウザには開発者ツールが搭載されています。このツールを活用すれば、JavaScriptのデバッグを効率よく行うことができます。
例えば、ソースコードの任意の箇所で一時停止してプログラムの状態を確認できるブレークポイントの設定を見てみましょう。
Sourcesタブに切り替えると現在表示されているWebページを構成するファイルが閲覧できるのですが、この画面からJavaScriptファイルの行番号をクリックすることでブレークポイントを設定できます。
複数箇所に設定ができて、実行するたびにブレークポイントの箇所でプログラムが止まり、ステップ実行を行えるわけです。この機能を活用すれば、console.log()などを使わなくてもちょっとした確認を手軽に行えます。
Chrome Dev Toolsには他にも多彩な機能が提供されており、それらの基本的な使い方について次の記事でまとめているのでぜひ参考にしてみてください!
MutationObserverでDOMを監視する
JavaScriptはDOM要素を制御できるため、動的にWebコンテンツを生成する処理を書くことも多いでしょう。このようなケースにおいて、プログラムからDOMの変更を検出して監視したい場合もあります。
そこで、JavaScriptでは標準でMutationObserverというAPIを利用することが可能で、これを利用することでDOM要素の変更を簡単に監視できるのです。
例えば、次のようなHTMLがあるとします。
こんにちは
この例で、ボタンをクリックしたらdiv要素の中身をpタグに変更する処理を記述してみましょう。
まずは、HTML要素とボタンのクリック処理を次のように作成します。
const wrap = document.getElementById('wrap'); const btn = document.getElementById('btn'); btn.addEventListener('click', function() { wrap.innerHTML = 'サンプルテキスト
'; });
ボタンをクリックするとinnerHTMLでp要素を挿入する処理になります。このときに、DOM要素が変更されたことを検出するためMutaionObserverを使ってみましょう。
次のサンプル例を見てください!
//最低限のオプションだけ設定しておく const config = { attributes: true, childList: true, characterData: true }; const observer = new MutationObserver( () => { console.log('DOMが変更されました!'); }) observer.observe(wrap, config);
MutationObserverのインスタンスを作成するときに、console.log()でメッセージを出力するように設定していますね。そして、observer.observe()を実行することでDOM要素が変更されているかを監視してくれるようになります。
ボタンをクリックしてp要素が挿入された瞬間に、コンソールログにメッセージが出力されるのが分かります。
バグの修正(フィックス)
この章では、バグを修正するときに知っておくと良いコツについて見ていきましょう!主に、Dev Toolsの活用とキャッシュについて学んでいきいます。
DevToolsでソースコードを編集する
ChromeのDev Toolsにはコンソールログの出力内容を確認するだけでなく、JavaScriptのソースコードを直接編集できる機能も搭載されています。
Sourcesタブに切り替えて、目的のファイルを選択するだけで画面にソースコードが表示されます。
この画面上でそのままコードを編集し、Ctrl + Sを押すとソースコードが保存されるのでリアルタイムに内容を編集することができるわけです。ブラウザを更新すると内容はリセットされるので、あくまで一時的な編集です。
これにより、エラー内容をそのままリアルタイムに修正して結果を確認するところまでをDev Tools1つで実現できるので非常に便利です。
キャッシュを止めて修正する
Dev ToolsにはNetworkタブが提供されており、ここでWebページの通信状況を把握することができます。ただ、もっと面白いのは【Disable cache】という項目にチェックを入れることでブラウザのキャッシュを無効化できる点です。
これにより、JavaScriptのコードを編集したにも関わらず編集内容が反映されない…という悩みを解消できるのです。
基本的にブラウザにはキャッシュ機能が搭載されているので、一度でも読み込んだJavaScriptファイルなどはキャッシュに格納されるのです。これにより、次回以降は通信をするのではなく格納されているファイルを読み込むわけです。
これはWebページの高速化に繋がるわけですが、デバッグ時にはあまり意味のない機能なのでDev Toolsでキャッシュを無効化しておくと良いでしょう。
まとめ
今回は、JavaScriptのバグについて原因・発見・修正方法を学習しました!
最後に、もう一度ポイントをおさらいしておきましょう!
- バグは論理エラーに起因する内容が多い
- consoleオブジェクトやMutationObserverによるDOMの監視を活用する
- Chrome Dev Toolsを活用してバグを未然に防ぐ
上記内容を踏まえて、ぜひ自分でもプログラミングに取り入れて活用できるように頑張りましょう!