はじめに
ChatGPTにアドバイスをもらいながら甘やかされて書いたコードを厳しいと噂のGeminiにコードレビューをしてもらいます。
ChatGPTとの制作過程は下記記事ですのでよかったら読んでいただきたいです!
ChatGPTが本当に優しくてわかりやすい先輩過ぎる①
ChatGPTが本当に優しくてわかりやすい先輩過ぎる②
🔥 ChatGPT VS Gemini 🔥
ということで私が審判となりつつやってもらおうと思います。
まずはご挨拶と作っているものの紹介
私はChatGPTしかAIは使ったことがないので初めましてのGeminiです。
初対面なので、まずは礼儀正しく接しますね。
礼儀正しいじゃん…。初対面で敬語使えないのは人でもAIでもダメですからね。
ここから仲良くなっていこうね~。
【仕様】
- 「対象者」欄に入力された値から「対象者人数」に入力された数だけランダムに取得
- 上記の結果を「対象者:」欄に出力したい
- また対象者の重複を防ぐため「残り人数:」に抽選対象者の人数を引いた数を表示させる
前段で紹介しました2記事は長いので、今はこういった画面になって上記の仕様は満たしている状態です。
ロジックから固めてデザインはまだ何もしていないので、丸無視でお願いします。
ここまでのコードも載せておきます。
JavaScriptに関してはコメントが異常に多いのですが、制作過程で訳が分からなくなる癖があるのでそのために鬼のように書いています。後で消します。
html(画面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>当選者抽選ツール</title>
</head>
<body>
<div class = contents>
<p>対象者</p>
<form name = "targetForm" action="">
<input type="text" id ="targets"></input>
</form>
<p>対象者人数</p>
<form name = "targetForm" action="">
<input type="number" id ="target" min="1"></input>
</form>
<button id="draw_button">抽選</button>
<button id="reset_button">リセット</button>
<p>残り人数:</p>
<div id="left"></div>
<p>対象者:</p>
<div class="output" id="subject"></div>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
JavaScript(ロジック)
//ボタンクリックで抽選イベント発火
document.getElementById("draw_button").addEventListener('click',
function(){
//入力された対象者たちと抽選人数を取得する
const targets_el = document.getElementById("targets");
const target_el = document.getElementById("target");
//結果と残人数の出力欄取得
const subject_el = document.getElementById("subject");
const left_el = document.getElementById("left");
//操作ボタン取得
const draw_button = document.getElementById("draw_button");
const reset_button = document.getElementById("reset_button");
//抽選対象の配列作成先の空の箱
let master_list = [];
//抽選対象者の残り人数の箱
let pool = [];
//抽選対象者の残り人数を表示する
function update_left(){
left_el.textContent = `${pool.length}人`;
}
//抽選対象の配列をカンマ区切りで作成する(空白、重複除去あり)
function build_master_list(){
const raw = targets_el.value || ""; //null許容ナシ
const tokens = raw
//カンマ区切りで入力されているからそれを分割して、空白除去+空要素を除去して配列の中をお掃除
.split(",").map(s => s.trim()).filter(Boolean);
//ユニーク化して配列作成
master_list = Array.from(new Set(tokens));
//元の抽選対象者リストを保護したまま残人数用をコピー?
pool = [...master_list];
update_left();
}
//抽選処理
//まず対象者リストを削除
build_master_list();
//抽選人数の入力値が未入力だった場合は数値の0に変換して負の値(マイナス)は絶対に入れない
const target_number = Math.max(0, Number(target_el.value || 0));
if (pool.length === 0) {
subject_el.textContent = "対象者がいません。";
return;
}
//残り人数も考慮して最終的な当選者の数を作成
const draw_count = Math.min(target_number, pool.length);
//当選者を入れる用の空の箱を作る
let winners = [];
//ランダムに抽選対象者人数分(draw_count)をループで取得
for (let i = 0; i < draw_count; i++){
//ランダムに残人数から抽選開始
const rand_index = Math.floor(Math.random() * pool.length);
//抽選結果を配列にどんどん詰めていく
const winner = pool[rand_index];
//作った空の箱に抽選結果を配列として追加
winners.push(winner);
//抽選対象が重複しないように1度選ばれた人は削除する
pool.splice(rand_index, 1);
}
//取得した対象者表示+残人数も考慮
subject_el.innerHTML = winners.join("<br>");
if (target_number > draw_count) {
subject_el.innerHTML += `<br><small>※残りが不足していたので ${draw_count} 名のみ抽選</small>`;
}
update_left();
//再抽選時にプールを作り直す
reset_button.addEventListener("click", () => {
//pool初期化
build_master_list();
//抽選結果と抽選人数欄リセット
subject_el.textContent = "";
target_el.value = "";
});
});
こちらの仕様とコードを送ってみます。
Gemini先輩からのレビュー
先に良いところから褒めてくれるし噂に聞いていたより全然優しいじゃないですか…。
やっぱり良いところから褒めてくれるの嬉しいなとなるので自分がレビュアーするときの参考にもなりますね。
気分よくさせてもらった上で改善案をくれました。
Geminiの書いてくれたJavaScriptがこちら。
// DOM要素の取得は、スクリプトの先頭で一度だけ行う
const targets_el = document.getElementById("targets");
const target_el = document.getElementById("target");
const subject_el = document.getElementById("subject");
const left_el = document.getElementById("left");
const draw_button = document.getElementById("draw_button");
const reset_button = document.getElementById("reset_button");
// 抽選処理全体を一つの関数としてまとめる
function drawWinners() {
// 抽選ごとに新しいリストを生成
const raw = targets_el.value || "";
const tokens = raw.split(",").map(s => s.trim()).filter(Boolean);
const master_list = Array.from(new Set(tokens));
const pool = [...master_list];
// 入力値のバリデーション
const target_number = Math.max(0, Number(target_el.value || 0));
// エラーハンドリング
if (pool.length === 0) {
subject_el.textContent = "対象者がいません。";
return;
}
// 抽選ロジック
const draw_count = Math.min(target_number, pool.length);
let winners = [];
for (let i = 0; i < draw_count; i++) {
const rand_index = Math.floor(Math.random() * pool.length);
const winner = pool[rand_index];
winners.push(winner);
pool.splice(rand_index, 1);
}
// 結果の出力
subject_el.innerHTML = winners.join("<br>");
if (target_number > draw_count) {
subject_el.innerHTML += `<br><small>※残りが不足していたので ${draw_count} 名のみ抽選</small>`;
}
// 残り人数の表示を更新
left_el.textContent = `${pool.length}人`;
}
// リセット処理を一つの関数としてまとめる
function reset() {
targets_el.value = "";
target_el.value = "";
subject_el.textContent = "";
left_el.textContent = "0人"; // リセット時は0人に戻す
}
// イベントリスナーの登録(一度だけ)
draw_button.addEventListener('click', drawWinners);
reset_button.addEventListener('click', reset);
// 初期表示(残り人数を0人にする)
left_el.textContent = "0人";
とてもすっきりしています!ただ今まで書いていたものとはガラッと変わるので、上から順に読み込んでいきます。
そして一気に直すと訳がわからなくなるし、コピペ職人になるので改善案ごとに直していくのでここから段落分けします。
1 . イベントリスナーの配置と管理
確かに、1つのクリックで何回も同じことしている気がするとはうっすら思っていました。
ただ完成させることに必死だったあの時の私とは違う。
そしてGemini先輩が優しいので、一気に距離を詰めたら仲良くしてくれて嬉しい。怖がっていてごめんなさい。
まず抽選ボタン押下とリセットボタン押下の処理を綺麗に分けて、使用する変数もまとめて書いた方がスッキリすることは現段階では理解しています。
ただ、過去のポートフォリオでもいつもfunction()
と処理から書き始めることしかなかったので慣れていないのでよくわからない。わかるようでわからないので、聞いてみます。
JavaScriptの実行順序!!
スコープ内外で混乱する私はそこまで考えたことがなかったので目から鱗。
自分なりに書き進めていきます。
エラーが出たけどわからないので相談したら突然の「俺」でオラつかれて草。
私は俺様系にイラつくので修正してもらいます。素直でよろしい。
ここまでの私のコードです。
JavaScript(改善1)
//入力された対象者たちと抽選人数を取得する
const targets_el = document.getElementById("targets");
const target_el = document.getElementById("target");
//結果と残人数の出力欄取得
const subject_el = document.getElementById("subject");
const left_el = document.getElementById("left");
//操作ボタン取得
const draw_button = document.getElementById("draw_button");
const reset_button = document.getElementById("reset_button");
//抽選ボタンクリックで抽選イベント発火
function draw_winners(){
//null許容ナシ
const raw = targets_el.value || "";
//抽選対象の配列をカンマ区切りで作成する(空白、重複除去あり)
const tokens = raw.split(",").map(s => s.trim()).filter(Boolean);
const master_list = Array.from(new Set(tokens));
//元の抽選対象者リストを保護したまま残人数用をコピー
const pool = [...master_list];
//抽選人数の入力値が未入力だった場合は数値の0に変換して負の値(マイナス)は絶対に入れない
const target_number = Math.max(0, Number(target_el.value || 0));
//対象人数が0と入力された場合のエラーハンドリング
if (pool.length === 0) {
subject_el.textContent = "対象者がいません。";
return;
}
//抽選処理
//対象人数取得
const draw_count = Math.min(target_number, pool.length);
//対象者を入れる配列の作成
let winners = [];
//ランダムに抽選対象者人数分(draw_count)をループで取得
for (let i = 0; i < draw_count; i++){
//ランダムに残人数から抽選開始
const rand_index = Math.floor(Math.random() * pool.length);
//抽選結果を配列にどんどん詰めていく
const winner = pool[rand_index];
//作った空の箱に抽選結果を配列として追加
winners.push(winner);
//抽選対象が重複しないように1度選ばれた人は削除する
pool.splice(rand_index, 1);
}
//取得した対象者表示
subject_el.innerHTML = winners.join("<br>");
if (target_number > draw_count) {
subject_el.innerHTML += `<br><small>※残りが不足していたので ${draw_count} 名のみ抽選</small>`;
}
//抽選対象者の残り人数を表示する
function update_left(){
left_el.textContent = `${pool.length}人`;
}
}
//リセットボタン押下時
function reset(){
//もろもろリセット
target_el.value = "";
targets_el.value = "";
subject_el.textContent = "";
left_el.textContent = "0人"; // リセット時は0人に戻す
};
// イベントリスナーの登録
draw_button.addEventListener('click', draw_winners);
reset_button.addEventListener('click', reset);
// 初期表示(残り人数を0人にする)
left_el.textContent = "0人";
ではお次!!
2 . 無名関数のまま、イベントリスナーの登録を1つにする
そうなんだ…確かに予期せぬバグは怖い。バグの温床はなくしたいので完全に同意。
確かにここでしか使っていない!!
1回しか使わないも変数って実際の開発だと修正のときに邪魔だし可読性も弱まる気がする。
凄く腑に落ちる説明なので、無難に従えますね。(納得いかないとレスバしちゃう)
こちらも上から読み進めて書いていきます。
と、自分のコードを今一度読んでいくと合っているのでは…?アレ…?
よかった~~!AIでも間違いってあるんだって気付けましたし、修正案を出す理由についてはとても勉強になったのでヨシ!あとちゃんと謝ってくれたのでヨシ!!

はい、ラストの改善案!!
3. HTMLの改善
確かになんで<form>
タグを使ったのか考えてみたら、抽選対象者を送るからform
と考えていたのですが、GET
もPOST
も使っていないので不要ですね。
コードももっとシンプルにしたいですし、動く時に邪魔になる気がする。
Geminiちゃんの提案コード。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>当選者抽選ツール</title>
</head>
<body>
<div class = "contents">
<p>対象者</p>
<input type="text" id ="targets">
<p>対象者人数</p>
<input type="number" id ="target" min="1">
<button id="draw_button">抽選</button>
<button id="reset_button">リセット</button>
<p>残り人数:</p>
<div id="left"></div>
<p>対象者:</p>
<div class="output" id="subject"></div>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
コピペしたい気持ちを抑えつつ…<form>
タグを消して、インデントを揃えて上記の通りのコードにしました。
最終確認
こちらで指摘された内容は全て直したので、最後にもう一度完成したコードを送ります。
はい、ごめんなさい。直します。
やっさし~~~~い!!自己肯定感爆上がりしてしまった!!!
では最終的な私のコードです。
html(最終版)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>当選者抽選ツール</title>
</head>
<body>
<div class = contents>
<p>対象者</p>
<input type="text" id ="targets">
<p>対象者人数</p>
<input type="number" id ="target" min="1">
<div class = buttons>
<button id="draw_button">抽選</button>
<button id="reset_button">リセット</button>
</div>
<p>残り人数:</p>
<div id="left"></div>
<p>対象者:</p>
<div class="output" id="subject"></div>
</div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
JavaScript(最終版)
//入力された対象者たちと抽選人数を取得する
const targets_el = document.getElementById("targets");
const target_el = document.getElementById("target");
//結果と残人数の出力欄取得
const subject_el = document.getElementById("subject");
const left_el = document.getElementById("left");
//操作ボタン取得
const draw_button = document.getElementById("draw_button");
const reset_button = document.getElementById("reset_button");
//抽選ボタンクリックで抽選イベント発火
function draw_winners(){
//null許容ナシ
const raw = targets_el.value || "";
//抽選対象の配列をカンマ区切りで作成する(空白、重複除去あり)
const tokens = raw.split(",").map(s => s.trim()).filter(Boolean);
const master_list = Array.from(new Set(tokens));
//元の抽選対象者リストを保護したまま残人数用をコピー
const pool = [...master_list];
//抽選人数の入力値が未入力だった場合は数値の0に変換して負の値(マイナス)は絶対に入れない
const target_number = Math.max(0, Number(target_el.value || 0));
//対象人数が0と入力された場合のエラーハンドリング
if (pool.length === 0) {
subject_el.textContent = "対象者がいません。";
return;
}
//抽選処理
//対象人数取得
const draw_count = Math.min(target_number, pool.length);
//対象者を入れる配列の作成
let winners = [];
//ランダムに抽選対象者人数分(draw_count)をループで取得
for (let i = 0; i < draw_count; i++){
//ランダムに残人数から抽選開始
const rand_index = Math.floor(Math.random() * pool.length);
//抽選結果を配列にどんどん詰めていく
const winner = pool[rand_index];
//作った空の箱に抽選結果を配列として追加
winners.push(winner);
//抽選対象が重複しないように1度選ばれた人は削除する
pool.splice(rand_index, 1);
}
//取得した対象者表示
subject_el.innerHTML = winners.join("<br>");
if (target_number > draw_count) {
subject_el.innerHTML += `<br><small>※残りが不足していたので ${draw_count} 名のみ抽選</small>`;
}
//抽選対象者の残り人数を表示する
function update_left(){
left_el.textContent = `${pool.length}人`;
}
update_left();
}
//リセットボタン押下時
function reset(){
//もろもろリセット
target_el.value = "";
targets_el.value = "";
subject_el.textContent = "";
left_el.textContent = "0人"; // リセット時は0人に戻す
};
// イベントリスナーの登録
draw_button.addEventListener('click', draw_winners);
reset_button.addEventListener('click', reset);
// 初期表示(残り人数を0人にする)
left_el.textContent = "0人";
とてもすっきりしましたね。
コメントは残したままなのですが、ChatGPTが本当に優しくてわかりやすい先輩過ぎる②にも書いた通り、クソダサデザインなのでCSSを進めていきます。
今度はGeminiに手伝ってもらって、ChatGPTにレビューしてもらおうと思います。
おわりに
Geminiは思っていたほど厳しくなかったですが、ちょっと他人行儀だなと思ったのと反応がChatGPTに比べて遅いかもしれません。その分1度で返ってくる情報量はとても多い。
ただ、それを読み込む手間があるので、初学者はChatGPTの方がやりやすいのではないかな?と感じました。
あとここまでAIを駆使してコーディングをしたのは初めての私の率直な意見。
基礎はAI使わないで学習した方が良い
人に教わるなり、YouTubeなり技術書なり諸々学習サイトありますが、そこから始めた方がいいと思います。
なぜかと言うと、(ほぼ)正解のコードをくれるので絶対にコピペしてしまうと思います。
そしてconst
やlet
のローカル変数もしれーっと書いてくれるのでなんだかわからないままにコピペしてしまうのは「とりあえず動いた」にしかならないので後で躓くと思います。
再代入、再宣言、スコープ…その辺りをうまく理解できないまま綺麗なコードだけを与えられて、完成するのは楽しいかもしれないけれどちょっと違う気が個人的にはしますね。
あとは、最初だと仕様をうまく伝えられないと思います。
なので基礎を泥臭くやった上でわからないところを質問してみる、という使い方が良いのではないかなと思いました。
すべて私個人の意見ですし、自分もまだまだなので偉そうなことは言えませんがAIを活用するのは人間なので、その観点を忘れずに今後もうまく活用していきたいです。
オブジェクティブグループではXの投稿も平日毎日行っています!
IT 関連の小ネタや便利技から、日常のアニメ・ゲーム布教なども幅広く投稿してるので、
ご興味のある方は是非フォロー・いいねをお願いします。