ソースコードのバグを探せ!デバッグの実演 #1
この記事の動画版はこちら(画像クリックでYoutubeに飛びます)
今回は、ソースコードの「デバッグ実演企画」ということで、実際のプログラムで発生したエラーを題材に、バグの原因を見つけるまでの思考方法と手順の解説を行っていきたいと思います。
プログラミングでは、エラーが発生することは日常茶飯事です。
今回はそんなエラーの一例ですが、実際にエラーに直面した時「どういう部分を、どういう手順で見ていけば良いのか?」といったような勘所が伝われば良いなと思います。
サンプルソースも掲載していますので、皆さんもぜひバグの発見にチャレンジしてみてください。
それでは早速いってみましょう!
今回のソースコード
今回のソースコードはこちらです。
<?php // 1.データベースに接続する $pdo = new PDO("mysql:dbname=sample_app;host=localhost", "app", "******"); // 2.登録値を取得する $user_name = "TEST USER"; $user_email = "xxx@example.com"; // 3.データベースに登録する $sql = "INSERT INTO user (user_name, user_email, created_at, updated_at) VALUES (:user_name, :user_email, now(), now())"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':user_name', $user_name, PDO::PARAM_STR); $stmt->bindValue(':user_email', $user_email, PDO::PARAM_STR); $flag = $stmt->execute(); unset($pdo); // 4.登録したデータのIDを取得する $user_id = $pdo->lastInsertId('user_id_seq'); $_SESSION["APP_USER_ID"] = $user_id; ?>
分かりやすくするために簡略化している部分がありますが、Webサービスの「新規ユーザー登録機能」のソースコードです。
userという名前のテーブルに対して、INSERT文でデータを登録しようとしているPHPのソースコードですね。
大まかな流れを見ていくと、
まず、処理1の部分でMySQLのデータベースに接続を行っています。
// 1.データベースに接続する $pdo = new PDO("mysql:dbname=sample_app;host=localhost", "app", "******");
次に、処理2の部分でユーザーが画面から入力したデータを変数に入れています。
// 2.登録値を取得する $user_name = "TEST USER"; $user_email = "xxx@example.com";
次に、処理3の部分でINSERT文を実行し、変数のデータをuserテーブルに登録しています。
// 3.データベースに登録する $sql = "INSERT INTO user (user_name, user_email, created_at, updated_at) VALUES (:user_name, :user_email, now(), now())"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':user_name', $user_name, PDO::PARAM_STR); $stmt->bindValue(':user_email', $user_email, PDO::PARAM_STR); $flag = $stmt->execute(); unset($pdo);
最後に、処理4の部分で、先ほど登録したuserのIDをデータベースから取得し、セッションに保存しています。
// 4.登録したデータのIDを取得する $user_id = $pdo->lastInsertId('user_id_seq'); $_SESSION["APP_USER_ID"] = $user_id;
このような流れになっています。
発生するエラー
このソースコードを実行すると、このようなエラーが発生してしまいました。
ソースコードに何らかのバグがあるようですね。
それでは、このエラー画面に表示された情報から、エラーの原因となっている「バグ」を探していきましょう。
皆さんは、このソースコードのバグを見つけられますでしょうか?
この後、正解とバグの探し方を説明していきますので、是非ソースコードの「どの部分」を「どのように直せば良いか」という自分なりの答えを用意してから確認してみてくださいね。
デバッグ実演
それでは、デバッグしていきます。
デバッグは基本的にこのようなアプローチで行っていきます。
①エラーメッセージから、エラーとなっている箇所(行)を特定する。
②その行自体のスペルミスなどをチェックする。
③その行で使っている変数の値をソースを遡ってチェックする。
落ち着いて、1つずつ順番に消去法でバグの存在を確認していきます。
まず、エラーメッセージを見ると、末尾に「signup.php on line 19」と出ていますので
signup.phpの19行目でエラーになっていることが分かります。
signup.phpの19行目は、この部分ですね。
見たところ、ここには特にスペルミスなどは無さそうです。
さらにエラーメッセージを見ると「pdo変数が定義されていない」「lastInsertId()というメンバーファンクションを実行しようとしたらエラーが発生した」と言っているので、
この「$pdo」変数に何らかの問題があると予想出来ます。
$pdoは、処理1で作成された「データベースとの接続を管理している変数」です。
処理3や処理4でデータベースのデータを操作する際に使用しています。
それが、処理4の部分でエラーになっているということのようです。
しかし、なぜか処理3の段階では、特にエラーは発生していません。
ということは、処理3完了後、処理4の前で何らかの問題が発生している可能性があります。
その辺りを見てみると、このような1行がありました。
処理3の最後でunset($pdo);されていることで、$pdoが解放されてしまっています。
$pdoが解放されてしてしまうと、データベースへの接続が切断されてしまい、その状態でデータベースに対する処理を行おうとすると、当然エラーになってしまいます。
処理4を行う前に、データベースへの接続が切断されてしまっていたことが、今回の原因という訳ですね。
解決!正しいソースコードは
本来、解放処理は、このように全てのデータベース関連処理が終わった後で行うべきです。
<?php // 1.データベースに接続する $pdo = new PDO("mysql:dbname=sample_app;host=localhost", "app", "******"); // 2.登録値を取得する $user_name = "TEST USER"; $user_email = "xxx@example.com"; // 3.データベースに登録する $sql = "INSERT INTO user (user_name, user_email, created_at, updated_at) VALUES (:user_name, :user_email, now(), now())"; $stmt = $pdo->prepare($sql); $stmt->bindValue(':user_name', $user_name, PDO::PARAM_STR); $stmt->bindValue(':user_email', $user_email, PDO::PARAM_STR); $flag = $stmt->execute(); // 4.登録したデータのIDを取得する $user_id = $pdo->lastInsertId('user_id_seq'); $_SESSION["APP_USER_ID"] = $user_id; unset($pdo); ?>
修正したところ、無事に処理が完了しました。
いかがでしたか?
予想した答えと合っていましたでしょうか?
まとめ
デバッグは、このような感じで
まず、エラーメッセージからエラーが発生している行を特定し、その行を起点に「スペルミス」や「変数値」を1つずつ確認しながら、処理を遡って探していきます。
慣れないうちは難しいと思いますが、何度もエラーを経験していく内に慣れていきます。
また、多くのエラーを経験することで、エラーメッセージを見るだけで原因が特定出来るようになったり、ソースコードを書いている段階から、エラーを生み出しそうな部分を注意出来るようになっていきます。
エラーを経験することは、その分スキルも上がるチャンスですので、恐れず多くのエラーを経験し、スキルを高めていきましょう!