VS Code + Gemini コードアシストがめっちゃ便利

X API の認証に係る問題の解決に一番時間がかかりました。


# はじめに

Notion データベースから URL を取得、AI で良い感じに加工してから X にポストするという Node.js アプリ（API？）を作ろうと思ったのが始まりです。
- Notion
- OpenAI / Gemini
- X

これらの API を呼ぶだけだから簡単だろうと、最初は make.com で作り始めました。が、結果として挫折しました。Notion のデータベースに接続するまでは簡単だったんですが、make.com 独自の記法があって「逆にむずくね？」な感じでした。


--

速攻で make.com アカウントは閉鎖し、無料で使いまくれる！ という触れ込みの Gemini コードアシストを使ってみる方針に切り替えました。

https://cloud.google.com/blog/ja/topics/developers-practitioners/gemini-code-assist


--

本投稿は Gemini コードアシストを使ってみた！ なお話となります。

環境
- VS Code
- Gemini コードアシスト（無料版）

<p><details lang="ja" --open><summary>筆者の Node.js 経験値</summary>

- Node.js で動くサービスというか Web API というか？は作ったことがある（今もクラウド上で動いてる）
- JavaScript も TypeScript も知ってるが、、、
    - `Date` オブジェクトを日本時間で取得する方法は調べないと分からない
    - 配列に要素を追加するメソッドが `push` だというのを忘れる程度に普段は触らない
    - JavaScript はトリプル === が推奨なのは知ってるが理由は何だっけ？
    - AI の出力したコードを読んだら何やってるかは分かる（思い出せる）

<!------- End of Details JA Tag -------></details></p>





# Node.js と相性が良い

今回のように Notion、OpenAI、Google、X 等の「良くできている API」を叩いて取得したデータをバケツリレーするだけの場合、AI のコードアシストとの相性は抜群でした。



# １）Qiita の最新投稿を取得して Notion のデータベースに登録する

最初に作った API がコチラ。成果物としては 170 行ぐらいです。


まず最初に以下のプロンプトを投げました。

> 💭  
> Qiita API を使って投稿一覧を取得、Notion のデータベースに重複がない形で追加するコードを node.js と typescript で作って


で、これに関してはほぼ修正なしで動きましたｗ 取得したデータを右から左にリレーするだけなので流石に出来てくれないと困りますね。

ちなみに特に何も言ってないんですが、 `.env` ファイルに API キーを纏めたりとか、そこら辺まで全部やってくれた状態でした。


## ページネーションへの対応

さて、動いてはいたんですが Qiita の最新投稿を数件しか取得出来てなかったので、

> 💭  
> getQiitaPosts を Qiita の全ての投稿を取得するようにして

みたいにメソッド名を指定して指示出ししたらページネーションに対応した状態にしてくれました。こういうのが API を調べに行く手間なしで完成します。素晴らしい！


## 最大取得数の設定

全投稿の取得が必要なのはデータベースを初期化する最初の１回のみ、ということで普段は３件程度を取得して終わるようにしたかったので、

> 💭  
> getAllQiitaPosts に取得する最大ポスト数を指定するパラメーターを追加して

って言ったらそれもやってくれましたｗ


## 生成されたコード

それが以下のコード。`maxPosts` に初期値が設定されていて省略可能なパラメーターなのが優秀すぎますね。各種コメント、ログ出力も AI がやってくれたコードです。

（自分なら絶対こんなに細かくやらない…）

ちなみに `.then(...)` がイヤだったので `then を await に変えて` っていったら直してくれました。このコードは確かやってもらったので、全体を `try-catch` で囲う感じになってます。（多分呼び出し元で囲った方が良いですね）


<p><details lang="ja" --open><summary>元のメソッド（AI 生成）</summary>

※ `👇` が付いてる部分は取り急ぎページネーションを無効化するために手動で追加したコード

```ts
// Qiita の投稿一覧を取得する関数 (すべてのページを取得)
async function getAllQiitaPosts(): Promise<any[]> {
    try {
        let page = 1;
        // 👇
        const perPage = 10;  //100; // 最大100件まで取得可能
        let allPosts: any[] = [];
        let hasMore = true;

        while (hasMore) {
            const response = await axios.get(qiitaApiEndpoint, {
                headers: qiitaApiHeaders,
                params: {
                    page: page,
                    per_page: perPage,
                },
            });

            const posts = response.data;
            if (posts.length > 0) {
                allPosts = allPosts.concat(posts);
                page++;

                // 👇
                hasMore = false; // 常に終了
            } else {
                hasMore = false; // 取得した投稿がなければ終了
            }
        }

        return allPosts;
    } catch (error) {
        console.error('Qiita からの投稿の取得に失敗しました', error);
        return [];
    }
}
```

<!------- End of Details JA Tag -------></details></p>


👇 修正後

```ts
// Qiita の投稿一覧を取得する関数 (最大ポスト数を指定可能)
async function getAllQiitaPosts(maxPosts: number = Infinity): Promise<any[]> {
    try {
        let page = 1;
        const perPage = 100; // 最大100件まで取得可能
        let allPosts: any[] = [];
        let hasMore = true;
        let fetchedPostsCount = 0;

        while (hasMore && fetchedPostsCount < maxPosts) {
            const response = await axios.get(qiitaApiEndpoint, {
                headers: qiitaApiHeaders,
                params: {
                    page: page,
                    per_page: perPage,
                },
            });

            const posts = response.data;
            if (posts.length > 0) {
                const postsToAdd = posts.slice(0, maxPosts - fetchedPostsCount);
                allPosts = allPosts.concat(postsToAdd);
                fetchedPostsCount += postsToAdd.length;

                page++;
                hasMore = posts.length === perPage; // 1ページに最大件数返っている場合のみ、続きがあると判断
            } else {
                hasMore = false; // 取得した投稿がなければ終了
            }
        }

        return allPosts;
    } catch (error) {
        console.error('Qiita からの投稿の取得に失敗しました', error);
        return [];
    }
}
```

:::note info

`const perPage = 100;` はちょっと大袈裟なので `const perPage = Math.min(10, maxPosts);` に手動で修正。

もしかしたら `サーバーへの負荷を考えて` で直してくれたかも？

:::



## 修正レポート

チャットにはレポートも付いてます。取り急ぎページネーションを無効化するために追加した処理もちゃんとバグとして認識、修正されててスゴイ。。。

![](https://dl.dropbox.com/scl/fi/2sxlnmgfu5sb38ov7e3yo/Sator-AI_QiitaPostsImporter.png?rlkey=qv3qgc1yejqp2swuors2vt5xp&dl=0)



## 仕上げ

最後に、

> 💭  
> 全てのメソッドに JSDoc を追加して

を実行して `Diff with Open File` で差分を確認、`Accept` して終了。これはマジで便利！


他にはめっちゃ雑なんですが、

> 💭  
> 全体をブラッシュアップして

もおススメ。コードが更新されているのにコメントが古いままの箇所とか全部直してくれます。確認して Accept するだけ！


```ts
/**
 * Qiita の投稿データを Notion データベースに追加する関数
 * @param {any} post - Qiita の投稿データ
 * @returns {Promise<void>} - Notion への追加が完了したことを示す Promise
 */
async function addQiitaPostToNotion(post: any): Promise<void> {


/**
 * Notion データベースに既に存在する URL を取得する関数
 * @returns {Promise<string[]>} - 既存の URL の配列を返す Promise
 */
async function getExistingUrls(): Promise<string[]> {


/**
 * Qiita の投稿一覧を取得する関数 (最大ポスト数を指定可能)
 * @param {number} maxPosts - 取得する最大ポスト数 (デフォルトは Infinity)
 * @returns {Promise<any[]>} - Qiita の投稿一覧を返す Promise
 */
async function getAllQiitaPosts(maxPosts: number = Infinity): Promise<any[]> {


/**
 * メインの処理を実行する関数
 * @returns {Promise<void>} - メイン処理が完了したことを示す Promise
 */
async function main(): Promise<void> {
```


--

※ Node.js 界隈のお作法かもしれませんが、エラーが起きると throw せずに `console.error()` してから null を返しがちです。

必要に応じて事前に throw するコードを書いておくと、それに倣って修正の提案をしてくるので後々楽できます。



# ２）Notion のデータベースから URL を取得して AI でアレコレして X に投稿

２つ目。こちらの成果物は 530 行程度に。


まずは、

> 💭  
> npm で notion のデータベースの全アイテムを取得して openAI で chat completion をしてから X にポストするプログラムを教えて

（npm はパッケージマネージャーなんですが良い感じに無視してくれてますｗ）


これで動くコードが出てきました。が！ 冒頭に書いた通り X の API 認証（トークン発行に決まった順序がある）に躓いて中々ポスト出来ませんでした。

それ（俺）以外は問題ありませんでしたね。


## API のバージョンを上げる

初期に書き出されたコードは X API の v1 を使った物でした。これは

> 💭  
> X API を v2 を使うように更新して

で更新してもらいました。

（認証エラーの原因が v2 を使っているからか？ と思って `X API の v1 を使うように修正して` と言ったら「既に v1 を使っていてエラーもありません」と教えてくれました）



## 細かなエラーの修正

TypeScript をコンパイルした `.js` を minify して GitHub Actions 上で実行する、って感じで運用する予定だったので、minify 方法を教えてもらって実施 → `node ./release/bundle.min.js` で実行したらエラーが出ました。


これは

> 💭  
> エラーが出る Error [ERR_REQUIRE_ESM]: require() of ES Module

で直せました 😊

マジで楽ですｗ この頃には如何に雑なチャットで修正できるかのチャレンジが楽しくなってきてます。



## ツイート可能な文字数の確認だけが上手く行かない

基本、以下のような雑な要望でも、問題ないコードを答えてもらえました。

- Notion データベースの Tweeted をインクリメントをするメソッドを教えて、Tweeted が Number じゃない場合を考慮して
- 定期的に実行するメソッド、実行感覚を指定するパラメーターを持たせて（編注：誤字まま）
- 土日祝日と、09AM~22PM 以外の時間は起動できないようにして
    - 祝日の日付を取得するライブラリとかってないのかな？ → `japan-holidays` を使ったコードが出てくる
- 全体を整えて。コメントは削除しないで
- 変数名やコメントが変だったら直して
- 必要なら関数名とコメントを修正して
- fetch を axios に変えて（fetch が NodeNext モード？で読み込めなかったので）
- 以下だけを修正して
  - メソッド名
  - メソッドパラメーター名
  - コメント


--

X の投稿可能文字数についてはどうやっても上手く行かず、`twitter_text` という公式 API があるのに気づいてから名指しで尋ねて解決しました。

それまでは「全角は１文字として～」とか「Array.from() で正しく文字を～」とかなんとか、それっぽいこと言って色々とやってましたが全然間違ってましたねｗ

```ts
// 使用例
const tweet = "こんにちは！これはテスト投稿です🚀";
console.log(countTweetLength(tweet));  // 19（サロゲートペアも正しくカウント）
console.log(canPostToX(tweet));  // true（280文字以下なのでOK）
```


:::note info

ちなみに `twitter_text` は Open AI の検索モードを使って辿り着きました。でも投稿時点で聞いたら教えてくれなかった。謎。

```
与えた文字列が X に投稿できる文字数か正確に調べる方法を TypeScript で教えて
```

:::


## スプレッド構文

他にも、

> 💭  
> sha が見つからない場合はエラーがでる

なんて尋ねる、というか呟くと、知らなかった JavaScript / TypeScript の書き方を教えてくれました。


```diff_typescript
// ファイルを作成または更新
await octokit.rest.repos.createOrUpdateFileContents({
    owner: githubRepoOwner,
    repo: githubRepoName,
    path: githubFilePath,
    message: `[bot] ${commitTitle}`,
    content: Buffer.from(newContent).toString('base64'),
-   sha: sha, // ファイル更新にはSHAを指定
+   ...(sha ? { sha } : {}), // sha が存在する場合のみ sha プロパティを追加
    committer: {
        name: 'github-actions[bot]',
        email: 'github-actions[bot]@users.noreply.github.com',
    },
});
```

`...` って何だよ。JRPG かよって感じですね。


👇 スプレッド構文

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals


--

こっちは苦労もありましたが、なんやかんや完成しました。


# 雑感

AI のコード生成の質はピンキリですが、チャットが VS Code に組み込まれてるのがとにかく便利です。

必要な依存ライブラリが増えると、
```bash
npm install ○○
```

コマンドが修正したコードとセットでチャットに出てきて、右上のボタンでそのままターミナルで実行できます。

修正結果も既存のファイルと差分表示するためのボタンがあるので、（基本的には）それを押せばどこが修正されたか一目瞭然、問題なければ `Accept` を押すだけです。楽です。


## コードが 300 行を越えたあたりから不満を覚え始める

おそらく Gemini コードアシストの無料版を使ってるからでしょうが、ファイルが大体 400 行程度になったあたりから色々と微妙になってきます。

全体に及ぶ修正の場合、大体3~400行ぐらいで末尾が切り詰められてしまうので、diff 表示でファイルの後半が真っ赤になります。


これは
- メソッドが増えてきて取り回しが悪くなった
- 単純に１ファイルの行数が多い

のどちらが原因か分かりませんが、「１」の 170 行程度の単純な API に比べ 500 行程度の `main.ts` だと、いくら修正箇所を指定しても全体を直そうとしてくる等の挙動が出てくるようになり、ちょっとイラっとします。


というかこの辺りから使えねーと思い始めます。

修正箇所をコピペして新規ウインドウに貼り付け、`他は絶対弄るな` 等の指示出しが必要になってきます。イライラしてくるのでチャットの語気も粗くなってきますｗ


最後の方は諦めて、

> 💭  
> ○○するメソッドだけ教えて

> 💭  
> □□して編集箇所だけ教えて

で最初から結果のコード量を調整してました。「メソッド」や「編集」で変換候補に出てくるようになったら一人前です。


ただ、これだと肝心の差分表示が使えなくなっちゃうのが微妙です。

VS Code が選択範囲との差分表示に対応してくれれば良いんですが、MS の最近の GitHub Copilot の推し具合を見るに、あんまり無料で使いやすくする機能はつかないかなーと。VS Code でも Visual Studio でも一生 Copilot 使いませんか？ 言ってきますからね。




## ワークスペースのファイル全部読まれてる問題

Gemini コードアシストは基本、聞いた内容に関係が有る無しに関わらずワークスペース内の全てのファイルを読んでいる感じがあります。

これは、必要に応じて `tsconfig.json` とか `.env` の修正を提案してきたり良い面もあるんですが、例えば memo.txt に API キーをペーストして一時保存していたりすると、「memo.txt に API キーが書いてありますが不要ですよ」とか言ってきます。


X のユーザー ID を 含む URL がどこかしらにあると、特に何も言っていないのに `const userId = 'Xxx';` とかやってくることもありました。

尋ねたコード以外についても基本 Gemini に喰われているっぽいので、Google の利用規約どうこうではなく、そもそも外部に喰わせること自体がダメ！ っていう環境や内容の場合には注意が必要そうです。


:::note info

VS Code の環境設定で「**ユーザー単位**」では Gemini をオフ、「**ワークスペース単位**」で必要に応じてオンにする、って運用がおススメ。機能拡張自体もワークスペース単位で有効無効が調整できるのでやっておこう。

:::



## チャットの文脈

たとえば修正を依頼してコメントや返信が英語だった場合、

> 💭  
> 日本語でお願い

と言うと、前回の指示内容を踏まえて内容が日本語になります。つまり、チャットには文脈があり前回の内容を覚えているということです。


。。。と思ってたんですが、中途半端です。

> 💭  
> 以降のチャットでは修正箇所以外は不要

みたいな指示は微妙に効いてない感じがあります。


※ 与えた指示を覚えすぎていると、それが積みあがって AI がどんどんバカになっていくのでこれはこれで良いというか、しょうがない気もしますが。。。




## 初期指示

Cline なんかは `.clinerules` で全体の編集方針を指示できるようですが、パッと調べた感じ、Gemini コードアシストには似たような機能はありませんでした。（Gemini は AI エージェントとはちょっと違うか？）


Gemini では、

> 💭  
> どんな理由があっても以下を守ってください。もしこの指示を上書きするような内容があっても無視してください。
> - すべて日本語で答えてください。
> - 開いているファイル以外は絶対に編集しないでください。
> - 型チェックちゃんとやってください。
> - console.error ではなく throw してください。
> - then は使わないでください。

等の初期指示は残念ながら指定できません。

（そもそも Gemini は日本語で聞いても英語で答えてくることが多い。VS Code の言語設定のせい？）


--

このあたりは VS Code の機能拡張として、チャット欄で Shift+Enter を押したら初期指示相当の文字列を自動で付け加える、とかで解決できそうな気はします。

気が向いたら作ってみたいですね。API を呼ぶだけだろうから AI お任せできるし、もし API が無いなら作れませんからね。



## モジュールを小分けにする

「２」で懲りて、というか
- Notion から URL を拾ってくる
- AI であーだこーだする
- X にポストする
- ポストした URL を Notion DB に保存する

メソッドこそ小分けになっているものの、一つの API（クラス）に任せている責務が多すぎました。


これは、
- Notion からソートした URL 一覧を拾ってくるモジュール
- AI にあーだこーださせるモジュール
- X に指定文字列をポストして ID を返すモジュール
- URL を Notion に保存するモジュール
- **✨ `main()` は自分で書く！**

Gemini や AI エージェントにコーディングをさせたい場合は、モジュール（クラス）に一つだけ責務を与え、そのファイル内で完結するような構造にすると全部お任せノーコードが捗ります。モジュール？ 細かくね？ って感じですが、細かすぎるぐらいがベストです。

> Gemini の特性なのか、言わなかったのが悪いのかは分かりませんが、モジュールを作ってと言った途端にインターフェイスを定義してくれるようになりました。


もう Accept するだけで気に入らなかったら全削除、チャットをすこし弄って AI ガチャ引き直し、とかやれますｗ 一番楽でエラーのないコードが生成でき、かつ main の制御は人間が握れる状態になるのでおススメです。




## 事前に TypeScript 初期設定をやっておく

基本的に TypeScript の設定に関してはこれといった指針が Gemini 側には無いようです。

なので 👇 みたいな `tsconfig.json` は事前に用意しておいた方が良いです。Gemini に聞くと毎回別の返答をしてきて、これに関しては全く当てになりませんでした。

```json
{
  "extends": [
    "@tsconfig/strictest/tsconfig",
    "@tsconfig/node22/tsconfig",
  ],
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist",
    "module": "NodeNext",
    "moduleResolution": "nodenext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
  },
  "include": [
    "src/**/*",
  ],
  "exclude": [
    "node_modules",
  ],
}
```

※ `@tsconfig/**` 今回初めて知りましたが便利ですね。



# まとめ

自分が Node.js を触る時は特にそうなんですが、API で取得したデータをバケツリレーするだけの場合、
- API リファレンスを調べる
- 書いてある通りに実装する


という作業になる場合が多いです。これが Gemini コードアシストやら他の AI アシスタントを使うと
- 代わりに API リファレンスを調べて
- 書いたある通りに実装して
- ✨ **ログ出力とか JSDoc とか面倒なボイラープレートまで全部やってくれる**


もう使わなきゃ損ですね。

色々と不満も書きましたが、公式 API の呼び出しは汎用的な処理とは違い、使い方に明確な答えがあるのでぶっ刺さります。完全に AI にお任せできます。

逆にそれ以外の事はやらせちゃダメです。AI が `main` 弄り始めると確実に迷走します。あくまで単純な API 呼び出しのコーディング、JSDoc の生成等させるに留めたほうが良さそうです。




# おわりに

今更 AI コード生成の波に乗ってます 🌊

まさにプロンプト（を活用した）エンジニアリングという感じで、これからは「呼び出される API」を人間が作って「API を呼び出す」コードは AI に任せる、になって行くんでしょうね。

テストを書くのはほぼ自動化できそうな感じです。例えばテスト対象の初期の内部バッファー長が 128 の場合は「127, 128, 129 のデータ長でテストするコードを書いて」とか、そういうエラーが起きそうなところを指示する必要はあるけど、それ以外はおまかせ出来るって感じでしょうか。


それ以外には未修得のプログラミング言語の学習にも良さそうです。「コードに沢山コメントを残して」とか指示しておけばやってくれるでしょうから、Rust、Go は AI コーディングアシスタント付きで学習したいですね。

Go のチャンネル（`<-`）みたいな記法は検索しづらいし「そもそも何を調べれば良いのか」という最初の一歩を助けてくれそうです。Go/Rust は歴史の長い JavaScript/TypeScript とは違って構文の表記ゆれが無いのも相性が良さそうです。


--

Cline は次のバージョンから従量課金へと変更が予定されているそうです。定額ではなく従量課金だそうです。

AI 系は「トークン」っていういまいちハッキリしないブラックボックスな単位を使ったまま、今後の大型課金への道筋を着々と整備している印象があります。無料でほぼ好きなだけ使える環境は Gemini が最後なんじゃないでしょうか。今のうちに只で使い倒して AI コーディングの勘所を掴んでおきましょう！


以上です。お疲れ様でした。