AIの進化でいつプログラマは不要になる？

### プログラマは不要なの？

AIの進化により、様々な職が失われるといわれています。
特に、プログラマ。すでにGoogleやMicrosoftなど、30%はAIによってコードが生成されているなんて話が耳に届く今日この頃。

確かに、最近はChatGPTに質問してそのコードをコピペすることが多くなりつつあるのは私も実感するところです。

ただそのような使い方は、ググってるということの延長線上。
これからは、もっと積極的にAIにコードを書かせないといけないと危機感を覚え、2～30年前によくあったような業務をAIにお願いしてみたいと思います。

### AIでコードを作って貰ってみる

早速ですが、以下の仕様でChatGPTにコード生成をお願いしてみます。

[仕様]

・Windows用
・C言語
・RS232Cで通信
・送信するコマンドの構造
　[シーケンス番号] [コマンドID] [パラメータ長] [パラメータ] [チェックサム]
・受信するレスポンスの構造
　[送信コマンドのシーケンス番号] [ACK or NAK] [データ長] [データ] [チェックサム]
 ・コマンドを送信するたびにシーケンス番号はインクリメント
・NAKを受信した場合は、コマンド送信をリトライする
・チェックサムが正しくない場合は、コマンド送信をリトライする。
・3秒以内に応答受信できない場合はタイムアウトとしてコマンド送信をリトライする
・コマンド送信リトライの場合、シーケンス番号はインクリメント
・最大３回リトライする

この処理に対して、更に上位の層のプログラムが存在すると考えますが、今回は、通信部分のプログラム生成をお願いしてみようと思います。

ChatGPTのプロンプトで指示します。

```
以下のプログラムを作って
[仕様]
・Windows用
・C言語
 :
```

結果は以下の通りです。

```c
#include <windows.h>
#include <stdio.h>

#define MAX_RETRY 3
#define TIMEOUT_MS 3000
#define ACK 0x06
#define NAK 0x15
#define MAX_PACKET_SIZE 256

// ----------------------------------------
// チェックサム計算（XOR）
BYTE calculate_checksum(const BYTE *data, int length) {
    BYTE cs = 0;
    for (int i = 0; i < length; i++) {
        cs ^= data[i];
    }
    return cs;
}

// ----------------------------------------
// シリアルポートオープン
HANDLE open_serial_port(const char *port_name) {
    HANDLE hSerial = CreateFileA(port_name,
        GENERIC_READ | GENERIC_WRITE,
        0, NULL, OPEN_EXISTING, 0, NULL);

    if (hSerial == INVALID_HANDLE_VALUE) {
        printf("ポートオープン失敗: %s\n", port_name);
        return NULL;
    }

    DCB dcb = { 0 };
    dcb.DCBlength = sizeof(dcb);
    if (!GetCommState(hSerial, &dcb)) {
        printf("GetCommState失敗\n");
        CloseHandle(hSerial);
        return NULL;
    }

    dcb.BaudRate = CBR_9600;
    dcb.ByteSize = 8;
    dcb.StopBits = ONESTOPBIT;
    dcb.Parity   = NOPARITY;

    if (!SetCommState(hSerial, &dcb)) {
        printf("SetCommState失敗\n");
        CloseHandle(hSerial);
        return NULL;
    }

    COMMTIMEOUTS timeouts = { 0 };
    timeouts.ReadIntervalTimeout = TIMEOUT_MS;
    timeouts.ReadTotalTimeoutConstant = TIMEOUT_MS;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    SetCommTimeouts(hSerial, &timeouts);

    return hSerial;
}

// ----------------------------------------
// コマンド送信
BOOL send_command(HANDLE hSerial, BYTE seq, BYTE cmd_id, const BYTE *params, BYTE param_len) {
    BYTE packet[MAX_PACKET_SIZE];
    int idx = 0;
    packet[idx++] = seq;
    packet[idx++] = cmd_id;
    packet[idx++] = param_len;
    for (int i = 0; i < param_len; i++) {
        packet[idx++] = params[i];
    }
    BYTE checksum = calculate_checksum(packet, idx);
    packet[idx++] = checksum;

    DWORD written = 0;
    return WriteFile(hSerial, packet, idx, &written, NULL) && written == idx;
}

// ----------------------------------------
// 応答受信
BOOL receive_response(HANDLE hSerial, BYTE expected_seq, BYTE *is_ack) {
    BYTE header[3];
    DWORD read_len;

    if (!ReadFile(hSerial, header, 3, &read_len, NULL) || read_len < 3) {
        printf("ヘッダ受信失敗/タイムアウト\n");
        return FALSE;
    }

    BYTE recv_seq = header[0];
    BYTE status   = header[1];
    BYTE data_len = header[2];

    BYTE data[MAX_PACKET_SIZE];
    if (!ReadFile(hSerial, data, data_len + 1, &read_len, NULL) || read_len < data_len + 1) {
        printf("データ受信失敗\n");
        return FALSE;
    }

    BYTE recv_cs = data[data_len];
    BYTE calc_cs = calculate_checksum(header, 3) ^ calculate_checksum(data, data_len);

    if (recv_seq != expected_seq) {
        printf("シーケンス番号不一致: 期待 %d, 受信 %d\n", expected_seq, recv_seq);
        return FALSE;
    }

    if (recv_cs != calc_cs) {
        printf("チェックサムエラー\n");
        return FALSE;
    }

    *is_ack = (status == ACK);
    return TRUE;
}

// ----------------------------------------
// コマンド送信とリトライ処理
BOOL send_command_with_retry(HANDLE hSerial, BYTE cmd_id, const BYTE *params, BYTE param_len) {
    BYTE seq = 0;
    for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
        seq++;
        printf("\n[送信試行 %d] シーケンス: %d\n", attempt, seq);

        if (!send_command(hSerial, seq, cmd_id, params, param_len)) {
            printf("送信失敗\n");
            continue;
        }

        BYTE is_ack = 0;
        if (!receive_response(hSerial, seq, &is_ack)) {
            printf("応答エラー（タイムアウトorチェックサム）\n");
            continue;
        }

        if (is_ack) {
            printf("ACK受信成功\n");
            return TRUE;
        } else {
            printf("NAK受信。再送します...\n");
        }
    }

    printf("最大リトライ回数に達しました。失敗。\n");
    return FALSE;
}

// ----------------------------------------
// メイン関数
int main() {
    HANDLE hSerial = open_serial_port("COM3"); // 必要に応じてCOMポート番号変更
    if (!hSerial) return 1;

    BYTE params[] = { 0x10, 0x20, 0x30 };  // 任意のパラメータ
    BYTE cmd_id = 0xA1;

    send_command_with_retry(hSerial, cmd_id, params, sizeof(params));

    CloseHandle(hSerial);
    return 0;
}
```

### このプログラムは、本当に仕様をみたしているでしょうか？

ぱっとみ、仕様を満たしていると思います。
ここで自分が気になったポイントして。

##### 指示していない部分を補完してコードを作成してくれる

ヘッダという単語を私は提示していないにも関わらず、ChatGPTは先頭の３バイト（シーケンス番号、ACK,NAK、データ長）をヘッダとして認識しています。

```c
    // ヘッダを受信する
    if (!ReadFile(hSerial, header, 3, &read_len, NULL) || read_len < 3) {
        printf("ヘッダ受信失敗/タイムアウト\n");
        return FALSE;
    }

      :

    // データを受信する
    BYTE data[MAX_PACKET_SIZE];
    if (!ReadFile(hSerial, data, data_len + 1, &read_len, NULL) || read_len < data_len + 1) {
        printf("データ受信失敗\n");
        return FALSE;
    }
```

可変長のコマンドなので、２段階で受信するのは、通常考えられるシンプルな実装方法だと思います。
そこまで私は指示していないにも関わらず、AIはそれを自然とやってのけます。

##### 生成されるコードの個人的な好き嫌い？

私が古い考えで保守的なのかもしれませんが、こういうコードの書き方はあまり好きでないです。
実際、チームのメンバがそれを書いたら、過去レビューで直させた可能性は高かったと思います。

```c
    if (!ReadFile(hSerial, header, 3, &read_len, NULL) || read_len < 3) {
```

read_lenはReadFileを実行した結果に得られる値であり、それらを一行で表現することに違和感があります。
確かに、先頭から順に実行される。なんの問題もないといえばそうかもしれません。

また、JAVAのStreamAPIなど、処理の結果を次の処理の入力として扱うなど、そのような書き方が流行り、それと何が違うんだと言われてしまうと、たぶん、自分が折れるとおもわれ。
（心の中では、いやそれでもなんか嫌いだなぁと、もやもやした気分にはなると思う。）

### AIはプログラマとして、一人の担当者として数えられるのか？

細かい点を挙げれば、まだまだ改良の必要があるソースだとは思います。

・プロセス化、スレッド化するなど、通信タスクとして仕上げる
・タイムアウトが30秒だった場合に、タイムアウトを待たずに処理を中断させる
・不正なデータを受信してしまったときに、それを破棄してリトライする

それらは、更に指示をさらに追加する必要があるのでしょう。
AIというメンバに適切な指示ができなかった、リーダーの問題なのだと思います。
（ただ、AIの場合、何度も指示しなおしたとしても、仕様変更ですか？と怒られることはないかもしれません。）

### 次の展開

今回の成果では、生成されたコードが正しいか、自分でレビューしテストする必要があります。
最近では、これらを行ってくれる有料のサービスがどんどん増えてきています。
それらが進化するまでの時間、まだプログラマは必要かと思いますが、あとどれだけの猶予が残されているのでしょうか？

AIとうまく付き合っていけるように、日々努力です。