Photoruction工事中!

Photoructionの開発ブログです!

Zenn投稿の通知を自動化したが手動運用に戻した話【GAS×Slack】

はじめに

現在、Photoructionでは月2本を目標にZennブログを投稿しています。 この取り組みを社内にも広げるため、これまでは広報チームがブログ公開のたびにSlackチャンネルへ手作業で通知を投稿していました。

ただ、手動だと通知を忘れそうになることもあり、手間もかかるので、Bot自動通知にしたいと思ったのがはじまりです。

試行1 - SlackのRSS購読機能を利用

ZennにはRSS配信機能があり、記事の更新情報をフィードとして取得可能です。 また、SlackにもRSSフィードをチャンネルに連携する機能が用意されているとのこと。

この2つを組み合わせれば簡単にブログ更新の通知を自動化できるのではと考え、まずはこの方法を試してみました。

https://slack.com/intl/ja-jp/help/articles/218688467-Slack-%E3%81%AB-RSS-%E3%83%95%E3%82%A3%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B

5分程度で設定ができ、届いたのは以下のような通知です。

手軽に実現できた点は良かったものの、実際の通知内容を見るとかなりイマイチです。

bot名もピンとこないし、絵文字もないので地味。slackのメンションもないので著者がぱっと見でわかりません。 情報収集のためだけなら簡単で用途としては向いてそうですが、この通知の目的はあくまでも社内にテックブログの取り組みを広げることなので不向きと判断しました。

試行2 - GAS × Incoming Webhooksで通知させる

調べてみましたが、SlackのRSS購読機能ではbotアイコン・本文をカスタムすることは不可能そうでした。そこで次に試したのが、GAS(Google Apps Script)Incoming Webhooksを組み合わせる方法です。

この方法であれば、以下のような柔軟なカスタマイズが可能になります。 - bot名・アイコンの変更 - メッセージフォーマットの調整 - メンションの付与 - 投稿内容の加工(タイトル・著者・サムネイルなど)

全体の流れはこちらです。 1. ZennのRSSフィードをGASで定期取得 3. 前回取得した記事との差分をチェック 4. 新着記事があればSlackにWebhookで通知

コードは以下のように書きました。

function notifyLatestZennInfo() {
  const props = PropertiesService.getScriptProperties();
  const slackWebhookURL = props.getProperty('SLACK_WEBHOOK_URL');
  const lastLink = props.getProperty('lastLink');
  const rssURL = props.getProperty('RSS_URL');
  const userMap = JSON.parse(props.getProperty('USER_MAP'));

  // RSSフィードをフェッチして取得
  const xml = UrlFetchApp.fetch(rssURL).getContentText();
  const doc = XmlService.parse(xml);
  const items = doc.getRootElement()
    .getChild('channel')
    .getChildren('item');

  // 最新記事のタイトル・URL・著者名などを取得する
  const latest = items[0];
  const title = latest.getChildText('title');
  const link = latest.getChildText('link');
  const dc = XmlService.getNamespace(
  'dc',
  'http://purl.org/dc/elements/1.1/'
);
const creator = latest.getChildText('creator', dc);
const description = latest.getChildText('description');
const formattedDescription = description
  .replace(/\n{2,}/g, '\n')   
  .replace(/https?:\/\/\S+/g, '') 
  .slice(0, 200) + '...';

// 通知済みの記事であればリターン
if (lastLink === link) return

// Slack通知用のメッセージ
const message = {
  blocks: [
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: `*🎊新しいZenn記事が投稿されました!🎊*`
      }
    },
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: `\n今回は、<@${userMap[creator.trim()]}> さんによる記事です!`
      }
    },
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: `📌 *${title}*`
      }
    },
    {
      type: "divider"
    },
    {
      type: "section",
      text: {
        type: "mrkdwn",
        text: `${formattedDescription}`
      }
    },
    {
      type: "divider"
    },
    {
      type: "actions",
      elements: [
        {
          type: "button",
          text: {
            type: "plain_text",
            text: "記事を読む 📖"
          },
          url: link
        }
      ]
    }
  ]
};


// Slackに通知を投げる
UrlFetchApp.fetch(slackWebhookURL, {
  method: 'post',
  contentType: 'application/json',
  payload: JSON.stringify(message),
});

// 通知した記事は定数lastLinkにセット
props.setProperty('lastLink', link);
};

できれば手動時のように内容の要約も行いたかったのですが、まずは自動化を実現したかったので今回は省きました。

とりあえず、このコードを毎日11:00に実行するように設定をすることで、自動化は実現できました。

また、GASにはスクリプトプロパティというものが用意されていて、簡単なデータをキー・バリュー形式で永続的に保存することができます。

イメージとしては、サーバーサイドでよく使われる環境変数のようなものです。 今回の実装では、このスクリプトプロパティを環境変数的に扱い以下の情報を管理しています。

  • SlackのWebhook URL
  • ZennのRSSフィードURL
  • 前回通知した記事のリンク
  • 著者名とSlackユーザーIDのマッピング

https://note.com/crefil/n/n2b68b3c4aa6b

トリガーもスクリプトプロパティも、web上でポチポチ簡単に登録・設定できました。

で、実際に流れてきた通知がこちら。

そして、手動に戻る

カスタマイズの結果、質素な通知には絵文字がつき、著者メンションが飛び、bot名も分かりやすくなり、手動投稿の手間も減りました。 ...

が、リアクションが目に見えて減少しました

手動投稿時。たくさんのリアクション...

自動投稿時。リアクションはたった一つ...

botにしてしまうと、一気にただの情報感がでて意識に入ってこなくなるんでしょうか。

もしこれがbotではなく、実在するメンバーのアカウントから投稿されていたらここまでリアクションが減ることはなかったのではないのかと思います。

「誰が投稿しているか」が見えるだけで、受け取る側の感じ方や反応のしやすさは思った以上に変わるのだと今回学びました...

調べてみましたが現状Slackでは特定のユーザーに代替して投稿するような機能は用意されていませんでした。botしか選択肢はなさそうです。 残念ですが自動化は一旦見送り、手動での通知に戻すことにしました。

さいごに

今回は手動に戻すことにしましたが、自動化しつつうまく反応も得られている事例があれば気になります。 同じようなことを試している方がいれば、ぜひ教えてください!

手動に戻したらリアクション復活。めでたし!

StrictModeでリークを検出する

はじめに

Androidチームでは現在、クラッシュフリー率の改善に取り組んでいます。 詳細は過去の記事をご覧ください。 https://kojichu.photoruction.com/entry/2025/04/02/090000 改善の結果、クラッシュフリー率は95%まで向上することはできましたが、そこから先97〜99%の間を行き来する状態が続いており、伸び悩みを感じていました。

目についたのはOOM(OutOfMemoryError)

Crashlyticsを確認していく中で、気になる傾向がありました。 それが OOM(OutOfMemoryError) です。 特定の重い処理ではなく、「一見なんてことのない画面」でOOMが発生しているケースが複数見つかりました。 実際のログは以下のようなものです。

Fatal Exception: java.lang.OutOfMemoryError:
Failed to allocate a 56 byte allocation with 2777520 free bytes and 2712KB until OOM,
target footprint 536870912, growth limit 536870912;
giving up on allocation because <1% of heap free after GC.

一見すると「たった56バイトの確保に失敗している」だけですが、GC後でもヒープの空きがほとんど残っていない状態になっていることが分かります。 この状況からまず疑ったのは、メモリリークでした。ActivityやContextが解放されていない or 不要なオブジェクトが保持され続けていると。 こういった典型的なリークが積み重なり、ヒープを圧迫しているのではないかと考えました。

StrictMode(VMポリシー)を使って調査する

メモリリークの調査といえばLeakCanaryが定番ですが、 今回はまず軽量に試せる方法として StrictModeのVMポリシー を使ってみました。 StrictModeというとスレッド周りの検知が有名ですが、VMポリシーを使うと「リソースやオブジェクトの扱いの問題」を検知できるのがポイントです。 設定は以下の通りです。

if (BuildConfig.DEBUG) {
    StrictMode.setVmPolicy(
        StrictMode.VmPolicy.Builder()
            .detectLeakedClosableObjects()
            .detectActivityLeaks()
            .detectLeakedSqlLiteObjects()
            .penaltyLog()
            .build()
    )
}

Activityリークは検出されなかった

まず想定していたのは、ActivityやContextのリークでした。しかし実際に画面を操作してみても、 Activity leaks や Context保持に関する違反を示すログは確認できませんでした。 ここだけ見ると、仮説は外れたように見えます。

代わりに出てきたのはClosable / SQLiteリソースのリーク

その代わりに出力されたのが、以下のログです。

StrictMode policy violation: android.os.strictmode.LeakedClosableViolation:
A resource was acquired at attached stack trace but never released.
...
Caused by: java.lang.Throwable: Explicit termination method 'close' not called
...
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:336)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:112)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:706)

LeakedClosableViolation という名前の通り、Closeableなリソースが取得されたまま解放されていないことを示しています。 さらに、Explicit termination method 'close' not calledというメッセージから、明示的な close() 呼び出し漏れが疑われます。 スタックトレースを追うと、 * SQLiteDatabase * Room * Repository層 へとつながっており、少なくとも SQLite関連リソースの管理に問題がある可能性 が見えてきました。

今回分かったこと

今回の調査では、当初想定していたようなActivityリークは確認できませんでした。 一方で、StrictModeのVMポリシーによって、Closable / SQLiteリソースの解放漏れが可視化されたという結果になりました。 これがOOMの直接的な原因かどうかは、現時点では断定できません。 ただし、リソースが適切に解放されていない&それが積み重なることでメモリやシステムリソースを圧迫する可能性があるという点は無視できないと感じています。

現時点の対応と今後

現時点では、該当箇所の修正方針までは整理できています。 * SQLite関連リソースのライフサイクル見直し * close漏れの洗い出し * Repository層の責務の整理 今後はこれらを段階的に修正しつつ、 * OOMの発生状況が改善するか * 他にも同様のリークがないか を引き続き検証していく予定です。

まとめ

OOMの原因調査としてStrictModeを導入しましたが、想定していたメモリリークではなく、Closable / SQLiteリソースの問題が先に見つかるという結果になりました。 StrictModeは「特定の問題をピンポイントで見つけるツール」というより、仮説と違っても、潜んでいる問題を可視化してくれるツールだと感じています。 まだ調査は道半ばですが、少なくとも「見えていなかった問題」が一つ明らかになったのは大きな収穫でした。 同じようにOOMや原因不明の不具合に悩んでいる場合は、 StrictModeのVMポリシーを一度有効にしてみるとヒントが得られるかもしれません。

図面ファイル非同期処理基盤のコンテナ化でやったこと

はじめに

こんにちは。株式会社フォトラクションでSREをしている角です。 現在、SREチームではシステムのコンテナ化を方針の一つとして活動しています。その中で私は、社内で変換サーバーと呼ばれている、図面ファイルを非同期で処理するシステムのインフラ移行を実施しています。 今回は、変換サーバーのコンテナ化について取り組んできたことを紹介したいと思います。

変換サーバーは何をしているか

変換サーバーは、ユーザーがアップロードした図面ファイル(画像/PDF)を、システム内で扱いやすい形に整えるバックグラウンド処理基盤です。主な役割は、フォーマット変換・複数ファイルの結合・分解といった図面データの前処理/加工を引き受けることにあります。 処理の流れはシンプルで、キューに積まれたタスクを定期ポーリングして拾い、対象ファイルをS3からダウンロード→変換処理→S3へアップロード、というワーカー型のパイプラインになっています。 実装は Windowsサーバー+.NET(C#)で、運用面の特徴として「サーバー上で実行ファイルをクリックするとGUIが立ち上がり、並行実行数などを入力してStartを押すと処理が走る」という、手動起動・手動設定のスタイルになっています。

変換サーバーの問題点

変換サーバーは主要な機能開発が約5年前に一段落して以降、大きな改修は多くありませんでした。Photoruction本体の機能開発とのトレードオフがある中で、限られたリソースで運用を継続してきた、という背景があります。その結果、手作業や暗黙知が少しずつ積み重なり、運用負荷が高くなっていました。

現状把握や変更にかかる負荷が大きい

具体的には、新しいインスタンスへデプロイする時、インスタンスを起動して、プロセスを起動して、設定を反映して、という作業を毎回手でやる必要がありました。そのため、作業のたびにヒューマンエラーのリスクがありました。 また、ログがWindowsサーバー内のテキストファイルに出ているため、調査のたびにまず「どのサーバーの、どのログファイルを見るべきか」から始まっていました。複数台にまたがる事象だと、ログを集めて時系列を揃えて突き合わせる必要が出てきて、横断検索ができないぶん、初動がどうしても重くなりました。

変更への抵抗感が、システムの安定性を落としていた

その結果、いらなくなったシステムが止められずに残っていたり、インスタンスのスペックに応じたプロセス配置が最適化されないままになっていました。特に、スペックが足りなくなったときに既存サーバーの構成を見直して載せ替えるのではなく、既存はそのままに高スペックなサーバーを追加で立てる、という方向に寄りやすく、負荷の偏りが解消されないまま積み上がっていきました。その結果、低スペック側はCPU 70〜100%でカツカツなのに、高スペック側は30%程度で余裕がある、といった歪みが起きていました。また、古いインスタンスは1台に複数システムが同居し続ける構成になっており、どこかの負荷増加が別システムの遅延や障害に波及しやすい状態が解消されずにいました。

運用負荷を軽減するためにECS Fargateに載せることにした

変換サーバーだけ特別な運用にならないように、社内で既に利用されているECS Fargateに載せ換えることにしました。普段の運用の延長で見られる形に揃えることで、判断や引き継ぎ、障害対応のコストを下げる狙いがありました。 Fargateで運用することにすると、自然とログをCloudWatch Logsに集約することになるので、横断検索と突合がしやすい状態にもなります。 また、デプロイはGitHub Actionsをつかって自動化することにしました。それによりあいまいになっていたデプロイ方法も標準化でき、期間が空いたり人が変わっても対応可能になります。

実際にやってみると思ったようにはいかなかった

実装を初めてから、とりあえず動くまではかなり順調に行きました。生成AIを利用しているので、知らないコードでも数日でメインの機能がFargate上で動くまで持っていけました。 それよりも、一番時間を使ったのは現状把握と動作確認でした。本番に出すためには、人間がちゃんと確認して、問題ないことを説明できる必要があります。そのために、機能が正常に動いているかや、パフォーマンスが劣化していないかなどの確認を実施しました。しかし、自分はこの移行プロジェクトで初めてまともに変換サーバーにかかわったため、細かい仕様や、動作確認のノウハウみたいなものを持ち合わせていませんでした。なので、ドキュメントやソースコードを見て動作確認手順を考えて実施したり、今まで開発や運用にかかわってきた人に依頼するなどで進めました。そのたびに足りない部分が見つかっては対応しました。 例えば、アップロードされた図面をまとめてZIPにしてダウンロードできる機能があるのですが、最初はそれが本番でどのくらいの規模で動いているのか掴めていませんでした。調べてみると、1回の処理で数十GBのデータを対象にしていて、しかも完了まで数日かかるケースがあることが分かりました。想像していたよりも重い処理で、正直驚きました。加えて、この機能は他と構成が違っていたので、それ専用のインフラも用意して検証を進めました。しかし、本番と同じ環境を用意できなかったためパフォーマンスに問題がないか十分な試験ができなかったことと、リリース直後の動作確認が素早くできないことから、今回のリリースのスコープからは外すことにしました。 もう一つ印象に残っているのは、この変換サーバーを開発していた人にコードレビューをお願いしたときのことです。「Javaのランタイムが入っていないけど大丈夫なのか」という指摘が来て、C#なのに何でJavaなのかと思って調べたところ、C#の中からJavaプロセスを起動している処理が確かに入っていました。とはいえ動作確認では問題が出ていなかったので理由を追うと、過去の個別要件向けの分岐が残っていた部分で、今は使われていない機能でした。念のためDBの実行履歴も確認しましたが、Java処理が走るオプション付きのレコードは見つかりませんでした。とりあえずJavaを入れて動く状態にしたものの、利用していない機能に深入りするのを避けて、細かい動作確認はせずリリースすることにしました。

結果

本番リリースにより、運用面では次の点が変わる想定です インフラを移行することで、これまで手作業で回していた作業の多くを仕組みとして実行できる状態に寄せられました。たとえば、台数の増減のように人が手順を覚えて都度実行するタイプの作業は、サービス側のスケーリング機能に寄せることで、手動オペレーションの頻度とヒューマンエラーの余地を減らせるようになります。 また、各システムをコンテナとして分離したことで、負荷や障害の影響範囲をサービス単位に閉じ込めやすくなりました。Fargateではタスクごとにリソースの割り当てと制限を分けられるため、少なくとも 同居による道連れは起きにくい構成に寄せられています。 さらに、ログがCloudWatch Logsに集約されるようになったので、サーバーごとのローカルファイルを探しに行く必要が減り、検索や突合がしやすくなりました。加えて、ECSのメトリクスはDatadogに送っているため、監視もよりやりやすくなりました。

まとめ

今回の移行で印象的だったのは、実装そのものよりも 現状把握と動作確認が一番重いという点でした。C#未経験の私でも、生成AIの補助を借りて実装自体はスムーズに終えられました。しかし、そもそも明文化されてないことは、まず人間が情報収集する必要がありますし、情報を集めた後の意思決定も人間がするので、まだまだやることはたくさんありました。 今後も、サービスの信頼性を高める改善を継続していきます。

株式会社フォトラクションでは一緒に働く仲間を募集しています

www.wantedly.com

Androidチームでチーム内勉強会を始めました

TL;DR

  • Androidチームで技術力向上とナレッジ共有を目的とした勉強会を開始した
  • これまでに「データ分析(BigQuery/Redash)」と「AI活用(Cursor)」の2回を開催
  • 第1回はユーザー行動分析のためのデータ基盤の構造とクエリ作成方法を学習
  • 第2回は各メンバーの「Cursorの推し機能・使い方」を共有
  • 勉強会を通してチームの課題(アーキテクチャ統一など)が明確になり、解決に向けた動き(.cursor/rules導入など)が生まれた

    はじめに

    こんにちは。Androidチームの藤井(Yosuke Fujii)です。 Androidチームでは去年の12月から、主にチーム内でのナレッジ共有を目的に勉強会を開催しています。 今回は、チームで始めたこの勉強会の取り組みと、そこで共有された内容(特に盛り上がったCursor活用術など)についてご紹介します。

    勉強会を始めた動機

    チーム内での開発効率化や、障害調査能力の底上げが主な動機です。 これまでも各メンバーが高いパフォーマンスを発揮していましたが、特定の分野において知見が属人化している部分があったため、チーム全体でノウハウを標準化することを目指しました。 現在までに以下の2つのテーマで開催しました。

  • ユーザー行動調査のナレッジ共有: 複雑なデータ分析や調査手法について、特定のメンバーに知見が集中しがちだったため、全員が同じレベルでBigQueryやRedashを活用できるようにする。
  • AIツール活用知見の共有: 各メンバーが個別にCursorを活用してはいたものの、具体的な使い方の共有までは行えていなかったため、お互いの知見を持ち寄ってチーム全体の生産性をさらに高める。

    第1回:BigQueryとRedash勉強会(振り返り)

    まず第1回は、ユーザーの行動ログやサーバーデータをどう調査するかをテーマに行いました。

  • BigQuery: Firebase Analytics等のログ用。モバイルからの送信データがメインで、ネストされたフィールドは UNNEST 関数で解除する必要があることなどを学びました。
  • Redash: 本番DB(リードレプリカ)への参照用。MySQL形式でクエリを投げ、具体的なレコード調査やプロジェクトごとのデータ確認に利用します。 この回での大きな学びは、「データの在り処さえ分かれば、AI(Gemini等)にスキーマを伝えることでクエリは生成できる」 という点です。複雑なSQL構文を暗記するよりも、どのデータがどこにあるかを知ることが重要だという共通認識ができました。

    第2回:Cursor Tips共有会

    続く第2回では、「Cursor」をテーマに、各メンバーが普段どのようにAIを活用しているかを持ち寄りました。 「意外とみんな似たような使い方をしているのでは?」という予想もありましたが、話してみると人によってアプローチが異なり面白い発見がありました。 ここでは、チーム内で共有された具体的な活用事例をいくつか紹介します。

    1. Plan Modeと対話型スタイル

    メンバーによってAIへの指示の出し方が大きく分かれました。

  • Plan Modeを活用するスタイル: いきなりコードを書かせるのではなく、まずは Plan Mode で修正プラン(Markdown形式のToDoリスト)を出力させます。そのプランを見て人間が理解・確認してから実行に移すことで、意図しない修正を防ぎつつ、自分が理解できないコードが生成されるのを防いでいます。
  • 対話型(Chat)で進めるスタイル: 事前にプランを作らず、チャットで会話をしながら少しずつタスクを進めるスタイルです。自分のアイデアをAIに見せてフィードバックをもらったり、理解しながら進めることで記憶に残しやすくしています。
  • 直接指示を出すスタイル: やりたい実装のイメージが明確に頭の中にある場合、Plan Modeは使わず直接指示を出してスピーディに実装を行います。

    2. スクリーンショットをプロンプトに活用

    「良い指示(プロンプト)が文章で思いつかない」という悩みに対し、画面のスクリーンショットを撮り、赤枠で修正箇所を囲ってCursorに読み込ませるという方法を共有しました。 これには他のメンバーからも「画面の文字まで読み取ってくれるのか」「便利そうだ」と驚きの声が上がりました。

    3. デバッグログの埋め込みと削除

    バグ調査やトラブル対応で特に便利だと話題になったのが「ログの埋め込み」です。 「この周辺の処理フローを知りたいからログを出して」と依頼し、調査が終わったら「さっき追加したログを消して」と指示するだけで、面倒なログ出力作業が一瞬で完了します。 デバッガーで追いきれない事象に対し、大量のファイルにログを仕込んで原因を特定した事例もありました。

    4. 高品質なユニットテストの生成

    実装をお願いしたチャットの流れで「ユニットテストもお願いします」と依頼するだけで、文脈(Context)を理解したテストコードを生成してくれます。 Mockの定義やテスト用JSONデータの準備など、人間がやると手間がかかる作業をAIが肩代わりしてくれるため、非常に効率的です。ただし、生成されたテストが正しいかは人間がチェックする必要があります。

    5. Figma連携とGit操作

  • Figma連携: 新規画面作成時にFigma連携を試した事例では、アコーディオン等のコンポーネント連携も含めて「かなりいい感じ」に作ってくれたという報告がありました。ただ、1ファイルにまとめて出力されるため分割が必要などの課題もありました。
  • Git操作: diff の確認や、修正内容に基づいたコミットメッセージの提案、タグの検索などにもCursorを活用している事例が共有されました。

    今後の展望:Rules機能による標準化

    この共有会での議論をきっかけに、「アプリ全体の構成や新規画面の作り方を Rules.cursor/rules)で定義できないか?」という話になりました。 現状、Figma連携などで画面を作ると、独自のコンポーネント分割やパラメータの渡し方がチームのアーキテクチャと異なる場合があります。 そこで、以下のようなルールを自然言語で定義することを検討しています。

  • ViewModelやScreenの構成ルール
  • 関数のパラメータの渡し方(プレビュー可能性の確保など)
  • ディレクトリ構成 これらをルール化しておくことで、AIにコード生成させる際にチームのアーキテクチャを遵守させ、レビューコストを下げることを目指しています。

    おわりに

    勉強会を始めてみて、メンバーそれぞれが異なるアプローチで開発していることが分かり、非常に刺激になりました。 ただ知識を共有するだけでなく、こうした勉強会で得た知見を実際の開発プロセス(今回はRulesの導入など)にフィードバックし、チーム全体の生産性を高めていきたいと思います。 今後も月1回程度の頻度で、持ち寄り形式の勉強会を継続していく予定です。


みなさんのチームではどのような勉強会を行っていますか? もしおすすめのテーマや、Cursorの便利な活用法があればぜひコメントで教えてください!

株式会社フォトラクションでは一緒に働く仲間を募集しています

www.wantedly.com

【入社4ヶ月インタビュー】技術+αで輝く。BuildWebチームで見つけた「自分の強み」の活かし方

BuildWebチームのポピポピです。 今回は、Webチームにジョインして4ヶ月が経ったメンバーあずにゃんさんに入社後のリアルな感想をインタビューしてみました。

開発スキルはもちろんのこと、持ち前のフットワークの軽さで、早くもチームに新しい風を吹き込んでくれているメンバーに、転職の経緯から現在の仕事ぶりまで根掘り葉掘り聞いてみます。

👨‍💻 まずは簡単な自己紹介をお願いします!

あずにゃん エンジニア歴は今年で6年目になります。 前職までは、受託開発やSES(客先常駐)での開発をメインに経験してきました。 実は開発業務だけでなく、プライベートでは技術カンファレンスの運営スタッフなどもやっていまして、イベントを企画したり、コミュニティを盛り上げたりすることも大好きです。今日はよろしくお願いします!


🚀 転職の軸と、当社を選んだ「決め手」

ポピポピ: 6年目というキャリアの節目で、転職活動では何を重視していましたか?また、数ある会社の中で最終的に当社を選んだ『決め手』は何だったのでしょうか?

あずにゃん 転職の第一の理由はシンプルに「スキルアップ」でした。ただ、選考が進む中で、この会社なら技術以外の部分でも輝けるかもしれないと感じたのが大きいです。

あずにゃん 一番の決め手は、面接官を担当してくれたチームリーダーたちの人柄と、その「眼」です。 面接の中で、単に「技術的に何ができるか」だけでなく、私のカンファレンススタッフの経験や、チームビルディングへの関心など、私自身も気づいていなかった「自分の強み」を見つけ出し、評価してくれたんです。 「ここなら、ただコードを書くだけの要員としてではなく、自分の個性を活かして働ける」と確信できた瞬間でした。


🔥「静か」だけど「熱い」。Webチームの印象とカルチャー

ポピポピ: 実際に入社してみて、Webチームの雰囲気はどうでしたか?

あずにゃん 最初の印象は「静かで温和」でした。 Webチームの皆さんは本当に穏やかで、常に落ち着いて開発に取り組んでいます。カリカリしている人は一人もいないので、常にピリピリした空気が苦手な私にとっては非常に働きやすい環境です(笑)。

開発プロセスへのストイックさ 一方で、ただ静かなだけではなく、開発プロセスに対しては非常にストイックだと感じました。 特に新鮮だったのが、「Findy Team+」というツールを使って開発のリードタイムやパフォーマンスをきっちり計測している点です。 KPT(振り返り)でも、感覚値ではなく数字ベースで議論が行われていて、「温和な雰囲気の中で、しっかりと数値を追う」というバランスがとても心地よいです。

開発だけじゃない!「+α」のフォロワーシップ また、開発業務だけでなく、広報活動やイベント運営など、「+α」の役割を持って働ける点も魅力です。 私はエンジニアリングも好きですが、チームを盛り上げたり、外に発信したりすることも好きなので、そういった「開発以外の別視点」で業務に関わることで、自身の挑戦心を満たすことができています。


🧗 未経験の建設業界、ぶっちゃけどうなの?

ポピポピ: 建設業界という未経験のドメインに対して、不安はありませんでしたか?

あずにゃん 正直、不安はほとんどありませんでした。 というのも、入社直後にPMの方から業界についての丁寧なプレゼンがありましたし、おすすめされた入門書を読むことで、大体の業務プロセスをイメージできたからです。 ドメイン知識のキャッチアップに関しては、チームのサポートが手厚かったですね。

ポピポピ: これまでの4ヶ月で、一番「達成感」を感じた仕事は何ですか?

あずにゃん 「IP制限機能」の実装ですね。 これは、PMと議論して仕様の方針を決めるところから始まり、UIの提案、実装、スプリントレビュー、そして改修まで、機能リリースのサイクルを一通り、ほぼ一人で完遂できたんです。 自分の提案が形になり、それがプロダクトに組み込まれる過程を丸ごと体験できたのは大きな自信になりました。

ポピポピ: 逆に、苦労したエピソードはありますか?

あずにゃん 「図面ピン機能」の実装ですね。あれは今の自分の実力ではかなりタフなタスクで、 実装を進める中で、複雑なロジックや技術的な壁にぶつかり、かなり苦戦しました。 当社はフロントエンドとバックエンドの線引きが厳密ではなく、フルスタックに開発することが求められれているということもありその分大変ですが、今まで使わなかった技術に触れられて、エンジニアとしての幅が確実に広がっている実感があります。


🌟 今後の目標と会社の魅力

ポピポピ: 今後、チームでやっていきたいことや目標はありますか?

あずにゃん まずは、チーム内のコミュニケーションもっと活発にしていきたいです! まだ少し緊張してしまうことがあるので、もっと気楽に喋れるようになるのが個人的な目標です。

その上で、「Builders Web会議」のような社内勉強会や、オフラインイベントの企画を積極的にやっていきたいです。 自分が担当していない実装箇所の話を聞くのは、将来自分がそこに関わる時の参考になりますし、何よりチーム全体の知見共有につながります。開発以外の部分でも自発的に動いて、チームを盛り上げていきたいですね。

ポピポピ: 最後に、これから入ってくる方に向けて「会社の魅力」を一言でお願いします!

あずにゃん 「自発的に動けば、開発もそれ以外もチャンスが広がる環境」 です。 技術的な挑戦(フルスタック開発)ができるのはもちろんですが、締め会のスタッフや広報など、手を挙げれば任せてもらえる文化があります。 自分の強みを活かして、色々なことに挑戦したい人には最高の環境だと思います!


📝 インタビュー後記(ポピポピより)

入社してまだ数ヶ月ですが、あずにゃんさんはすでにWebチームにとって欠かせない存在になっています。 インタビューでも触れていた通り、技術への探究心はもちろんのこと、「チームのために何ができるか」を常に考えて動いてくれるフォロワーシップの塊のようなメンバーです。

「図面ピン」のタスクで苦戦していた時も、決して投げ出さずに泥臭く食らいつく姿勢を見て、私も頼もしさを感じていました。 これからもその「開発+α」の強みを活かして、Webチームを、そして会社全体を巻き込んで盛り上げてくれることを期待しています!

株式会社フォトラクションでは一緒に働く仲間を募集しています

www.wantedly.com

スプリントレトロが形骸化したのでレトロを振り返ってみた(メタレトロ)

TL;DR

  • ベロシティを安定させたくて、4人チームでスプリントレトロを半年間回した
  • 数値(達成率・標準偏差・Cycle Time)を見ることで計画と実行を改善できた
  • ただし同じアジェンダではレトロは形骸化する
  • 課題が見つからなくなったら「レトロのやり方」を振り返る(メタレトロ)
  • レトロの目的はイベントを回すことではなく、計画→実行→改善を回すこと

はじめに

こんにちは。フォトラクション Android エンジニアのさいとうです。

ここ半年ほど4人チームでスプリントレトロを継続的に実施してきました。

この記事では、

  • なぜスプリントレトロを始めたのか
  • どんなやり方で回してきたのか
  • 何がうまくいき、何が形骸化したのか
  • そして最後に「メタレトロ(レトロのレトロ)」をやって何が見えたのか

について書きます。

「レトロって本当に意味あるの?」

「やってはいるけど、正直惰性になっている」

そんな感覚を持っている人に読んでもらえたら嬉しいです。


スプリントレトロを始めた動機:ベロシティを安定させたかった

レトロを始めた一番の理由は、ベロシティを安定させたかったからです。

  • 計画したストーリーはどれくらい達成できたのか
  • 計画と実行はどれくらい乖離していたのか
  • なぜその乖離が起きたのか
  • 次にどう改善するのか

これらをチームとしてきちんと評価・学習する場がありませんでした。

アジャイルの文脈では

「計画と実行の乖離は失敗ではなく学習の機会」と言われます。

ただ、学習するタイミングがなければ、それはただの理想論です。

また、アジャイルであろうとなかろうと、

  • チームが計画し
  • チームが実行し
  • そこに乖離があるなら

その説明責任はチームにあります。

原因を知り、改善するところまで含めて、チームの責任だと考えています。


実際にやっていたスプリントレトロ

基本情報

  • チーム規模:4人
  • タイミング:スプリント終了〜次スプリントのプランニングの間
  • フレームワークKPTは使わず、Notionに作った固定アジェンダ
  • 目的:感想ではなく「計画と実行の差分を分析し、次に活かす」

KPTはどうしてもダレやすく、「良かった」「困った」で終わりがちだったため、

あらかじめ問いを固定したアジェンダにしました。


レトロのアジェンダ概要

1. 留意点の確認

  • 実装の詳細や技術の深掘りはしない
  • SPイベントでどう修正できるかを話す
  • 自分たちがコントロールできる範囲の話に限定する
  • 外部依存は現状共有まで
  • 同じ問題を何度も出さないための改善を考える
  • 大きな問題は分割して扱う

2. カンバン(恒久ルール置き場)

レトロで決まった「恒久的に守るルール」を記載する場所です。

一時的な対処ではなく、

今後もずっと効くルールを残すために設けました。

後に、

ルールの多くがリファインメント関連だと分かり

「リファインメントルールブック」として別ドキュメントに切り出しました。


3. 前回の Next Action の確認

  • 前回決めた改善アクションは実行されたか?
  • 実行されていないなら、なぜか?

4. ベロシティレポートの確認

  • JIRA のベロシティレポートを確認
  • 計画 / 実績 / 達成率を記録

数字を見ずに振り返ることはしませんでした。


5. ベロシティの標準偏差

  • 過去5スプリントの標準偏差を算出
  • チーム目標:10以下
  • 未達の場合は原因分析

「平均」ではなく「ブレ」を見るためです。


6. Findy Team+ による開発フロー分析

  • Cycle Time を確認
  • Open → Review
  • Review → Approve
  • それぞれ平均24時間以内を目標

スプリント内での滞留がなかったかを確認しました。


7. 振り返り(目標達成)

  • スプリント開始時に設定した目標は達成できたか
  • 未達なら、なぜか

8. 計画と実績の所感共有

  • 各メンバーがスプリントの所感を記入
  • うまくいかなかった点を中心に分析

9. 次のスプリントまでの Next Action

  • 次回までにやる改善を決める
  • 実行することを約束する

レトロをやってよかったこと

一番大きかったのは、

  • 計画の立て方
  • リファインメントのやり方

構造的に改善できたことです。

具体的な改善例

リファインメントはキックオフから1スプリント以内に終わらせる

  • 問題:リファインメント精度が低い
  • 分析:キックオフ時の説明が忘れられている
  • 改善:リファインメント完了期限を明確化

見積もりはチームでプランニングポーカーを行う

  • 問題:ストーリーの認識ズレ
  • 分析:「どこまでやるか」の理解が人によって違う
  • 改善:必ずチームで見積もる

結果として、

  • 課題を見つける力
  • 計画と実行に対する責任感

がチーム全体に生まれたと感じています。


そして形骸化した

6月から始めたレトロは、11月頃には初期に作ったアジェンダでは改善点が見つかりにくくなりました。

  • 毎回同じ項目
  • 大きな問題はすでに潰れている
  • 「特にないですね」で終わる空気

レトロ自体が悪いわけではなく、やり方が今のチームに合わなくなっていたのだと思います。


メタレトロ:レトロを振り返る

レトロはスプリントの結果を振り返る場

メタレトロは、その振り返り方を振り返る場

現状のレトロアジェンダをすべて棚卸ししました。

やったこと

  • Miro 上にアジェンダを並べる
  • 各項目を10点満点で評価
  • コメントを書いてもらう
  • 合計点が低いもの、不要と感じられているものを削除

メタレトロで得た気づき

レトロを通じて、チームの共通認識はかなり揃ってきていると感じました。

だからこそ、同じ切り口では新しい課題が見えなくなっていました。

しかしこれは悪いことではなく、チームが成熟してきたサインだと思っています。


メタレトロ後に変えたこと

アジェンダを更新して「達成率に影響したこと」という項目を追加しました。

  • スプリントレポートをより丁寧に見る
  • 気になる点があれば、そこを深掘りする

有効かどうかはまだ分かりませんが、「有効か分からないが試す」という意思決定自体がレトロの成果だと思っています。


おわりに

計画したら、

  • 達成したかどうかを見る必要があります
  • 結果の責任は、計画したチームにあります
  • 未達なら、原因を知る必要があります
  • 改善する必要があります
  • それもチームの責任です

この改善が回るなら、必ずしもレトロスペクティブである必要はないと思っています。

大事なのは名前ではなく、計画・実行・振り返り・改善 が回っているかどうか。

もしレトロに「意味ある?」と感じているなら、

それはやめ時ではなく、変え時なのかもしれません。


みなさんはレトロが効かなくなったときどうしていますか? 👀

今回、レトロが形骸化してきたタイミングで

「レトロのやり方自体を振り返る(メタレトロ)」をやってみました。

個人的には、

  • 課題が出なくなった=レトロが不要 ではなく
  • 課題が出なくなった=切り口が今のチームに合っていない

という状態だったのかなと感じています。

ただ、これはあくまで自分たちのチームでの話です。

  • レトロをやめたことがある人
  • レトロの頻度を減らした人
  • KPT 以外でうまく回っているやり方がある人
  • レトロという名前にこだわらず改善を回している人

もしよければ、

「レトロが効かなくなったとき、どうしているか」 をコメントで教えてもらえると嬉しいです。

株式会社フォトラクションでは一緒に働く仲間を募集しています

www.wantedly.com

入社後3ヶ月目のメンバーにインタビューしてみた

Androidエンジニアの藤井です。

今回は私がリーダーを務めさせていただいているAndroidチームに今年の10月ジョインした、 パポウ・アントンさんが入社3ヶ月目ということで、インタビューをしてみました。

写真左:アントン(敬称略)写真右:藤井(著者) 五反田駅の近くにて

👨‍💻 まずは簡単な自己紹介をお願いします!

藤井: まずは簡単な自己紹介をお願いします!

アントンさん: アントンと申します。ベラルーシ出身で、来日して4年になります。年齢は28歳です。

Android開発の経験は7年で、これまでに株式市場アプリ、タクシーサービス、IoT関連など、様々な分野のアプリケーション開発を経験してきました。これまで培ってきたスキルを、御社のAndroidチームで存分に発揮したいと思っています。どうぞよろしくお願いいたします!


🚀 入社を決めた最大の理由:決め手は「人」と「成長のスピード感」

藤井: 数ある選択肢の中で、最終的に当社のAndroidチームに入社を決めた一番の理由は何でしたか?

アントンさん: 理由はいくつかありますが、最も大きかったのは、選考プロセスを通じて感じた印象です。

1. 「一緒に働きたい」と感じた面接での印象

アントンさん:

まず、一次面接と二次面接で面接官の皆さんに対して非常に良い印象を抱きました。皆さんの人柄が良さそうで、面接が始まってすぐに安心して気軽に話すことができたんです。

大手企業からもオファーをいただいていましたが、面接を通じて、ここでは「正しい答え」だけを求めているのではなく、私の人間性を見てくれていると感じました。この、人間関係を重視する姿勢が、私にとって非常に重要でしたし、「このチームなら自分の意見を出しやすいだろう」と確信できました。

2. 成長を確信した「業界」と「チームの規模」

アントンさん:

次に、当社のサービスが扱う建設業界が、私にとって全く新しい分野だったことです。これまでの経験を発揮できるだけでなく、新しい知識や課題に挑戦し、学ぶことができると考えました。

また、チームの規模も決め手の一つです。私は当社くらいのサイズ感が一番好みで、小さな規模のチームでこそ、プロダクトの改善にすばやく動け、貢献できるというスピード感に魅力を感じました。実は、他社の大きなオフィスを見て、自分がそこで働けるか少し不安になったこともあり、この規模感が自分に向いていると感じました。


💡 期待以上だった「人の良さ」と「プロジェクトの品質」

藤井: 入社前に抱いていたイメージと、実際に2〜3ヶ月働いてみて感じた良い意味でのギャップがあれば教えてください。

アントンさん: 入社前のイメージは、面接で皆さんとお話しして感じた「人柄が良く、一緒に働きたいと思える方々がいる」という印象と、「オフィスが綺麗そうだ」というものが核になっていました。

技術的な面で言えば、面接でプロジェクトのお話を聞いた時に、開発経験やセンスのある人が改善を重ねて作ってきたアプリだと感じていたので、プロジェクトの品質に対する期待も高かったんです。

藤井: 実際に2〜3ヶ月働かれてみて、その期待に対してはいかがでしたか?

アントンさん: はい、イメージはほとんど変わっておらず、むしろ良い意味で期待以上でした

特に良かったのは、同僚の皆さんが非常に明るい方が多く、全員が前向きに頑張っているというチームの雰囲気です。この環境なら、必ず良い結果を生み出せるだろうと確信しました。

アントンさん: また、プロジェクトの品質については改めて良い意味で驚きました。基礎となるアーキテクチャがしっかりと整っています。前職では、未経験者が作ったプロジェクトで苦労した経験がありましたが、ここでは基礎がしっかりしているため、アプリやコードの改善提案を積極的に行いやすいと感じています。技術的な挑戦がしやすい、理想的な環境だと感じています。


💎 誇れる文化:情報共有を加速させる「モブプロ」と「洗練された開発プロセス

藤井: チームのメンバーや働き方の中で、「これは他の会社にはないだろう」「誇れる」と感じる点は何ですか?

アントンさん: 最も誇れるのは、情報共有が円滑に進むチームの文化と、洗練された開発プロセスの2点です。

1. チームの成長を支える「モブプロ」文化

アントンさん: まず、「モブプロ」(モブプログラミング)の文化が好きで、非常に助けられています。

前職では、誰かに個人的に質問する必要があり、返答を待っている間に仕事が止まってしまうことがよくありました。しかし、ここでは毎日決まった時間に皆で集まり、気軽に質問し、理解を深め合う機会があります。特に、入社したばかりの頃は分からないことだらけでしたが、このモブプロのおかげで安心して質問でき、すぐにチームの一員として仕事に取り組むことができました。

2. 効率的で透明性の高い開発プロセス

アントンさん: 次に、開発プロセスが確立されていることも素晴らしい点です。

  • リファインメントとプランニングポーカー: 私は入社して初めて、リファインメントセッションプランニングポーカーを経験しました。これにより、タスクのスコープや難易度をチーム全員で正確に把握しやすくなり、あらゆるチケットに積極的に自信を持って取り組めるようになりました。
  • タスクの透明性: Jiraを使ったタスク管理や進捗報告が容易に行えるため、「誰が今何をしているか」がチーム内で把握しやすいことも、スムーズな開発に繋がっていると感じます。

3. リラックスできる環境も魅力

アントンさん: また、細かな点ですが、オフィスにお菓子やコーヒーが設置されていることも良い点だと感じています。以前の会社にはなかったので、社員のためにリラックスして働ける環境を整えようという会社の姿勢が伝わってきて、とても良い印象を受けています。


🧗 挑戦と成長:あえて難しい新規機能開発に飛び込む

藤井: この2〜3ヶ月で、最も「挑戦した」と感じた仕事は何ですか?また、その経験を通じて、どのような成長がありましたか?

アントンさん: 最も挑戦したと感じたのは、プロジェクトのソースコードや構造を深く理解するために、難易度の高い新規機能作成や機能追加のタスクを積極的に選んだことです。

入社直後は、もちろん不具合改修のタスクも担当していましたが、それだけでは表面的な理解に留まってしまうと考えました。

藤井: あえて難しいタスクを選ばれたのはなぜでしょうか?

アントンさん: 私は、「難しいことに挑戦しないと成長しない」という考えを常に持っています。

「今の自分にとって、少し難しいかもしれない」と感じるタスクこそ、自分の能力を最大限に発揮し、深く学ぶチャンスだと捉えました。

実際に、新規作成のタスクを通じて、アプリの基礎的なアーキテクチャやコード構造をより深く理解することができました。この経験のおかげで、プロダクト全体への理解度が格段に上がり、今後の開発においても積極的に提案や貢献ができるという成長につながったと感じています。


🌟 会社の魅力(総括):ズバリ「人」と「文化」

藤井: 最後に、アントンさんから見て、「ズバリ、ここが会社の魅力だ」と言えるところを一言でお願いします。

アントンさん: 迷わず「人」、そして「文化」です。

アントンさん: 私は、会社は「人」から始まるものだと考えています。どんなに技術や制度が優れていても、人が良くなければ会社全体も良くなりません。

当社の面接で、そして入社後の日々の業務を通じて、従業員の人柄が良いことが最大の強みだと感じています。この素晴らしい「人」が、情報共有が活発な「文化」を作り上げている。これこそが、当社の最も大きな魅力だと断言できます。


📝 インタビュー後記(藤井より)

今回、初めて新入社員インタビューという形式で記事を執筆しましたが、アントンさんの率直な言葉や、当社の文化を客観的に評価してくださる姿勢が非常に新鮮でした。

アントンさんは入社3ヶ月目にして、既に高い技術スキルプロフェッショナルな姿勢で、プロジェクトにおいて目覚ましいパフォーマンスを発揮してくれています。難しいタスクにも果敢に挑戦し、すぐに結果に繋げられる点は、チームにとって大きな刺激となっています。

今後は、技術面だけでなく、チームメンバーとの交流や文化の中で、今以上にアントンさんの能力が最大限に発揮されることをチーム一同期待しています!

株式会社フォトラクションでは一緒に働く仲間を募集しています

www.wantedly.com