Smart CodeInsightのご紹介

# Smart CodeInsightのご紹介

## Smart CodeInsight とは
Smart CodeInsightは、Delphi 12.2で追加された AIを活用したスマート支援機能です。

3つのオンラインソリューション（OpenAI、Google Gemini、Anthropic Claude）と1つのオフラインソリューション（Ollama）をサポートしています。

具体的な機能は、以下の内容がメニューより利用可能です。

|メニュー|内容|
|:--|:--|
|AIチャット|チャットビューを開く|
|バグの検知|選択したコードの潜在的なバグを検知する|
|コードの説明|選択したコードを説明する|
|コメントの追加|選択したコードにコメントを追加する|
|コードの補完|選択したコードを補完する|
|コードの最適化|選択したコードを最適化する|
|ユニットテストの追加|選択したコードにユニットテストを追加する|
|アセンブリに変換|選択したコードをアセンブリコードに変換する|
|Delphi に変換|選択したコードをDelphiコード(C++またはアセンブリ言語から)に変換する|
|C++に変換|選択したコードを C++Builderコードに変換する|

参照：[RAD Studio IDEで「AI LLM」と「Smart CodeInsight」を使用する](https://blogs.embarcadero.com/ja/using-ai-llms-in-the-rad-studio-ide-with-smart-codeinsight-ja/)

今回は、OpenAI を利用して「バグの検知」「コードの説明」「コメントの追加」「コードの最適化」を試してみたいと思います。

## OpenAIのAPIキーの取得と設定

まだ、OpenAIのAPIキーを取得していない場合、APIキーを取得し、IDEに設定する必要があります。

[生成AIとエディタが密連携して「最強」に、手が止まらない「AI連携エディタ」の世界 - 窓の杜](https://forest.watch.impress.co.jp/docs/topic/special/1624360.html)
に、OpenAIのAPIキー取得に関する記事がありますので、それらを参考にAPIキーを取得してください。

取得したら、IDEの[ツール] > [オプション]ダイアログの[IDE] > [スマート支援機能]で下図のように設定します。
![delphi_ide_ai3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318490/8316d0d6-9d7e-01bf-e418-863a379eb7fb.png)

参照：[Smart CodeInsight Options](https://docwiki.embarcadero.com/RADStudio/Athens/en/Smart_CodeInsight_Options)

<font color="Green">「モデル」欄は、参照のページでは「gpt-4」となっていますが、今回、試したのは「gpt-4o-mini」になります。必要に応じて「gpt-4o」など試すことができます。</font>



## 実際に試してみよう

試すコードは次の2つ。

```pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
    with DModuleDB do
    begin
        try
            with FDQuery1 do
            begin
                Close;
                SQL.Clear;
                SQL.Add('SELECT CODE, NAME FROM PREF');
                Open;

                cbxPref.Items.clear;
                first;
                while not EOF do
                begin
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next;
                end;
            end;
        except
            on E: Exception do
            begin
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;
```

```pascal:Unit2.pas
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then    continue;    // 先頭行はタイトルのため

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then           continue;

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;
```

どちらもボタンのクリックイベントでコンボボックスに都道府県のデータを格納しています。

1番目のコードは都道府県テーブルから格納し、2番目のコードはCSVファイルから格納しています。読み込みを行っているCSVファイルは、次のファイルです。

```csvs:pref.csv
"コード","都道府県名","フリガナ"
1,"北海道","ホッカイドウ"
2,"青森県","アオモリケン"
3,"岩手県","イワテケン"
4,"宮城県","ミヤギケン"
5,"秋田県","アキタケン"
6,"山形県","ヤマガタケン"
7,"福島県","フクシマケン"
8,"茨城県","イバラキケン"
9,"栃木県","トチギケン"
10,"群馬県","グンマケン"
11,"埼玉県","サイタマケン"
12,"千葉県","チバケン"
13,"東京都","トウキョウト"
14,"神奈川県","カナガワケン"
15,"新潟県","ニイガタケン"
16,"富山県","トヤマケン"
17,"石川県","イシカワケン"
18,"福井県","フクイケン"
19,"山梨県","ヤマナシケン"
20,"長野県","ナガノケン"
21,"岐阜県","ギフケン"
22,"静岡県","シズオカケン"
23,"愛知県","アイチケン"
24,"三重県","ミエケン"
25,"滋賀県","シガケン"
26,"京都府","キョウトフ"
27,"大阪府","オオサカフ"
28,"兵庫県","ヒョウゴケン"
29,"奈良県","ナラケン"
30,"和歌山県","ワカヤマケン"
31,"鳥取県","トットリケン"
32,"島根県","シマネケン"
33,"岡山県","オカヤマケン"
34,"広島県","ヒロシマケン"
35,"山口県","ヤマグチケン"
36,"徳島県","トクシマケン"
37,"香川県","カガワケン"
38,"愛媛県","エヒメケン"
39,"高知県","コウチケン"
40,"福岡県","フクオカケン"
41,"佐賀県","サガケン"
42,"長崎県","ナガサキケン"
43,"熊本県","クマモトケン"
44,"大分県","オオイタケン"
45,"宮崎県","ミヤザキケン"
46,"鹿児島県","カゴシマケン"
47,"沖縄県","オキナワケン"
```

では、「バグの検知」「コードの説明」「コメントの追加」「コードの最適化」を試してみ
試してみます。

### バグの検知
コードを選択し、右クリックのメニューから[Smart CodeInsight] > [バグの検知]を実行。

~~~pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
    with DModuleDB do
    begin
        try
            with FDQuery1 do
            begin
                Close;
                SQL.Clear;
                SQL.Add('SELECT CODE, NAME FROM PREF');
                Open;

                cbxPref.Items.clear;
                first;
                while not EOF do
                begin
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next;
                end;
            end;
        except
            on E: Exception do
            begin
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;

{//************ バグを見つけます ***************
提供された Delphi コードにはいくつかの潜在的なバグや改善点があります。以下にそれらを挙げます：

1. **`cbxPref.Items.Clear` の呼び出し位置**:
   - `cbxPref.Items.Clear` は、`Open` メソッドの前に呼び出すべきです。
      これにより、クエリが開かれる前にコンボボックスがクリアされ、古いデータが残ってしまうのを防げます。
      また、`cbxPref.Items.Clear` が `Open` よりも後にある場合、万が一クエリが失敗したとしても、
      アイテムが追加されることはないため、無駄なクリアが発生します。

   ```delphi
   cbxPref.Items.Clear;
   Open;
   ```

2. **`FieldByName` メソッドの戻り値の型**:
   - `FieldByName('CODE').AsInteger` は、`AsInteger` が他の型、
      例えば `LongInt` である場合に問題になる可能性があります。
      明示的に型を変換することを考慮するか、適切な型を確認してください。
      また、**整数型にキャストする前に値が存在することを確認**することが重要です。

3. **`first` と `next` の使用**:
   - `first` メソッドと `next` メソッドは、クエリが空でない場合に呼び出す必要があります。
      EOF が真である場合はループに入らないため、このコードが想定通りに動作するかどうかを
      確認する必要があります。`try` 内で EOF もチェックすると良いでしょう。

4. **エラーハンドリング**:
   - エラーが発生した場合、コンボボックスが空のままになり、以前の状態が保持される可能性が
     あります。エラー処理時に `cbxPref.Items.Clear` を呼び出すと、ユーザに示す意図しない
     古いデータを表示するリスクがあります。

5. **`TObject` へのキャスト**:
   - `AddObject` に渡す際、`TObject(FieldByName('CODE').AsInteger)` は適切に
     キャストされていますが、コードの明確さのために `IntToStr` で文字列に変換することを
     検討するか、`TObject` そのものの使用を再評価することを推奨します。
     通常、`TObject` キャストはあまり良いプラクティスとは言えません。

以下のように修正したコードを提供します：

```delphi
procedure TForm1.Button1Click(Sender: TObject);
begin
  with DModuleDB do
  begin
    try
      with FDQuery1 do
      begin
        Close;
        SQL.Clear;
        SQL.Add('SELECT CODE, NAME FROM PREF');
        Open;

        cbxPref.Items.Clear; // Move this line before opening the query
        First;

        while not EOF do
        begin
          cbxPref.Items.AddObject(FieldByName('NAME').AsString,
                                  TObject(FieldByName('CODE').AsInteger));
          Next;
        end;
      end;
    except
      on E: Exception do
      begin
        MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
        cbxPref.Items.Clear; // Optionally clear items if an error occurs
      end;
    end;
  end;
end;
```

以上の改善により、コードの堅牢性と可読性を向上させることができます。
}
~~~

~~~pascal:Unit2.pas
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then    continue;    // 先頭行はタイトルのため

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then           continue;

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;

{//************ バグを見つけます ***************
この Delphi コードにおいて、いくつかの潜在的なバグや改良点が考えられます。以下にそれらをリストアップします：

1. **CSV形式の確認**:
   `slCsv.CommaText := sl.Strings[i];` によって、`sl.Strings[i]`の内容が
   適切にカンマ区切りの形式であることが前提になっています。
   もし、カンマが含まれない場合や、余分なカンマがあった場合、`slCsv.Count`が
   期待した数にならない可能性があります。この場合のエラーハンドリングを追加した方が
   良いでしょう。

2. **`StrToIntDef`の使用**:
   `StrToIntDef(slCsv.Strings[0], 0)`が0をデフォルト値としているため、
   CSVの最初の列に0が入っていた場合の扱いに注意が必要です。
   0が適切な値であるか、あるいは何らかのエラー状態であるかを判定するロジックを
   追加すべきかもしれません。

3. **例外処理**:
   `TStringList.LoadFromFile`は、ファイルが存在しない、またはフォーマットが無効な場合に
   例外をスローする可能性があります。このため、`try..except`ブロックを追加して、
   エラーハンドリングを行うことが推奨されます。

4. **空白行の考慮**:
   CSVファイル内に空行がある場合、`slCsv.Count < 2`の判定に引っかかり、
   何も処理されずにスキップされてしまうことになります。あなたの意図として、
   空行を既にスキップするつもりでしたが、特に`sl.Strings[i]`が空である場合の
   明示的なチェックがあるとより明確になります。

5. **オブジェクトの格納内容**:
   `TObject(StrToIntDef(...))`によってアイテムをオブジェクトとして追加していますが、
   後でこのオブジェクトを使用する際に型を安全にキャストする必要があります。
   このことを念頭に置いて、警告やエラーが生じないようにチェックを行うか、
   適切な型を使用してください。

以下は、上記の点を考慮に入れたコードの修正例です：

```pascal
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
    id: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');

        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then  Continue;  // 先頭行はタイトルのため

            if Trim(sl.Strings[i]) = '' then Continue; // 空行をスキップ

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then Continue;

            id := StrToIntDef(slCsv.Strings[0], -1);
            if id = -1 then Continue; // 無効なIDをスキップ

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(id));
        end;
    except
        on E: Exception do
        begin
            ShowMessage('エラーが発生しました: ' + E.Message);
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;
```

この修正例では、ファイル読み込み時のエラー処理、空行の処理、無効なIDの処理を改善しています。
}
~~~




### コードの説明
コードを選択し、右クリックのメニューから[Smart CodeInsight] > [コードの説明]を実行。

~~~pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
    with DModuleDB do
    begin
        try
            with FDQuery1 do
            begin
                Close;
                SQL.Clear;
                SQL.Add('SELECT CODE, NAME FROM PREF');
                Open;

                cbxPref.Items.clear;
                first;
                while not EOF do
                begin
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next;
                end;
            end;
        except
            on E: Exception do
            begin
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;

{//************ 説明 ***************
このコードは、Delphiプログラミング言語で書かれたボタンのクリックイベントハンドラーです。
クリックイベントが発生したときに、指定されたデータベースから都道府県のコードと名前を取得し、
コンボボックス（`cbxPref`）に追加する処理を行います。以下はコードの詳細な説明です。

1. **プロシージャの定義**:
   `TForm1.Button1Click`というプロシージャは、`Sender`を引数に取ります。
   このプロシージャはボタンがクリックされた際に呼び出されます。

2. **データモジュールへのアクセス**:
   `with DModuleDB do`により、データモジュール`DModuleDB`内のメンバーにアクセスします。
   これにより、データベース接続やクエリの操作が簡素化されます。

3. **例外処理**:
   `try...except`ブロックが使われており、データ処理中にエラーが発生した場合に備えて、
   例外をキャッチします。

4. **クエリ設定**:
   - `FDQuery1`が参照されており、このクエリオブジェクトでデータベース操作を行います。
   - `Close`メソッドでクエリを閉じ、`SQL.Clear`でSQL文をクリアし、新たにSQL文を設定します。
     ここでは、`SELECT CODE, NAME FROM PREF`というSQL文が設定されています。

5. **クエリの実行**:
   `Open`メソッドを呼び出し、設定されたSQL文を実行します。

6. **コンボボックスのクリア**:
   `cbxPref.Items.clear`でコンボボックスのアイテムをクリアします。

7. **データの取得**:
   - `first`メソッドで結果セットの最初のレコードに移動します。
   - `while not EOF do`ループを使用して、結果セットの各レコードを処理します。
     EOF（End Of File）は、結果セットの終了を示します。
   - 各レコードから`NAME`と`CODE`の値を取得します。`FieldByName('NAME').AsString`で名前を取得し、
     `FieldByName('CODE').AsInteger`でコードを整数として取得します。
   - 取得した名前をコンボボックスのアイテムとして追加し、その際にコードをオブジェクトとして
     関連付けます。

8. **例外が発生した場合**:
   もし何らかのエラーが発生した場合、例外がキャッチされ、エラーメッセージがダイアログで表示されます。

このコード全体の目的は、`PREF`テーブルから都道府県の情報を取得し、
それをコンボボックスに表示することです。
ユーザーがボタンをクリックすると、最新の都道府県リストがコンボボックスに反映されます。
}
~~~

~~~pascal:Unit2.pas
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then    continue;    // 先頭行はタイトルのため

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then           continue;

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;

{//************ 説明 ***************
このコードは、Delphi (Object Pascal) で書かれている GUI プログラムの一部であり、
ボタンクリックイベント (`Button2Click`) ハンドラーです。このプロシージャの主な目的は、
CSVファイルからデータを読み取り、その内容をコンボボックス (`cbxPref`) に追加することです。
具体的に何をしているかを以下に説明します。

1. **コンボボックスの初期化**:
   - `cbxPref.Items.Clear` によって、コンボボックスのアイテムをすべてクリアします。
     この処理は、新しいデータを追加する前に必ず行われます。

2. **TStringListの作成**:
   - `TStringList` 型の変数 `sl` と `slCsv` を作成します。これらのリストは、
     テキストの読み込みや処理に利用されます。

3. **CSVファイルの読み込み**:
   - `sl.LoadFromFile` を使用して、実行ファイルのあるパスから `pref.csv` というファイルを
     読み込みます。
   - `ExtractFilePath(Application.ExeName)` は、現在のアプリケーションの実行ファイルの
     ディレクトリを取得します。

4. **ループ処理**:
   - 読み込んだ `sl` の内容をループで処理します。
     for ループは `0` から `sl.Count - 1` までのインデックスを持ちます。
   - ループ内で `if i = 0 then continue;` により、最初の行 (通常はヘッダーやタイトル) を
     スキップします。

5. **CSV行の処理**:
   - 各行を `slCsv.CommaText` に代入し、コンマで区切られたエントリを解析します。
   - もし `slCsv.Count < 2` の場合（データが2つ未満）、その行をスキップします。

6. **コンボボックスへの追加**:
   - 各行のデータから、最初のカラムを整数に変換し、2番目のカラムのテキストを
     コンボボックスに追加します。
   - `cbxPref.Items.AddObject` メソッドを使い、表示するテキスト（2番目のカラム）と、
     そのオブジェクト（最初のカラムの整数値）を追加します。

7. **メモリ解放**:
   - `finally` ブロック内で、`sl` と `slCsv` のインスタンスを解放します。
     これにより、使用したメモリが適切に管理されるようにします。

要するに、このコードは `pref.csv` というCSVファイルからデータを読み込み、
その内容をコンボボックスにアイテムとして追加するための処理を行っています。
最初の行はタイトルとして無視され、各データ行は、2つのカラムから情報が抽出され、
整数値とテキストとしてコンボボックスに追加されます。
}
~~~


### コメントの追加
コードを選択し、右クリックのメニューから[Smart CodeInsight] > [コメントの追加]を実行。

~~~pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
    with DModuleDB do
    begin
        try
            with FDQuery1 do
            begin
                Close;
                SQL.Clear;
                SQL.Add('SELECT CODE, NAME FROM PREF');
                Open;

                cbxPref.Items.clear;
                first;
                while not EOF do
                begin
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next;
                end;
            end;
        except
            on E: Exception do
            begin
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;

{//************ コメントを追加します ***************
以下は、与えられた Delphi コードに対して、各部分についての説明コメントを追加したコードです。

```delphi
procedure TForm1.Button1Click(Sender: TObject);
begin
    // DModuleDB を使用してデータベース操作を行う
    with DModuleDB do
    begin
        try
            // FDQuery1 を使用して SQL クエリを実行する
            with FDQuery1 do
            begin
                Close; // 既存のクエリを閉じる
                SQL.Clear; // SQL ステートメントをクリアする
                SQL.Add('SELECT CODE, NAME FROM PREF'); // PREF テーブルから CODE と NAME を取得する SQL を追加
                Open; // クエリを実行して結果をオープンする

                // combo box をクリアする
                cbxPref.Items.clear;
                first; // 最初のレコードに移動
                while not EOF do // 終了条件に達するまでループ
                begin
                    // CODE をオブジェクトとして扱い、NAME をアイテムとして combo box に追加する
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next; // 次のレコードに移動
                end;
            end;
        except
            // 例外が発生した場合の処理
            on E: Exception do
            begin
                // エラーメッセージをダイアログで表示する
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;
```

このコメントは、コードの各部分が何をしているかを説明しており、読みやすさを向上させることができます。
}
~~~

~~~pascal:Unit2.pas
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then    continue;    // 先頭行はタイトルのため

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then           continue;

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;

{//************ コメントを追加します ***************
以下に、あなたのDelphiコードに説明コメントを追加したものを示します。

```delphi
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList; // TStringList 型の変数 sl と slCsv を定義
    i: Integer; // ループ用の整数変数 i を定義
begin
    cbxPref.Items.Clear; // コンボボックス cbxPref のアイテムをクリアする

    sl := TStringList.Create; // 新しい TStringList を作成し、sl に割り当て
    slCsv := TStringList.Create; // 新しい TStringList を作成し、slCsv に割り当て
    try
        // pref.csv ファイルを読み込み、sl に格納
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        // sl の各行をループ処理
        for i := 0 to sl.Count - 1 do
        begin
            // 先頭行はタイトル行のため、スキップ
            if i = 0 then continue;

            // sl の i 番目の行をカンマで分割し、slCsv に格納
            slCsv.CommaText := sl.Strings[i];
            // slCsv の項目数が2未満の場合はスキップ
            if slCsv.Count < 2 then continue;

            // slCsv の2番目の項目をコンボボックスに追加し、1番目の項目をオブジェクトとして関連付ける
            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        // メモリを解放する
        sl.Free; // sl のメモリを解放
        slCsv.Free; // slCsv のメモリを解放
    end;
end;
```

このコメントは、各行のコードが何をしているのかを説明しています。これにより、
コードの理解が深まり、将来的なメンテナンスや修正が容易になります。
}
~~~


### コードの最適化
コードを選択し、右クリックのメニューから[Smart CodeInsight] > [コードの最適化]を実行。

~~~pascal:Unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
begin
    with DModuleDB do
    begin
        try
            with FDQuery1 do
            begin
                Close;
                SQL.Clear;
                SQL.Add('SELECT CODE, NAME FROM PREF');
                Open;

                cbxPref.Items.clear;
                first;
                while not EOF do
                begin
                    cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
                    next;
                end;
            end;
        except
            on E: Exception do
            begin
                MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
            end;
        end;
    end;
end;

{//************ コードを最適化します ***************
Delphi コードを最適化する際、以下の点に注意を払います。

1. SQL を実行する前に必要な操作を十分に明確にし、コードの重複を避ける。
2. 例外処理は外で行うことを考慮する。
3. `with` 文の使い方を改善し、可読性を向上させる。
4. 不要な `Close` を避ける。

```delphi
procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    with DModuleDB.FDQuery1 do
    begin
      // SQLの実行
      SQL.Clear;
      SQL.Add('SELECT CODE, NAME FROM PREF');
      Open;

      // cbxPref の初期化
      cbxPref.Items.Clear;
      while not EOF do
      begin
        cbxPref.Items.AddObject(FieldByName('NAME').AsString, TObject(FieldByName('CODE').AsInteger));
        Next;
      end;
    end; // FDQuery1
  except
    on E: Exception do
      MessageDlg('エラー：' + E.Message, mtError, [mbOk], 0);
  end;
end;
```

### 最適化のポイント:

1. **例外処理を最小限に**:
   `try-except` ブロックを `DModuleDB` の外に出し、すべての処理をこのブロック内に
   入れることで、エラーをキャッチしやすくしました。
2. **読みやすさの改善**:
　 `with` 文の使い方を整理しました。
3. **Clear の適切な使用**:
  `Items.Clear` をループの開始前に移動させ、不要な `Close` を削除しました。

このように、コードの可読性とエラー処理の明確さを高めています。
}
~~~

~~~pascal:Unit2.pas
procedure TForm1.Button2Click(Sender: TObject);
var
    sl, slCsv: TStringList;
    i: Integer;
begin
    cbxPref.Items.Clear;

    sl := TStringList.Create;
    slCsv := TStringList.Create;
    try
        sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');
        for i := 0 to sl.Count - 1 do
        begin
            if i = 0 then    continue;    // 先頭行はタイトルのため

            slCsv.CommaText := sl.Strings[i];
            if slCsv.Count < 2 then           continue;

            cbxPref.Items.AddObject(slCsv.Strings[1], TObject(StrToIntDef(slCsv.Strings[0], 0)));
        end;
    finally
        sl.Free;
        slCsv.Free;
    end;
end;

{//************ コードを最適化します ***************
この Delphi コードは、CSV ファイルを読み込み、特定のデータを ComboBox に追加するためのものです。
いくつかの最適化や改善が可能です。以下のポイントに基づいてコードを改善します。

1. **メモリ管理を効率化**:
   TStringList の代わりに `TStringList` の `LoadFromFile` を使うことで、直接ファイルを
   読み込むことができますが、内容を一つ一つ処理している部分が効率的でない可能性があるため、
   CSV のパース処理を外に出すことを考慮します。
2. **ループの条件を簡潔に**:
　 ループ内で先頭行をスキップするのではなく、文字列リストのインデックスを直接 i=1 から
  始める方法に変更します。
3. **変数のスコープの管理**:
   使用していない変数の宣言を避け、必要なものだけを使用します。

以下は改善されたコードの例です。

```delphi
procedure TForm1.Button2Click(Sender: TObject);
var
  sl: TStringList;
  slCsv: TStringList;
  i: Integer;
  prefValue: Integer;
begin
  cbxPref.Items.Clear;

  sl := TStringList.Create;
  slCsv := TStringList.Create;
  try
    sl.LoadFromFile(ExtractFilePath(Application.ExeName) + 'pref.csv');

    // 1行目をスキップするために、インデックスを1で初期化
    for i := 1 to sl.Count - 1 do
    begin
      slCsv.CommaText := sl.Strings[i];
      if slCsv.Count < 2 then
        Continue;

      // 変数を一度計算しておくことで、複数の位置での計算を回避
      prefValue := StrToIntDef(slCsv.Strings[0], 0);
      cbxPref.Items.AddObject(slCsv.Strings[1], TObject(prefValue));
    end;
  finally
    sl.Free;
    slCsv.Free;
  end;
end;
```

この最適化により、次のような改善がなされます：
- 不要な条件文を削減し、コードをよりシンプルかつ効率的に保つ。
- 先頭行をスキップするために、ループの開始位置を1に設定しました。
- `prefValue` を一度計算することで、可読性を向上させつつ、計算を減らします。

これにより、コードはより明確で効率的になりました。
}
~~~



## 最後に
今回の投稿は、[OpenAIのAPIキーの取得と設定](#openaiのapiキーの取得と設定)で挙げた記事 [生成AIとエディタが密連携して「最強」に、手が止まらない「AI連携エディタ」の世界 - 窓の杜](https://forest.watch.impress.co.jp/docs/topic/special/1624360.html)のテキストエディタ EmEditor Professional版でAIを使ってみたくて、OpenAIのAPIキーの取得し、ついでにDelphi 12.2で追加された Smart CodeInsight を試したのがきっかけでした。

実際、試してみると、自分では気づかなかったことを指摘されることがあり、非常に有益でした。
この投稿も、EmEditorにて OpenAIの機能で確認しながら執筆していたので、AIの有用性を実感できた機会にもなりました。


今回試したのは OpenAI ですが、他の方が書かれた Ollama を使った記事もありますので、興味がある方はそちらも参考にしてください。
[Ollamaを使ってcodellamaを試す](https://qiita.com/p_kato/items/1d99d41677392512f7ca)
[Delphi 12.2のスマート支援機能を試す](https://qiita.com/p_kato/items/63615480249e85dc8f29)
[Delphi 12.2のスマート機能でCodeLlamaの日本語化されたモデルを試す](https://qiita.com/p_kato/items/6554b5d9479e33e06763)
[DelphiのSmart CodeInsightを使ってみました！ | Object Pascalと僕と](https://coding-tips-memoranda.com/delphi%E3%81%AEsmart-codeinsight%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81/)