GPT-4にGASスクリプトのコードレビューとリファクタリングをやらせてみた結果

# 概要
にわかに騒がれて久しいgptですが、やはりエンジニアの端くれとして「こいつにプログラミングやらせてみたらどうなるんだ？」という好奇心は尽きないものです。

今回は、その一端として「レビューとリファクタリング」をやらせてみた。という内容です。

# やってみたこと
以前プライベートで公開したQiitaの記事の中でオープンにしているスクリプトに対して、レビューとリファクタリングをやってみました。

使用したスクリプトはこちら（2019年作成のものなのでかなり古めかしい…）

https://qiita.com/WdknWdkn/items/b78ae572e7cb5c9dfdca

## 元のスクリプト

```js
/** 
 * reply
 * ユーザからのアクションに返信する
 */
function reply(data) {
  // POST情報から必要データを抽出
  var lineUserId = data.events[0].source.userId;
  var postMsg    = data.events[0].message.text;
  var replyToken = data.events[0].replyToken;
  var action    = data.events[0].message.action;
  // 記録用に検索語とuserIdを記録
//  debug(postMsg, lineUserId);
  debug(action, lineUserId);

  // 検索語に対しての回答をSSから取得
  var answers = findResponseArray(postMsg);

  // 回答メッセージを作成
  var replyText = '「' + postMsg + '」ですね。かしこまりました。以下、回答です。';
  // 回答の有無に応じて分岐
  if (answers.length === 0) {
    // 「類似の検索キーワード」がないかチェック
    var mayBeWord = findMaybe(postMsg);
    if (typeof mayBeWord === "undefined") {
      // 回答がない場合の定型文
      sendMessage(replyToken, '答えが見つかりませんでした。別のキーワードで質問してみてください。');        
    } else {
      sendMayBe(replyToken, mayBeWord);
    }
  } else {
    // 回答がある場合のメッセージ生成
    answers.forEach(function(answer) {
      replyText = replyText + "\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\nQ：" + answer.key + "\n\nA：" + answer.value;
    });

    // 1000文字を超える場合は途中で切る
    if (replyText.length > 1000) {
      replyText = replyText.slice(0,1000) + "……\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\n回答文字数オーバーです。詳細に検索キーワードを絞ってください。";
    }
    // メッセージAPI送信
    sendMessage(replyToken, replyText);
  }
}
```

＃やってみた

## とりあえずレビューを依頼

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/76227e4e-5f34-2f02-ab2c-77d2ba34eab6.png)
こんなお返事が返ってきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/91afdcbd-1a61-24d7-550c-45b115f3fb81.png)
ほうなるほど、どれどれ。

### 1. コメントへの指摘
> コメントの言語が英語と日本語が混在しているため、一貫性を保つために、すべてのコメントを同じ言語（英語または日本語）に統一することをお勧めします。

仰るとおりです、直します。

### 2. 関数定義の確認
> GASスクリプトには、findResponseArray()、debug()、findMaybe()、およびsendMessage()関数が含まれていることが前提となっていますが、これらの関数が定義されていないため、その実装が適切であることを確認してください。

あーこれはですね、大丈夫です。このファイルの外に定義してありますので動作します。ありがとう。

### 3. 未使用の変数

> 以下の行に関して、action変数がdata.events[0].message.actionから取得されていますが、これはLINEのWebhookイベントオブジェクトから直接取得することはできません。action変数は使用されていないため、この行を削除しても問題ありません。

```js
var action    = data.events[0].message.action;
```

仰るとおりです。消します。

### 4. 仕様へのコメント
> コードの一部が、特定の文字数を超えた場合にメッセージを切り取るための処理が含まれています。ただし、LINEのメッセージには最大文字数が制限されているため、代わりに複数のメッセージに分割して送信することを検討してください。

確かに。これは仕様検討します。

### 総評
> 以上の点を修正することで、コードの品質と可読性が向上すると考えられます。全体的に、良いスクリプトであると言えますが、いくつかの改善点があります。

押忍、あざす。

## 次に早期returnする修正を指示
雑い感じに修正指示を投げてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/5b59f8a7-5a82-989b-8c83-e0ee264e2396.png)
余裕です、と返事。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/fd7bbd6a-908b-9fa3-e0dd-7ff73e1bf509.png)

### 修正内容

```js
function reply(data) {
  var lineUserId = data.events[0].source.userId;
  var postMsg    = data.events[0].message.text;
  var replyToken = data.events[0].replyToken;
  // 記録用に検索語とuserIdを記録
  debug(lineUserId, postMsg);

  var answers = findResponseArray(postMsg);

  if (answers.length === 0) {
    var mayBeWord = findMaybe(postMsg);
    if (typeof mayBeWord === "undefined") {
      sendMessage(replyToken, '答えが見つかりませんでした。別のキーワードで質問してみてください。');
      return;
    } else {
      sendMayBe(replyToken, mayBeWord);
      return;
    }
  }

  var replyText = '「' + postMsg + '」ですね。かしこまりました。以下、回答です。';
  answers.forEach(function(answer) {
    replyText = replyText + "\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\nQ：" + answer.key + "\n\nA：" + answer.value;
  });

  if (replyText.length > 1000) {
    replyText = replyText.slice(0,1000) + "……\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\n回答文字数オーバーです。詳細に検索キーワードを絞ってください。";
  }
  sendMessage(replyToken, replyText);
}

```

おおお、たしかに処理を修正するタイミングが見極められている。素晴らしい。

## 関数に切り出させてみた
さらに雑い感じで整理を依頼しました。果たして適切な関数に切り出しができるか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/cbe38ffd-848f-2f26-554b-6a3a1dc9bb66.png)
やりよった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/c3c4714f-fcea-0d25-b6d9-68f434484501.png)

### 修正後のスクリプト

```js
function reply(data) {
  var lineUserId = data.events[0].source.userId;
  var postMsg    = data.events[0].message.text;
  var replyToken = data.events[0].replyToken;
  
  // 記録用に検索語とuserIdを記録
  debug(lineUserId, postMsg);

  var answers = findResponseArray(postMsg);

  sendResponse(replyToken, postMsg, answers);
}

function sendResponse(replyToken, postMsg, answers) {
  if (answers.length === 0) {
    var mayBeWord = findMaybe(postMsg);
    if (typeof mayBeWord === "undefined") {
      sendMessage(replyToken, '答えが見つかりませんでした。別のキーワードで質問してみてください。');
    } else {
      sendMayBe(replyToken, mayBeWord);
    }
    return;
  }

  var replyText = buildReplyText(postMsg, answers);
  sendMessage(replyToken, replyText);
}

function buildReplyText(postMsg, answers) {
  var replyText = '「' + postMsg + '」ですね。かしこまりました。以下、回答です。';
  
  answers.forEach(function(answer) {
    replyText = replyText + "\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\nQ：" + answer.key + "\n\nA：" + answer.value;
  });

  if (replyText.length > 1000) {
    replyText = replyText.slice(0,1000) + "……\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\n回答文字数オーバーです。詳細に検索キーワードを絞ってください。";
  }
  
  return replyText;
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215128/3a5080a7-ecfd-5b7a-6d16-6d15a074f704.png)

やりよる…命名も悪くない（気がする）。

# 終わりに。
ソースコド丸投げは多分業務上しばらくできない気もしますが、普段貰わない人からレビューを受けている間隔でとても新鮮でした。ご参考までに。

# おまけ

Class作らせてみたりもしましたが、これは如何に（まだ未判断）

```js
class ReplyHandler {
  constructor(data) {
    this.lineUserId = data.events[0].source.userId;
    this.userMessage = data.events[0].message.text;
    this.replyToken = data.events[0].replyToken;
  }

  processReply() {
    // 記録用に検索語とuserIdを記録
    debug(this.lineUserId, this.userMessage);

    const answers = findResponseArray(this.userMessage);

    this.sendResponse(this.userMessage, answers);
  }

  sendResponse(userMessage, answers) {
    if (answers.length === 0) {
      const mayBeWord = findMaybe(userMessage);
      if (typeof mayBeWord === "undefined") {
        sendMessage(this.replyToken, '答えが見つかりませんでした。別のキーワードで質問してみてください。');
      } else {
        sendMayBe(this.replyToken, mayBeWord);
      }
      return;
    }

    const replyText = this.buildReplyText(userMessage, answers);
    sendMessage(this.replyToken, replyText);
  }

  buildReplyText(userMessage, answers) {
    let replyText = '「' + userMessage + '」ですね。かしこまりました。以下、回答です。';
    
    answers.forEach(function(answer) {
      replyText = replyText + "\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\nQ：" + answer.key + "\n\nA：" + answer.value;
    });

    if (replyText.length > 1000) {
      replyText = replyText.slice(0,1000) + "……\n\n＝＝＝＝＝＝＝＝＝＝＝＝＝\n\n回答文字数オーバーです。詳細に検索キーワードを絞ってください。";
    }
    
    return replyText;
  }
}

function reply(data) {
  const replyHandler = new ReplyHandler(data);
  replyHandler.processReply();
}

```