System.out.println("Java学習中");

Java Silver対策 / Suno AI作曲 / 星読み×IT 💻「技術と感性」を学ぶポートフォリオブログ

Java数当てゲームをJavaScriptに移植したときに発生したバグや気づき④

前回の記事では、JavaからJavaScriptへの移植作業中に「難易度ボタンまわり」で起きたバグや、データの保存方法についての気づきをシェアしました。

📝 前回の記事はこちら👇

blog.kenichikamoi.com

今回の記事では、ゲームのプレイ回数をカウントするタイミングや、ランダムな数字を作る処理の裏側など、「プログラムをより安全に、よりスマートに動かすための改善点」について気づいたことをシェアしたいと思います。

「動けばOK!」から一歩進んで、実際のアプリ開発でも役立つであろう考え方をまとめてみましたので、ぜひ参考にしてみてください。


💡 改善①:カウントするタイミングと「役割」の整理

まずは、ゲームの「プレイ回数」「最終プレイ日時」記録するタイミングについてです。

⚠️ プレイ回数がズレる?

最初は、プレイ回数をカウントする処理playCount++)を「ゲーム終了時」に置いていました。

しかし、これだと「途中でゲームをやめた場合」にカウントされず、プレイ回数が1回分ズレてしまう可能性がありました。

✅ どう直したか?

カウント処理「ゲーム開始時(startGame)」の先頭に移動させました。

遊園地のアトラクションと同じで、「乗り物に乗った瞬間(開始時)=1回」とカウントする方が、ユーザー体験(UX)としても自然です。

⚠️ 「記録係」と「審判」を分ける

また、最終プレイ日時(lastPlayed)の更新を、最初は勝敗を判定する judge() メソッドの中で行っていました。

しかし、これも少し不自然です。

例えるなら、「試合の審判(judge)」「スタジアムの戸締まり記録(lastPlayed)」までやっているような状態です。

役割が混ざってしまっていますよね。

✅ どう直したか?

judge() からは記録の処理を削除し、「ゲームが終わったタイミング(startGameの最後)」で更新するように直しました。

これでそれぞれの処理の「責務(役割)」がスッキリしました✨

さらに、画面への表示も少し唐突だったので、こんな風に改善しました!

System.out.println("=== プレイ情報 ===");
System.out.println("プレイ回数:" + playCount);
System.out.println("最終プレイ:" + lastPlayed);

☕ 改善②:Randomは毎回作らない!(オブジェクトの使い回し)

ゲーム内でランダムな答えを作るための Random クラス。

最初はゲームが始まるたびに以下のように書いていました。

Random random = new Random(); // 毎回作ってる

これでも動くのですが、設計としては少しもったいない状態です。

💡 例えるなら…

コーヒーを飲むたびに「新しいコーヒーメーカーを買ってきて、使い終わったら捨てる」のと同じです。すごく無駄ですよね😂

✅ どう直したか?

クラスの先頭で static を使って1回だけ作っておき、それをずっと使い回すようにしました!

static Random random = new Random();

「1台のコーヒーメーカーをずっと使う」ことで、無駄なオブジェクト生成を防ぐことができます。


🛡️ 改善③:外部データは信用しない!(防御的プログラミング)

今回一番の学びだったのが「データの安全性」についてです。

ゲームのスコアをファイルから読み込む(ロードする)機能を作ったのですが、ここには大きな罠が潜んでいました。

最初はこう書いていました。

String[] parts = line.split(",");
// データをカンマで区切って、数値に変換して保存
bestScores.put(parts[0], Integer.parseInt(parts[1]));

🤔 これの何がまずいのか?

もし、保存されているデータが壊れていて、以下のような状態だったらどうなるでしょうか?

かんたん,10 ふつう,abc ← ここがおかしい!!

プログラムは「ふつう」のスコアとして「abc」という文字を数値(Integer)に変換しようとします。

もちろん変換できず、エラー(例外)が発生してアプリが強制終了(クラッシュ)してしまいます💥

ファイルなどの「外からやってくるデータ」は、手動で編集されたり、保存中に電源が切れたりと、予期せぬ理由で壊れることがあります。

✅ どう直したか?

「データが壊れているかもしれない」という前提で、防御コード(try-catchを追加しました。

if (parts.length == 2) {
    try {
        // 数値への変換を「トライ」する
        int score = Integer.parseInt(parts[1]);
        bestScores.put(parts[0], score);
    } catch (NumberFormatException e) {
        // もし数値じゃなくてエラーになったら、ここでキャッチ!
        System.out.println("⚠ データ破損スキップ: " + line);
    }
}

宅配便の荷物を受け取って、中身が安全かチェックしてから部屋に入れるイメージです。

これで、万が一データがおかしくてもアプリが落ちることは絶対になくなりました


🧩 改善④:Java特有の罠「NullPointerException」を防ぐ

最後に、スコアを管理する Map の初期状態についてです。

「まだ一度もプレイしていない難易度」を表現するために、最初はスコアを「空っぽ(null)」にしておきたいと考えました。

Javaでは、static な変数を初期化する順番にルールがあります。

❌ 危険なパターン

比較する際にいきなりこう書いてしまうと…

count < bestScores.get(difficultyName)

もしスコアが null だった場合、「今回のスコア」「空っぽ(null)」を比較しようとして、Javaで最も有名なエラーNullPointerException(ヌルポ)」が発生してしまいます💥

✅ 安全な書き方

変数を用意した上で、先に「nullじゃないか」を確認する処理を挟みます。

static Map<String, Integer> bestScores = new HashMap<>();

static {
    bestScores.put("かんたん", null);
    bestScores.put("ふつう", null);
    bestScores.put("むずかしい", null);
}

// 〜中略〜

Integer best = bestScores.get(difficultyName);

// まず「null」かどうかを先にチェック!
if (best == null || count < best) {
    // スコア更新の処理
}

左側の best == null が本当(true)だった時点で右側の判定は行われないので、安全に処理を進めることができます✨


✍️ まとめ

今回の開発を通して、以下の機能を実装することができました。

入力バリデーション ✔

難易度設計 ✔

ベストスコア管理 ✔

永続化(ファイル保存) ✔

データ破損対策 ✔

プレイ履歴 ✔

【今回の開発の振り返り】

特に、データ破損を考慮した例外処理や、プレイ回数・最終プレイ日時の管理など、実際のアプリ開発を意識した設計を心掛けました。

単なるロジック実装にとどまらず、「状態管理」「データの安全性」を意識して開発できたことが大きな学びになりました。


🎮【Javaの数当てゲームをJavaScriptに移植】シリーズまとめ

Javaのコンソールで動いていた「数当てゲーム」を、ブラウザ上で動くJavaScriptへと移植(お引っ越し)していく開発の記録です。

まずはJavaでの基本的なゲームの作り方からスタートし、いざJavaScriptへ書き換えた際に直面した思わぬバグやエラー、そしてその解決策までを綴っています。

初学者がつまずきやすい「言語の違いによる壁」をどう乗り越えたのか、プログラムと対話しながら進めたリアルな試行錯誤のプロセスをまとめてみました。


【プロフィール・作品置き場】

Javaの学習記録や、ビジュアルノベル風のオリジナルAI楽曲などをポートフォリオで公開しています!

kenichikamoi.com


🎵 Youtube(AI楽曲一覧)はこちら

www.youtube.com