トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、超直前予約アプリ「トレタnow」を開発・提供するスタートアップ企業です。

TrelloAPI+GASでTrelloのカード移動をSlackに通知する仕組みを作った話

この記事は トレタ Advent Calendar 2019 の9日目です。

三度の飯より寿司が好き、QAエンジニアの林です。気付けばトレタに入社して半年経っていました。
今回は、入社1ヶ月目くらいのときに作って以来ちまちま改良している Trelloのカード移動をSlackにメンション付きで通知するスクリプト について書いていきます。

背景

社内の一部のチームではTrelloを使用し、ボードに以下のようなリストを作成してカードでタスクを管理しています。
よくあるカンバン方式ですね。

開発担当者が Doing にあるタスクを実装し終わったらカードを QA に移動し、QA担当者がテストを実施して再修正が必要な場合は BugFix へ移動させて再び開発者ボールに、問題なければ Close へ…という運用です。

リスト名 ステータス
ToDo 未着手
Doing 実装中
BugFix QA中に手戻りが発生し、修正中
Code Review 実装が完了し、レビュー中
QA QA実施中
Close 完了

公式のTrelloアプリをSlackに追加することでSlackのチャンネルにカードの動きを通知することはできます (下記画像のような通知がチャンネルに投稿されます)。

f:id:tkgnori:20191209174227p:plain:w400

しかし、これだけだと通知を見逃しがちです。
開発担当者からQA担当者へ、またはその逆へボールが渡されたことに気付かないとタスクが停滞してしまいます。

Slack上で「実装完了し、staging環境にデプロイしました」「QA実施し、不具合ありましたので再修正お願いします」など直接メンション付きでリマインドすれば良いかもしれませんが、カード移動のたびに毎回するのは手間ですし、それ自体も忘れる可能性があります。
どうせならその辺を自動化しちゃおう、と考えたのが発端です。

やりたいこと

  • Trello上でQAリストにカードが移動されたらQA担当者にSlack上でメンション付き通知
    • QA担当者は主に1名
    • 仮に担当者が増えた場合も、そのうちの誰かがQA実施という形になるので全員に通知したい
  • Trello上でBugFixリストにカードが移動されたら開発担当者にSlack上でメンション付き通知
    • 開発担当者は複数存在し、各タスクにアサインされる
    • そのタスクを担当している人物だけに通知したい

使ったもの

実装方法

まずはGASプロジェクトを新規に作成し、以下の順で code.gs にガリガリ書いていきます。
各プロパティ (APIキーやボードIDなど) はスクリプトプロパティで扱うこととしました。

1. Incoming WebhookをSlackに追加し、Webhook URLを取得

このページ でアプリを検索し、Incoming Webhookを追加します。
追加後、 インテグレーションの設定 で各設定を実施します。

  • チャンネルへの投稿: 通知をPOSTするチャンネルを選択
  • Webhook URL: スクリプトで使用するのでコピーしておく
  • 説明ラベル: 任意。アプリの用途など説明を書いておく
  • 名前をカスタマイズ: 任意。Slack上で表示されるアプリのユーザー名になる
  • アイコン: 任意。同じくアプリのアイコンになる

2. TrelloのAPIキー・トークンを取得

Trelloにログインしている状態でこちらにアクセスするとAPIキーが表示されます。 トークンは、同ページ内の トークン リンクから遷移したページで 許可 を選択すると表示されます。
どちらもスクリプトで使用するのでコピーしておきます。

3. TrelloのボードID・リストIDの取得

以下を参考に、ボードIDとリストIDを取得します。

各パラメータには以下を当てはめます。

  • id
    • ボード一覧取得APIの場合: Trelloのユーザー名 (プロフィール画面から確認可能)
    • リスト一覧取得APIの場合: ボード一覧取得APIで取得した、監視対象とするボードのID
  • key: 2. で取得したAPIキー
  • token: 2. で取得したトークン

4. Callback URLの取得

GASプロジェクトの 公開 > ウェブアプリケーションとして導入... を選択し、ダイアログを開きます。 以下の設定に変更し、公開 を選択するとURLが発行されるのでコピーしておきます。これがCallback URLとなります。

  • アプリケーションにアクセスできるユーザー: 自分のみ
  • プロジェクトバージョン: New

5. 各プロパティの設定

GASプロジェクトの ファイル > プロジェクトのプロパティ > スクリプトのプロパティ で各プロパティを設定します。
今回は以下のプロパティを作成しました。

key 内容
CALLBACK_URL 4. で発行されたCallback URL
TRELLO_API_KEY 2. で取得したAPIキー
TRELLO_TOKEN 2. で取得したトークン
BOARD_ID 3. で取得したボードID
LIST_ID_QA 3. で取得したリストID (QA)
LIST_ID_BUGFIX 3. で取得したリストID (BugFix)
SLACK_WEBHOOK_URL 1. で取得したWebhook URL

f:id:tkgnori:20191209192450p:plain:w400

6. Webhook登録処理の実装

WebhookAPI(POST)を使用してWebhookを登録します。callBackURL はpayloadの中に詰め込みます。
また、5. で設定したプロパティ群は
PropertiesService.getScriptProperties().getProperties() を使用して参照します。

function createWebhook(){
  var scriptProp =  PropertiesService.getScriptProperties().getProperties();
  var trelloKey = scriptProp.TRELLO_API_KEY;
  var trelloToken = scriptProp.TRELLO_TOKEN;
  var callbackUrl = scriptProp.CALLBACK_URL;
  var boardId = scriptProp.BOARD_ID;

  var requestUrl = 'https://api.trello.com/1/tokens/' + trelloToken + '/webhooks/?key=' + trelloKey;
  var options = {
    'method' : 'post',
    'payload' : {
      'description': 'Webhook of Trello',
      'callbackURL': callbackUrl,
      'idModel': boardId
    }
  }
  Logger.log(UrlFetchApp.fetch(requestUrl, options));
}

7. Webhookの登録

実行 > 関数を実行 > createWebhook を選択し、6. で作成した関数を実行します。
ログに表示されたレスポンスが正常であればOKです。

8. 通知処理の実装

doPost()
Trelloのカードに対し何かしらのアクション (移動、作成、コメントなど) が行われた際に呼び出される doPost 関数を実装していきます。
まず、目的のアクション (QAリストまたはBugFixリストへのカード移動) 以外だった場合はすべてreturnします。

QA担当者は固定なのでそのままです。
開発担当者は getDevMember()を呼び出してTrelloIDを取得し、それをkeyにして DEV_MEMBERS からSlackIDを取得し、Slack上でメンションが飛ぶようにします。
ここでDEV_MEMBERSでIDを紐付けしていないTrelloIDが返ってきた場合は、メンションの代わりに専用のメッセージを出力します。

なお、SlackIDは表示名 (@hogehoge など) をそのまま埋め込んでもテキストとして表示されるだけで、メンションになりません。 こちらを参考にしてSlack上から メンバーID をコピーする必要があります。

getDevMember()
カードオブジェクトにはアクションの履歴が保存されています。
1つのカードにつき1人だけオーナーを設定できるのであればここまでする必要はないのですが、Trelloはカードのメンバーを複数設定できる仕様のため、履歴から開発担当者 (の可能性が高いユーザー) を特定しています。

具体的には、フィルタを利用して履歴の中から「カードを移動した」というアクションのみを取得し、その中で一番最後にQAリストにカードを移動したユーザーを取得 というロジックを組んでいます。
(カードの移動を行うのは、今現在そのカードを担当している人物であることはほぼ確実であるため)

/*
 メイン処理
 Trello上で何かしらの変更が発生した時に毎回実行され、特定の条件下でSlackへ通知する
*/
function doPost(e){
  var contents = JSON.parse(e.postData.contents);
  var actionType = contents.action.type;
  var destinationList = contents.action.data.listAfter;
  
  // カードが目的のリストに移動された場合のみ通知し、それ以外の場合は終了
  if(actionType !== 'updateCard' || !destinationList) { return; }
  
  var scriptProp =  PropertiesService.getScriptProperties().getProperties();
  var qaListId = scriptProp.LIST_ID_QA;
  var bugfixListId = scriptProp.LIST_ID_BUGFIX;
  var movedUser = contents.action.memberCreator.fullName;
  var mentions = '';
  
  // key=TrelloID, value=SlackID
  // dev memberがjoinした際はここにIDを追加する
  var DEV_MEMBERS = {
    '5xxxxxxxxxxxxxxxxxxxxxxxxx': '<@UAAAAAAAA>',   // @hogehoge
    '5yyyyyyyyyyyyyyyyyyyyyyyy': '<@UBBBBBBBB>',    // @fugafuga
    '5zzzzzzzzzzzzzzzzzzzzzzzzz': '<@UCCCCCCCC>'    // @piyopiyo
  };
  // QA担当者のSlackID
  var QA_MEMBERS = '<@UDDDDDDDD>';   // @hayashi
  
  switch (destinationList.id) {
    case qaListId:
      mentions = QA_MEMBERS;
      break;
    case bugfixListId:
      var devMember = getDevMember(contents.action.data.card.id);
      // TrelloとSlackのIDがまだ紐付いていないユーザーが指定された場合に表示するメッセージ
      var msgUserNotFound = '@' + devMember.fullName + ' (TrelloIDとSlackIDを紐付けてください: TrelloID=' + devMember.id + ')\n';
      mentions = !DEV_MEMBERS[devMember.id] ? msgUserNotFound : DEV_MEMBERS[devMember.id];
      break;
    default:
      return;
  }
  
  var postUrl = scriptProp.SLACK_WEBHOOK_URL;
  var cardName = contents.action.data.card.name;
  var listName = destinationList.name;
  var shortLink = 'https://trello.com/c/' + contents.action.data.card.shortLink;
  var message = mentions + ' カード *<' + shortLink + '|' + cardName +'>* が ' + movedUser + ' さんにより *' + listName + '* リストに移動されました。ご確認をお願いします。';
  
  var jsonData = { "text" : message };
  var payload = JSON.stringify(jsonData);
  var options =
  {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };

  UrlFetchApp.fetch(postUrl, options);
}

/*
 QAリストからBugFixリストにカードが移動した際、
 Slack上でメンションを飛ばすために開発担当者を特定する
*/
function getDevMember(cardId) {
  var scriptProp = PropertiesService.getScriptProperties().getProperties();
  var trelloKey = scriptProp.TRELLO_API_KEY;
  var trelloToken = scriptProp.TRELLO_TOKEN;
  var qaListId = scriptProp.LIST_ID_QA;
  var requestUrl = "https://api.trello.com/1/cards/" + cardId + "/actions?key=" + trelloKey + "&token=" + trelloToken + "&filter=updateCard:idList";
  var options = {
    'method' : 'get'
  }
  var response = UrlFetchApp.fetch(requestUrl, options);
  var cardActions = JSON.parse(response.getContentText());
  
  // 必ずカードにメンバーとして追加されているとは限らないので、
  // 「最後にQAリストにカードを移動したユーザー」を開発担当者とする
  for (var i = 0; i < cardActions.length; i++) {
    if (cardActions[i].data.listAfter.id === qaListId) {
      Logger.log(cardActions[i].memberCreator);
        return cardActions[i].memberCreator;
    }
  }
  return;
}

9. 公開

4. と同様、再度 公開 > ウェブアプリケーションとして導入... を選択し、ダイアログを開きます。
設定を以下のように変更し、更新 を選択します。

  • アプリケーションにアクセスできるユーザー: 全員(匿名ユーザーを含む)
  • プロジェクトバージョン: New

ここで「アプリケーションにアクセスできるユーザー」を変更しないとうまく動きません。ちょっとハマりました…。

結果

▼ Code Review → QA にカードを移動させた場合の通知
f:id:tkgnori:20191209205459p:plain

▼ QA → BugFix にカードを移動させた場合の通知 (開発担当者のID紐付け済み)
f:id:tkgnori:20191209200240p:plain

▼ QA → BugFix にカードを移動させた場合の通知 (開発担当者のID未紐付け)
 ここで表示されたユーザー名とTrelloIDを参考にし、コード上でSlackIDと紐付けます
f:id:tkgnori:20191209201617p:plain

これでもうカードの移動を見逃さない!

おわりに

開発→QAに転向してから仕事でコードをあまり書かなくなったので、良いリハビリになりました。
GASがES6に対応する時代はまだでしょうか…。
かなり拙いコードとは思いますが、似たような仕組みを作りたい方にとって少しでも参考になれば幸いです。

また、実装・執筆に当たり以下の記事を参考にさせていただきました。ありがとうございます!

gRPCとGoでサーバサイド開発を始めるために用意したこと

トレタ Advent Calendar 2019 の7日目の記事です。

こんにちは!前回の対策として、からをやぶるパルシェンを採用したところ徐々に勝てるケースが増えてきたサーバサイドエンジニアの川又です。

ミミッキュ・ドラパルトに対しては優位に立てることが多くなった(気がする)のですが、最近増えてきた(気がする)バンギラスが完全に止まるようになってしまったので、そちらの対策を考えないとツラい感じになってきました。


この記事では、自分が開発に携わっているgRPCとGoを使ったサーバサイドのコンポーネントの開発をするにあたって用意したことについて書いてみます。

(注: まだ開発中のフェーズなので、運用に乗せるところまでは考えられていません><)

トレタのサーバサイドはRuby on Railsを採用することが多かったのですが、Toreta nowのサーバサイドなど一部のコンポーネントではGoが採用されていました。(トレタのプロダクト開発 / toreta tech talk#2 より)

いま自分が開発をしているコンポーネントもGoを採用しており、ざっくり通信の流れを書くと

                  --1. gRPC-->  (2. gRPC to JSON)   --3. JSON-->
[gRPCクライアント]                [★コンポーネント★]                [別のAPIサーバ]
                  <--6. gRPC--  (5. JSON to gRPC)   <--4. JSON--

というように、クライアントからはgRPCでリクエストを受け、別のAPIサーバに対してはREST API(JSON)で通信し、クライアントに対してgRPCでレスポンスを返す、というようなものになっています。

自分はgRPCもGoも初めてだったのでまだ手探り状態ではありますが、次に紹介するモノたちを活用して開発を行っています💪

1 GoLand

JetBrains社が提供しているIDEです。

Railsのコンポーネントを開発していた頃からRubyMineを使っていた(以前C++で開発してたときはCLionも使っていた)ので慣れが大きいかもですが、とても使いやすいと思います!

単にコードジャンプだけでなく、今開いているソースファイルに対応するテストファイルを「Cmd + Shift + T」で開けたり、デバッグ実行するときの変数一覧がコマンドラインよりも見やすかったりと、イロイロな点で捗るポイントがあります。

プラグインは、(ターミナルではneovimを使っているので)Vimキーバインドで操作できるようになる IdeaVim だったり、「Cmd + ;」を入力してから文字を入力すると、画面内に表示されている位置に移動できる AceJump とかを使ってます 🙆‍♀️

2 CircleCI

他のコンポーネントもほとんどCircleCIを使っていたので、今回のコンポーネントも同じようにCircleCIを使うことにしました。

CIでは、

  • go mod tidy, go vet ./..., goimportsの実行漏れのチェック
  • protoファイルへのclang-format, protocの実行漏れのチェック
  • テスト実行

などをやっています。

開発してるコンポーネントのディレクトリ構成を抜粋すると、このような感じになっていまして↓

.
├── .circleci
│   └── config.yml # CircleCIの設定ファイル
├── Dockerfile.sandbox-envoy # envoy用のDockerfile
├── Dockerfile.sandbox-server # 今回のコンポーネント用のDockerfile
├── Makefile
├── bin
│   └── .gitkeep
├── cmd
│   └── grpc-health-probe
│       └── main.go # grpc-health-probeのmain
│   └── server
│       └── main.go # 今回のコンポーネントのmain
├── config
│   ├── envoy.yml # envoyの設定ファイル
│   └── sandbox.yml # 今回のコンポーネントの設定ファイル
├── internal
│   └── (内部ロジックなど)
├── proto
│   └── (protoファイルの置き場)
├── proto-generated
│   └── (protocで生成されたファイルの置き場)
├── test
    └── (テスト)

.circleci/config.yml は次のような内容になっています。

version: 2.1


executors:
  golang:
    docker:
      - image: golang:1.13-buster


commands:
  check_git_status_empty:
    steps:
      - run:
          command: |
            changed_files=$(git status -s)
            if [ -n "$changed_files" ]; then
              echo "git-diff found in these files! Maybe you forgot lint."
              echo "$changed_files"
              exit 1
            fi
  install_clang_format_and_protoc:
    steps:
      - run: apt-get update
      - run: apt-get install -y clang-format protobuf-compiler
      - run: GO111MODULE=off go get -u github.com/golang/protobuf/protoc-gen-go
  install_goimports:
    steps:
      - run: GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports
  run_clang_format:
    steps:
      - run: find ./proto -regex ".*\.proto" -type f | xargs clang-format -style=google -i
  run_go_mod_tidy:
    steps:
      - run: go mod tidy
  run_goimports:
    steps:
      - run: goimports -w cmd/**/*.go internal/**/*.go test/**/*.go
  run_protoc:
    steps:
      - run: find ./proto -regex ".*\.proto" -type f | xargs protoc -I./proto --go_out=plugins=grpc:./proto-generated/go
  run_go_vet:
    steps:
      - run: go vet ./...


jobs:
  # go mod tidy を実行して、
  # gitの差分が出た場合はエラーにするjob
  check_go_mod_tidy:
    executor:
      name: golang
    steps:
      - checkout
      - run_go_mod_tidy
      - check_git_status_empty

  # go vet ./... を実行して、
  # gitの差分が出た場合はエラーにするjob
  check_go_vet:
    executor:
      name: golang
    steps:
      - checkout
      - run_go_vet
      - check_git_status_empty

  # goimports を実行して、
  # gitの差分が出た場合はエラーにするjob
  check_goimports:
    executor:
      name: golang
    steps:
      - checkout
      - install_goimports
      - run_goimports
      - check_git_status_empty

  # protoファイルに対してclang-formatとprotocを実行して、
  # gitの差分が出た場合はエラーにするjob
  check_proto:
    executor:
      name: golang
    steps:
      - checkout
      - install_clang_format_and_protoc
      - run_clang_format
      - run_protoc
      - check_git_status_empty

  # テストを実行して、
  # gitの差分が出た場合はエラーにするjob
  run_test:
    executor:
      name: golang
    steps:
      - checkout
      - # テスト実行の command
      - check_git_status_empty


workflows:
  version: 2
  ci:
    jobs:
      - check_go_mod_tidy
      - check_go_vet
      - check_goimports
      - check_proto
      - run_test:
          requires:
            - check_go_mod_tidy
            - check_go_vet
            - check_goimports
            - check_proto

これを実行すると、↓のような順序・依存関係でCIが回ってくれます。

f:id:rkmathii:20191207092148p:plain

開発を進めているとどうしてもウッカリで抜け漏れが出てしまうことがあるので、早めにCIを用意しておくのは正解だったなと改めて感じました🧹

3 ECS

クライアントとE2Eで疎通確認ができる環境が欲しかったので、 SREチーム と相談して、とりあえずの環境としてAmazon ECS(using Fargate)に乗せてみることにしました。

このような構成です↓

f:id:rkmathii:20191207061052p:plain

とりあえず開発するための環境なので、前段にはALBを置いて、ECRにenvoyとコンポーネントのDockerイメージをアップロードし、ECSでそのイメージを使って起動させる、という簡単なものになっています。

自分はECSを使った経験はなかったのですが、ドキュメントを読んだりググったりしながら試してみたところ、割と簡単に立ち上げることができたし、サービスの更新作業も簡単であるため便利だと思いました✨

Dockerfile, Makefileはこんな感じで用意しています↓

# Dockerfile.sandbox-envoy

FROM envoyproxy/envoy:v1.12.1

WORKDIR /

COPY ./config/envoy.yml /etc/envoy/envoy.yaml

# expose envoy & envoy-admin
EXPOSE 9080 9081

CMD ["/usr/local/bin/envoy", "--config-path", "/etc/envoy/envoy.yaml"]
# Dockerfile.sandbox-server

### バイナリをビルドするイメージ
FROM golang:1.13-buster AS build_image

WORKDIR /go/src/github.com/hogehoge/fugafuga

COPY . /go/src/github.com/hogehoge/fugafuga

RUN make build-for-docker


### バイナリを実行するイメージ
FROM alpine:3.10

RUN apk --update add --no-cache ca-certificates

WORKDIR /app

COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/bin/server /app
COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/bin/grpc-health-probe /app
COPY --from=build_image /go/src/github.com/hogehoge/fugafuga/config /app/config

EXPOSE 8800

CMD ["./server", "-c", "./config/sandbox.yml"]
# Makefile

.PHONY: build-on-docker
build-for-docker:
    GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o ./bin/server ./cmd/server/main.go
    GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o ./bin/grpc-health-probe ./cmd/grpc-health-probe/main.go

(あといろいろ)

特に、コンポーネント用の Dockerfile.sandbox-server ではmulti stage buildを取り入れているため、ECRにアップロードするイメージサイズが60MB程度で済んでいます🏋


かなりざっくりな紹介になってしまいましたが、以上になります。

引き続き、gRPCとGoの開発もポケモンバトルも頑張ります💪

エンジニアリング・マネージャーとして入社6ヶ月後の振り返り

この記事はトレタ Advent Calendar 2019 の4日目です。

皆様おはこんばんちわ!Toreta now開発グループマネージャーの @ikegori です。Switchで久々にグラディウスをやったらモアイステージをどうしてもクリアできずふて寝気味です。

トレタにエンジニアリングマネージャー(以下EM)としてジョインし半年ちょっと経ちました。ここでは、その半年でやってきたこと、これからやっていく事を書いていこうと思います。EMは職務範囲が広いですし、EMとして何やってるの?何を考えてるの?などの一例としてちょっとでも参考になれば幸いです。

また、少々「想い」強めの内容ですが、そこは年末という事で(どういう事だ)ご容赦頂ければと思います。

やってきたことの前に・・・

入社前から、やる・やらないと決めていたことがあります。

いきなり1on1をしない

マネジメント専任の人が来ていきなり1on1入れられたら受け手はどうとらえるだろう?変に権威勾配を作ってしまわないだろうか?という点を懸念しました。今思えば取り越し苦労であり、早めにやっておけばよかったと後悔しています笑。しかし、入社早々、サシで話す場で屈託ない雰囲気を作れるほど人たらしでもありませんし、まずは実務を通してコミュニケーション上慣れてからと考えていました。

余計な管理的な仕事を作らない

新任のリーダー、マネージャーが来て早々に、突然工数管理や不具合管理票の運用が始まったりして「あれっ?」と思った経験はないでしょうか?個人的には、効果が出るのに時間がかかる組織”経営”ではなく、組織”管理”面から手を付けるのは悪手な気がしています。仕事時間にインパクトが出る管理的な仕事をいきなり導入するのは、必要性に関わらず、その職場に対し理解と尊敬を欠くやり方に映るかもしれない、という考えからです。

管理的な仕事の導入には十分な説明が必要です。しかし、十分な説明には十分な理解が必要であり、入っていきなりやることではないと考えていました。

よく観察してから意思決定する

後述しますが、直感や早い思考で意思決定するのは控え、とにかくしっかり観察して意思決定しようと考えていました。

直前まで別の会社にいたわけです、別の文化圏にいた人間と言っても過言ではありません。そういった人の考えが目新しく、リフレーミングなどポジティブな効果を生むことも多々あります。しかしそれも入社早々では博打に近いし狙ってやることでもありません。まずその文化圏における動き方や考え方に慣れようと考えていました。

やってきたこと

OODA的なアプローチでの状況把握

OODAは組織における意思決定プロセスで、Observe(観察)→Orient(状況判断)→Decide(意思決定)→Act(行動)という段階に分かれます。ただ、各段階を1ステップづつ踏んでいくのではなく、相互信頼を軸に暗黙的な統制・誘導が可能な環境を醸成し、高速に回していくのが理想とされています。

これを参考にして個人に応用し、早めのキャッチアップに努めました。 具体的には、

  • 善し悪しに関わらず何か気になった出来事をメモする(観察)
  • それが起こった理由や気になった理由を考える(状況判断)
  • その時点での仮の結論を出して再度観察へ戻る、または、結論に基づき行動してみる(意思決定・行動)

というものです。これら全てをスプレッドシートにメモっていました。本来のOODAと違い高速ではなく遅いループです。また、いちいちメモっている点も、スピード重視で暗黙的であることが理想的とされるOODAとは異なるかもしれません。

観察ネタは色々です。ミーティングでの発言、1対1のコミュニケーション、プロジェクトで起こっていること等など、いったん観察事項としてメモし、後でWhyをじっくり考え、一旦の結論を出す、という事を繰り返していました。これにはセンシティブな内容も含みます。入社して2ヶ月ぐらい続けていました。

これによって、入ったばかりではぼんやりしている空気感のようなものが可視化され、整理・把握がしやすくなりました。また、Whyを深めに考えることによりバイアスが排除される効能も多少あったと思います。特に、自分の中に感情的な反応が起こった出来事については、感情に惑わされず本質を探る良い訓練になりました。

Toreta now のプロジェクト管理

私の仕事のうちプロダクトへ最も直接的に関わる仕事です。Toreta now はまだ始まったばかりのプロダクトです。計画している施策だけでなく、ユーザ様や店舗様からのフィードバックを織り交ぜつつ、柔軟に優先度を判断して進めています。基本的にはマイルストーンを置きそこめがけて開発を進めるスタイルですが、状況によりスケジュールか機能のいずれかをトレードオフさせます。

開発における管理ツールとしてはJira、ドキュメンテーションはesaとGoogle ドキュメント、コミュニケーションはSlackです。Jiraではかんばんのレーンを結構細かく分けています。施策はエピックとして立て、起票→実施検討→開発用に選択→要件定義→進行中(開発中)→QA→リリース済み→プロジェクトレビュー→完了、と遷移します。大小様々なエピックが混在しますが、どのエピックがどの段階にあるかは比較的わかりやすい状態です。

Slackでのコミュニケーションがかなり活発なのと、出来るだけコミュニケーションコストを積まない意図からデイリーミーティングはあえて行わず、固定的なミーティングは週2回の定例のみです(入社前の状況を維持しています)。ただ、開発量も増えてきて少々限界を感じているところです。

ウォーターフォールかアジャイルかと言われたらその中間ぐらいの感じです。まず重要なのは情報・状況を透明化しメンバー間、チーム間での情報の非対称性を下げること。それにより、見通しが立てやすく、直前でのサプライズが少なく、計画を遵守しつつも計画変更に柔軟に対応出来る状態を目指しています。

兼務のメンバーもおり、ヴェロシティの計測は私にとって難易度が高く着手できていません。とほほ・・・。しかし、専任化を進めてヴェロシティを計測しやすくし、より見通しの立てやすい状態を目指しています。採用含め、その環境を作るのもEMの仕事です。

年末に向けての施策はスケジュール優先としたものもあり、伝家の宝刀"運用でカバー"も少々発生してきました。その対応は最優先度で取り組む事にしています。ガンガン新機能をリリースしていきたい欲求はありますが、技術的、運用的に負債を積むのが分かっていたらその後必ずカバーリングも行うように心がけています。

目標設定

トレタでは、事業目標をトップレベルとし、部署、チーム、メンバーの目標へブレークダウンさせます。これを半年単位で行います。

Toreta now開発グループにおいては、トレタの3大Valueである Issue First、Respect All、Dive! に沿った方針も明文化しており、特に大事にしたい事としては Respect All に関わる HRT(謙虚、尊敬、信頼)です。HRTや心理的安全性などはみんな耳にタコな話だと思いますが、大事なので事あるごとに表明しています。

トレタnowはいわばスタートアップです。組織図では所属が分かれていますが、実質的に全ての職種がひとまとまりになった一つのチームです。複数の職種の人達が率直に意見を交わせる状態はとても強い武器であり、そのベースとして心理的安全性と、それを醸成する上でコミュニケーション面での基礎と言えるHRTは必要不可欠です。

評価制度

これについては、残念ながら効果的なアウトプットはまだありなく、書くことがありません。とほほ。しかし議論と検討を進めています。どこかの会社でやっている事をはめ込むようなやり方ではなく、トレタに合う形で整備を進めています。

これからやっていくこと

採用プロセスのブラッシュアップ

これは既に着手しています。トレタで働く上でのコンピテンシーを抽出して求める人材像を明確にし、それを測るための質問や判断基準の作成を進めようとしています。目的は、

  • 確度を上げつつ幅も広げること
  • 出来るだけバイアスを排除すること
  • 出来るだけ属人性を排除し、再現性をもたせること

であり、結果としては構造化面接の手法になると思います。

直感や第一印象はどうしてもバイアスが混ざり込みます。その後熟考したとしても、判断材料が明確でないと直感や第一印象を後付で証明するストーリー作りになってしまう恐れがあります。このヒューリスティクスに抗えるかどうかは個人差があるでしょうし、採用においてかなり属人性が高い(=再現性が低い)部分です。

そのほかいろいろなこと

Toreta now の開発プロセス改善、評価制度の整備、1on1などを通じて良き壁打ち役になること等など、やるべき事は多々ありますが、ものすごくざっくり言ってしまうと、マネジメント(経営)は未来のことをやる役割だと考えています。

現在、何か問題が起こっているとすれば、それは半年か1年前の自分がやった(または、やらなかった)事の結果であり、大抵はやらなかった事の結果です。現状維持を選択し続けると、その時の問題解決ばかりに追われる「その日暮らしな組織」になってしまいます。その事を肝に銘じ、空振りを恐れずにEM業に邁進したいと思います。

そしてもちろん、一緒に事業をグロースさせていく仲間を募集しています! エンジニア採用 | 株式会社トレタ

© Toreta, Inc.

Powered by Hatena Blog