🤖

チーム全体の生産性を加速する!開発ナレッジを活かした自動化の実践

2025/04/02に公開

開発現場では、日々の作業の中で手作業による負担が蓄積し、チーム全体の生産性が低下することがあります。そこで重要になるのが「自動化」です。本記事では、開発ナレッジを活かしてチーム全体の生産性を向上させるための自動化の具体的な実践例を紹介します。

対象読者

  • GitHub Actionsを使って手作業を自動化したい方
  • 開発チームの生産性向上に関心があるマネージャーやリーダー
  • 継続的インテグレーション/デリバリー(CI/CD)の導入を検討しているエンジニア

自動化はチーム全体の生産性を向上させる

自動化の導入は、個人の作業効率を向上させるだけでなく、チーム全体の生産性向上にも貢献します。

  • 手作業の削減による効率化
    • 繰り返し発生する作業を自動化することで、開発者が本来の業務に集中できるようになります
    • CIとして実装することでその自動化をチームメンバーも利用できるようになります
  • ミスを防ぐための仕組み作り
    • 手動作業によるヒューマンエラーを防ぎ、一貫した品質を保つことができます

設計レビューに関する自動化

背景

私が所属するチームでは、PRを出す前に設計レビューを行うことがあります。

自動化前の課題

  1. Issueに設計や実装方針について記載
  2. 書けたらレビュー依頼を行う
    1. 同期レビュー: 昼礼の際にレビューをしてもらいたいIssueのリンクを議事録に記載
    2. 非同期レビュー: Slackで「<issueのリンク> こちら非同期レビューをお願いいたします」と依頼
  3. 合意が取れたら planned ラベルを付与

自動化後の改善

ほとんどのレビューは非同期レビューとして行われますが、Slackにリンクを貼る作業が面倒だったため、以下の自動化を導入しました。

レビュー依頼を自動化

新たに設計レビュー中であることを示す plan: planning ラベルを作成し、そのラベルを付けることでSlackに通知が行くようにしました。


設計レビュー依頼をSlackに通知する自動化の例

# このワークフローは、"plan: planning" ラベルが付けられたIssueをSlackに通知するためのものです。
name: On Planning

on:
  issues:
    types:
      - labeled

jobs:
  # "plan: planning" ラベルがつけられたらslackに通知
  notify-planning:
    if: ${{ endsWith(github.event.label.name, 'planning') }}
    runs-on: ubuntu-latest
    steps:
      - name: Post to a Slack channel if planning label is added
        uses: slackapi/slack-github-action@v2.0.0
        with:
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          method: chat.postMessage
          # sec-app
          payload: |
            channel: <channel-id>
            text: "<!subteam^group-id>\n<${{ github.event.issue.html_url }}|${{ github.event.issue.title }}>\n非同期プランニングをお願いいたします!"

これにより非同期レビューの流れは以下のように変わりました。

  1. Issueに設計や実装方針について記載
  2. 書けたら plan: planning ラベルを付与
  3. 合意が取れたら plan: planned ラベルを付与

レビュー催促を自動化

非同期でレビュー依頼を行った場合、忘れてしまうこともあるため、plan: planning ラベルが付いたIssueがあれば毎朝催促を行うようにしました。


プランニング中のIssueをSlackでリマインドする自動化の例

name: Check Planning

on:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * 1-5"

jobs:
  hoge:
    runs-on: ubuntu-latest
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - name: Find planning issues
        run: |
          MESSAGE=$(gh issue list --label "plan: planning" --json title,url,assignees --jq '.[] | "<\(.url)|\(.title)> from \(.assignees.[].login), "' | sed 's/, /\\n\\n/g')
          {
            echo 'MESSAGE<<EOF'
            echo -e $MESSAGE
            echo EOF
          } >> "$GITHUB_ENV"
      - name: Post to a Slack channel if exists
        uses: slackapi/slack-github-action@v2.0.0
        if: ${{ env.MESSAGE != '' }}
        with:
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          method: chat.postMessage
          # sec-app
          payload: |
            channel: <channel-id>
            text: "<!subteam^group-id>\nプランニング中のIssueがあります。確認をお願いします。\n${{ env.MESSAGE }}"

作業イシューのコピーの自動化

背景

私が所属するモバイルチームでは、iOSとAndroidそれぞれのリポジトリが存在し、共通する課題管理のためのリポジトリもあります。

自動化前の課題

  • 共通の課題は共通リポジトリに親Issueを作り、それぞれのリポジトリに移動して手作業でIssueを作る必要がある

自動化後の改善

  • 親Issueで /copy というコメントをすることで、作業が自動化されます

工夫した点

  • 単純なスラッシュコマンドで起動する
  • イシューコピー用のPersonal Access Tokenを作成し、用途を限定させる
.github/workflows/create-child-issue.yml
name: Create Child Issues

on:
  issue_comment:
    types:
      - created

jobs:
  create-child-issue:
    if: ${{ startsWith(github.event.comment.body, '/copy') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: copy issue to each repositories
        env:
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          GH_TOKEN: ${{ secrets.MOBILE_TEAM_ISSUE_COPIER_TOKEN }}
        run: |
          TEMPLATE=$(cat .github/workflows/child-issue-template.md)
          BODY=$(echo -e "## 要件\n- 親issue: $ISSUE_URL\n\n$TEMPLATE")
          gh api repos/owner/android-repo/issues \
            --header "Authorization: Bearer $GH_TOKEN" \
            -f title="$ISSUE_TITLE" \
            -f body="$BODY"
          gh api repos/owner/ios-repo/issues \
            --header "Authorization: Bearer $GH_TOKEN" \
            -f title="$ISSUE_TITLE" \
            -f body="$BODY"
.github/workflows/child-issue-template.md
## スケジュール
- テスト実施日、リリースはいつ?

## 既存仕様
### 概要
- そのままテストコードや動作確認テストに落ちる粒度

### クラス図・シーケンス図など
|  |  |
|---|---|
| <img src="" width=400> | <img src="" width=400> |

## 要件・仕様
### 詳細
- そのままテストコードや動作確認テストに落ちる粒度

### Links
- []()

## 確認用アカウント情報
| id(mail) | password |
|---|---|
|  |  |

## 実装方針、設計
### 概要
- 

### クラス図・シーケンス図など
|  |  |
|---|---|
| <img src="" width=400> | <img src="" width=400> |

## TODO
- [ ] 作業開始commit `git commit --allow-empty -m '対応開始'`

リリース作業の半自動化

背景

リリース作業は手順が多く、ミスが発生しやすいため、自動化を導入しました。

自動化前の課題

  • 手動でリリース用のブランチを作成し、バージョンをインクリメントする必要があった
  • リリースPRをマージした後手動でデプロイする必要があった

自動化後の改善

  • リリース用のIssueを作成し、/release とコメントするだけでPRを作成できるようになりました
  • リリースPRのマージ後にリリースの作成、タグの作成、デプロイまで自動化されました

工夫した点

  • 完全自動化ではなくスラッシュコマンドを起点とした半自動化に留めたこと
    • リリース作業は週1で行なっているので、cronを使って完全に自動化することも可能ですが、hotfixのような作業が発生すると手作業で行った方が良いため、エントリーポイントだけは人の手で行うようにすることで柔軟性を持たせています
ワークフローファイル
name: Release Command
on:
  issue_comment:
    types:
      - created

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    env:
      # アクセストークンを作っておかないとPR作成をトリガーとするCIが動かない
      GH_TOKEN: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
    if: ${{ startsWith(github.event.comment.body, '/release') }}
    steps:
      - uses: actions/checkout@v4

      - name: Get current versionCode from libs.versions.toml
        run: |
          VERSION_CODE=$(grep 'versionCode' ./gradle/libs.versions.toml | awk -F '"' '{print $2}')
          NEW_VERSION_CODE=$((VERSION_CODE + 1))
          echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
          echo "NEW_VERSION_CODE=$NEW_VERSION_CODE" >> $GITHUB_ENV

      - name: Get current versionName from libs.versions.toml
        run: |
          VERSION_NAME=$(grep 'versionName' ./gradle/libs.versions.toml | awk -F '"' '{print $2}')
          MAJOR=$(echo $VERSION_NAME | cut -d'.' -f1)
          MINOR=$(echo $VERSION_NAME | cut -d'.' -f2)
          NEW_VERSION_NAME="$MAJOR.$((MINOR + 1))"
          echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_ENV
          echo "NEW_VERSION_NAME=$NEW_VERSION_NAME" >> $GITHUB_ENV

      - name: Create and checkout release branch
        run: |
          git checkout -b release/$NEW_VERSION_NAME

      - name: Update versionCode/versionName in libs.versions.toml
        run: |
          sed -i "s/versionCode = \"$VERSION_CODE\"/versionCode = \"$NEW_VERSION_CODE\"/" ./gradle/libs.versions.toml
          sed -i "s/versionName = \"$VERSION_NAME\"/versionName = \"$NEW_VERSION_NAME\"/" ./gradle/libs.versions.toml

      - name: Commit and push updated version
        run: |
          git config --local user.name "GitHub Action"
          git config --local user.email "actions@github.com"
          git add ./gradle/libs.versions.toml
          git commit -m "chore: Bump version to $NEW_VERSION_NAME ($NEW_VERSION_CODE)"
          git push --set-upstream origin release/$NEW_VERSION_NAME

      - name: Get the lowest open Milestone number
        run: |
          LOWEST_MILESTONE_TITLE=$(gh api repos/${{ github.repository }}/milestones?state=open --jq '. | sort_by(.number) | .[0] .title')
          echo "LOWEST_MILESTONE_TITLE=$LOWEST_MILESTONE_TITLE" >> $GITHUB_ENV

      - name: Create Pull Request to Master
        run: |
          PR_TITLE="Merge release/${NEW_VERSION_NAME} into master"
          gh pr create \
            --base master \
            --head release/"$NEW_VERSION_NAME" \
            --title "$PR_TITLE" \
            --body-file ./.github/workflows/merge_release_into_master_template.md \
            --label "maintenance" \
            --milestone "$LOWEST_MILESTONE_TITLE"

      - name: Create Pull Request to Develop
        run: |
          PR_TITLE="Merge release/${NEW_VERSION_NAME} into develop"
          gh pr create \
            --base develop \
            --head release/"$NEW_VERSION_NAME" \
            --title "$PR_TITLE" \
            --body-file ./.github/workflows/merge_release_into_develop_template.md \
            --label "maintenance" \
            --milestone "$LOWEST_MILESTONE_TITLE"

本番リリースを作成 & Firebase App Distributionにデプロイ & Google Playにデプロイ

ワークフローファイル
name: on-merge-into-master

on:
  push:
    branches:
      - master

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

      - name: Set up JDK 17
        uses: actions/setup-java@v4.7.0
        with:
          distribution: 'temurin'
          java-version: 17
          architecture: x64
          cache: gradle

      - name: Get Version Name
        id: extract_version
        run: |
          VERSION_NAME=$(grep 'versionName ' gradle/libs.versions.toml | cut -d '"' -f 2)
          echo "Version Name is $VERSION_NAME"
          echo "version_name=${VERSION_NAME}" >> $GITHUB_OUTPUT

      - name: Decode Keystore
        run: echo ${{ secrets.KEYSTORE_BASE64 }} | base64 --decode > ./app/release.keystore

      - name: Generate AAB
        run: ./gradlew :app:bundleProdRelease
        env:
          RELEASE_KEYSTORE_STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
          RELEASE_KEYSTORE_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          RELEASE_KEYSTORE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

      - name: Temporaly uploading for other steps
        uses: actions/upload-artifact@v4
        with:
          name: aabProdRelease
          path: app/build/outputs/bundle/prodRelease
          retention-days: 1
    outputs:
      version_name: ${{ steps.extract_version.outputs.version_name }}

  create-release:
    needs: generate-aab
    runs-on: ubuntu-latest
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      VERSION_NAME: ${{needs.generate-aab.outputs.version_name}}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Download signed aab
        uses: actions/download-artifact@v4
        with:
          name: aabProdRelease

      - name: Create Tag And Release
        run: |
          git tag $VERSION_NAME master
          git push origin $VERSION_NAME
          gh release create ${VERSION_NAME} --title Release_${VERSION_NAME} --generate-notes ./app-prod-release.aab

  # Firebase App Distribution にデプロイ
  distribute-release:
    needs: [generate-aab, create-release]
    runs-on: ubuntu-latest
    env:
      VERSION_NAME: ${{needs.generate-aab.outputs.version_name}}
    steps:
      - uses: actions/checkout@v4

      - name: Download signed aab
        uses: actions/download-artifact@v4
        with:
          name: aabProdRelease

      - name: title into release notes.
        run: |
          message="message for release"
          echo -e "$message" >> ./.github/workflows/release_notes.txt

      - name: Upload apk to Firebase Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{secrets.RELEASE_FIREBASE_APP_ID}}
          token: ${{secrets.FIREBASE_TOKEN}}
          serviceCredentialsFile: path/to/google-services.json
          groups: android_develop_testers,android_testers
          file: ./app-prod-release.aab
          releaseNotesFile: ./.github/workflows/release_notes.txt

  # google play に内部テストとしてデプロイ
  upload-google-play:
    needs: generate-aab
    runs-on: ubuntu-latest
    env:
      VERSION_NAME: ${{needs.generate-aab.outputs.version_name}}
    steps:
      - uses: actions/checkout@v4

      - name: Download signed aab
        uses: actions/download-artifact@v4
        with:
          name: aabProdRelease

      - name: Create service_account.json
        run: echo '${{ secrets.PLAY_PUBLISH_SERVICE_ACCOUNT_JSON }}' > service_account.json

      - name: Upload aab to Play Console
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJson: service_account.json
          packageName: <package.name>
          releaseFiles: ./app-prod-release.aab
          releaseName: Release_${{ env.VERSION_NAME }}
          status: draft
          track: internal

まとめ

自動化を導入することで、開発チーム全体の生産性を向上させることができます。特に、レビュー依頼やリリース作業の自動化によって、開発者がより価値のある作業に集中できるようになります。今後も継続的に自動化を進め、さらなる効率化を目指していきます。

参考文献

https://speakerdeck.com/tmknom/introducing-issue-ops
https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs
https://api.slack.com/reference/surfaces/formatting

GitHubで編集を提案
カラビナテクノロジー デベロッパーブログ

Discussion