プログラマのためのChatGPT

ChatGPT (GPT-4) を使いながら仕事をすることが当たり前の毎日になってきたので、最近どんな使い方をしたか、まとめてみました。

## ChatGPT活用のポイント

### 人間だと思って接する

正解がもらえるわけではなく、AIが自分の知識で答えているだけです。優秀な同僚だと思いましょう。

### プロンプトは適当でも大丈夫

「プロンプトエンジニアリング重要！」みたいな話をよく見るけど、そこまで気にしなくて大丈夫です。指示が明確でさえあればok。まずは気軽に聞いてみましょう。

### 質問より指示

ChatGPTは質問「〜は何ですか？」よりも指示「〜してください」をうけるほうが得意です。

## 色々やってもらう

まずは簡単な用途から。

DevToys的に使えます。遅いけどオールインワンで使えるので結構便利です。

```plain
🧑 日本の15:00はUTCで何時ですか？
```

```plain
🧑 URLエンコードしてください
https://clipkit.co/?こんにちは
```

```plain
🧑 整形してください
select * from users left join groups on users.id = groups.user_id
```

```plain
🧑 強度の高いパスワードを10個作ってください
```

```plain
🧑 ダミー文章を500文字くらいで作ってください
```

## 変換処理が得意

よくやる変換もChatGPT上でできます。

### CSS → SCSS

```plain
🧑 このCSSをSCSSに変換してください。

ul.list {
    padding: 1px 5px;
    overflow: auto;
}
ul.list li {
    display: block;
    margin: 2px 5px 2px 0;
}
```
↓
```scss
ul.list {
    padding: 1px 5px;
    overflow: auto;

    li {
        display: block;
        margin: 2px 5px 2px 0;
    }
}
```

### ERB → Slim

```plain
🧑 このERBをSlimに変換してください。

<% @records.each do |record| %>
  <tr>
    <td><a href="<%= admin_user_path(record) %>"><%= record.email %></a></td>
    <td class="text-right"><%= record.decorate.created_at %></td>
  </tr>
<% end %>
```
↓
```slim
- @records.each do |record|
  tr
    td
      a href=admin_user_path(record) = record.email
    td.t
```

## 高度な変換

既存のツールでは難しかった高度な変換も可能！！

```plain
🧑 jQueryを使わずES6以降のVanillaJSで書き直してください。

$(function() {
  $('#button1').click(function() {
    $('#area1').append($('#text1').val()).append('\n');
  });
});
```
↓
```javascript
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('button1').addEventListener('click', () => {
    const textValue = document.getElementById('text1').value;
    document.getElementById('area1').value += textValue + '\n';
  });
});
```

長くなるところは変数を使ったり、ロジックを理解して書き換えてくれています。

ES6が苦手な人はjQueryで書いてChatGPTに変換してもらう。という使い方ができます。ChatGPTで学習スピード爆上げになる一例といえます。

## 軽いリファクタリング

がっつりリファクタリングしてもらうこともできますが、もっと軽い修正に使っても便利です。テキストエディタでがんばるの結構大変なやつですね。

```plain
🧑 スネークケースの変数名をキャメルケースに変えてください。

const first_number = 10;
const second_number = 20;
const sum_of_numbers = first_number + second_number;
console.log('The sum of the numbers is: ' + sum_of_numbers);
```
↓
```javascript
const firstNumber = 10;
const secondNumber = 20;
const sumOfNumbers = firstNumber + secondNumber;
console.log('The sum of the numbers is: ' + sumOfNumbers);
```

## 翻訳も得意

単純な翻訳だけでなく、フォーマットを考慮して翻訳してくれたりも。（ちゃんとlangをenにしてくれてる！）

```plain
🧑 これはI18nの辞書ファイルです。英語版を作ってください。

ja:
  site:
    mypage:
      index:
        title: マイページ
        menu_writer: 記事を作成
```
↓
```plain
en:
  site:
    mypage:
      index:
        title: My Page
        menu_writer: Create Article
```

## フォーマットチェック（バリデーター）

XMLやJSONのフォーマットに問題がないかサクッとチェックできます。

```plain
🧑 このJSONに問題はありますか？

{
  "article": {
    "title": "VR技術を使った仮想体験が可能に！",

    ・・・以下略・・・
```
↓

> このJSONファイルは問題なく、正しくフォーマットされています。各セクションが適切に "heading" と "body" の2つのキーを持つオブジェクトとして表現されています。"title", "description", そして "sections" の3つのキーを持つ "article" オブジェクトも適切に定義されています。このファイルはJSONの構文に従っているため、JSONをサポートするほとんどのプログラミング言語で読み込み、解析することができます。

## エラーを説明してもらう

分からないものが出てきたらとりあえず聞く。一発で答えがでなくてもヒントが得られることが多いです。あとはGoogleで調べる。

発生源がわかっている場合はコードも一緒に貼ってあげるともっと具体的なアドバイスがもらえるかもしれません。

```plain
🧑 このエラーの原因を教えてください。

Google::Apis::ClientError
forbidden: User does not have sufficient permission for site
```
↓

> このエラーは、Google APIへのアクセス時に発生するもので、ユーザーがサイトに対して十分な権限を持っていないときに表示されます。具体的な原因は次のようなものが考えられます：
> 1. **誤った認証情報**：アプリケーションがGoogle APIにアクセスするためには、正しい認証情報（通常はAPIキーまたはOAuth 2.0トークン）が必要です。認証情報が正しくない場合、または認証情報が期限切れである場合には、この種のエラーが発生することがあります。
> 2. **権限が不足している**：Google APIには多くの種類があり、それぞれ異なる権限が必要です。例えば、Google Analyticsのデータを読み取るためには、そのアカウントに対する適切な閲覧権限が必要です。対象となるサイトに対する十分な権限を持っていない場合、このエラーが発生します。
>
> 問題の解決には、まず使用しているAPIと認証情報を確認することが重要です。それから、Google Cloud Consoleを確認して、必要な権限が正しく設定されているかどうかを確認します。さらに詳しい解決策を求める場合には、どのGoogle APIを使っていて、どのような操作を試みたか、そしてどのような認証情報を使用しているか等の詳細情報が必要になります。

# コミットメッセージの書き方を聞く

最近プロジェクトで[Conventional Commits](https://www.conventionalcommits.org/ja/v1.0.0/)を採用しているのですが、慣れるまで型（接頭辞）の選択に迷うことがあります。

```plain
🧑 Conventional Commitsで、UIの変更を行なった場合の型は何にしたらいいでしょうか。
```
↓

> Conventional Commitsのスタンダードでは、特定のUI変更に対する具体的なコミットメッセージの型(type)は厳密に定められていません。このため、どの型を使うべきかはプロジェクトによります。ただし、一般的には以下の型がUIの変更によく使われます:
>
> - **`feat`**: 新しい機能を追加した場合や大きなUIの変更を行った場合。
> - **`fix`**: UIのバグを修正した場合。
> - **`style`**: UIの見た目やスタイルを変更した場合で、これによる機能的な変更は無い場合。
> - **`refactor`**: UIのコードのリファクタリングを行った場合で、これによる外部から見た動作の変更は無い場合。
>
> しかし、これらのルールはあくまで一般的なもので、特定のプロジェクトでは独自の型を定義している場合もあります。そのため、プロジェクトごとの規約をチェックしてください。

# 命名を考える

変数名やテーブル名などの命名をAIと一緒に考えるといい感じです。

```plain
🧑 AIが提案した記事のテーマを格納するDBMSのテーブル名は何がいいでしょうか
```
↓

> 1. **`ai_article_suggestions`** : AIが提案した記事全体に関する情報を格納する場合。
> 2. **`ai_theme_suggestions`** : AIが提案したテーマだけに関する情報を格納する場合。
> 3. **`suggested_themes`** : AIによる提案であることは明らかである場合や、テーブル名をよりシンプルにしたい場合。

ここからさらに「他には？」「themeという単語は使いたくないです」などチャットでキャッチボールしていくと理想的な名前が見つかります。

# RuboCopの警告に対してアドバイスをもらう

[RuboCop](https://rubocop.org/)はドキュメントが英語なので調べるのが辛かったり、自分のコードではどう直したらいいのか、具体的な修正方法に迷うことも多いです。

ChatGPTに聞けば解説付きで具体的な修正を提案してくれます。

```plain
🧑 次のコードでconvention:Style/RedundantConditionという警告が出ました。
どうすれば解消できますか？

direction ? direction : site.directions.find_by(is_default: 1)
```
↓

> Rubocopの`Style/RedundantCondition`という警告は、通常、冗長な条件分岐を指摘します。
>
> あなたのコードは`direction`変数が存在するかどうかをチェックし、存在しない場合に`site.directions.find_by(is_default: 1)`を使用します。これはRubyの`||=`演算子を使ってシンプルに書くことができます。`||=`演算子は、左辺の変数が`nil`または`false`の場合に右辺の値を代入します。
>
> したがって、あなたのコードは以下のように書き換えることができます：
> ```ruby
> direction ||= site.directions.find_by(is_default: 1)
> ```
> このコードは、「`direction`が`nil`または`false`の場合、`site.directions.find_by(is_default: 1)`の結果を`direction`に代入する」という意味になります。これにより、Rubocopの警告を解消できます。

## HTML、CSSも作ってもらう

最近のCSSキャッチアップしきれていなくてちょっと苦手なので、ChatGPTにサポートしてもらいます。

```plain
🧑 HTMLで3つの要素をそれぞれ左、中央、右に配置するにはどうしたらいいですか？
```
↓

> CSSのフレックスボックス（Flexbox）やグリッド（Grid）を使用することで、HTML要素を簡単に左、中央、右に配置することができます。以下にそれぞれの方法を示します。
>
> **フレックスボックスを使用する方法：**
> ```html
> <!DOCTYPE html>
> <html>
> <head>
> <style>
> .container {
>   display: flex;
>   justify-content: space-between;
> }
> </style>
> ・・・以下略・・・
> ```
> ここでは、フレックスコンテナ（`.container`）内の要素が左、中央、右に均等に配置されます。`justify-content: space-between;`により、各要素の間に均等なスペースが確保されます。
>
> **グリッドを使用する方法：**
> ```html
> <!DOCTYPE html>
> <html>
> <head>
> <style>
> .container {
>   display: grid;
>   grid-template-columns: 1fr 1fr 1fr;
> }
> .container > div {
>   text-align: center;
> }
> </style>
> ・・・以下略・・・
> ```
> この例では、`.container`をグリッドコンテナに設定し、その内部に3つの等しい幅の列を作成しています。各`<div>`要素はそれぞれの列に配置され、テキストはその列の中央に配置されます。
>
> どちらの方法でも、3つの要素を左、中央、右に配置することができます。使用する方法は、要件や好みによります。

## コードの説明をしてもらう

コメントもドキュメントもない拙作のJavaScriptのコードを読んでもらいます。

```plain
🧑 このコードを説明してください

(function () {
  function defineAsyncAction(button) {
    const buttonOriginalHtml = button.html();
    button.on('click', function (e) {
      const pushedButton = $(this);
      const asyncAction = pushedButton.data('async-action');
      const url = pushedButton.attr('href');
      const redirectUrl = pushedButton.data('redirect-path') ? pushedButton.data('redirect-path') : '';

      function wait(sec) {
        const objDef = new $.Deferred();
        setTimeout(function () {
          objDef.resolve(sec);
        }, sec * 1000);
        return objDef.promise();
      }
・・・以下略・・・
```
↓

> このJavaScriptコードは、Ajaxリクエストを用いて非同期処理を行うボタンの動作を定義しています。具体的には、「data-async-action」属性を持つボタンがクリックされたとき、特定の非同期処理（長期間かかる場合や背景で実行する必要がある場合など）を実行し、その処理が完了するまでボタンを無効化します。
>
> 以下に各関数と処理の詳細を説明します：
>
> 1. **defineAsyncAction(button)**: この関数では、ボタンのクリックイベントに対する動作を定義します。クリックイベントが発生した際には、以下の処理が実行されます：
>    - ボタンが無効化され、スピナーアイコンが表示されます（performAction関数）。
>    - サーバーに非同期処理の開始をリクエストし、その処理が終了するまでポーリングを繰り返します（polling関数）。
> 2. **wait(sec)**: 指定された秒数だけ待機する関数です。非同期処理の一部として、ポーリング間隔を制御します。
> 3. **prepend_url_params(url, params)**: URLにパラメータを付加する関数です。
> 4. **polling(job_id)**: サーバーに非同期処理の状態を問い合わせ、処理が終了するまでポーリングを繰り返す関数です。
> 5. **performAction()**: ボタンを無効化し、非同期処理を開始する関数です。
>
> 最後に、HTMLが読み込まれたときやページ遷移が完了したときに、上記の非同期処理を定義するイベントリスナーが設定されています。
	
かなり適切な解説が出ました。人間がコードを読んでドキュメント化するよりも確実な感じもします。

## SQLを書いてもらう

あえてあまり情報与えず、ふわっと聞いてみました。

```plain
🧑 次のテーブルがあります。

テーブル名: users（ユーザー）
カラム: id, email

テーブル名: view_logs（閲覧ログ）
カラム: id, user_id, url, created_at

ユーザーごとの閲覧数を多い順で取得するSQLを書いてください。
閲覧のないユーザーもすべて含めてください。
```
↓

> 以下のSQLクエリを使うと、ユーザーごとの閲覧数を多い順に取得し、閲覧のないユーザーも含めることができます。
> ```sql
> SELECT users.id, users.email, COUNT(view_logs.id) AS view_count
> FROM users
> LEFT JOIN view_logs ON users.id = view_logs.user_id
> GROUP BY users.id, users.email
> ORDER BY view_count DESC;
> ```
> このクエリは、ユーザーのidとemail、そしてそのユーザーが閲覧したページの数（view_count）を選択します。
>
> `LEFT JOIN`を使うと、ユーザー表のすべての行（閲覧のないユーザーも含む）と、それに一致する閲覧ログ表の行が結合されます。そして、`GROUP BY`を使ってユーザーのidとemailでグループ化します。
>
> 最後に、`ORDER BY`を使ってview_countを降順（多い順）で並べ替えます。これにより、閲覧数が多いユーザーが上に表示されます。

ちゃんとテーブルとカラムの役割を理解して回答してくれました。解説付きで答えてくれるので、とても勉強になる

## SQLの問題点を指摘してもらう

スロークエリの調査に。

```plain
🧑 以下はPostgreSQLのSQLです。性能を改善できそうな点を教えてください。

SELECT "articles"."id" AS t0_r0, "articles"."key" AS t0_r1, "articles"."site_id" AS t0_r2, "articles"."user_id" AS t0_r3, "articles"."title" AS t0_r4, "articles"."description" AS t0_r5, 
・・・以下略・・・
```
↓

> このSQLクエリはかなり大規模で、特定の範囲で最適化できるかもしれない要素がいくつか存在します。以下、一部の提案を記載します：
>
> 1. **カラム選択**：クエリが非常に多くのカラムを選択しています。必要なカラムだけを選択することで、不要なデータの読み取りを避け、パフォーマンスを向上させることができます。
> 2. **インデックス**：検索が速くなるように、検索を行うカラムにインデックスを追加することを考えてみてください。これには `articles.site_id`、`articles.page_type`、`articles.enabled`、`articles.expired_at`、`articles.published_at` と `articles.key` などが含まれます。ただし、インデックスはデータベースの書き込み性能に影響を与える可能性があるため、適切に設計・評価する必要があります。
> 3. **LIKE句の最適化**：現在、多くのLIKE句がクエリに存在しています。LIKE句は一般的に遅いです、特に `%` を先頭に持つとインデックスが利用できない可能性があります。全文検索エンジン（PostgreSQLのFTSやElasticSearchなど）を利用することを検討してみてください。
> 4. **OR句の最適化**：複数のOR句がクエリに存在しています。OR句は一般的に遅いです、特に複数のOR句が組み合わさると最適化が困難になります。これを改善する方法の一つは、複数のクエリに分割し、結果をアプリケーション側で結合することです。
> 5. **サブクエリの最適化**：一部のサブクエリは最適化が可能かもしれません。例えば、`articles.id IN (SELECT DISTINCT "articles"."id" FROM "articles" INNER JOIN...` の部分は JOIN を使って書き換えることが可能です。

## クエリー実行計画の分析

実行計画の分析は難解です。AIにやってもらいましょう。

```plain
🧑 以下は先ほどのSQLのEXPLAINの結果です。これを見て改善できそうな点を教えてください。

Limit  (cost=9201.12..9566.12 rows=100 width=4446)
  ->  Nested Loop Left Join  (cost=9201.12..151245.13 rows=38916 width=4446)
        Join Filter: (sites_categories.id = categories.site_id)
        ->  Nested Loop Left Join  (cost=9201.12..150269.20 rows=38916 width=3133)
              ->  Nested Loop Left Join  (cost=9200.85..136879.31 rows=38916 width=2666)
                    Join Filter: (sites.id = articles.site_id)
                    ->  Index Scan using index_articles_on_last_published_at_desc on articles  (cost=9200.85..136292.54 rows=38916 width=1353)
                          Filter: (((expired_at IS NULL) OR (expired_at > '2023-05-27 12:25:29.484963'::timestamp without time zone)) AND (published_at <= '20
・・・以下略・・・
```
↓

> 提供されたSQLのEXPLAINの結果を見て、以下の改善点が考えられます：
>
> 1. **インデックスの追加と調整**: articlesテーブルに対するインデックススキャンでは多くの行がフィルタリングされています。これはインデックスが最適化されていないか、適切なインデックスが存在しない可能性があります。site_id、page_type、enabled、そして文字列検索を行っているフィールド(key, title, description)に対するインデックスの追加や調整がパフォーマンス向上に寄与するかもしれません。
> 2. **クエリの改善**: 'GUCCI'に対するフィルタリングが重複しています。これはクエリの効率を低下させます。また、同じタグIDに対するSubPlanが2回現れています。これらのクエリの改善が必要です。
> 3. **文字列操作の最適化**: upper関数を用いた大文字変換と~~を用いたパターンマッチングは、パフォーマンスに影響を与えます。この部分の最適化も検討するべきです。
> 4. **シーケンシャルスキャン**: sitesとsites_categoriesテーブルに対してシーケンシャルスキャン（全行スキャン）が行われています。もし頻繁にクエリされるのであれば、適切なインデックスを作成することで改善されるかもしれません。
>
> これらの改善点は一般的な推測であり、実際の改善はテーブルのデータ分布、DBの設定、ハードウェアの性能などによります。各提案を実装する前には、その影響を正確に理解し、必要に応じて詳細なベンチマーキングやテストを行うことをお勧めします。

## シーケンス図を描いてもらう
	
PlantUMLやMermaid形式を指定することで図を描いてもらうができます。かなり適当な指示でも大丈夫です。

```plain
🧑 次のシーケンス図をMermaidで書いてください。

- ブラウザからリバースプロキシとCloudFrontを経由してEC2オリジンにアクセスする
- CloudFrontのキャッシュが期限切れならEC2オリジンにアクセスする
- CloudFrontのキャッシュが期限切れでなければEC2オリジンにアクセスしない
- If-Modified-Sinceで問い合わせ、更新されていなければ304を返す
```
↓

> 次のMermaidシーケンス図は、指定されたシナリオを表現しています。
>
> ```plain
> sequenceDiagram
>     participant ブラウザ
>     participant リバースプロキシ
>     participant CloudFront
>     participant EC2オリジン
>
>     ブラウザ->>リバースプロキシ: HTTPリクエスト
>     リバースプロキシ->>CloudFront: リダイレクト
>     alt キャッシュが期限切れ
>         CloudFront->>EC2オリジン: HTTPリクエスト(If-Modified-Since)
>         alt 更新あり
>             EC2オリジン-->>CloudFront: HTTPレスポンス(200, データ)
>         else 更新なし
>             EC2オリジン-->>CloudFront: HTTPレスポンス(304, No Change)
>         end
>         CloudFront-->>リバースプロキシ: HTTPレスポンス(データまたは304)
>     else キャッシュが有効
>         CloudFront-->>リバースプロキシ: HTTPレスポンス(キャッシュからのデータ)
>     end
>     リバースプロキシ-->>ブラウザ: HTTPレスポンス(データまたは304)
> ```
>
> この図では次の手順が表現されています:
>
> 1. ブラウザはリバースプロキシにHTTPリクエストを送ります。
> 2. リバースプロキシはリクエストをCloudFrontにリダイレクトします。
> 3. CloudFrontはキャッシュが期限切れかどうかを確認します。
> 4. キャッシュが期限切れの場合、CloudFrontはEC2オリジンに`If-Modified-Since`を使ってHTTPリクエストをします。EC2オリジンはデータが更新されたかどうかをチェックします。更新があれば200とデータをレスポンスとして返し、更新がなければ304を返します。
> 5. キャッシュが有効な場合、CloudFrontはキャッシュからデータを取得し、それをレスポンスとしてリバースプロキシに送ります。
> 6. リバースプロキシは最終的なレスポンス（データまたは304）をブラウザに送ります。
>
> 以上のステップが、リクエストがブラウザからEC2オリジンに到達するまでのプロセスを視覚化しています。

Mermaidで書いてもらったので、簡単に図としてレンダリングできます。	

```mermaid
sequenceDiagram
    participant ブラウザ
    participant リバースプロキシ
    participant CloudFront
    participant EC2オリジン

    ブラウザ->>リバースプロキシ: HTTPリクエスト
    リバースプロキシ->>CloudFront: リダイレクト
    alt キャッシュが期限切れ
        CloudFront->>EC2オリジン: HTTPリクエスト(If-Modified-Since)
        alt 更新あり
            EC2オリジン-->>CloudFront: HTTPレスポンス(200, データ)
        else 更新なし
            EC2オリジン-->>CloudFront: HTTPレスポンス(304, No Change)
        end
        CloudFront-->>リバースプロキシ: HTTPレスポンス(データまたは304)
    else キャッシュが有効
        CloudFront-->>リバースプロキシ: HTTPレスポンス(キャッシュからのデータ)
    end
    リバースプロキシ-->>ブラウザ: HTTPレスポンス(データまたは304)
```

## 最後に

まだまだ色々な使い方が生まれそうです。