kickflow Tech Blog

株式会社kickflowのプロダクト開発本部によるブログ

AIを使ってYARDからrbs-inlineへ移行しました

整理された部屋で稼働するロボット掃除機
ロボット掃除機に効率よく掃除させるには、部屋を整理整頓するのが重要です。整理整頓もロボットがしてくれればいいのに(本文には関係ありません)。

kickflowで主にバックエンドを担当している小本です。今回は、既存のRailsプロジェクトについて、AIを活用してYARDコメントをrbs-inlineに自動変換した事例を紹介します。

RBS / rbs-inlineとは

RBSはRuby 3.0から導入されたRubyのコードに型情報を記述するための言語です。

そしてrbs-inlineはRubyのコメントにRBSの型定義を直接記述できるツールです。ソースコードと型定義を同じファイルで管理できるため、メンテナンスが容易になります。 rbs-inlineのコメントやRDocやYARDに似ていますが互換性はありません。

class Person
  attr_reader :name #: String
  
  # @rbs name: String
  # @rbs return: void
  def initialize(name:)
    @name = name
  end
end

なぜRBS / rbs-inlineを導入したいのか?

kickflowではAIコーディングを積極的に使っており既に本番コードの一部をAIで生成しています。そして静的型付けはAIコーディングと相性が良いと言われています。AIが参照できる情報が増えますし、型検査によってコーディングのミスを(AIが思考中に自動で)検出できるからでしょう。実際、kickflowではTypeScriptも使っていますが、こういった静的型付けのメリットを感じる場面があります。

そこで、RubyにもRBSによる静的型付けを導入すべきであると考えました。すでに、kickflowでは一部のメソッドでYARDによりメソッドの引数・戻り値の型が宣言されていました。ならば、YARDコメントをrbs-inlineに移行することで部分的にでもAIコーディングの精度が上がると考えました。

また、rbs-inlineとYARDは機能が被っているツールなので、「今後メインストリームになる方」に移ったほうが何かと利益があると考えました。

AI使ったYARDからrbs-inlineへの移行

変換作業の概要

rbs-inlineの文法は複雑なため正規表現での一括変換は現実的ではありません。yard_to_rbs_inlineというGemもありましたが、開発途上のようでした。

そこでAIに書き換えさせることにしました。

AIエージェントとしてはRoo Code(モデルはGemini 2.5 Pro)を使いました *1 。「YARDコメントをrbs-inlineに書き換えて」と指示するだけで基本的な変換はできたのですが、上手くいかない部分もあり、以下のような工夫をしました:

  • まずルールファイル(後述)を定義しスタイルを指定する
    • rbs-inlineの複数の記法があり統一するため
    • AIがrbs-inlineには無い文法を捏造するのを防ぐため
  • 50ファイル程度の小さな単位で処理させる
    • これはAIというより人間による確認・コードレビューをしやすくするため
  • YARDの@paramsを変換させてから、@returnを変換させる
    • 一度に変換させるとYARDコメントが無いメソッドに勝手に型定義を追加するなど、誤動作が起きたため

ルールファイルは以下のような内容です:

- クラスやPublicなメソッドにはrbs-inlineによるコメントを書くことを推奨します。
  - rbs-inlineのコメントはDoc style syntaxで書くことを推奨します。
  - マジックコメント `rbs_inline: enabled` は不要です。

```ruby
class Person
  attr_reader :name #: String

  attr_reader :addresses #: Array[String]

  # @rbs name: String
  # @rbs addresses: Array[String]
  # @rbs return: void
  def initialize(name:, addresses:)
    @name = name
    @addresses = addresses
  end
end
```

- rbs-inlineによるコメントでは、型定義の後の `--` によるコメントの前後にスペースを加えてください。

NGな例:

#  @rbs name: String--名前

OKな例:

#  @rbs name: String -- 名前

- rbs-inlineの型定義の後に誤って `#` でコメントを書いている場合は `--` に書き直してください。ただしrubocop:disableなどのコメントは `#` のままで問題ありません。
- nilを含むユニオン型については `A | nil` ではなく `A?` を優先して使用してください。ただし、`A | B | nil` のように複数の型がある場合は`| nil` を使用してください。

GitHub ActionsによるRBSファイルの自動生成

rbs-inlineコメントからRBSファイルへの変換はbundle exec rbs-inlineコマンドで簡単に実行できます。

しかし、開発効率を考慮しGitHub Actionsで自動化しました*2

Github Actionsのワークフロー設定ファイルもAIに生成させました。

# .github/workflows/generate-rbs.yml

name: Generate RBS Files

on:
  schedule:
    # 毎日午前13時(JST)に実行(UTC 04:00)
    - cron: '0 4 * * *'
  workflow_dispatch: # 手動実行も可能にする

jobs:
  generate-rbs:
    permissions:
      contents: write
      pull-requests: write
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@master
        with:
          fetch-depth: 1

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true
        env:
          BUNDLE_JOBS: 4
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle

      - name: Generate RBS files # generate-rbs.shはbundle exec rbs-inline を実行するだけのスクリプト
        run: |
          set -euo pipefail
          ./generate-rbs.sh
      
      - name: Check for updated files in sig/ directory
        id: check-changes
        run: |
          # sigディレクトリで変更されたファイルを確認
          changed_files=$(git status --porcelain sig/ | awk '{print $2}')
          if [ -n "$changed_files" ]; then
            echo "Updated RBS files:"
            echo "$changed_files"
            echo "has_changes=true" >> $GITHUB_OUTPUT
            # 変更されたファイルの内容も表示
            echo "Changes detected in the following files:"
            git status --porcelain sig/ | awk '{print $2}'
          else
            echo "No RBS files were updated."
            echo "has_changes=false" >> $GITHUB_OUTPUT
          fi
      
      - name: Set current datetime as env variable
        if: steps.check-changes.outputs.has_changes == 'true'
        env:
          TZ: 'Asia/Tokyo'
        run: echo "CURRENT_DATETIME=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV

      - name: Commit and push changes
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          
          # ブランチを作成
          git checkout -b ci/generate-rbs_${{ env.CURRENT_DATETIME }}
          
          # RBSファイルを追加
          git add sig/
          
          git commit -m "chore: RBSファイルを自動生成

          - generate-rbs.shスクリプトを実行してRBSファイルを更新
          - 型定義ファイルの自動生成により開発効率を向上"
          git push origin ci/generate-rbs_${{ env.CURRENT_DATETIME }}

      - name: Create PR
        if: steps.check-changes.outputs.has_changes == 'true'
        run: |
          gh pr create \
            -B develop \
            --head $(git rev-parse --abbrev-ref HEAD) \
            -t "[CI] Generate RBS files" \
            -b ""
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: No changes to commit
        if: steps.check-changes.outputs.has_changes == 'false'
        run: |
          echo "No RBS files were updated. Skipping commit."

このワークフローは毎日定期的に実行され、変更があればPull Requestを自動作成します。人間によるレビューを経てマージされます。

まとめ

AIを使用することでYARDコメントからrbs-inlineへの移行を効率的に実現できました。

また、Github actionsを使うことで今までの開発サイクルを維持したまま型定義によるメリットを享受できました。

最後に

kickflowでは、私たちと一緒にプロダクト開発を推進してくれる仲間を募集しています。 このような技術的な取り組みや、より良い開発プロセスを追求することに興味がある方は、ぜひ採用サイトをご覧ください。

careers.kickflow.co.jp

*1:書き換え当時はRoo Codeを使っていました。今は社内ではClaude Codeをメインに使っています

*2:Steepなどの型検査ツールを導入する際にはコミット時にrbs-inlineとSteepを自動実行するような仕組みを作るべきでしょう。しかし、現時点ではSteepを導入できておらず、rbs-inlineはAI向けのヒントとしてしか使用していないので、1日1回の自動生成で十分だと考えました。