Notionのページプロパティを ChatGPT APIが作った文書で更新する!

はじめに

こんにちは小松です。

プレックスでは日々のタスク管理ツールとしてNotionを利用しています。 美しいUI、その印象とは裏腹に機能は骨太、ここ1〜2年でユースケースも劇的に増えて「こんな使い方もありなのか〜」と思う日々が続いております。 (そう、私はNotionが大好きです)

そして世は生成AI時代ですがNotionでも今年の春にAI機能がリリースされました。 めっちゃ便利ですよね。テキストのちょっとした言い換えであったり、フォーマットの修正であったり、当然翻訳もしてくれるし。 本当に良いところばかりです。ただ少しお値段が、可愛くないんですよね。ChatGPTのAPIと比べちゃうと。もちろん利用頻度によっても変わってくるので、参考までに、記事の最後に今回のユースケースでの費用比較を掲載しています。

全社的に使うのであれば良いのですが、やりたかったのは1プロパティに本文の要約を入れてほしいだけだったので今回はChatGPTに働いてもらうことにしました。

今回やりたいこと

今回はNotionのAIプロパティ編集機能("AIによる要約","AI:重要情報","AI:カスタム自動入力"みたいなやつ)をChatGPT APIを利用して実現したいと思います。

現在プレックスジョブのプロダクト開発では毎月60〜70程度のタスクが起票〜クローズされています。プロダクト開発を運用する上で、例えば今月何をやったか一覧で振り返りたいな、という時があり一覧表示にしたりするのですがタイトルを一覧表示してもそれが何を成したタスクなのかって思い出せる事もあれば思い出せない事もかなりあります。そこで私たちはそのタスクで何を成したかを要約するプロパティがほしいなと思いました。ただし、運用上今まで書いていなかった項目を新しく各タスクの担当者が書いていくというのも難しい話なので、ここでAIに要約してもらおうとした次第です。

なので今回はプレックスジョブの開発タスクを管理するデータベースにタスクページの'目的/ゴール'プロパティを本文から抽出して登録する。

これを今回のゴールとします。

これをDIYするぞ!

今回登場する予定の人たち

  • Notion API
    • 本文の提供
    • プロパティの更新
  • ChatGPT API
    • Notionの本文を要約したテキストを生成
  • node.js(+TypeScript)の実行環境(今回作るスクリプト)(何でも良い)
    • Notionから更新対象のページを取得する
    • Notionから本文を取得してChatGPTへ渡す
    • ChatGPTから返ってきた値をNotionのプロパティに戻す

今回触れないこと

  • Notion API tokenの取得、インテグレーションの設定
  • Open AI tokenの取得
  • AIプロンプトの精度
  • スクリプト実行環境の詳細、トリガー

のあたりは今回触れません。特にプロンプト周りで書いてある事は参考情報で、これで良い応答が得られることを約束するものではありません。

実装!

今回はNotion周りのライブラリが充実していたのでnode.js(TypeScript)でスクリプトを書きます。

詳細はステップ毎に説明しますが、今回は以下のようなステップでNotionのページプロパティを更新します。

①Notionから更新対象のページリスト(ページID)を取得する
②本文を取得してマークダウンに変換する
③マークダウンに変換した本文をChatGPTに投げ込む
④ ③で出力した情報をNotion構造に変換してNotionプロパティを更新する

事前にページIDが明らかになっており、単ページを更新するだけであれば②〜④のステップで実現できます。

①Notionから更新対象のページリスト(ページID)を取得する

実用を考えると、まずは更新対象のレコード(ページ)を抽出する必要はあります。コード見本にあるfilterオブジェクトに関しては公式のこのあたりに説明がありますが、Notionの画面から設定する要領とほとんど一緒になります。

  • 前提
    • 今回NotionAPIへのアクセスは全てNotionSDKを使う
    • 対象のレコード(ページ)は'目的/ゴール'プロパティが空欄のものとする
  • ここでやること
    • 対象のデータベースから今回更新対象のページリスト(ページID)を取得する
import { Client } from "@notionhq/client";
import { QueryDatabaseResponse } from "@notionhq/client/build/src/api-endpoints";

// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";
const databaseId = "xxxxxxxxxxxx";

const notion = new Client({
  auth: notionToken,
});

const pages = async (): Promise<QueryDatabaseResponse> => {
  const response = await notion.databases.query({
    database_id: databaseId,
    // 更新対象のプロパティが未記入のものが対象
    filter: {
      property: "目的/ゴール", 
      rich_text: {
        is_empty: true,
      },
    },
  });

  return response;
};

この後の処理はここで取得したpagesに対して1ページずつ処理を行っていくイメージになります。

②本文を取得してマークダウンに変換する

Notion APIを使うとまず驚くのですが、notionのpageオブジェクトはかなり複雑な構造体になっています。notionのリッチなテキストエディタを実装する上で必要な情報なのでしょうが、今回の用途では必要のない要素も多いので本ステップでは本文をマークダウン形式へ変換してChatGPTのトークンを節約しつつ理解のし易い形に変換します。

  • 前提
    • 本文のマークダウンへの変換はライブラリNotion-to-MDを使う
  • ここでやること
    • ①で取得したページリストの本文を取得する
    • 本文をマークダウン形式のテキストに変換する
import { Client } from "@notionhq/client";
import { NotionToMarkdown } from "notion-to-md";

// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";

const notion = new Client({
  auth: notionToken,
});

const pageId: string = "xxxxxxxxxxxx"; // ステップ①で取得したpageId

const n2m = new NotionToMarkdown({ notionClient: notion });

const mdblocks = await n2m.pageToMarkdown(pageId);
const mdString = n2m.toMarkdownString(mdblocks);
const mdPageBody = mdString.parent;

③マークダウンに変換した本文をChatGPTに投げ込む

これでようやくChatGPTが食べやすい文書になったのでこれを渡します。

  • 前提
  • ここでやること
    • ChatGPTにテキストを要約してもらう
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
const gptModel = "gpt-3.5-turbo"; // or "gpt-4"

const mdPageBody: string = 'xxx'; // ステップ②で作成したマークダウン形式の本文、実際の運用では1000字切り捨てとしている

const openAiApiKey = "xxxxxxxxx";
const openAiOrganization = "xxxxxxxxx";

const openAiConfiguration = new Configuration({
  apiKey: openAiApiKey,
  organization: openAiOrganization,
});

const openai = new OpenAIApi(openAiConfiguration);

const messages: ChatCompletionRequestMessage[] = [
  {
    role: "system", // 全ページの処理に共通するプロンプト
    content:
      "以下のテキストで目指しているゴール、目的を抽出して1〜6個程度の箇条書きで提出してください。箇条書きの始まりは'- 'から始まるマークダウンの形式で出力してください。",
  },
  {
    role: "user",
    content: mdPageBody.slice(0, 1000), // 1000字まで渡す
  },
];

const chat_completion = await openai.createChatCompletion({
  model: gptModel,
  messages: messages,
  temperature: 0.5, // お好みで
  max_tokens: 2000,  // 運用に合わせて設定してください
});

const contents: string = `${
  chat_completion.data.choices[0].message?.content // 生成された本文
} \n\n- Created by ${chat_completion.data.model} Usage: ${JSON.stringify(
  chat_completion.data.usage // この処理で利用したトークン数が取得できるので残している
)}`;

プロンプトはこの記事では重点を置きませんが、とりあえず'マークダウンぽいリストで返して'ってお願いするとGPT3.5でも4でも返却フォーマットをコントロールしやすかったです。

プロンプト実行時に設定できるパラメータは以下のページに説明があります。temperatureは数字が大きい(~2)ほど揺れの大きい回答になるそうですが、今回はあまり調整していません。

platform.openai.com

④ ③で出力した情報をNotion構造に変換してNotionプロパティを更新する

まず、今回データを投入したいプロパティですが、Notionの画面上はテキスト(Text)で作成されています。これはNotion APIの世界ではrich_textタイプになるようです。

rich_textについてはNotionの公式に説明はあります。上手く使えれば書式をいじったり色々遊べるのですが、なかなか仕様も複雑なので今回は極力シンプルな実装としています。

  • ここでやること
    • ChatGPTから取得したテキストをrech_textのブロックに入れる
    • Notionプロパティを更新する
import { Client } from "@notionhq/client";
// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";


const contents: string = 'xxxxxx'; //前ステップでChatGPTが生成した文字列
const pageId: string = "xxxxxxxxxxxx"; // ステップ①で取得したpageId

const richText: any = []; // notionに返すrich_text構造体 (any...)

// プロパティの内容を作成
richText.push({
  type: "text",
  text: {
    content: contents,
    link: null,
  },
  plain_text: contents,
  href: null,
});

const notion = new Client({
  auth: notionToken,
});

// Notionのページプロパティを更新
await notion.pages.update({
  page_id: pageId,
  properties: {
    "目的/ゴール": {
      rich_text: richText,
    },
  },
});

これでNotionのプロパティが更新されているはずです。お疲れ様でした!

こうなった!

ちなみに私のプロンプト力だとGPT-4の力を持ってしてもNotionAI程気の利いた要約を作れませんでした・・・。NotionAIではそこまでプロンプト作成に気を遣わずにイケちゃったので、Notion側でどのようなベースプロンプトが実装されているのかはかなり気になります。

おわりに

今回、NotionAIの費用にびびってなんちゃってNotionAIをChatGPTを利用してDIYしてみましたが、結果的に本家NotionAIの便利さを再確認することになりました。やはり組み込みで機能が提供されているという点での完成度の高さに敵いません。ただし、この後付録としてつけますが、利用用途によってはDIYした方が遥かに安価に運用できる可能性が高いので、まずはAIの運用を試してみるというのは全然ありじゃないかなーと思いました。

また、プレックスでは一緒に働いて頂けるエンジニア、デザイナーをまだまだ募集しております!気になるところがあれば、こちらに採用情報まとまっておりますので、ご覧いただければと思います。

以上、長くなってしまいましたがここまでお付き合い頂きありがとうございました!

付録:NotionAIとChatGPT APIの費用比較

以下全ての利用料は2023年7月19日時点の情報です。

notionはワークスペースの参加メンバーに対する課金、ChatGPTは処理量に対する課金なのでケース・バイ・ケースではあります。今回以下のような条件で比較します。

  • 処理が必要なページは月100ページ
    • 800字程度のページを140字程度に要約するという想定
  • Notionワークスペースのメンバーは10名、年払い
サービス 基本情報 計算方法 コスト(ドル) コスト(円)
1ドル140円で計算
Notion API 基本人数課金(処理量に関係なく人数に対して定額) 10名 × 8ドル 80ドル 約11,200円
GPT3.5 Turbo input:$0.0015 / 1K tokens
output: $0.002 / 1K tokens
[input] 750 * 100 / 1000 * 0.0015 + [output] 150 * 100 / 1000 * 0.002 約0.15ドル 約21円
GPT4 input:$0.03 / 1K tokens
output: $0.06 / 1K tokens
[input] 750 * 100 / 1000 * 0.03 + [output] 150 * 100 / 1000 * 0.06 約3.0ドル 約420円

今回スクリプトの実行環境は考慮していないので、

  • 何をトリガーとして実行するか
  • 行基盤を何にするか

によってもChatGPT API実装の方は費用感変わってくると思います。

が、処理する件数が多くない場合はGPT-4を使ってもChatGPTの方が圧倒的に安いです。

【Stable Diffusion】API経由で画像を大量に生成する方法

はじめに

こんにちは、プレックスの池川です。

最近、AI の話題を聞かない日はないと言っても良いくらい AI が身近になりました。 弊社でも ChatGPTGitHub Copilot を会社負担でエンジニアに配布しており、日々の開発に役立てています。

テスト環境の構築時には大量の画像や写真が必要となることが多く、それらの準備には一定の時間と労力がかかります。 この問題を解決するために、画像生成AIを使用して大量の画像を準備してはどうかと考え、今回、それを検証してみました。 Web UI からでも画像生成は可能ですが、生成できる枚数に上限があり、また保存先が限定されるため、API経由で画像生成を行うスクリプトを作成しました。

この記事では検証する中で得たノウハウについて、共有できればと思います!

【この記事で触れること】

  • Stable Diffusion を使って API 経由で画像生成する方法

【この記事で触れないこと】

  • 画像生成AIモデルの詳しい説明
  • プロンプトやパラメータのこと

実行環境

  • Stable Diffusion 2.1
  • Stable Diffusion web UI(AUTOMATIC1111)
  • Google Colab Pro
    • Runtime type: Python3
    • Hardware accelerator: GPU
    • GPU type: A100(何でもOKだが A100 > V100 >T j4 の順にスペックが高い)

手順

1. Google Colab の環境設定を行う

今回は簡単に環境構築ができる Google Colab 上で stable-diffusion-webui を動かしました。 Google Colab の設定は実行環境にまとめていますので参考にしてください。

注意点として Google Colab には無料枠がありますが、UI上で画像生成をする用途だと制限されるため、有料のプランを準備する必要があります。 無料枠のまま stable-diffusion-webui を動かそうとすると下記のような警告のメッセージが表示されます。 日本語の規約には2023年5月29日時点では反映されていなかったため、英語版の規約を確認ください。

2. Stable Diffusion web UI を起動する

Google Colab で下記のコマンドを実行します。最後の行に書かれてある python コマンドのオプションに—-apiをつけることで、APIが実行できます。

# 必要なライブラリをインストールする
%pip install torch==2.0.0+cu118 torchvision==0.15.1+cu118 torchtext torchaudio torchdata==0.6.0 --index-url https://download.pytorch.org/whl/cu118

# stable-diffusion-webui のソースコードをクローンしてくる
!git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui
%cd /content/stable-diffusion-webui

# モデルをインストールする
!wget https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -O /content/stable-diffusion-webui/models/Stable-diffusion/sd_v2.1.safetensors

# 起動
!python launch.py --share --api --xformers --enable-insecure-extension-access

起動すると、public URL が発行されますのでこちらにアクセスします。 後ほど API を実行する際にもこの URL を使います。

アクセスして下記のような画面になれば起動完了です。

3. API を実行するスクリプトを作成する

続いて画像生成用のスクリプトを作成します。 スクリプトは普段、Rubyを使うことが多いのでRubyで書きました。 public URL と生成したい画像の枚数をオプションで渡すことで、画像を生成後ローカルに保存することができます。

require 'uri'
require 'net/http'
require 'net/https'
require 'json'
require 'base64'
require 'date'
require 'optparse'

# コマンドラインオプションをハンドルする
def parse_arguments
  opt = OptionParser.new
  params = {}
  opt.on('-i VAL') { |v| params[:i] = v }
  opt.on('-n VAL') { |v| params[:n] = v }
  opt.parse!(ARGV, into: params)
  params
end

# 画像生成に必要なプロンプトやパラメータを設定する
def build_payload
  {
    "prompt" => "warehouse entrance",
    "negative_prompt" => "human",
    "sampler_name" => "DDIM",
    "restore_faces" => true,
    "steps" => 150,
    "width" => 1200,
    "height" => 675,
  }
end

# リクエストを送信する
def send_post_request(uri, payload)
  Net::HTTP.post(uri, payload.to_json, "Content-Type" => "application/json")
end

# 画像をローカルに保存する
def save_image(data, index)
  decode_image = Base64.decode64(data["images"][0])
  file_name = "#{DateTime.now.strftime('%Y%m%d%H%M%S')}_#{index + 1}.png"
  File.open("./output/#{file_name}", "wb") do |file|
    file.write(decode_image)
  end
  file_name
end

# 画像を生成する
def create_and_save_images(input_url, number_of_images)
  uri = URI.parse("#{input_url}/sdapi/v1/txt2img")
  payload = build_payload

  number_of_images.times do |i|
    puts "Generate #{i + 1} image"

    response = send_post_request(uri, payload)
    image_data = JSON.parse(response.body)

    file_name = save_image(image_data, i)

    puts "Download #{file_name} to local"
  end
end

params = parse_arguments
create_and_save_images(params[:i], params[:n].to_i)

build_payload メソッドでは画像生成に必要なプロンプトやパラメータを指定しています。

  • prompt
    • 画像生成系AIでどのような絵を出力して欲しいかを指示する文字列
  • negative_prompt
    • 排除したい要素
  • sampler_name
  • restore_faces
    • true にすることで顔の崩れを防ぐ
  • steps
    • ノイズを除去する回数、多いほどノイズが除去されるが生成に時間がかかる
  • width、height
    • 生成する画像のサイズ

APIの仕様は「public URL/docs」(例:https://1234566789abcde.gradio.live/docs)で確認できますので、そちらを参考にしてください。今回はテキストからイメージを生成したいので、使用するのは「/sdapi/v1/txt2img」です。プロンプトに加えてステップ数が画像のサイズなどが指定できます。

4. スクリプトを実行して画像を生成する

3で作ったスクリプトを実行します。

$ ruby ファイル名 -i https://1234566789abcde.gradio.live -n 100

実行中は Web UI で生成した時と同様に、Colab 上で状況を確認できます。

画像生成が完了するとローカルの指定していたディレクトリに画像が保存されています。

生成された画像の一例です。

今回指定した条件だと、100枚の画像を生成でおよそ40分かかりました。また、使用した Google Colab のリソースは9コンピューティングユニットで、費用としては約106円でした。(費用はGoogle Colab のプラン一覧参照)

さいご

本記事では画像生成AIを使ってAPI経由で画像生成する方法について、触ってみて得られたノウハウをまとめました。 はじめて WebUI 上で画像生成したときも面白いなと思いましたが、API経由で画像生成ができることでより幅が広がりそうです。

API 経由で画像生成する方法としては、今回、取り上げたもの以外にも DreamStudio が提供しているStability APIがあり、今後もどのようなことができるかも含め様々な検証を行っていきたいと思います。

最後になりますが、プレックスではソフトウェアエンジニアフロントエンドエンジニアを募集しています。 ご興味がある方はぜひご応募ください!

FactoryBot運用ガイドを作りました

はじめに

こんにちは、プレックスの種井です。

PlexJob開発チームではRSpecによるテストに使用するfixtureの作成に、FactoryBotを使用しています。 テストコードに対してはrubocop-rspecにより一定のルールに則ったコードが作成されていますが、Factoryの定義やオブジェクトの生成方法などは個々のメンバーに委ねられており、オンボーディングやコードレビューの際に方針に対して方針に対して疑問が上がる箇所になっていました。 今回、所属するチーム向けに運用やコーディングのルールを作成したので、この場を借りて紹介したいと思います。

目次

  • はじめに
  • 目次
  • 運用ガイド
    • 運用の観点
    • 運用時のルール
  • 終わりに
  • 参考資料

運用ガイド

前提と運用の観点

運用するにあたっての観点として「可読性」や「再利用性」はもちろんですが、DBへのアクセスを伴うこともあるため「パフォーマンス」にも配慮する必要があります。

また、オペレーションやコーディングの作業上のどの部分で考慮すべきかがわかりやすいように、各方針やルールを「設定」、「定義」、「作成」に分類しました。

運用時のルール

  • 設定
    • FactoryBot::Syntax::Methodsを設定、クラス名を省略して、メソッドの呼び出しをする
    • FactoryBot公式のlinterを使う
  • 定義
    • ファイル名は複数形にする
    • 例: Userを定義する場合はusers.rbにする
    • 1ファイル1定義にする
    • デフォルトデータはなるべく最小限かつ簡潔にする
      • テストに必要なattributeのみを定義する
      • traitでhook(after(…))を使用して、関連データ(has_manyな)の作成はオプションにする
      • 特殊な状態の最小単位をtraitで定義する
        • ただし、なんでもtraitにせず汎用的なデータ状態のみに限る
    • 固定値ではなく本物に近いデータを定義する
      • sequenceやFakerの活用を検討する
  • 作成
    • 何でもcreate(…)にしない
      • association先はassociationで定義する
    • 1度に複数のオブジェクトが必要になる場合はbuild_list,create_listを利用する

設定

クラス名を省略して、メソッドの呼び出しをする

公式のセットアップガイドにもありますが、rails_helper.rbFactoryBot::Syntax::Methodsを定義することで、クラス名を省略してメソッドを呼び出すことができます。

# rails_helper.rb

RSpec.configure do |config| 
  config.include FactoryBot::Syntax::Methods 
end
# クラス名(FactoryBot)を省略してメソッドを呼び出します。

# build 
user = build(:user) 

# create 
user = create(:user)

FactoryBot公式のlinterを使う

FactoryBot.lintを実行することで、定義された全Factoryに対してcreateを実行し、データの作成時に例外が発生する場合は、該当するFactoryの一覧とともにFactoryBot::InvalidFactoryErrorを例外として投げてくれます。

未定義の必須フィールドのような、Factoryのデータ定義の不備を事前に検知することができます。

公式にあるようにrakeタスクを作成して、GitHub Actionsなどから呼び出すことが推奨されています。

# lib/tasks/factory_bot.rake

namespace :factory_bot do
  desc "Verify that all FactoryBot factories are valid"
  task lint: :environment do
    if Rails.env.test?
      conn = ActiveRecord::Base.connection
      conn.transaction do
        FactoryBot.lint
        raise ActiveRecord::Rollback    
      end
    else
      system("bundle exec rake factory_bot:lint RAILS_ENV='test'")
      fail if $?.exitstatus.nonzero?
    end
  end
end
$ bundle exec rake factory_bot:lint RAILS_ENV='test'

定義

ファイル名は複数形にする

例として、Userモデルが定義されている場合に対応するFactoryのファイル名はusers.rbにします。

1ファイル1定義にする

例として、users.rbを作成する場合

FactoryBot.define do 
  factory :user do # 1ファイルに対して1定義にする
    ... 
  end
end

デフォルトのFactory定義はなるべく最小かつ簡潔にする

テストに必要なattributeのみを定義し、使用しないattributeまでデフォルトのデータを定義しないようにします。

traitでhook(after(...))を使用し、has_manyな関連データの作成はオプションとして呼び出し時に選択できるようにします。

FactoryBot.define do
  factory :user do
    trait(:with_posts) do # 最小単位にする&オプションとして、使用時に選択できるようにする
      transient do
        posts_count { 5 }
      end

      # has_manyな関連データの作成
      after(:create) do |user, evaluator|
        create_list(:post, evaluator.posts_count, user: user)
        user.reload
      end
    end
  end
end

使用頻度が高い、特定の状態はtraitで定義しておきます。

ただし、なんでもtraitにするのではなく、汎用的なデータ状態のみに限ります。

FactoryBot.define do
  factory(:post) do
    trait :published do
      published { true }
    end  

    trait :unpublished do
      published { false }
    end
    
    trait :week_long_publishing do
      start_at { 1.week.ago }
      end_at { Time.now }
    end
    
    trait :month_long_publishing do
      start_at { 1.month.ago }
      end_at { Time.now }
    end

    factory :week_long_published_post, traits: [:published, :week_long_publishing]
    factory :month_long_published_post, traits: [:published, :month_long_publishing]
    factory :week_long_unpublished_post, traits: [:unpublished, :week_long_publishing]
    factory :month_long_unpublished_post, traits: [:unpublished, :month_long_publishing]
  end
end

固定値ではなく本物に近いデータを定義する

一意性の必要な値にはsequenceの利用を検討します。

また、本物に近い適当な値を設定したい場合はFakerの利用を検討します。

FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "person#{n}@example.com" } # sequence
    
    name { Faker::Name.name } # Faker
  end
end

作成

何でもcreate(...)にしない

createメソッドは毎回SQLが実行されるため、テスト実行のパフォーマンスに影響を与えます。

モデルのバリデーションなど、DBに値が生成されている必要がないテスト対象の場合はbuildメソッドをはじめとした、メモリ上にオブジェクトを作成する機能を使用します。

build(:user)
build_stubbed(:user) # buildで済ましたいが、idやtimestampが必要な場合はbuild_stubbedを使用する

association先を事前に作成する場合は、associationで定義しておくことが推奨されます。

createメソッドを使用してassociation先を作成すると、buildした際に常にcreateが伴ってしまうためです。

FactoryBot.define do 
  factory :post do
    user # belongs_toの関連(associationの省略記法) 
    user { create(:user) } # NG postをbuildした場合でもuserがcreateされてしまう
  end
end

また、作成時にかかる処理はinstance_double,spy(RSpecの機能) > build(…) > create(…) の順で速いです。

1度に複数のオブジェクトが必要になる場合はbuild_list,create_listを使用する

必要以上にオブジェクトを作成しないようにします。

また、transientを使用して、作成数に対してラベル付けをしておくようにします。

FactoryBot.define do 
  factory :user do
    trait(:with_posts) do
      transient do
        posts_count { 5 } 
      end

      # 必要な分だけ作成する 
      after(:create) do |user, evaluator| 
        create_list(:post, evaluator.posts_count, user: user) 
        user.reload 
      end 
    end 
  end 
end

終わりに

公式ドキュメントや先人達がベストプラクティスとして公開してくださっている資料を参考に、まず弊社チームでも取り組んでみるとよさそうなものをピックアップし、ガイドの作成を行いました。

今回作成したものをベースに運用しつつ、その中でさらに取り入れたものや、工夫したものは今後もブログなどで紹介したいと思います。

最後になりますが、プレックスではソフトウェアエンジニアフロントエンドエンジニアを募集しています。

上述のような、開発上の取り組みや課題に対してご一緒していただける方、少しでも興味を持っていただけた方は業務委託や副業からでも、ぜひご応募いただけると幸いです。

参考資料

【入社エントリ】事業とエンジニアの関わりからみたプレックスの働きやすさ

はじめまして、小松と申します。2ヶ月連続入社エントリで恐縮です。

昨年2022年11月に入社しました。なんだかんだ入社5ヶ月程たっており、先月の池川の1ヶ月先輩です。ちょっと何を書こうか考えているうちに先をこされたりなぞしてしまいました。えへへ。

簡単な自己紹介

社会に出てから何年か数えてみましたが12年目だと思います。ここまで一瞬です。なぜプレックスに来たのかと言うと、みんな元気があって強そうだったからです。

以下が私の略歴です。ざっくり社会人になってからエンジニアを志して、気がついたらここにいました。

  • 2013年 SES企業へ入社、ITエンジニアとしてのキャリアを開始
  • 2016年 保険代理店へ転職、情シスとして事務業務の改善業務を行う
  • 2018年 オンラインでカーメンテナンスを行う事業会社へ転職、WEBエンジニアとしてのキャリアが始まる
  • 2022年11月 プレックスへ入社 ソフトウェアエンジニアとしてスキルを広げたいと考える

入社前のプレックスの働き方への不安について

まず正直に感じた不安から書いておきます。みんなが元気そう、という理由で入社を決めたのとは矛盾しますが、今までフルリモートで働いていたところから出社を伴う働き方になるというところには不安を感じていました。

入社前はフルリモートだったが果たして出社に戻れるか

事前にリモートは週3回までと聞いていており、個人的にプレックス入社前に感じていた不安はこれにつきました。毎日でないにせよちゃんと通えるのかと。

前職はほとんどフルリモートであった為、出社に対する抵抗感は正直少なからずありました。かといって前職も始めからフルリモートだったわけではなく、例のごとくコロナ禍を経てのリモート化だったので初めからフルリモートの会社に勤める事とは事情が違った事もあり、はなからフルリモートの会社を選択することにも抵抗がありました。

最初の2週間は慣らしの為に全日出社しました。意外にも、新しい職場に慣れる事に精一杯であった為、出社のつらさはあまり感じませんでした。そして、現状は私は週2リモート、週3出社というスケジュールで行動しています。リモートの日はプロダクトチーム内で共有して基本的に作業集中日として、出社日にコミュニケーションを凝縮させるというスタイルの働き方をしています。今の所これは中々心地よく効率的に感じます。

ただし、出社/リモートのハイブリッドワークに伴う課題は月並みに存在する為、メンバーが増えてくれば課題解決をもっと真剣に考える必要があると思っています。

事業とエンジニアの関わりから見るプレックスの働き易さについて

プレックスのエンジニア文化の良さ、面白さというのは過去のエントリーから分かるかなと思うので、今回はエンジニアの組織とビジネスサイド(という呼び方は好きじゃないですが便宜上)との関係からプレックスの意外(?)な働きやすさを書いてみます。

事業部とエンジニアグループの距離感

現在の組織図はこちらのエントリーから変わらず、エンジニアグループの中に事業部で割られているマトリクス状の組織図となっています。プレックスのエンジニア組織ではこの形が上手く機能しているなと感じます。

組織の中に階層や役割分担はありますが、プレックス内ではそれがゆるやかで、プロダクト開発において情報のやり取りを誰かをハブとする点の形ではなく、各々が能動的に取り合う正に網のような形のコミュニケーションを行っています。また、プロダクト(事業)軸の定例の他、網の中で見つけた情報や課題を持ち帰り共有できるエンジニア定例も行っており、事業部とのコミュニケーションとエンジニア間のコミュニケーションが安心して両立できる仕組みとなっています。

事業とプロダクトの絶妙な関係

プレックスには(必ずしも1対1ではありませんが)いくつかの事業部とプロダクトがあります。

広義の意味でのソフトウェアエンジニアとして、プロダクトを前面に出して自社サービスを行っている事業会社で働くというのはある種のロマンがあります。しかし、すげープロダクトを作ってやるぞと入社してみると、多くのプロダクトの表に出ている面は全体のごく一部で、それは営業、CS、バックオフィスなど多くのメンバーのオペレーションで成り立っている事を知ったりします。また、ここでのバランス感覚が崩れると事業部とプロダクトチームの気持ちがばらばらになってしまう事もままあります。

プレックスで私はPlexJobというサービスに関わっており、出面としてはWEBサービスですが、例に漏れずに多くのメンバーのオペレーションで成り立っています。そんなPlexJobの事業責任者はオペレーションとプロダクトに対して中立的であると感じます。非常に現実主義ではありますが、良いものを作りたい気持ちも強く持っています。おかげで事業部内では職種に関係なく互いをリスペクトする文化が醸成されており、エンジニアが働きやすい環境の大きな要因となっていると感じます。

経済的に独立した事業会社であるということ

エンジニアって技術を武器に社内でそれなりに独立した姿勢を見せられそうな感じは見せますが、実際のところはトップの経営判断や社内の政治情勢の波をそれなりに強く受けますよね〜。会社にもよるのでしょうが、私が以前勤めた会社のいくつかは母体の大きい子会社だったので本当に良くも悪くも親会社の出す意見、方針の影響が仕事にありました。

この辺りは専門外である為細かい記載はしませんが、現状そうでないプレックスはここにいる代表、事業責任者、メンバーが本気でプレックスの成長の為に会社を動かしています。当たり前とい言われればそれまでですが、自分たちの作るものが本気で自社の成長を願って作られているものであるという実感は大きなモチベーションとなっています。

最後に

前回の池川さんも書いていましたが、プレックスに来てから毎日が爆速で進んで行きます。各職種に様々なキャラクターのメンバーがおり、日々が刺激的です。

そして恒例になりますが、プレックスでは一緒に働くメンバーを募集しています。組織にもプロダクトにもまだまだ改善点があり、人の手も脳みそも足りていません。

現在エンジニアの他、UIデザイナーの募集もありますので、今回の記事を読んで少しでも興味を持って下さった方はお気軽にご連絡ください。

dev.plex.co.jp

宣伝:私のプレックス内の私的な活動について

Plex電子工作部やってます

なぜ私がエンジニアリング(というより最初はプログラミング)の世界に興味を持ったかというと、なんとなく格好良い感じがしたからです。というより出身が文系というのもあり、工学全般に憧れを感じています。

そこで最近ははんだごてを購入して電子工作をはじめました。まずは電子工作入門の王道、自作キーボードから始めています。

そしてなんとプレックスにはそういった事に興味を持ってくれるメンバーがおりましたので、(非公式ですが)電子工作部の活動も始まりました。プレックスでは一緒に働くメンバーを募集しておりますが、私は一緒に電子工作をしてくれるメンバーも探しております。今回の記事を読んで少しでも興味を持って下さった方はお気軽にご連絡ください!

↓は個人活動ですがこんな感じでやってます・・・!

自作キーボード チェリーパイ(CherryPie)ビルドログ 前編

youtu.be

【入社エントリ】プレックスに入社しました!

はじめまして、プレックスの池川と申します。

2022年12月に株式会社プレックス(以下、プレックス)にエンジニアとして入社しました。

今回、入社して3ヶ月経って新しい環境にも慣れてきたので、入社経緯や感想、課題に感じていることをまとめておきたいと思います。

何か新しいことにチャレンジしたいエンジニアの方や、ベンチャー企業に興味のある方にプレックスのことを知ってもらえると嬉しいです。

自己紹介

私のこれまでのキャリアを簡単に紹介します。

- 2008/4  新卒で地方銀行に入行
- 2019/6  エンジニアとしてIT企業に転職
- 2022/12 プレックスにジョイン

新卒では地元の地方銀行に入って、融資や資産運用の営業を行っていました。

銀行には10年ほど勤めたのち、一念発起してエンジニアにキャリアチェンジして受託開発系の会社に転職しました。

その会社ではバックエンドエンジニアとして業務システムの開発・運用を行ったり、プロジェクトマネージャーとして新規プロジェクトの管理を行いました。

その後、プレックスに転職して今はドライバー向けの求人サービスであるプレックスジョブの開発にフロントエンド・バックエンドの両面で携わっています。

プレックスはこれまでのキャリアで3社目、エンジニアとしては2社目の会社です。

プレックスに入社した理由

転職活動を行うにあたり、以下の3点を軸としていました。

- エンジニアとして開発ができる(マネジメントやプロジェクトマネージャーの業務も面白かったが、開発がしたい)
- 自社開発をメインで行っている(前職で受託開発は経験したので自社開発もやってみたい)
- 社会的に意義のあるサービスを展開している(自分の仕事に意味を見出したい)

上記の点を満たしていたことに加えて、カジュアル面談や面接で代表や先輩社員と話す中で、よく分からないけど面白そうなことやってるな、と感じたことがプレックスに入社を決めたポイントでした。

入社前、不安だったところ

とはいえプレックスへの入社を決める際にいくつか不安な点もありました。

同じような不安を持つ人もいるかもしれないので、ここで払拭しておきます。

おじさんだけど大丈夫?

大丈夫です!

プレックスは平均年齢28歳と若い人が多いですが、エンジニア組織は少し年齢層高めなので安心してください。

それでもエンジニアの中では一番年上ですが、各々がプロフェッショナルとして業務を行うなかで互いに尊重する文化があり、すぐに馴染めました。

おじさんだからこそできることをやっていきましょう💪

不安な方はカジュアル面談やオフィスへの訪問もいただけますし、まずは業務委託や副業といった形でお手伝いいただくことも可能です。

物流・インフラのことよく分からないけど大丈夫?

大丈夫です!!

私も入社までよく分かっていませんでした。

プレックスでは事業部制を採用しており、各事業へエンジニア個人が注力できるような組織作りを行っています。

ビジネス側との距離も近いため、日々コミュニケーションをする中で不明点を解消することができます。

また、エンジニア組織の中でもドメインモデル図やシーケンス図の整備に取り組んでおり、ドメイン知識をいち早くキャッチアップして開発できる体制作りを進めています。

ドキュメント整理については過去の記事「開発合宿でドキュメント整理やER図の自動生成やってみた」に詳細が書いてありますので参照ください。

入社後やったこと

入社後1ヶ月はオンボーディング期間として設定されており、小さめのタスクを担当しながらドメイン知識やプロダクト、使用されている技術のキャッチアップを行いました。

初めてのリリースは入社して4日目だったのですが、リリース時の報告でお祝いのメッセージをいただけたのが印象に残っています。

2ヶ月目以降は既存サービスの新機能開発やデザインリニューアルを行ったり、オンボーディング改善や勉強会の主催など、改めて振り返ってみると色々なことをやりました。

今回の記事ではその中でオンボーディング改善の取り組みの一環でやったことを紹介できればと思います。

オンボーディング改善とは

入社後のオンボーディング期間を終えた後、1スプリント分(一週間)をメインの開発から外れて、オンボーディング中で出た課題の改善時間として使ってもらう というものです。

オンボーディング改善については過去の記事「2人目のエンジニアとして入社してオンボーディングを改善した話」に詳細が書いてありますので参照ください。

実施したオンボーディング改善の取り組み

下記の2つを行いました。

1. デバッグ環境の整備
2. 社内用語集の作成

1. デバッグ環境の整備

プレックスで使用している技術は私にとって未経験のものが多かったため、同じような人のためにデバッグ環境の整備を行いました。

使用している人が多い IDE やブラウザごとに、設定の方法やデバッグの仕方を Notion 上にドキュメント化しました。

取り組んだことでデバッグ関連の知識の共有化がチーム内で図れたほか、今後新しく入ってくるメンバーが開発に早く入れる、効率的に開発ができるような環境づくりができました。

言語や IDE も日々アップデートされているため、適宜情報をブラッシュアップして開発の効率化に繋げたいと思います。

2. 社内・専門用語集の作成

私自身、入社後一番困ったのが専門用語の理解だったため、社内・専門用語集の作成を行いました。

ミーティング中やドキュメントを読んでいると知らない単語(例:CSやDR)が出てくるのですが、都度確認もできず、ミーティングが終わってから聞いたり、過去のドキュメントを検索したりしていました。

特に社内用語が分からないのが辛かったです💦(Google検索しても出てこないので)

そこでスプレッドシートに用語をまとめた上で、普段使用している Slack 上で管理できるように Slack Bot を作りました。

こちらについてもデバッグ環境の整備と同様、定期的に見直しを行い機能の拡充など行っていきたいです。

入社して感じたこと

入社してからは本当に一週間が早く、それだけ刺激的な毎日が過ごせているのかなと思います。

プレックスには「仮説検証して学びを次に活かす文化」があり、技術的なことはもちろんのこと、事業やプロダクトへの課題感を持ち改善をしようとする意識が高い点は入社してすごいと感じました。

一方で組織の体制や制度が整っていない、開発においても属人化されている部分があるなどの課題もあるため、できることやるべきことはまだまだあります。

最後にはなりますが、プレックスではソフトウェアエンジニア、フロントエンドエンジニアを募集しています。

もしこの記事を読んで、一緒に挑戦してみたいと思った方がいましたら是非ご連絡をお待ちしています!!

dev.plex.co.jp

DevOpsの指標を開発の振り返りに活用しはじめた話

こんにちは、プレックスの種井です。

今回は、2022年Plex開発合宿(秋)シリーズ最後の記事になります。(過去の記事はこちら)

10月の開発合宿で私が取り組んだ内容「開発チームの生産性の可視化」とPlexJob開発チームにおける「振り返り」の取り組みに関してご紹介させていただきます。

開発合宿で扱ったテーマ「振り返り」

私の所属するPlexJob開発チームではスクラム開発を取り入れています。合宿時点では月に一回、その月に取り組んだ各スプリントの振り返りをまとめて行っていました。

振り返りを行うなかで

  • 個々人の課題感やその粒度に関してばらつきがある
  • 課題に対してのアクションの優先度がつけづらい

という課題感がありました。

開発合宿で取り組んだ内容

振り返りの場では、各メンバー感じている課題を思い思い自由に出してもらいつつも開発チームとして重要視することを明確にしておくことは振り返りの質の向上や、個々人の課題感の洗い出しのしやすさという観点から重要だと考えました。

書籍エッセンシャルスクラムにも振り返り(スプリントレトロスペクティブ)の事前準備として、「フォーカスの定義」や「客観的データの収集」が要素として上げられています。 今回は、取り掛かりとして、「スプリント毎のベロシティ」と「デリバリのパフォーマンス(Four Keys)」の一部を計測することにしました。

上記の指標は

  • スプリントごとのベロシティ
    • チームの健全性の把握や提供した顧客価値の程度を測るものとして
  • デリバリのパフォーマンス(Four Keys)
  • 合宿の限られた時間内で試せるもの

という観点で選びました。

また、週次のスプリントにおけるベロシティは開発だけではなく、設計からリリースまでの間に複数の役割や要素が影響します。デリバリのパフォーマンス(Four Keys)も合わせて計測することで、開発者内でボトルネックの発見や改善に向けての意識が向きやすいのではないかと考えました。

Four Keysは4項目ありますが、今回は合宿の時間に限りがあるため比較的計測の容易な「デプロイの頻度」と「変更のリードタイム」の2項目のみをスコープとしました。

スプリントごとのベロシティの計測

PlexJob開発チームでは、スクラムにおけるタスクをNotionのデータベースとBoard viewを使用して管理しています。

  1. Notion APIを使用し、データベースからスプリント毎の完了タスクを収集
  2. BigQueryに保存
  3. Metabaseから集計

という流れで、ダッシュボードを作成することにしました。 1,2はスクリプトを作成し、自分の手元のPCで実行するような簡易的なものにしました。

スプリント毎の完了ポイント

Four Keysの計測

前述の通り今回「デプロイの頻度」と「変更のリードタイム」の2項目を対象とします。 両指標ともGitHub APIにて提供されているデータを活用しました。 また、

  • 計測の対象としてPlexJobのAPIリポジトリ上のデータのみを使用
  • 土日は除く
  • dependabotによるPRのマージは除く

という前提で集計を行いました。

デプロイの頻度

本番環境へのリリース頻度です。 master(main)ブランチへのPRがマージされたタイミングをリリースとして、1日あたりのリリース頻度を月毎の平均として算出してみました。

リリース頻度

変更のリードタイム

初回コミットから本番環境へのリリースまでにかかった所要時間です。 master(main)ブランチへマージされたPRの初回コミットからマージまでにかかった時間(日数)を月毎の平均として算出してみました。

リリースまでのリードタイム

合宿時点までの直近3ヶ月の指標を、パフォーマンスレベルに照らし合わせると両項目で「High」にあたることが分かりました。

まとめ

簡易的ではありますが、上記の指標を出してみて改めて個人の負荷状況、開発上のボトルネック、改善点について考える客観的な指標として活用できそうだと実感しました。

実際、合宿の最後の発表の際にも

  • さらにリリース頻度を上げるためにはリリース対象を分割していくべきか?
  • 逆に分割しすぎてQAやプロセスの最適化をしないと、リリースが遅くなることはないか?
  • Four Keysの指標とスプリントのヴェロシティを合わせて計測すると分割の最適化ができるのはないか?

のような議論をすることができました。

PlexJob開発チームでの振り返りの取り組みについて

ここまで、10月の開発合宿で取り組んだ内容の紹介をさせていただきましたが、早いもので気がつけば3ヶ月以上経過しており、開発の体制としてもいくつか変化がありました。

この間、振り返りに関しても新たな取り組みをはじめました。 合宿で取り組んだDevOpsの指標もこの取り組みの中で活用しはじめたこともあり、この場を借りて紹介させていただきます。

前述させていただいた通り、10月時点では月に1回の振り返りを行っていましたが、11月、12月で開発チームに新たなメンバーが計2名ジョインしてくれたこともあり、よりチームとして情報の共有をオープンにし課題や方針に対してのアクションなどに共通認識を持てるようにすべきだと考え、振り返りの場の頻度や内容を再設計することにしました。

振り返りの内容としては、アジャイルなチームをつくる ふりかえりガイドブックなどを参考に以下のようなものにしました。

  1. 週1回金曜日に1時間程度の定例MTGを設ける
  2. 事前に振り返りシートを共有し、各メンバーにKPT用のJamboardに今週のアクションや課題と負荷状況を記入してもらう
  3. 前週のTryの実施状況の確認をする
  4. Jamboardに各自作成してもらった今週のアクションや課題を発表しながらKeepとProblemに振り分ける
  5. 全員のKeepとProblemが分類でき次第、Tryを議論して決め、次週のアクションにする
  6. Tryの中で開発Issueとして扱うべきものはタスク化する
  7. 各メンバーの調子と一言コメントを発表する

以下は、使用している振り返りシートの一部です。

振り返りシート(一部抜粋)

運用をはじめて1ヶ月程度経ちました、まだまだ試行錯誤の段階ではありますが取り組みの中で、プロダクトチーム全体での勉強会や、問い合わせ対応の当番化などの実際のアクションにつながるものもいくつか出てきています。 引き続きメンバーのフィードバックをもらいながら続けていきたいと思います。

合宿で取り組んだ内容のその後と活用

最後に合宿で取り組んだ内容の活用ですが、Four Keysの2項目を振り返りシートに埋め込んで各自の振り返り時に使用してもらうことにしました。 KPTの洗い出しや次アクションを決める際の観点として活用してもらいたいという意図があります。

  1. GitHub APIから取得したデータを加工してtsvとして書き出すスクリプトを実行
  2. tsvをスプレッドシートにインポート
  3. Google Looker Studioで2.のスプレッドシートをデータソースにしてダッシュボードを作成
  4. Notionの振り返りシートに埋め込み

指標の活用例

おわりに

今回、合宿での振り返りの効率化としてのDevOps指標の検証と、PlexJob開発チームでの「振り返り」の取り組みに関して紹介させていただきました。

効果的な振り返りを継続的に実施することは難しいことです。 なるべく各メンバーの負荷を最低限にしつつも、効果的な振り返りを続けることは重要であると考えています。 事例として少しでもご参考になればと思います。

振り返りの取り組みやFour Keysをはじめとした指標の活用の変化に関しても引き続きアップデートがあれば、本ブログで紹介していきたいです。

最後になりますが、プレックスではソフトウェアエンジニアフロントエンドエンジニアを募集しています。

上述のような、開発上の取り組みや課題に対してご一緒していただける方、少しでも興味を持っていただけた方は業務委託や副業からでも、ぜひご応募いただけると幸いです。

dev.plex.co.jp

開発合宿でコードレビューのプロセスを改善した話

こんにちは、プレックスの石塚です。 この記事は開発生産性 Advent Calendar 2022の21日目の記事になります。

前々回の記事で、10月に開発合宿を開催し、「普段できていない開発プロセスの改善」というテーマのもと、各々が開発に取り組んだ様子をご紹介させていただきました。その中で、個人が取り組んだ内容を次回以降のブログで詳しく書いていくと言っていましたが、気付けば早2ヶ月が経過、さすがに年をまたいでしまってはまずいという思いで、焦り筆を取っている次第です。

そういったわけで、今回は自分が開発合宿の中で取り組んだテーマである「コードレビューのプロセス改善」のお話をさせていただきます。

コードレビューの目的

まずプロセス改善の話をする前に、なぜコードレビューを開発プロセスに組み込んでいるのかという話をしたいと思います。 コードレビューは多くの現場で開発プロセスの一貫として取り入れられていることが多いですが、絶対必要というわけではありません。 特に事業フェーズが初期段階かつシニアなメンバーが集まる組織では、コードレビューを省略しているパターンが多いイメージがあります。

個人的にコードレビューを導入する目的に関してはエンジニアリング的観点とビジネス的観点の2つがあると考えていますが、弊社の開発組織においては導入するメリットの方が大きいと感じています。

エンジニアリング的観点

エンジニアリング的観点としては以下のような、いわゆるコードレビューのメリットとして語られることが多い事柄です。

  • プロダクトをチームでメンテナンス可能な状態に保つ
    • リーダビリティや一貫性、品質の担保
  • プロジェクト、ドメイン、技術における知識の共有
  • オーナーシップを高める
  • ログが残る

ビジネス的観点

  • コードが目的を達成できる正しい挙動をしているか確認する
  • 不具合を減らす

ビジネス的観点は上記の2つです。 現在のプレックスの開発では、PdM、エンジニア、デザイナーの分業体制がきっちりと明確になっているわけではないので、コードの変更がユーザーストーリーのゴールを達成するものかを確認するといった目的も含まれています。

コードレビューの責務が増えてしまい、余計なコストが掛かってしまうので、本来は仕様の是非まで確認するのは理想的とは言えません。とはいえユーザーに提供してからこの機能は無駄だったよねとなってしまうと、今まで開発に掛けてきたコストが無駄になってしまうので、開発プロセスのなるべく早い段階で発見できるように、タスクの種類や大きさによって確認するようにしています。

※このあたりの生産管理の話はHIGH OUTPUT MANAGEMENTが詳しいです。

以前のプロセスと発生していた課題

前置きが長くなってしまい恐縮ですが、改善前のコードレビューのプロセスとそれにおいて発生していた課題についても軽く紹介しておきたいと思います。

以前のプロセスではSlackのワークフローを活用していました。 このようなプロセスに落ち着いた背景として、最初はSlackのメッセージでテキストで直接コードレビュー依頼を実施していたので、ワークフローを使って自動化したという経緯があります。 入力値は以下の3つで、レビュワーとレビューイにメンションが飛ぶようになっています。

  • レビュワー
  • 緊急度(お手すき・なる早・大至急の3種類)
  • プルリクのURL

Slackワークフローでのコードレビューのイメージ
Slackワークフローでのコードレビューのイメージ

この運用では特に大きな問題はなかったものの、以下のような課題がありました。

  • コードレビューが溜まりがち
  • コードレビューが漏れる
  • コードレビューに時間が掛かる
  • レビュワーのコンテキストスイッチが発生する
  • コードレビューの知識がレビュワー、レビュイー以外のメンバーに共有されない
  • Githubでのコードレビューの後、Slackでメンションする手間がある

どう改善したのか

原因の特定

合宿の前からどんな課題があるかを議論したり、書籍やブログでのインプットをした結果、主に以下の2点が根本的な原因だと考えました。

  • プルリクエストのサイズが大きい
  • Githubのリマインダーを適切に使えていない
    • 定期的にコードレビューの時間を確保できていない
    • 自分がアサインされているコードレビューを一覧できる場所がない
      • 溜まりがち、漏れる
    • Githubでのコードレビューの後、Slackでメンションする手間がある

「コードレビューの知識がレビュワー、レビュイー以外のメンバーに共有されない」という課題に関しては、比較的チームの人数が少ないこともあり、今回はコストパフォーマンスを考慮して改善のスコープ外としました。

プロセスの改善

そうした流れから、合宿では次の2つの改善を実施していきました。

1. プルリクエストの大きさを小さくする

こちらの改善はプロセスというよりは、心構えに近いですが、コードレビューガイドというドキュメントを用意して、オンボーディング時に共有する形にしました。 実際のコードレビューガイドに記載されている内容をそのまま添付しておきます。

1. プルリクエストを小さくする
- プルリクエストの元となる仕様を小さくする
  - 実装の前にWHYやWHATについて議論しておく
    - スプリントMTGでできると全員参加しているので良い
    - WHYやWHATは本来コードレビューで蒸し返されるべきポイントではないが、リリースしてから気付くよりはコードレビュー時に気付けるほうが手戻りが少ないので気付いたら言うべき
  - 実装の元となるタスク(HOW)を細かく分割する
- 変更が大きくなる際は、設計レビューを実装前に行う
  - モデリングにはドメインモデル図を推奨

コードレベルでプルリクエストを小さくすることも大事なのですが、それ以前の仕様を決める段階での動きがより重要だと思っています。 ユーザーストーリーのゴールを達成するミニマムの仕様はどういったものか、仕様が大きいのであればマイルストーンを引いて検証を分割することはできないかを考えるようにしています。 また大きな変更であれば、設計レビューも事前に行うことで、プルリクエストの責任範囲を小さくすることも意識しています。

2. GithubのSlack連携を軸にプロセスを構築

Githubにはscheduled remindersというSlackと連携したプルリクエストのリマインド機能があります。この機能は大変便利なのですが、個人ごとに設定をする必要があり、設定がばらついているとうまく恩恵を得ることができません。

なのでプレックスでは以下のようなルールを作って運用していくことを決めました。

  • SlackのGithub連携をしている部屋にジョインしておく
    • Slackのユーザーグループに設定して、デフォルトでジョインするようにしておく
  • 開発者は scheduled reminders を必ず設定しておく
  • 平日に1日2回はリマインダーを設定し、コードレビューの時間を取るようにする
    • 時間は任意
  • コードレビューの依頼はプルリクエストのレビュワーのアサインで行う
    • 急を要する場合のみSlack上でメンションする
  • レビューの修正が完了したらプルリクエストからレビューの再リクエストを行う

またGithubでは自分のアサインされているレビューを一覧できるリンクが取得できるので、そのリンクをSlackのカスタムレスポンスに設定したり、Slackのチャンネルのブックマークに貼っておくことで、区切りのいい時間でレビューを実施する際の手間を減らしています。

↓ゴニョゴニョしていたら取得できたリンク

https://github.com/pulls/review-requested?q=is%3Aopen+is%3Apr+archived%3Afalse+user%3A{GithubのOrganization名}

Slackのカスタムレスポンス
Slackのカスタムレスポンス

終わりに

以上の改善の結果、レビューの漏れや溜まりが減る、レビューを依頼する手間が少なくなるといった結果を実現することができました。 振り返ってみると、プロセスを改善できたというのはもちろんですが、合宿という全員が参加している場でお互いのレビューに関する共通認識を揃えることができたことも大きかったなと感じています(自分の実装とレビューのどちらが大事なのか等)。

嬉しいことに11月、12月に1名ずつ、フルタイムのエンジニアの方がジョインしてくださり、フルタイムで4名、業務委託・インターンを含め10名弱の開発組織になってきました。 組織が拡大していく中で更なる改善として

  • レビュワーに誰をアサインするのかをどうやって決めていくのか
  • コードレビューの知識の共有

などの課題にも今後取り組んでいきたいと思っています。

最後になりますが、プレックスではエンジニアを絶賛募集中です。 今回のプロセス改善のような、開発上の取り組みや課題に対してご一緒していただける方、少しでも興味を持っていただけた方は業務委託や副業からでも、ぜひご応募いただけると幸いです。

dev.plex.co.jp