チームに浸透させるのが近年では難しくなっている

昨日「動いたけどチームメンバーを説得するのが面倒で、秘蔵のブランチになってしまう」って言ったけど、この気持ちはどこから出てくるのか。

分かりやすい Cons があると、反発が予想できて、その反発を解決するところまで労力を割くほどの気持ちが無いので困る。「直ちに問題になるわけじゃないが、どちらかというとやった方がいい、でもリスクもある」という選択肢を選べずにズルズルと現状維持に向かう圧力は、ある。チームの同質性が高いうちはほとんど困らないんだが、人数が増えたり、別の職種が増えたりするごとに「面倒」さはどうしても増していく。

我々の信念として以下を持ってはいるが、現状維持に向かう圧力がある中で変化を加えるのはそこそこ労力が要り、閾値を超えると変化が発生しなくなってしまう。

業務・開発フローは「変えることは無条件に正しい」くらいに思って良いと思っています。 素早く変えてもし仮にダメだったら素早く戻せばよい。

Mackerel開発チームカイゼンの旅(振り返り・モブプロ・スキルマップなど) - Hatena Developer Blog

現代の Web アプリケーションを作るのに、「PdM、エンジニア、デザイナーの 3 人で作る」というのは難しくなっていて、そもそも 6 人とか 10 人とか 30 人とかのチームでやる場合が増えています。そうすると、変化を始めるのは勢いだけでも可能かもしれないけど、やりきるのは以前より難しくなっていませんか。

この問題を倒すのに、進め方を知っていると楽になるよね、というのが今日の主題です。

進め方 is 問題解決や意志決定、組織変革のプロセス。

例えば

  • トヨタ生産方式の 5S
    • 躾 が含まれている
  • 問題解決の 8 ステップ
    • 目標設定や計画、定着させることが含まれている
  • ADR (Architecture Decision Records) や Design Doc
    • 意志決定をテンプレ化し、記録を取っていく
  • インセプションデッキ
    • 前提を丁寧に揃える役に立つ
  • DACI フレームワーク
    • 意志決定に関わる人の役割を明示することで推進力を出す
  • コッターの 8 段階プロセス
    • 変革に当たっての 8 段階プロセス
  • ADKAR モデル
    • 同じく変革のための 5 段階プロセス

辺りを僕は意識しながらやっているかしら。

勢いだけでは導入することができない場合があり、そういうときはちゃんと変革をマネジメントしなければいけない、という意識で物事を進めていくと、導入できない・最後までやりきれない問題を倒しやすくなる。前提を丁寧に共有しようねとか、共有用のいいテンプレートがあるよとか、やりきるところまで含めて計画しようねとか。

横から細かい Cons を刺され続けて疲弊する場合に対しても、DACI フレームワークを使うと、「Informed ですよね?」と封じ込めることができて (というか、すべてに対応する必要は無い、Approver が判断できれば十分である、という意識に全員を持って行くことができて)、進めやすくなることもあるかもしれません。

定着までをゴールに見据えていると、外部登壇を経由して社内により効率的に広めるとか、意識を向けるための定例の場の設定とか、横展開のためにあらかじめ楔を打っておくとか、そういう動きをやっていくこともあります。

開発合宿のようなイベントを企画して、勢い MAX で乗り越えるという手段を執ることもありますね。

この話はどこに向かっているのか

  • 変化が大変になっているという事実があることを認識する
  • 進め方が世の中にあることを認識する
    • 試しに使ってみて、馴染むものを普段使いする

というのをやっていけると、変化を起こしたり、浸透させたりするのが難しい問題に対応しやすくなる。

んだけど、その上で「丁寧に進めなければいけないことは分かっているがめんどくさい」は乗り越えられないので、こういうエンジニアのエンパワーメントを一緒にやってくれる人や、丁寧に進めなくても良いチームを一緒に作っていく人を強く募集しています。

まずはカジュアル面談からいかがでしょうか。

meety.net

branch を寝かせるときは TODO.txt を置いている

タイトルがすべて。

思いついたら手を動かしてしまう性質なので、秘蔵のブランチを大量に持っている。

秘蔵のブランチというのは、動いたけどチームメンバーを説得するのが面倒とか、テスト書くのが面倒とか、だいたい動いているけどやりきるのが面倒とかで main にマージしていないヤツ。 例えばフレームワークのメジャーバージョン上げるブランチとか、依存ライブラリをより一般的/現代的なものに交換するブランチや、より良い設計を思いついたのでガッと書き換えてしまうブランチが多いかな。

だいたい 1 年所属していると 80 ブランチぐらい溜まるので、週 1 つ以上は何か作りかけてる計算になる。

もちろん自分でいいアイディアだと思っているから実装しているので、何か起きたときに「こんなこともあろうかと」と出せるのが強み。 脆弱性が見つかったときとか、データ量が増えたこと等で設計の悪さが発火したときとかに、スッと懐から出して、爆速で修正リリースできることがある。 何かしら問題が起きた後だと、多少の体裁の整ってなさは些細な問題になるので、エイってやりやすい。

秘蔵のブランチというとカッコイイ(?)んだけど、要は仕掛かり品です。在庫。完成させていないのでリリースできない、JIT 生産方式で嫌われるヤツ。稀に繰り出せると最高に気持ちがいいので、ついやってしまうんだよね。。

で、一晩グワーッて書いた後に、完成していないと数日〜数ヶ月寝かせることがあって、そういうときは続きは何をやろうとしていたのか完全に忘れているので、最後に TODO.txt を commit しておくようにしている。

例えば

  • 本家が対応していないので issue #xxx が解消するまで待ち
  • xxx の場合にエラーになるので対策する必要があるんだが、そういう仕組みがまだ存在していない
  • model の a-h 始まりまで終わったので、残りの 200 ファイルやる
  • 先に Route53 に移管する必要があった……。
  • xxx の場合の動作確認まだしていない
  • 全員のローカル環境を更新させるのが面倒

とかとか、完成させていない理由を書いてある。*1

TODO を書いておくことによって、次にブランチ一覧を眺めたときに「これ何だっけ」というのが減って、1 年後でも続きの作業ができるというのがオススメポイントです。異動/退職するときも、あと何をしたらいいのかが書いてあるので、続きを人にやってもらいやすい。

ブランチに限らず、あまりヨシヨシできていないリポジトリは TODO.txt を書いて commit しておくと、次に作業をするときに当時の記憶が蘇りやすい。僕はよく TODO.txt を読んで脳のワーキングメモリに乗せた後に git reset HEAD~1 して作業開始しています。

*1:1 行で済むことは滅多に無いです!それぐらいの障害物なら完成まで持って行ける

体制を考えるときに意識していること

1on1 で伝えたので外にも書いておく。

プロダクトやチーム、メンバーのフェーズ

まず現状分析。

  • 自プロダクトは PPM で言う花形、金のなる木、問題児、負け犬のいずれに当たるのか
    • 勢い MAX でめっちゃ盛り上げるのか、地味に役割を達成するのか。自チーム全集中なのか他チームのフォローに回るのかみたいな方針が変わる
  • 自チームは エラスティックリーダーシップ で言うサバイバルモード、学習モード、自己組織化モードのいずれに当たるのか
    • チームを改善しなければいけないのか、プロダクトだけを見ていて良いのか。チームで改善できるのか、リーダーや外部の強い意志が必要なのか
  • 各メンバーは、期待される役割において SL理論 で言うとどのフェーズなのか
    • 指示的行動が必要だとマイクロマネジメントすることになり、マネージャ/メンター的な人/行動を増やす必要がある

役割を網羅しているか

こういう軸で考えていることが多い。

  • 体制図上の役割
    • サービス開発に必要な役割を (チーム規模によっては兼務アリで) 網羅しているか
    • PdM、PjM、TL、etc
  • 問題発見、課題化、遂行
    • 問題を見つけられる人、タスクに落とし込める人、タスクを遂行する人はそれぞれいるか
  • 専門家、ジェネラリスト、ムードメーカー、気が利く人
    • 専門家:深く掘らないと解決できない問題を、解決まで持って行ける人
    • ジェネラリスト:連携しないと解決できない問題を、人と人を繋げたり、両方の仕事をやったりして、解決まで持って行ける人
    • ムードメーカー:ハードな状況でもチームをポジティブなムードに持って行ける人。提案に対して第一声が「面白そう」な人
    • 気が利く人:なんか漏れてるものを拾ってくれたり、ムラがあるので解消しませんか or 私がやりますって言い出したりする人
  • EM の四象

コミュニケーションパス

動きが変わるってことはつまり話す相手や内容が変わっているってことなので、その変化を設計する。

  • 体制図上の役割に則った定例設計
    • 会議体だったり、ただのコミュニケーションの場だったり
  • 体制図上の役割以外のコミュニケーション設計
    • 体制図上の役割を持たない人でも何らかの役割は持っているので、その役割を最大限に発揮できるようなコミュニケーションパス(「この人同士が話しておくと全体で上手くいきそう」という目論見)を考えておく
    • 明確に期待を伝えることもあるし、例えばペアで作業に当たるようにタスクを差配して、雑談から何らかの同期が発生するのを期待することもある
  • 各メンバーに対するバックアップ設計
    • SPoF になっていると退職・異動・病気等に弱いので、バックアップとして誰が入れるかを考えたり、バックアップを作っていけるようなコミュニケーション設計をしたり
    • 既にスキルを持っている人がフォローに入る場合と、サブを育成していく場合とがある

期末は来期体制を考えるためにひたすら丸と四角と線を書いて、「この人に TL を担って貰いたいが、少しチャレンジングなのでフォロー体制や壁打ち相手を用意しておかないと」とか「インフラ強めの人が居ないので他チームからフォローできる体制を作っておかないと、ということはこのチームに目標と余裕が必要」とか「変化を言い出す人が少ないので、世間とのギャップを認識しやすいプラクティスを導入して育成しよう」とか「来期にフロントエンド刷新を狙うために、今からフロントエンドチームを作って、早いうちにタックマンモデルで言う形成期、混乱期を乗り越えておきたい」とかをパズルしています。

観測して、状況を整理して、ハコとコミュニケーションパスを設計する、を繰り返すことで前に進む力を作っているんだと思う。キーマンがいっぱい居たらパズルが楽なので、いいチームにしたい。

いつ考えるのか

期初のチームのキックオフで提示できればチームの自走力が高まるので、そこに合わせて。

あとは 1on1 や四半期面談、半期面談のタイミングで現状認識を更新しておけば、なんか風呂場とかで思いつくはずという設定で暮らしています。細かいコミュニケーションの問題は、チームのイテレーションのふりかえりで拾えるので、設計するのは半期ごとにやっています。

寿限無とピカソ

# https://ja.wikipedia.org/wiki/%E5%AF%BF%E9%99%90%E7%84%A1
jugem = <<EOS.gsub(/(?<=[^])(|)/, "\\1\n").gsub(/|/, "\n").gsub(/\n+/m, "\n").lines(chomp: true)
寿限無、寿限無、
五劫の擦り切れ、
海砂利水魚の、
水行末・雲来末・風来末、
喰う寝る処に住む処、
藪ら柑子の藪柑子、
パイポ・パイポ・パイポのシューリンガン、
シューリンガンのグーリンダイ、
グーリンダイのポンポコピーのポンポコナの、
長久命の長助
EOS

# https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%96%E3%83%AD%E3%83%BB%E3%83%94%E3%82%AB%E3%82%BD
picasso = <<EOS.chomp.split("")
パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・マリア・デ・ロス・レメディオス・クリスピーン・クリスピアーノ・デ・ラ・サンティシマ・トリニダード・ルイス・イ・ピカソ
EOS

puts jugem.zip(picasso).flatten.compact.join("")

寿限無・パブロ・寿限無・ディエゴ・五劫の擦り切れ・ホセ・海砂利水魚の・フランシスコ・水行末・デ・雲来末・パウラ・風来末・ホアン・喰う寝る処に・ネポムセーノ・住む処・マリア・藪ら柑子の・デ・藪柑子・ロス・パイポ・レメディオスパイポ・クリスピーン・パイポの・クリスピアーノシューリンガン・デ・シューリンガンの・ラ・グーリンダイ・サンティシマ・グーリンダイの・トリニダードポンポコピーの・ルイス・ポンポコナの・イ・長久命の・ピカソ・長助

AWS Lambda でコンテナに入れた Sinatra を動かす

何番煎じか分からないけど、最近やったので。

前提知識

つまりコンテナ化した Sinatra アプリを Lambda 上にデプロイして HTTP リクエストを受け付けることができる。

動かす準備はもう全部整っていて、お手軽そうですね。

Ruby アプリを Lambda で動かすコンテナイメージを作る

Sinatra 以前に、そもそも Ruby はどうやって Lambda Container Image 上で動くのか。公式にチュートリアルがあるのでこの通りで良い。

Deploy Ruby Lambda functions with container images - AWS Lambda

https://gallery.ecr.aws/lambda/ruby の Usage をなぞる。

  1. https://gallery.ecr.aws/lambda/ruby から base image を選んで、Dockerfile を作って
  2. docker build して
  3. docker run で Image を立ち上げると HTTP で待ち受けるので
  4. curl で発火させる
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'

謎 URL だけど「こういうもの」として覚えておけば良い。

特筆すべき点

ENV["GEM_PATH"]
#=> "/var/task/vendor/bundle/ruby/2.7.0:/opt/ruby/gems/2.7.0"

なので、ここに gem を入れておくと bundle exec しなくても gem を使える。いやまぁ bundler 使えば良いと思いますが。。

Rack アプリを Lambda で動かす

前述した公式の https://github.com/aws-samples/serverless-sinatra-sample の他に、 https://github.com/logandk/serverless-rack というものもある。

仕組み

どちらも肝は Rack::Builder.parse_file です。 config.ru を eval することで実行したい Rack App を取り出す処理。

App を取得できたので、env を組み立てて

app.call(env) して

Rack の status, headers, body の組から、Lambda の response になるように JSON を組み立て直す

Lambda の response というのはコレ。https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format

{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}

実装

これを自分の Rack アプリにどう組み込むかというと

app/config.ru と lambda.rb を置いて Dockerfile の CMD では lambda.handler を指定すると良い。

PROJECT_ROOT
├── app/
│  ├── config.ru
│  ├── Gemfile
│  └── Gemfile.lock
├── Dockerfile
└── lambda.rb

lambda.rb は https://github.com/aws-samples/serverless-sinatra-sample/ からコピーします。*1

他のファイルはこんな感じ。

# app/Gemfile
source "https://rubygems.org"
gem "rack"
# app/config.ru
run ->(env) { ["200", { "Content-Type" => "text/plain" }, ["OK"]] }
FROM public.ecr.aws/lambda/ruby:2.7

COPY lambda.rb ${LAMBDA_TASK_ROOT}

# rack を ${LAMBDA_TASK_ROOT}/vendor/bundle に入れたい
WORKDIR ${LAMBDA_TASK_ROOT}/app
COPY app/Gemfile app/Gemfile.lock .
RUN bundle config set path ${LAMBDA_TASK_ROOT}/vendor/bundle
RUN bundle install

COPY app/config.ru .

WORKDIR /var/task
CMD [ "lambda.handler" ]

で、実行するときは

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"httpMethod":"GET","requestContext":{}}'

すると、Lambda の JSON response が取得できる。

{"statusCode":"200","headers":{"Content-Type":"text/plain"},"body":"OK"}

渡す JSON のパラメータは https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format を参照。

{
    "resource": "Resource path",
    "path": "Path parameter",
    "httpMethod": "Incoming request's method name"
    "headers": {String containing incoming request headers}
    "multiValueHeaders": {List of strings containing incoming request headers}
    "queryStringParameters": {query string parameters }
    "multiValueQueryStringParameters": {List of query string parameters}
    "pathParameters":  {path parameters}
    "stageVariables": {Applicable stage variables}
    "requestContext": {Request context, including authorizer-returned key-value pairs}
    "body": "A JSON string of the request payload."
    "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"
}

rackup した HTTP Server として Docker Image を実行したい

Lambda の Docker Image として実行するときは Lambda function for proxy integration の形式で JSON をやりとりしないといけない、というのは今まで書いてきた通り。

開発中は bundle exec rackup しておけばいいんだけど、正しく焼けてるか不安なときがあるので、Docker Image 上の Rack アプリをブラウザから実行したい。

つまり

  • HTTP サーバが立って
  • リクエストをいいかんじに JSON に変換して Lambda に投げて
  • Lambda からのレスポンスをいいかんじに HTTP に変換して返す

してくれる proxy (つまり API Gateway や ALB 相当のもの) があると便利だよね。というわけで雑にこんなのを用意しました。

コンテナを立ててた上でこの proxy を立てておくと、http://localhost/ でコンテナの中の Lambda の中の Rack アプリとやりとりできる。

絶対どこかにもっと良いやつあると思うので全然作り込んでない。(例えば multiValueHeadersisBase64Encoded は対応していない)

#!/usr/bin/env ruby
require "json"
require "net/http"
require "webrick"

LAMBDA_ENDPOINT = "http://localhost:9000/2015-03-31/functions/function/invocations"

s = WEBrick::HTTPServer.new
s.mount_proc("/") do |req, res|
  uri = URI(LAMBDA_ENDPOINT)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme === "https"

  headers = {}
  req.each do |k, v|
    headers[k] = v
  end
  json_data = {
    httpMethod: req.request_method,
    path: req.path,
    queryStringParameters: req.query,
    body: req.body,
    headers: headers,
    requestContext: {},
  }
  lambda_res = http.post(
    uri.path,
    JSON.generate(json_data),
    { "Content-Type" => "application/json" },
  )

  lambda_inner_res = JSON.parse(lambda_res.body)

  res.status = lambda_inner_res["statusCode"]
  lambda_inner_res["headers"].each do |k, v|
    res[k] = v
  end
  res.body = lambda_inner_res["body"]
end
Signal.trap("INT") { s.shutdown }
s.start

まとめ

  • Lambda でコンテナに入れた Sinatra アプリを動かすことができる
  • HTTP Request を Rack に変換して実行する仕組みを説明した
  • Lambda function for proxy integration の形式で JSON をやりとりすることになるので、proxy 立てると便利

*1:実際はディレクトリ構造変えたり apigatewayv2 対応したりでもはや別物になっているけど説明面倒なので省略

株式会社はてなに入社しました

株式会社はてなに入社しました

株式会社はてなに入社しました - hitode909の日記

4年目ですね。 さすがにそろそろ知らないことがあると恥ずかしい時期なので、だいたい手の中にはあると思います。 前提整える必要が無い=加速できるので、出力にご期待ください。

今年もよろしくお願いします。

Slack キーワード通知に設定しているワード 100 連発

エゴサが趣味なので、社内の Slack のキーワード通知をめちゃくちゃ便利に使っている。

slack.com

何か見ておくと良いことがありそうなものを片っ端から通知するようにしているんだけど、100 個が上限なの知ってた?

f:id:onk:20210331222523p:plain
100 個以上設定しようとするとこうなる

僕はこんなキーワードを入れています。適当に分類しながら見てみよう。

自分

onk, o/nk, on/k, おんく, 大仲, onaka, 眼科

自分の名前やハンドルネームを入れておくと、どこかで自分が呼ばれたときに気づけるようになる。

定時外だと親切でキーワード通知避けのために / を入れて発言する人もいるので、それも通知させるためにこういうキーワードになっています。戸籍ネームはほっっとんど呼ばれることは無いんだけど、念のため。

「眼科」は中心性漿液性網脈絡膜症になってるっぽいけど放置してたら T シャツが作られたので、戒めのためにずっと通知しています。

suzuri.jp

チーム

自分が所属しているチーム名だったり、プロダクトのコードネームだったり、サブ会 名だったり。

他のチームから言及されたときにすぐに気づける用。

サービス

小説, ノベル, カクヨム, kakuyomu, 魔法, maho, らんど, ランド

カクヨム魔法のiらんどを運営しているチームに居るので、サービスに関連しそうな話題が出たときに気づけるように。

障害検知

CRITICAL, 障害, 不具合, 負荷, 脆弱性, 重い, 重く, Incident, 申し送り

障害っぽかったら通知が来るようにしてある。

「申し送り」は障害対応後とかに使われることが多いワードですね。「重い」は月初に勤怠管理システムに対しての発言をよく見る。

インフラ系

infra, インフラ, SRE, SLA, SLO, SLI, 監視, PWG

インフラっぽい話題が出たら通知が来るようにしてある。

「PWG」は Performance Working Group の略で、パフォーマンス等が悪くなっていないかを定期的に (主に月次開催) チェックする会です。サービスのレスポンス速度やエラー率、発報されたアラートや障害などを3ヶ月ぐらいの目線で確認し、キャパシティプランニングを行ったり、監視設定や根本対策に思いを馳せたりします。

はてなにおける日々の仕事の中にあらわれるMackerelの活用 - Mackerel ブログ #mackerelio でも紹介されていますね。当時とは組織体制はかなり変わっていますが、目的はあまり変わっていません。

mackerel.io

コミュニケーション

(グループウェアのドメイン), 議事録

bot の発言ではなく、人間が URL を貼ったなら、何かしら人に伝えたい意図が含まれた面白い記事なんだろうってところから。実際、キャッチアップしておくと便利な記事がよく流れてきます。

「議事録」は割と興味本意かな。社内のミーティングでは「議事録」ってワードはあまり出ないので、他社さんとの会話が見えることが多い。

マネージャ仕草

cost, コスト, 予実, 予算, 実績, 費用, 数字, 稟議, 申請, release, リリース, キックオフ

はい。

他部署のものでも数字に敏感になっておくと色んな場面で役に立つし、キックオフやリリース時点で話題をキャッチできていると、これも会話の種になる。「デプロイ」はさすがに流速がヤバかったのでキーワードから外した。

マネージャ仕草 人事編

全社, ミッション, ビジョン, バリュー, MVV, 目標, 評価, グレード, 昇格, 昇給, 1on1, 面接, 面談, 異動, 採用, 新卒, インターン, CTO, チーフ, シニア, リーダー, メンター, メンティー, マネージャ, 方針, オンボーディング

1on1 した感想とか、目標設定や評価に対しての感想とかを拾っておきたい。

ふりかえり

ふり返り, 振り返り, 振りかえり, ふりかえり, KPT, YWT

ふりかえりだけでも見ておくとチームの現状が分かるので、覗きに行ってます。

技術

MySQL, RDS, Aurora, redis, Elasticache, Elasticsearch, ruby, rb, rails, レイルズ, レールズ, アジャイル, scrum, スクラム

この辺のワードが出たらいっちょかみしたい技術ワード。

こんなに登録していて仕事できるの

かろうじてなんとかなってるんじゃないかな……。

集中したいときはガン無視しているので、呼ばれても返事してないし、何ならときどきミーティングすっぽかしてる orz