コパイロットでプログラミング

Microsoft Copilotを使って、簡単なプログラムを作ってみた話です。

背景

私は普段はPHPを使う機会がなく、あまり詳しく知りません。何かをさせたいと思っても、適切なシステム関数を思い出せませんし、細かいプログラム作法も詳しくありません。MYSQLなどでデータベース操作を行おうと思うと毎回ネット検索に頼ってしまいます。

ただし、PHPのプログラムを見れば、システム関数の名前などから何をしようとしているのかはぼんやり判ります。

そこで、AIにプログラム作成(コーディング)を任せつつ、できたものをチェック(コードレビュー)していけば目的のプログラムを簡単に作れるのではないか?と思った次第です。

なお、今回使ったのはMicrosoft Copilot(以降はコパイロットと記載します。)です。AIでにプログラム生成なら似たような名前のGithub Copilotの方がはるかに適任ではありますが、無料では使えません。無料のコパイロットでも、どの程度プログラム生成をできるのかが知りたいための実験なのです。

実践

指示1:とりあえずのたたき台

さっそく作ってみましょう。まずMicrosoft Copilot(以降はコパイロットと記載します。)を起動し、下のように命令します。

PHPスクリプトの作成を手伝ってください。

コパイロットからはどのようなプログラムを作成するのか、質問が返ってきました。指示を出します。

JSONファイルを読み込み、データを追加した後、元のJSONファイルに書き込みます。

すると、次のようなプログラムを表示してくれました。

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// JSONファイルを読み込む
$jsonData = file_get_contents($jsonFilePath);

// JSONデータをデコード
$dataArray = json_decode($jsonData, true);

// 新しいデータを追加
$newData = array(
    'id' => 123,
    'name' => '新しいユーザー',
    'email' => 'newuser@example.com'
);

$dataArray[] = $newData;

// 更新されたデータをJSON形式にエンコード
$jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

// JSONファイルに書き込む
file_put_contents($jsonFilePath, $jsonData);

echo "データが追加されました!";
?>

日本語でコメントも入っており、何をやっているのかわかりやすいですね。

もちろん、このまま使うことはできませんが、これをたたき台として変更を加え、プログラムを完成させるのも良さそうです。

無料のAIに質問するだけでこんなものがサクッと出てきてしまっては、プログラム入門書の類(「初心者のための実用XX入門」のようなプログラム本)はもう売れなくなりますね。

指示2:追加データについて仕様を指定する

次のような指示を追加しました。

追加するデータは、id, title, html のパラメーターでPOSTされます。
またidが既存データに存在する場合は、追加ではなく更新してください。

コパイロットはすぐにプログラムを変更してくれました。

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// JSONファイルを読み込む
$jsonData = file_get_contents($jsonFilePath);

// JSONデータをデコード
$dataArray = json_decode($jsonData, true);

// POSTデータを取得
$postData = array(
    'id' => $_POST['id'],
    'title' => $_POST['title'],
    'html' => $_POST['html']
);

// 既存のデータを更新または新規追加
$updated = false;
foreach ($dataArray as &$item) {
    if ($item['id'] == $postData['id']) {
        $item['title'] = $postData['title'];
        $item['html'] = $postData['html'];
        $updated = true;
        break;
    }
}
if (!$updated) {
    $dataArray[] = $postData;
}

// 更新されたデータをJSON形式にエンコード
$jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

// JSONファイルに書き込む
file_put_contents($jsonFilePath, $jsonData);

echo $updated ? "データが更新されました!" : "新しいデータが追加されました!";
?>

指示通りに動作するように修正されていることはもちろんですが、変数名もnewDataからpostDataと意味の分かるものに変えてくれているのがすごいと思いました。

人力でコーディングしていると、そういった変更は行わずに、ついついnewDataのままにしてしまうものです。

指示3:エラー処理を追加

このプログラムはエラー処理…というよりもデータチェック処理が入っていません。変なデータが入ってしまうと困ります。そこで次の指示を追加しました。

id, title, htmlでPOSTされるデータが空の場合は、JSONファイルの入出力をスキップするようにしてください。

変更後のプログラムは次の通りです。

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// POSTデータを取得
$postData = array(
    'id' => $_POST['id'],
    'title' => $_POST['title'],
    'html' => $_POST['html']
);

// POSTデータが空でないか確認
if (!empty($postData['id']) && !empty($postData['title']) && !empty($postData['html'])) {
    // JSONファイルを読み込む
    $jsonData = file_get_contents($jsonFilePath);

    // JSONデータをデコード
    $dataArray = json_decode($jsonData, true);

    // 既存のデータを更新または新規追加
    $updated = false;
    foreach ($dataArray as &$item) {
        if ($item['id'] == $postData['id']) {
            $item['title'] = $postData['title'];
            $item['html'] = $postData['html'];
            $updated = true;
            break;
        }
    }
    if (!$updated) {
        $dataArray[] = $postData;
    }

    // 更新されたデータをJSON形式にエンコード
    $jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

    // JSONファイルに書き込む
    file_put_contents($jsonFilePath, $jsonData);

    echo $updated ? "データが更新されました!" : "新しいデータが追加されました!";
} else {
    echo "空のデータがPOSTされました。処理をスキップします。";
}
?>

チェック機能が入りました。

さらに、コードレビューしていると、1つ気にあることを発見しました。それをコパイロットに聞いてみます。

入力するJSONファイルにデータが全くない場合でも、上のプログラムは正常に動作するでしょうか?

指示ではなく、質問です。

それでもコパイロットからは次のように回答がありました。「良い質問です。JSONファイルにデータが全くない場合や、空のJSONファイルが存在する場合にもプログラムが正常に動作するようにするには、ファイルを読み込んだ後にその内容が空であるかどうかを確認する必要があります。」

そして修正案を提示してくれました。

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// POSTデータを取得
$postData = array(
    'id' => $_POST['id'],
    'title' => $_POST['title'],
    'html' => $_POST['html']
);

// POSTデータが空でないか確認
if (!empty($postData['id']) && !empty($postData['title']) && !empty($postData['html'])) {
    // JSONファイルを読み込む
    $jsonData = file_get_contents($jsonFilePath);

    // JSONデータが空であれば空の配列を作成
    $dataArray = !empty($jsonData) ? json_decode($jsonData, true) : array();

    // 既存のデータを更新または新規追加
    $updated = false;
    foreach ($dataArray as &$item) {
        if ($item['id'] == $postData['id']) {
            $item['title'] = $postData['title'];
            $item['html'] = $postData['html'];
            $updated = true;
            break;
        }
    }
    if (!$updated) {
        $dataArray[] = $postData;
    }

    // 更新されたデータをJSON形式にエンコード
    $jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

    // JSONファイルに書き込む
    file_put_contents($jsonFilePath, $jsonData);

    echo $updated ? "データが更新されました!" : "新しいデータが追加されました!";
} else {
    echo "空のデータがPOSTされました。処理をスキップします。";
}
?>

指示4:コードを整理する

コードを読んでいて、ちょっとメリハリがない感じがしました。そこで、関数化して整理しようと思いました。次の指示を追加しました。

POSTデータを取得してpostDataにセットする処理を関数にしてください。その際にデータが空でないかチェックを行い、空の場合はnullを返すようにしてください。

変更後のプログラムは次の通りです。

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// POSTデータを取得し、空チェックを行う関数
function getPostData() {
    if (isset($_POST['id']) && isset($_POST['title']) && isset($_POST['html'])) {
        $postData = array(
            'id' => $_POST['id'],
            'title' => $_POST['title'],
            'html' => $_POST['html']
        );
        // データが空でないことを確認
        if (!empty($postData['id']) && !empty($postData['title']) && !empty($postData['html'])) {
            return $postData;
        }
    }
    return null;
}

// 関数を使用してPOSTデータを取得
$postData = getPostData();

// POSTデータが空でないか確認
if ($postData !== null) {
    // JSONファイルを読み込む
    $jsonData = file_get_contents($jsonFilePath);

    // JSONデータが空であれば空の配列を作成
    $dataArray = !empty($jsonData) ? json_decode($jsonData, true) : array();

    // 既存のデータを更新または新規追加
    $updated = false;
    foreach ($dataArray as &$item) {
        if ($item['id'] == $postData['id']) {
            $item['title'] = $postData['title'];
            $item['html'] = $postData['html'];
            $updated = true;
            break;
        }
    }
    if (!$updated) {
        $dataArray[] = $postData;
    }

    // 更新されたデータをJSON形式にエンコード
    $jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

    // JSONファイルに書き込む
    file_put_contents($jsonFilePath, $jsonData);

    echo $updated ? "データが更新されました!" : "新しいデータが追加されました!";
} else {
    echo "空のデータがPOSTされました。処理をスキップします。";
}
?>

しかし、このコードは明らかに無駄があります。さっそく指示…というよりも無駄な点を指摘しました。

POSTデータのチェックがissetによるチェックとemptyによるチェックで二重になっているのは無駄ではないでしょうか?

コパイロットはミスを認め、修正してくれました。

さらに疑問に思ったことがあり、コパイロットに質問しました。

json_decode($jsonData, true)の結果が配列でない場合も正常に動作するでしょうか?

コパイロットは次のように回答し、プログラムを修正しました。「良い点に気づかれました。json_decodeが失敗した場合や、結果が配列でない場合には、その場合の処理を追加してエラーハンドリングを行う必要があります。」

<?php
// JSONファイルのパス
$jsonFilePath = 'data.json';

// POSTデータを取得し、空チェックを行う関数
function getPostData() {
    if (!empty($_POST['id']) && !empty($_POST['title']) && !empty($_POST['html'])) {
        return array(
            'id' => $_POST['id'],
            'title' => $_POST['title'],
            'html' => $_POST['html']
        );
    }
    return null;
}

// 関数を使用してPOSTデータを取得
$postData = getPostData();

// POSTデータが空でないか確認
if ($postData !== null) {
    // JSONファイルを読み込む
    $jsonData = file_get_contents($jsonFilePath);

    // JSONデータをデコード
    $dataArray = json_decode($jsonData, true);

    // JSONデコードに失敗した場合や結果が配列でない場合は、空の配列を作成
    if (!is_array($dataArray)) {
        $dataArray = array();
    }

    // 既存のデータを更新または新規追加
    $updated = false;
    foreach ($dataArray as &$item) {
        if ($item['id'] == $postData['id']) {
            $item['title'] = $postData['title'];
            $item['html'] = $postData['html'];
            $updated = true;
            break;
        }
    }
    if (!$updated) {
        $dataArray[] = $postData;
    }

    // 更新されたデータをJSON形式にエンコード
    $jsonData = json_encode($dataArray, JSON_PRETTY_PRINT);

    // JSONファイルに書き込む
    file_put_contents($jsonFilePath, $jsonData);

    echo $updated ? "データが更新されました!" : "新しいデータが追加されました!";
} else {
    echo "空のデータがPOSTされました。処理をスキップします。";
}
?>

指示5:最終調整

おおむね良さそうなのですが、コードでちょっと不明な部分があったので、質問をしました。

json_encode()の第2引数のJSON_PRETTY_PRINTの意味を教えてください。

コパイロットからは「json_encode()の第2引数にJSON_PRETTY_PRINTを使用することで、出力されるJSONデータが整形(フォーマット)されて、読みやすくなります。」と回答があり、実際のファイルイメージも表示されました。これは便利ですね。

しかし、今作りたいものとJSON_PRETTY_PRINTはあっていないと思い、次のような指示を追加します。

出力するJSONファイルは、別のHTML等で読み込む予定のためインデントは不要です。またファイルサイズを小さくするために漢字のエスケープをしないようにしてください

また、次の指示も行いました。

このPHPスクリプトはjavascriptからfetchAPIを使って呼び出されます。そのため、echo "空のデータがPOSTされました。処理をスキップします。"のような表示は不要です。 その代わりにfetchAPIに状況を返すようにしたいです。
JSONファイルの入力、および、出力に失敗したときもステータスを返すようにしたいです。

こうして、最終的なPHPスクリプトが出来上がりました。

<?php
// ヘッダー設定
header('Content-Type: application/json');

// JSONファイルのパス
$jsonFilePath = 'data.json';

// POSTデータを取得し、空チェックを行う関数
function getPostData() {
    if (!empty($_POST['id']) && !empty($_POST['title']) && !empty($_POST['html'])) {
        return array(
            'id' => $_POST['id'],
            'title' => $_POST['title'],
            'html' => $_POST['html']
        );
    }
    return null;
}

// 関数を使用してPOSTデータを取得
$postData = getPostData();

// POSTデータが空でないか確認
if ($postData !== null) {
    // JSONファイルを読み込む
    $jsonData = @file_get_contents($jsonFilePath);

    // ファイル読み込みに失敗した場合
    if ($jsonData === false) {
        echo json_encode(array("status" => "read_error"));
        exit;
    }

    // JSONデータをデコード
    $dataArray = json_decode($jsonData, true);

    // JSONデコードに失敗した場合や結果が配列でない場合は、空の配列を作成
    if (!is_array($dataArray)) {
        $dataArray = array();
    }

    // 既存のデータを更新または新規追加
    $updated = false;
    foreach ($dataArray as &$item) {
        if ($item['id'] == $postData['id']) {
            $item['title'] = $postData['title'];
            $item['html'] = $postData['html'];
            $updated = true;
            break;
        }
    }
    if (!$updated) {
        $dataArray[] = $postData;
    }

    // 更新されたデータをJSON形式にエンコード
    $jsonData = json_encode($dataArray, JSON_UNESCAPED_UNICODE);

    // JSONファイルに書き込む
    if (file_put_contents($jsonFilePath, $jsonData) === false) {
        echo json_encode(array("status" => "write_error"));
    } else {
        // ステータスを返す
        echo json_encode(array("status" => $updated ? "updated" : "added"));
    }
} else {
    // 空のデータがPOSTされた場合のレスポンス
    echo json_encode(array("status" => "no_data"));
}
?>

まとめ

今回私は1行もプログラムをコーディングしていません。行ったのは、次の2つだけです。

  • プログラムの仕様を提示する。
  • コードをレビューし、間違い、動作に不安のあるコード、機能不足を指摘する。

詳細設計やコーディングはAIがしてくれたことになります。簡単なプログラムなら、これだけでも十分に出来てしまうのですね。

もっと大規模なものや漠然としたものを作ることは難しそうですが、細かく仕様を切って指示すればそれもできそうですね。

今後は「仕様→AIへの指示に落とし込むスキル」「AI生成コードのレビュースキル」がプログラム開発者として求められるスキルになっていくのでしょうか?

コメント