スパイダープラス Tech Blog

建設SaaS「スパイダープラス」のエンジニアとデザイナーのブログ

迷走しないプロジェクトのために —— 計画づくりで私がよくチェックする4つのポイント

こんにちは。スパイダープラスで開発チームのEMを担当している細矢です。

最近プロジェクトマネジメントに注力するタイミングがあったので、これまで学んだこと・経験してきたことを整理してみようと思います。

まず始めに、

「プロジェクトの開始時は順調そうだったのに、後半になるにつれて想定外の事態が重なり、終わりが見えなくなる……」

ということはありませんか?

プロジェクトマネジメントに完璧な正解はありませんが、大きな失敗を避けるために「ここは気をつけておくべき」というポイントは存在します。
今回は、私が日々の業務で計画を立てる際、特に意識してチェックしているポイントを4つに絞ってお伝えします。

続きを読む

Rubyのクラスメソッドの仕組みを理解する

はじめに

初めまして、スパイダープラスでWebエンジニアをしているizkです。
普段は、S+Reportというプロダクトでバックエンドを中心にRubyPHPなどを書いています。

さて、Rubyでコーディングしていると、モジュールをクラスにincludeしてメソッドを呼び出す場面はよくあると思います。

ある日、同じようにモジュールをincludeしてメソッドを呼び出そうとしたところ、undefined methodエラーが発生しました。
原因を調べてみると、メソッドを呼び出そうとした場所がクラスメソッド内だったためでした。includeextendに書き換えると、無事にメソッドを呼び出せるようになりました。

この時の私は「インスタンスメソッドにはinclude、クラスメソッドにはextend」くらいの認識でコードを書いていたため、なぜそうなるのか仕組みを調べてみることにしました。

本記事の対象読者

本記事のゴール

Rubyのオブジェクトモデル(クラス、モジュール、特異クラス)を整理しつつ、クラスメソッドの呼び出しの仕組みを理解すること

続きを読む

スパイダープラスTechBlog 2025年の締め~目標を大きく超えたテックブログ運用方法~

こんにちは。プラットフォーム開発部 SREチームのKです。
本記事では、テックブログ運営に関わったメンバーの視点で2025年の取り組みを振り返り、投稿ペースの維持と目標超過のPVを実現できた仕組みを整理します。
テックブログ通算90本目🎉2025年を締めくくる記事でもあります。


2025年の運用成果

続きを読む

AIに仕事をさせるためのお膳立てとRustという強力なガードレール

こんにちは。EMの本田です。

AIで開発していますか? 世間では「AIで開発生産性が〇〇倍!」という景気の良い話が飛び交っていますが、現場のエンジニアとしては「いきなりそこを目指すと、逆に管理コストで疲弊しそうだな」と感じることも多いのではないでしょうか。

私のチームでもAI活用を進めていますが、「生産性の定量化」までは今のところ落とし込んでいません。

まずは、「人間がビジネスのコア(価値・設計)に集中するために、それ以外をAIとツールに任せる」というスタンスで、地に足のついた状態を目指しました。

今回は、RustとClaude Codeを中心に、私のチームで実践しているAI活用方法について紹介します。

AIに夢を見すぎない:役割分担の再定義

まず前提として、「すべてのコンテキストをAIに伝えることはできない」ということを受け入れるところから始めました。

仕様判断やアーキテクチャの核心部分をAIに丸投げするのは、現時点では時期尚早だと考えています。まず最初にAIに期待すべきは「自律的な思考」よりも「ルールの徹底」や「定型作業の遂行」です。

  • 人間がやること: ビジネスロジックの設計、仕様の決定、最終的な品質責任。
  • AIがやること: ボイラープレートの記述、Git操作(Issue/PR作成等)、一次レビュー。
  • ツールがやること: フォーマット整形、静的解析、セキュリティチェック(CI/CD)。

失敗談:コンテキストの詰め込みすぎ問題

当初は「機能実装からGitのコミット、プッシュまで全部一つの流れでAIにやってもらおう」と考えたことがありました。しかし、これはうまくいかないことがよくありました。

実装の詳細、プロジェクトのルール、Gitの操作手順...これらすべてを一つのコンテキスト(チャットセッション)で処理しようとすると、コンテキストウィンドウが不足したり、AIの注意力が散漫になって出力品質が安定しなかったのです。

そこで私は「作業単位でコンテキストを区切ることができる」方針で環境設計をしました。

  • 設計は設計のセッション。
  • 実装は実装のセッション。
  • コミットやPR作成は別のセッション(あるいはカスタムコマンド)。

結果として、各工程での成果物が明確になり、人間がチェック(レビュー)を入れるタイミングも作りやすくなりました。「認知負荷を作業単位にとどめておける」というのは、人間にとっても管理しやすいメリットがありました。

Claude Code × Git:定型作業の半自動化

現在、Issue作成、ブランチ作成、コミット、PR作成といったGit操作は、基本的にAI(Claude Code)を経由して行っています。

ただし、毎回プロンプトを手打ちするのではなく、プロジェクトのルールを反映した カスタムコマンドを用意しています。

create-issue, create-branch, create-commit, create-pr といったコマンドを用意し、必要なパラメータ(例: 機能名、変更内容)を渡すだけで、AIがルールに従って適切なIssueやPRを作成してくれます。

さらに、以下のようなルールを事前にドキュメント化し、AIが参照するようにカスタムコマンドの中で指示しています。

  • Issue/PRテンプレート: フォーマットを統一。
  • 命名規則: ブランチ名やコミットメッセージの一貫性をAIに強制させる。

人間は方向性を指示するだけで、AIがルールに従って定型作業をこなします。

AIを戦力化する「自動フィードバック」

ここで重要になるのが「AIが出力したコードを誰がチェックするか」という問題です。

AIは疲れを知らず、爆速でコードを生成しますが、その品質は完璧ではありません。インデントがズレたり、存在しない関数を呼んだり、プロジェクトの禁止事項を無視したりします。 これらをすべて人間がレビューして指摘していたら、AIを使う前よりも時間がかかってしまいます。

私たちが目指したのは、「人間が見る前に、システムがAIにダメ出しをする環境」です。

  • 構文・フォーマット: 汚いコードは自動で整形されるか、エラーになる。
  • 整合性: 動かないコード(型エラー等)は弾かれる。
  • 設計: 不適切な依存関係は禁止される。
  • 品質: 複雑すぎるコードは禁止される。
  • セキュリティ: 脆弱性のあるライブラリは禁止される。怪しいソースから取ってきているライブラリは禁止される。
  • ライセンス: 許可されていないライセンスの使用は禁止される。

この「ガードレール」が整備されていると、AIはエラーメッセージを読み取って自律的に修正を行うことができます。 「AIが書き、システムが指摘し、AIが直す」。このループを人間抜きで回せる環境こそが、AI開発において最も生産性が高く、ストレスのない状態だと言えます。

その実現になぜ「Rust」が最適なのか

この「システムによる厳格なフィードバック環境」を構築する上で、Rustは私たちのチームにとって非常にコストパフォーマンスが良い選択肢でした。もちろん、TypeScriptでも厳格な型チェックやESLint設定で近いことは実現できますが、Rustはそれが「デフォルト」である点が大きな違いです。

コンパイルエラー」という最強のフィードバック

PythonJavaScriptのような動的型付け言語では、AIが「なんとなく動く嘘のコード」を書いても、実行するまで気づかないことがあります。 しかしRustなら、型システムや所有権のルールに違反していれば即座にコンパイルエラーになります。

AIにとって、コンパイラからの明確なエラーメッセージは「最高の修正指示書」です。 人間が「ここ間違ってるよ」と教える代わりに、「コンパイルを通せ」と指示するだけで、AIは試行錯誤して正しいコードに到達できます。

公式ツールによる「迷いのない」環境構築

Rustは、ビルド(cargo build)、テスト(cargo test)、フォーマット(cargo fmt)、ドキュメント(cargo doc)、Linter(cargo clippy)といった開発に必要なツールがすべて公式で標準化されています。

JS/TS等でありがちな「Linterは何を使う?」「設定ファイルはどう書く?」といった環境構築の試行錯誤が一切ありません。 rustup でインストールさえすれば、人間もAIもすぐに開発に入れます。 AIに対して「標準のフォーマッターをかけて」と指示すれば、文脈を説明しなくても100%正解の挙動をしてくれるのは、プロンプトの簡略化にも繋がります。

明示的な構文:AIが「読みやすく書きやすい」

Rustは型、ライフタイム、可変性(mut)、エラーハンドリング(Result)などが構文上で明示されます。 暗黙の挙動が少ないため、AIはコードの意図を正確に読み取りやすく、生成時も「何を書くべきか」が明確です。

例えば「この関数は失敗する可能性がある」という情報が戻り値の型(Result<T, E>)に表れているため、AIはエラーハンドリングを忘れずに書いてくれます。

コンパイルの厳密性

AIが「なんとなく動く嘘のコード」を書いても、Rustの厳格な型システムと所有権ルールが即座にコンパイルエラーとして弾いてくれます。 「コンパイルが通るように直して」と指示するだけで、ある程度の品質が保証される点は、動的型付け言語にはない安心感です。

アーキテクチャによる「物理的」なガードレール

Rustの強力な型システムに加え、アーキテクチャ構成そのものもAI運用のためのガードレールとして活用しています。具体的には、クリーンアーキテクチャを実践し、各レイヤーをRustのワークスペース機能を使った別クレート(モノレポ構成)に分割しています。

依存関係の「物理的な」強制

単一のディレクトリ内でフォルダ分けするだけでは、AI(や人間)がついうっかり上位レイヤーから下位レイヤー(例:ドメイン層からインフラ層)をimportしてしまうミスを防げません。

しかし、レイヤーごとにクレート(ライブラリ)を分けてしまえば、Cargo.toml に依存関係を記述しない限りコードを利用できません。

.
├── Cargo.toml (workspace)
├── crates
│   ├── kernel      # ドメイン層。依存なし
│   ├── app         # アプリケーション層。kernelにのみ依存
│   ├── xxx-adapter # インフラ層。app,kernelに依存。
│   └── driver      # 最も外側の層。すべての層に依存。

このように構成することで、「ドメイン層がDB操作(インフラ層)に依存する」といったアーキテクチャ違反は、論理的以前に物理的に(コンパイルレベルで)不可能になります。 AIが間違った依存関係のコードを書いても、ビルドが通らないため、AIは自律的に「あ、この構成は許されないんだ」と気づき、正しいインターフェース経由の実装に修正します。

AIとの共通言語としての「定石」

クリーンアーキテクチャは世界中で広く知られたパターンであり、AI(LLM)の学習データにも大量に含まれています。

独自のオレオレ・アーキテクチャだと、AIに毎回コンテキスト説明が必要になりますが、クリーンアーキテクチャであれば、比較的雑な指示でも、AIは意図をある程度正確に汲み取ってくれます。

「AIにとって設計しやすい(予測しやすい)構造」を採用することは、結果としてプロンプトエンジニアリングのコストを下げることにも繋がります。

PostToolUseフックによるフォーマット強制

Claude Codeの設定で .claude/settings.jsonPostToolUse フックを仕込んでおくと、AIがファイルを編集した直後に自動コマンドを実行できます。 Rustの場合、ここでフォーマッターを走らせるのが効果的です。以下は自動フォーマットの設定例です。

  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "file_path=$(jq -r '.tool_input.file_path'); if [ -n \"$file_path\" ] && [ -f \"$file_path\" ] && echo \"$file_path\" | grep -q '\\.rs$'; then cargo fmt -- \"$file_path\" 2>&1 || true; fi"
          }
        ]
      }
    ]
  }

これにより、AIが書いたコードは常にプロジェクトのスタイルガイドに沿った状態に保たれます。

CI/CDによる品質ゲート:人間が楽をするための厳格化

「AIにとって開発しやすい環境 = 人間にとっても開発しやすい環境」です。 人間がやる必要性の薄いチェックは、CI/CDで徹底的に自動化しています。

品質ゲート

以下のコマンド群が通らない限り、マージはできません。

  • cargo check (コンパイル)
  • cargo fmt --check (フォーマット確認)
  • cargo clippy -- -D warnings (Linterによる静的解析)
    • 単純なLintだけでなく、コードの複雑さを制限するルールを厳しめに設定しています。
      • clippy::cognitive_complexity: 認知的複雑度が一定以上のコードを禁止。
      • clippy::too_many_lines: 1つの関数が長すぎる場合に警告。
    • AIは文句も言わずに数百行の巨大な関数や、深いネスト構造を生成することができます。そのため、「人間が理解できる粒度(行数・複雑さ)に分割すること」をLintでAIに強制させています。これに引っかかると、AIは自らリファクタリングを行うようになりました。
  • cargo test / cargo test --release (テスト)
  • cargo doc (ドキュメント生成確認)
  • cargo build --release (リリースビルド確認)
# clippy.toml の設定例
cognitive-complexity-threshold = 7 # 複雑度が7を超えたら警告
too-many-lines-threshold = 50      # 50行を超えたら警告

以下は実際に使用しているGitHub Actionsの設定例です。

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches:
      - main
      - production
    paths:
      # Rustのソースコードとビルド設定ファイルが変更された場合のみCIを実行
      - 'crates/**'
      - 'Cargo.toml'
      - 'Cargo.lock'
      - 'rust-toolchain.toml'
      - 'clippy.toml'
      - '.github/workflows/ci.yml'
  workflow_dispatch:

env:
  RUST_BACKTRACE: 1

jobs:
  # Fast compilation check
  check:
    name: Check
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-check"
          cache-on-failure: true

      - name: Run cargo check
        run: cargo check --workspace --all-targets --verbose

  # Code formatting check
  fmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Check formatting
        run: cargo fmt --all --check

  # Linting with clippy
  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-clippy"
          cache-on-failure: true

      - name: Run clippy
        run: cargo clippy --workspace --all-targets -- -D warnings

  # Unit and integration tests (debug mode)
  test:
    name: Test (Debug)
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-test"
          cache-on-failure: true

      - name: Run tests
        run: cargo test --workspace --verbose

  # Unit and integration tests (release mode)
  test-release:
    name: Test (Release)
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-test-release"
          cache-on-failure: true

      - name: Run tests (release)
        run: cargo test --workspace --release --verbose

  # Documentation generation
  doc:
    name: Documentation
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-doc"
          cache-on-failure: true

      - name: Generate documentation
        run: cargo doc --workspace --no-deps --document-private-items --verbose
        env:
          RUSTDOCFLAGS: "-D warnings"

  # Release build verification
  build-release:
    name: Build (Release)
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Setup Rust cache
        uses: swatinem/rust-cache@v2
        with:
          shared-key: "ci-build-release"
          cache-on-failure: true

      - name: Build release
        run: cargo build --workspace --release --verbose

  # Summary job - all checks must pass
  # このジョブの目的:
  # 1. GitHub Branch Protectionで必須チェックとして設定する場合、
  #    個別のジョブ全てを設定する代わりに、このジョブ1つだけを設定すればよい
  # 2. PRのステータスチェックで「全てのCIが成功したか」を一目で確認できる
  # 3. ジョブの追加/削除時にBranch Protectionの設定を変更する必要がない
  ci-success:
    name: CI Success
    runs-on: ubuntu-latest
    needs:
      - check
      - fmt
      - clippy
      - test
      - test-release
      - doc
      - build-release
    if: always()
    steps:
      - name: Check all jobs succeeded
        run: |
          if [ "${{ contains(needs.*.result, 'failure') }}" == "true" ]; then
            echo "One or more CI jobs failed"
            exit 1
          elif [ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]; then
            echo "One or more CI jobs were cancelled"
            exit 1
          else
            echo "All CI jobs succeeded"
          fi

セキュリティチェック (cargo-deny)

依存関係(クレート)の管理も自動化しています。cargo-deny を使用し、4つの観点でスキャンを行います。

チェック 内容
advisories RustSec Advisory DBを使った脆弱性検出
bans 禁止クレートの検出、複数バージョン警告
licenses 許可ライセンスのみ使用か確認(MIT, Apache-2.0, BSD等)
sources crates.io以外からの怪しい依存関係を禁止

以下はGitHub Actionsでの設定例です。週次での定期実行も設定しています。

# .github/workflows/security.yml
name: Security

on:
  pull_request:
    branches:
      - main
      - production
    paths:
      # 依存関係やセキュリティ設定が変更された場合のみセキュリティチェックを実行
      - 'crates/**'
      - 'Cargo.toml'
      - 'Cargo.lock'
      - 'deny.toml'
      - '.github/workflows/security.yml'
  schedule:
    # Run weekly on Sunday at 00:00 UTC (09:00 JST)
    - cron: '0 0 * * 0'
  workflow_dispatch:

jobs:
  # Comprehensive dependency and security checks using cargo-deny
  deny:
    name: Cargo Deny
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        check:
          - advisories
          - bans licenses sources
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Rust toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Install cargo-deny
        run: cargo install cargo-deny --locked

      - name: Run cargo deny (${{ matrix.check }})
        run: cargo deny check ${{ matrix.check }}

  # Summary job
  # このジョブの目的:
  # 1. Matrix jobの結果を集約して単一のステータスチェックにする
  # 2. GitHub Branch Protectionで必須チェックとして設定する場合に便利
  # 3. PRのステータスチェックで「全てのセキュリティチェックが成功したか」を一目で確認できる
  security-success:
    name: Security Success
    runs-on: ubuntu-latest
    needs:
      - deny
    if: always()
    steps:
      - name: Check all security jobs succeeded
        run: |
          if [ "${{ contains(needs.*.result, 'failure') }}" == "true" ]; then
            echo "One or more security checks failed"
            exit 1
          elif [ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]; then
            echo "One or more security checks were cancelled"
            exit 1
          else
            echo "All security checks passed"
          fi

レビュープロセスの変革:Lint指摘からの卒業

これらのお膳立てをした結果、コードレビューの風景が大きく変わりました。

Before:

  • 「この変数名は命名規則に従っていません」
  • 「不要なimportが残っています」
  • 「このコードは使われてなさそうなので不要でしょうか?」
  • 「この関数、ドキュメントコメントがないので書いてほしいです」
  • 「こことあそこで一貫性が取れていないように見えます。どちらかに統一していただけないでしょうか。」

以前はこうした細かいLintやコーディングスタイルの指摘に、人間の貴重なリソースを使っているシーンがありました。

After:

  • 一次レビューはAIが実施: AIエージェントが自動でコードを巡回し、指摘を行う。
  • 対応案の提示: 指摘に対して「どう修正するか」の案もAIが出し、人間はそれを承認・選択するだけ。
  • CIによるブロック: スタイル違反やLintはそもそもCIで落ちる。ルールを厳しくしても人間への負荷がかからない。

これによって、人間のところにレビューが回ってくる頃には、ほとんどが純粋な「設計の議論」や「ビジネスロジックの妥当性」だけに集中できる状態になっています。

「これ、AIが直してくれるからいいや」と割り切れる部分が増えたことで、心理的な負担も大きく減りました。

まとめ

  1. ルールを決める: AIに守らせるための厳格なルール(型、Lint、テンプレート)を人間が決める。
  2. 作業を区切る: コンテキストを混ぜず、Issue作成、実装、PR作成を作業単位でAIに依頼する。
  3. コアに集中する: 浮いた時間で、エンジニアは本来やるべき「価値の創造」に向き合う。

Rustは単に「安全で速い言語」であるだけでなく、「AIの手綱を握り続けるための最適なパートナー」でもありました。 コンパイラが常に横でAIのコードを監査してくれる安心感があるからこそ、私たちはコアに集中できる時間が増えた気がします。

AI活用は今のところ銀の弾丸ではありません。どこから手をつけていくか迷っている方は、以下の順で試してみてはいかがでしょうか。

  • まずはコミットメッセージの作成だけAIに任せてみる
  • Issue/PRテンプレートを整備し、AIが参照できるようにする
  • Linterの設定を厳しくして、AIが自動修正するループを作る

最初から「全自動化」を考えるのがしんどい場合は、まずは人間が指示を出しAIが手を動かす「半自動化」をしやすい環境を整えることから始めてみることをおすすめします。

ところで、スパイダープラスでは仲間を募集しています。 少しでも興味が出てきたなという方はお気軽にご連絡ください。

レガシーコードの仕様整理にAIをフル活用してみた ― Claude Codeとsubagentで生産性爆上げした話

皆さんこんにちは〜スパイダープラスの舘です!!

もう年の暮れが近くなってきていますが、みなさんは年末の整理や掃除は進んでいますか? 今回は、プライベートだとそこら辺は苦手で放置気味になってしまっている僕がSPIDER+の仕様・ドキュメント整備をやった際にAIを活用して 「これはよかった」「効果的だった」と思ったことを、備忘録として皆さんに共有できたらなと思っています。

きっかけ ― なぜAIに頼ることにしたのか

SPIDER+はSwift/Objective-Cが混在する、それなりに規模の大きいコードベースです。 機能ごとの仕様ドキュメントを整備したいと思っていたものの、コードを読み解きながらまとめていくのはなかなか骨の折れる作業…。

「これ、AIに手伝ってもらえないかな?」と思って試してみたのが Claude Code でした。

続きを読む

Universal Links によるAppコンテンツへのアクセスが可能になりました

はじめに

こんにちは、スパイダープラス開発メンバーのちょっくんです。 みなさんはディープリンクという手法をご存知でしょうか? ウェブリンクからアプリを起動して特定の画面を表示させる、アレです。 ディープリンクにはいくつか手法があり、SPIDER+では長らくカスタムURLスキームを採用していました。しかし、この手法ではGMailでリンクを受け取った場合に、アプリを起動できなくなっていることがわかりました。 そこでiOS9以上で推奨されるUniversal Linksを採用したことについて紹介します。

続きを読む

AIレビューで負担を半減した方法:GitHub Copilotの活用事例

1. レビュー体制の限界とAIレビューへの期待

スパイダープラスでWeb開発を担当している高森です。

今回は、直近の開発フローの課題であった「コードレビュー工数の増大」を解決するために、AIレビューを本格導入し、開発体制をどう変革したかについてお話しします。

これまでの開発体制と課題

スパイダープラスのWeb開発チームでは、コードの品質を担保するため、Pull Request (以下PR) のレビューを必ず2名体制で行っていました。

続きを読む