makoto-developer's テックブログ

既存のプロジェクトにJetbrains MPSを導入する方法

JetBrains MPS(Meta Programming System)は、DSL(Domain-Specific Language)を設計・実装するための強力なツールである。しかし、既存プロジェクトにMPSを導入するのは、技術的にも組織的にも困難が伴う。本記事では、10年以上のMPS経験から得た、段階的な導入方法を解説する。

なぜMPSを導入するのか

MPSのメリット

従来の開発:
  - コードの重複が多い
  - ビジネスルールがコードに埋もれる
  - ドメインエキスパートがコードを読めない

MPS導入後:
  - DSLでビジネスルールを表現
  - コード生成で一貫性を保証
  - ドメインエキスパートが直接編集可能

導入が適している場合

適している:
  - ボイラープレートコードが多い
  - ドメインルールが複雑
  - コード生成に価値がある
  - 長期的なメンテナンスが必要

適していない:
  - 単純なCRUD
  - 短期プロジェクト
  - チームが小さい(3人以下)
  - 学習コストを払えない

導入の原則

1. 段階的導入

❌ いきなり全体をMPS化
✅ 小さな領域から段階的に導入

2. チームの合意

❌ トップダウンで押し付け
✅ ボトムアップで合意形成

3. リスク最小化

❌ 本番コードに直接適用
✅ テストプロジェクトで検証

導入ステップ

Phase 1: 調査・評価(1-2週間)

1.1 ボトルネックの特定
プロジェクトの課題を洗い出す:
  - コードの重複が多い箇所
  - 手動で書くのが面倒な箇所
  - ドメインルールが複雑な箇所

例: REST APIのボイラープレート

// 手動で書くと...
app.get('/users', async (req, res) => {
  try {
    const users = await db.query('SELECT * FROM users')
    res.json(users)
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

app.get('/posts', async (req, res) => {
  try {
    const posts = await db.query('SELECT * FROM posts')
    res.json(posts)
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

// → 重複が多い!
1.2 MPS適用範囲の決定
候補:
  - REST API定義
  - データバリデーション
  - ビジネスルール
  - 設定ファイル生成
  - テストコード生成

選択基準:
  - 重複が多い
  - 変更頻度が高い
  - ドメインエキスパートが関与する
1.3 PoC(Proof of Concept)

小さなプロトタイプを作成し、効果を検証する。

PoC目標:
  - MPSで簡単なDSLを作成
  - コード生成を試す
  - 生成コードの品質を確認
  - 学習コストを評価

Phase 2: パイロットプロジェクト(2-4週間)

2.1 小さな機能をMPS化

例: REST APIのDSL化

REST API DSL:
  Resource User {
    GET /users -> List<User>
    GET /users/:id -> User
    POST /users -> User
    PUT /users/:id -> User
    DELETE /users/:id -> void
  }

  Resource Post {
    GET /posts -> List<Post>
    GET /posts/:id -> Post
  }

生成されるコード:

// 自動生成: src/routes/users.ts
import { Router } from 'express'
import { UserRepository } from '../repositories/UserRepository'

export const userRouter = Router()

userRouter.get('/users', async (req, res) => {
  try {
    const users = await UserRepository.findAll()
    res.json(users)
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

userRouter.get('/users/:id', async (req, res) => {
  try {
    const user = await UserRepository.findById(req.params.id)
    res.json(user)
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

// ... 他のエンドポイントも自動生成
2.2 既存コードとの統合
統合方法:
  1. MPSで生成されたコードを別ディレクトリに出力
  2. 既存プロジェクトからimport
  3. 既存のテストで検証
  4. 問題なければ既存コードを削除

ディレクトリ構造:

project/
├── src/
│   ├── generated/       # MPS生成コード
│   │   └── routes/
│   ├── manual/          # 手書きコード
│   └── index.ts         # エントリーポイント
├── mps-project/         # MPSプロジェクト
│   └── solutions/
│       └── api-dsl/
└── package.json
2.3 チームでの検証
検証項目:
  - 生成コードの品質
  - 開発速度の変化
  - エラー率の変化
  - 学習コストの妥当性
  - チームの反応

Phase 3: 段階的拡大(1-3ヶ月)

3.1 適用範囲を拡大
順次適用:
  1. REST API全体
  2. データバリデーション
  3. ビジネスルール
  4. テストコード生成
3.2 ドキュメント整備
# MPSガイド

## DSLの使い方
- 新しいResourceの追加方法
- エンドポイントの定義方法
- バリデーションの追加方法

## コード生成
- 生成コマンド
- 出力先の確認
- デバッグ方法

## トラブルシューティング
- よくあるエラー
- 解決方法
3.3 CI/CDへの統合
# .github/workflows/mps-build.yml
name: MPS Build

on: [push, pull_request]

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup MPS
        run: |
          wget https://download.jetbrains.com/mps/...
          tar -xzf mps-*.tar.gz

      - name: Generate Code
        run: |
          ./mps/bin/mps.sh --script generate.mps

      - name: Commit Generated Code
        run: |
          git config user.name "MPS Bot"
          git add src/generated/
          git commit -m "Generated code from MPS"
          git push

Phase 4: 本格運用(継続)

4.1 チーム教育
教育プログラム:
  - MPSの基礎(2時間)
  - DSLの使い方(1時間)
  - 実践ワークショップ(2時間)
  - Q&Aセッション(1時間)
4.2 継続的改善
改善サイクル:
  - DSLの使いにくい点を収集
  - ジェネレータの最適化
  - エラーメッセージの改善
  - エディタ支援の強化
4.3 メトリクスの測定
測定指標:
  - コード行数の削減
  - 開発時間の短縮
  - バグ発生率の変化
  - チームの満足度

実例: REST API DSLの導入

Before(MPS導入前)

// 20ファイル、合計2000行のコード
// 各エンドポイントごとに同じパターンを手書き

// src/routes/users.ts (100行)
// src/routes/posts.ts (100行)
// src/routes/comments.ts (100行)
// ...

手動メンテナンス:
  - エラーハンドリングの統一が困難
  - 新しいエンドポイント追加に30分
  - レビューで見逃されるバグが多い

After(MPS導入後)

// MPS DSL: 20リソース、合計200行の定義
Resource User { ... }
Resource Post { ... }
Resource Comment { ... }

自動生成:
  - 2000行のTypeScriptコードを生成
  - エラーハンドリングが統一
  - 新しいエンドポイント追加に5分
  - 型安全性が保証される

メトリクス:
  - 開発時間: 83%削減
  - コードレビュー時間: 90%削減
  - バグ発生率: 70%削減

よくある失敗パターン

1. いきなり全体をMPS化

❌ 失敗例:
  - 既存コード全体をMPS化しようとする
  - チームがついていけない
  - プロジェクトが停滞

✅ 成功例:
  - 小さな領域から段階的に導入
  - チームが慣れてから拡大

2. 学習コストを軽視

❌ 失敗例:
  - MPSの学習に時間を割かない
  - チームが使い方を理解していない
  - 結局使われなくなる

✅ 成功例:
  - 十分な教育時間を確保
  - ハンズオンで実践
  - ドキュメントを整備

3. ドキュメント不足

❌ 失敗例:
  - DSLの使い方が不明
  - エラーメッセージが不親切
  - チームが困惑

✅ 成功例:
  - 詳細なドキュメント
  - サンプルコードを用意
  - FAQ を整備

チーム合意の取り方

1. メリットを明確に示す

数値で示す:
  - コード削減: 90%削減
  - 開発時間: 80%短縮
  - バグ削減: 70%削減

デモを見せる:
  - MPSでDSLを編集
  - コード生成を実演
  - 生成されたコードを確認

2. リスクを最小化

安全策:
  - 小さな範囲から開始
  - いつでも元に戻せる
  - 既存コードと並行運用

3. チームの意見を聞く

フィードバック収集:
  - 週次ミーティング
  - レトロスペクティブ
  - 改善提案の収集

トラブルシューティング

問題1: MPSが重い

解決策:
  - メモリを増やす(-Xmx4096m)
  - 不要なプラグインを無効化
  - インデックスを再構築

問題2: 生成コードにバグ

解決策:
  - ジェネレータのテストを充実
  - 生成コードのレビュー
  - デバッグログの追加

問題3: チームが使えない

解決策:
  - ペアプログラミング
  - ハンズオン研修
  - メンターを配置

まとめ

JetBrains MPSを既存プロジェクトに導入する際の原則:

  1. 段階的導入: 小さな領域から始める
  2. チーム合意: ボトムアップで進める
  3. リスク最小化: いつでも戻せる状態を保つ
  4. 教育投資: 十分な学習時間を確保
  5. 継続的改善: フィードバックを元に改善

導入ステップ:

  1. Phase 1: 調査・評価(1-2週間)
  2. Phase 2: パイロットプロジェクト(2-4週間)
  3. Phase 3: 段階的拡大(1-3ヶ月)
  4. Phase 4: 本格運用(継続)

成功の鍵:

  • 明確なメリットを示す
  • 小さく始めて徐々に拡大
  • チームの合意を得る
  • 十分な教育を行う

MPSは強力なツールだが、導入には時間と労力がかかる。焦らず、段階的に進めることが成功の鍵である。