ChatGPTに質問しながら開発してみた


この記事は [LIFULL Advent Calendar 2022](https://qiita.com/advent-calendar/2022/lifull) (vol2) の9日目の記事です。

ちょうどvol1カレンダーもChatGPTネタで、「[MinikubeでRedmineコンテナを起動する方法をChatGPTに聞いてみた](https://qiita.com/YoshinagaYuta/items/2098a0f2c0bc456cd548)」が投稿されておりますので、よろしければこちらもご覧ください。

# はじめに
AI対話チャットの進歩がすごいですね。
n番煎じだと思いますが[ChatGPT](https://chat.openai.com/chat)を触ってみたので、Advent Calendarの賑やかしにしてみます。

結論としては、簡単な質問は精度が高いのですが、
**質問文が間違っている場合はその間違いの通りにしれっと嘘が返ってきます**。

上手く使うと効率的に仕事ができそうですが、
あくまでも提案として受け止め、信憑性の確認は別途行う必要がありそうです。

# 読んでおくと良さそうな記事

いくつかまとめてくださった方がいらっしゃいます。
特に入力情報として、渡すべきでないものを入れない点は注意したほうがいいですね。

https://qiita.com/sakasegawa/items/82069c97a1ee011c2d1e

https://qiita.com/autotaker1984/items/5b5ac8c01d11fbbbc4a7

https://qiita.com/autotaker1984/items/c6769f9ea7f2df1fdeb5

# 質問と回答

以下、
質問文：見出し
回答：注釈
私のコメント：本文
の形で記載します。

## 単発質問
### (質問)mysql inner joinのonの略記法

`USING`句を忘れていたので質問しました。
このように単純な質問に関しては、素早く正確な答えが返ってきます。
丁寧な説明も付けてくれます。

Google検索で調べる場合は、
文字入力→リンクを選ぶ→リンク先文章全体から目的文章を見つける→目的文章を読む
の４ステップ必要なのですが、
このうち真ん中２つを省いて２ステップで返してくれます。
上手く使えれば作業が捗りそうです。


:::note ok
(回答)
MySQLでは、INNER JOIN構文の「ON」句を略記することができます。略記すると、次のような文法になります。

```
SELECT [列1], [列2], ...
FROM [テーブル1]
INNER JOIN [テーブル2]
USING ([列])
```
上記の文法では、「USING」句を使用して、結合条件となる列を指定します。「USING」句を使用する場合、結合条件に合致する行のみが結果として得られることは変わりません。

例えば、次のような2つのテーブルがある場合、

```
[テーブル1]
id | name  | age
---+-------+----
 1 | Alice | 20
 2 | Bob   | 22
 3 | Carol | 24

[テーブル2]
id | score
---+------
 1 | 90
 2 | 80
 3 | 70
 4 | 60
```
次のようなクエリを実行すると、

```
SELECT [テーブル1].name, [テーブル2].score
FROM [テーブル1]
INNER JOIN [テーブル2]
USING (id)
```

次のような結果が得られます。

```
name  | score
------+------
Alice | 90
Bob   | 80
Carol | 70
```

このように、「ON」句を略記することで、INNER JOIN構文をより簡潔に書くことができます。
:::


### (質問)webサイトのバックエンドがあり、リクエストを受けると別のバックエンドに接続しデータを取得します。接続に失敗した場合リトライしますが、リトライ回数は何回が一般的でしょうか？

それっぽい数字が返ってきました
数字をどれくらいにしようか、と考えるときにそれっぽい数字を出してくれるので便利ですね。

上手く使うと経験知の補完に使えそうです。
（ただし返ってきた値の検証には経験知が必要）

:::note ok
(回答)
一般的なリトライ回数は、接続が失敗した場合にどのくらい頻繁にリトライするかによります。多くの場合、3～5回のリトライが一般的です。ただし、特定の環境や要件によっては、異なる回数を使用することもあります。例えば、高いレスポンス性が求められる場合は、リトライ回数を増やすことが必要になるかもしれません。また、一部のバックエンドサービスでは、特定のリトライ回数が規定されている場合があります。その場合は、その規定に従うように設定する必要があります。
:::

## コード生成
### (質問)pythonのboto3にて、与えられたパスのs3にオブジェクトが存在するかどうかを判別するスクリプト

シンプルなコードをさっと書いてくれました。
`boto3.client('s3').head_object`も実在する関数です。
コード片をすぐ出してもらえるのは良い使い方だと思います。

:::note ok
(回答)
boto3とは、Amazon Web Services (AWS) 上のアプリケーションを操作するための Python モジュールです。

指定されたS3パスに対して、オブジェクトの存在を判別するには、以下のようなスクリプトを書くことができます。

```
import boto3

# S3のクライアントを作成
s3 = boto3.client('s3')

# 判別したいS3のパス
path = 's3://my-bucket/my-object.txt'

# パスからバケット名とオブジェクト名を分離
bucket_name, object_name = path.replace('s3://', '').split('/')

# バケット内にオブジェクトが存在するか確認
try:
    s3.head_object(Bucket=bucket_name, Key=object_name)
    print(f'{path} にオブジェクトが存在します')
except s3.exceptions.ClientError:
    print(f'{path} にオブジェクトは存在しません')
```

上記のスクリプトでは、まず boto3.client('s3') を使用して、S3のクライアントを作成します。次に、path 変数にS3のパスを指定します。そして、path 変数からバケット名とオブジェクト名を分離します。最後に、s3.head_object メソッドを使用して、バケット内にオブジェクトが存在するかを確認します。 s3.head_object メソッドは、指定されたバケット内に指定されたオブジェクトが存在する場合は、そのオブジェクトのメタデータを返します。そのため、このメソッドを try 句で呼び出し、成功した場合にはオブジェクトが存在すると判断し、失敗した場合にはオブジェクトが存在しないと判断することができます。


上記のスクリプトを実行するには、まず boto3 モジュールをインストールする必要があります。boto
:::


コード片を入れたい既存スクリプトでは、`boto3.resource('s3')`を利用しているため、そちらを利用するよう合わせてもらいましょう


### (質問)上記をboto3 s3リソースを用いて書き直して

動きはするのでしょうが、`bucket.objects.all()`で全件取得しており、
オブジェクトが多い場合にパフォーマンスに難がありそうなコードが返ってきました。

暗黙的な非機能要件として、
同等の表現ができるのであれば処理速度や処理コストが最小のものを選ぶというような、
コードの選択をして貰う必要がありそうです。

:::note alert
上記のスクリプトをboto3のS3リソースを用いて書き直すと以下のようになります。

```
import boto3

# S3のリソースを作成
s3 = boto3.resource('s3')

# 判別したいS3のパス
path = 's3://my-bucket/my-object.txt'

# パスからバケット名とオブジェクト名を分離
bucket_name, object_name = path.replace('s3://', '').split('/')

# バケット内にオブジェクトが存在するか確認
bucket = s3.Bucket(bucket_name)
if object_name in bucket.objects.all():
    print(f'{path} にオブジェクトが存在します')
else:
    print(f'{path} にオブジェクトは存在しません')
```

上記のスクリプトでは、まず boto3.resource('s3') を使用してS3のリソースを作成します。次に、path 変数にS3のパスを指定します。そして、path 変数からバケット名とオブジェクト名を分離します。最後に、s3.Bucket オブジェクトを作成し、その objects プロパティに対して all メソッドを呼び出し、その結果をリスト内包表記でフィルタリングします。これにより、バケット内に指定されたオブジェクトが存在するかを確認することができます。
:::

### (質問)bucket.objects.all()は処理に時間がかかりそうです。より高速に処理できる方法はありませんか？


`bucket.objects.filter`を用いた記法に書き換えてくれました。

この関数が実在するか覚えてなかったので、ググったら過去に自分で書いた記事が出てきました。
https://qiita.com/elyunim26/items/a513226b76b3cb8928c2

自分でも忘れてることをちゃんと返してくれて捗ります。


:::note ok
(回答)
boto3のS3リソースを使用して、バケット内のオブジェクトを一覧する場合、上記のように bucket.objects.all() メソッドを呼び出すと、バケット内のすべてのオブジェクトを含むイテレータが返されます。このイテレータを使用すると、バケット内のすべてのオブジェクトを順番に処理することができますが、バケット内のオブジェクトが多い場合、処理に時間がかかる可能性があります。

そのため、バケット内のすべてのオブジェクトを一度に取得する代わりに、指定したプレフィックスを持つオブジェクトを取得するようにすることで、処理が高速化される可能性があります。以下は、その方法を示す例です。

```
import boto3

# S3のリソースを作成
s3 = boto3.resource('s3')

# 判別したいS3のパス
path = 's3://my-bucket/my-object.txt'

# パスからバケット名とオブジェクト名を分離
bucket_name, object_name = path.replace('s3://', '').split('/')

# バケット内にオブジェクトが存在するか確認
bucket = s3.Bucket(bucket_name)
if object_name in [obj.key for obj in bucket.objects.filter(Prefix=object_name)]:
    print(f'{path} にオブジェクトが存在します')
else:
    print(f'{path} にオブジェクトは存在しません')
```

上記のスクリプトでは、bucket.objects.filter(Prefix=object_name) メソッドを呼び出して、指定したプレフィックスを持つオブジェクトを含むイテレータを取得します。このイテレータを使用して、指定したプレフィックスを持つオブジェクトを一覧し、指定したオブジェクトが存在するかを確認することができます。
:::

## 質問文が誤りの場合
### (質問)istioのVirtualServiceでのretryにて、exponential backoffを設定する方法


exponential backoffがあるといいなと思って質問したのですが、(実際は無い)
僕の願望をそのまま叶えたyamlが返ってきました。

すごくそれっぽいyamlなのですが、
`backoff:`以降は**chatGPTによる創作**で、公式ドキュメントには存在しないので注意してください
https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPRetry

またドキュメントと比較すると、
地味ですが`retries:`のインデントも間違っていて、`destination`ではなく、`route`と並列要素になるはずです。
逆にそれ以外は大体あってます。すごい

:::note alert
(回答)
IstioのVirtualServiceでのretryについて説明します。 retryについては、VirtualServiceの「http」セクション内で設定することができます。

具体的には、以下のように設定します。

```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: my-virtual-service
spec:
  hosts:
  - my-service
  http:
  - route:
    - destination:
        host: my-service
      retries:
        attempts: 3
        perTryTimeout: 1s
        retryOn: gateway-error,connect-failure
        backoff:
          baseInterval: 1s
          maxInterval: 30s
          maxElapsedTime: 3m
```

上記の設定では、3回のリトライを行い、各リトライのタイムアウトを1秒に設定しています。また、「backoff」セクションで、指数バックオフを設定しています。ここで設定された値は、最初のリトライでは1秒、2回目のリトライでは2秒、3回目のリトライでは4秒となります。また、最大インターバルは30秒、最大経過時間は3分となります。

Istioでは、このようにVirtualServiceを使用して、リトライの設定を行うことができます。VirtualServiceは、Istioサービスメッシュを構成する重要なコンポーネントの1つです。また、リトライにより、サービスの不安定な接続やエラーを検知して対応することができるため、サービスの信頼性を向上させることができます。
:::

### (質問)公式ドキュメントによるとretries項目にbackoff項目は存在しないようです


指摘した場合どう返ってくるか試してみました。

代わりに`tcp:`セクションのものを生成してくれました。
`tcp:`セクションは実在するのですが、`retries:`以降が**まるごと創作**です。
https://istio.io/latest/docs/reference/config/networking/virtual-service/#TCPRoute

何を質問しても、質問者を満足させてくれようとする姿勢と捉えると感慨深いですが、
うかつに信じると危険ですね。
質問が間違ってくれるとは言ってくれないので、正しい質問を作るスキルが問われます。


:::note alert
(回答)
ご指摘いただいた通り、Istioの公式ドキュメントには、VirtualServiceの「http」セクション内で「backoff」項目を設定する方法は記載されていません。確かに「backoff」項目は「http」セクション内では設定できません。

しかし、Istioでは「backoff」項目を設定することができる別の方法があります。こちらは、VirtualServiceの「tcp」セクション内で「backoff」項目を設定する方法です。

具体的には、以下のように設定します。

```
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: my-virtual-service
spec:
  hosts:
  - my-service
  tcp:
  - match:
    - port: 80
    route:
    - destination:
        host: my-service
      retries:
        attempts: 3
        perTryTimeout: 1s
        retryOn: gateway-error,connect-failure
        backoff:
          baseInterval: 1s
          maxInterval: 30s
          maxElapsedTime: 3m
```

「tcp」セクション内で「backoff」項目を設定することで、指数バックオフを実現することができます。また、「http」セクション内で「backoff」項目を設定することはできませんが、「http」セクション内では「retries」項目を設定することができます。「http」セクション内で「retries」項目を設定することで、HTTPリクエストに対するリトライを行うことができます。

VirtualServiceを使用することで、Istioサービスメッシュ内での接続の詳細な制御を行うことができます。また、「tcp」セクション内で「backoff」項目を設定することで、指数バックオフを実現することができるため、サービスの信頼性を高めることができます。ご指摘ありがとうございます。
:::

# 終わりに
以上、chatGPTに質問しながら開発してみました。

現状だと、適切な質問を上手く投げるスキルが重要そうですね。
また、回答が正しいかの検証も必要そうです。
こういうのは触って覚えるしか無いのかなと思います。

便利なツールは使い倒して、良いプロダクト作りに繋げていきたいですね。