$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

良い制度には「カイゼンループ」が組み込まれている

最近いろんな制度や仕組みを見ていて、良い制度には「カイゼンループ」が組み込まれているなと感じた。(カイゼンループは、トヨタの「カイゼン」と、繰り返しの「ループ」を組み合わせて、勝手に造語してみた)

カイゼンループとは、制度・仕組み・関わる人・チーム・組織などの「カイゼン(改善)」がくり返し行われるようになっている状態のこと。カイゼンループが組み込まれていると、

  • 制度が組織の現状に合わせて改良され、それにより組織にフィットした制度になり、形骸化しない
  • メンバーがどんどん成長し、より高度で柔軟な動きができるようになる

などといった効果があるように見える。

抽象的な話をしてもよく分からないと思うので、ここからはそのように感じた具体的な例をいくつか挙げてみたい。

開発フローのスクラム

まずいちばん分かりやすい例が開発フローであるスクラムスクラムにはスプリント期間ごとの振り返りにより、「カイゼンループ」が組み込まれている。

振り返りにより、チームのカイゼンや、メンバーの成長が促進される。これがスプリント単位でくり返し行われる。正しくカイゼンのループが回っていれば、そのチームではどんどん学習が行われ、より良い仕組みとなる。

VOYAGEの技術力評価会

もう少し具体的な例として、VOYAGEの技術力評価会にはカイゼンループが組み込まれていると感じた。技術力評価会自体については以下を参照してほしい。

speakerdeck.com

この制度の中で、僕が一番すごいなと思ったのは、二種類のカイゼンのループが組み込まれていることだ。

一つ目は、技術力評価会の全体のカイゼンループ。技術力評価会のフローの中に、全体振り返りが組み込まれている。ここに興味のある人が参加し、KPTをすることにより、仕組み全体のカイゼンを行っている。これにより、制度にどんどん改良が加えられ、一番最初に導入した時と現在では、かなり仕組みが変わり、現在のVOYAGEの状況にあったものになっていそうだ。

二つ目は、評価者のカイゼンループ。この制度は、一人の被評価者に対して、最低でも二人の評価者が付き、それぞれが評価のコメントを書く。このため、評価者は評価が終わった後にもう一方の評価者のコメントを参照することができる。評価者はそこからフィードバックを得て、次の評価に活かすことができる。これが評価会のたびに行われ、評価者自体のカイゼンがくり返し行われる。

このように二種類のカイゼンループが回って、制度がどんどん良いものになっているのだろうと感じた。

参考

ヤフーの1on1

ヤフーの1on1にもカイゼンループが組み込まれている。ヤフーの1on1については以下を参照。

これには、「1on1チェック」という、部下から上司への定期的なフィードバックの仕組みが組み込まれている。1on1チェックとは、3ヶ月に1回、部下が幾つかの項目の点数付けと、定性的なコメントによるフィードバックを行うもの。これにより、上司は自身の1on1の振り返りを行うことができ、3ヶ月に1回のスパンでカイゼンのループが回っている。

まとめ

以上に三つの例を挙げたが、このようにうまく運用されている制度には、カイゼンループが組み込まれていると感じた。そこで新しい制度を考えるときには、これらに倣ってどうカイゼンを回せるようにするかを一緒に考えていきたいと思った。

【蛇足】この記事を書いてて、これって経験学習と呼ばれるものなのでは?と思ったので、その関連の本も読んでみたいと思った。

Emacsで現在見ている行を変更したPRを開けるようにした

このコードどうしてこうなってるのかという経緯を知りたい時、git blameなどのコマンドを利用することが多い。しかし、git blameだとその行を変更したcommitが分かるだけであり、経緯が結局分からないということがよくある。

そういう時にその行を変更したPRを開けるようにしたいなーと思って、いろいろやったところ、Emacsで現在見ている行を変更したPRを開けるようになったのでメモ。

特定のコミットが含まれるPull Requestを開くには

前段階として、特定のコミットが含まれるPull Requestを開くということをやってみる。これは既にいろいろやっている人がいて

この中で Commit Hash から、該当 Pull Request を見つける方法 - Qiita

hub browse -- `git log --merges --oneline --reverse --ancestry-path $1...master | grep 'Merge pull request #' | head -n 1 | cut -f5 -d' ' | sed -e 's%#%pull/%'`

を使うと良さそうだった。しかし、これはgithubのdefault branchがmasterでないといけないという制約がある。そこで色々試行錯誤したところ、 github - git - how to get default branch? - Stack Overflow を参考にして、default branchを判定できるようにし、最終的にopen-pr-from-commitとして、以下のようにした。

open-pr-from-commit

#!/bin/bash

default_branch=`git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'`
hub browse -- `git log --merges --oneline --reverse --ancestry-path $1...$default_branch | grep 'Merge pull request #' | head -n 1 | cut -f5 -d' ' | sed -e 's%#%pull/%'`

これで、open-pr-from-commit f2cf9cdcadbなどとすれば、そのcommitがなされたPull Requestに飛ぶことができるようになった。

tig blameから該当行が変更されたPull Requestを開く

tig から当該コミットがマージされた Pull Request 画面を開く - Qiita を参考にする。tigから先程のコマンドを実行するようにするだけ。.tigrcに以下のように記述する。

bind generic P @open-pr-from-commit %(commit)

これでtig blame中に該当行でPを押すとPull Requestに飛べる!こんな感じ。


Emacsでvc-annotate中に該当業が変更されたPull Requestを開く

tigもいいんだけど、普通コードリーディングをしている時は、tigではなくて自分のエディタで見ている。なのでここまで来たらEmacsでも開けるようにしましょうと考えた。

Emacsでgit blameっぽいものを見るにはvc-annotateというのを使う。参考は Emacs の vc-annotate - naoyaのはてなダイアリー

vc-annotate中にその行のrevisionを取るにはvc-annotate-extract-revision-at-lineが使えそうだったので、それを利用して以下のような関数を定義し、vc-annotate-mode-mapにキーバインドを設定する。

;; vc-annotateで現在の行がmergeされたPRを開く
(defun vc-annotate-open-pr-at-line ()
  (interactive)
  (let* ((rev-at-line (vc-annotate-extract-revision-at-line))
         (rev (car rev-at-line)))
    (shell-command (concat "open-pr-from-commit " rev))))

(define-key vc-annotate-mode-map (kbd "P") 'vc-annotate-open-pr-at-line)

あとはこれでvc-annotate中にPを押すだけでPRに飛べる!


まとめ

今回は特定のコミットが含まれるPull Requestを開くコマンドを作りつつ、それを使うことでEmacsで現在見ている行を変更したPRを開けるようにした。シュッと関係するPRを見れるようになったのは非常に便利。

Play Frameworkのサーバ立ち上げ部分のコードを追いかける

Play Frameworkとsbtを使っていて、sbt runを実行した時にどういうことが起こっているのか、compileしたときのmain関数はどこにあるのかあたりが分かってなかった。この辺が分かってないと、開発でrunしている最中に変な挙動をした場合のデバッグや、本番アプリケーションサーバのチューニングなどがしづらい。そこで今回はPlay Frameworkのサーバ立ち上げ部分のコードを追いかけてみたのでメモ。あんまりsbtに詳しくないので、内容が間違っていたら教えてください。

playframeworkを使っているプロジェクトでbuild.sbtに書いていること

build.sbt書いている内容はこれだけ。

lazy val root = (project in file(".")).enablePlugins(PlayScala)

またproject/plugins.sbtには以下が書かれている。

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.13")

これだけでrunやcompileがうまく動くようになってて不思議。

sbt runで立ち上がるもの

まずsbtでinspect runするとどこで定義されているか分かる。PlaySettingsにありそう。

[play-scala-slick-example] $ inspect run
...
[info] Defined at:
[info]  (play.sbt.PlaySettings.serviceSettings) PlaySettings.scala:105
...

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/sbt-plugin/src/main/scala/play/sbt/PlaySettings.scala#L104..L106 あたりで、runしたときはPlayRun.playDefaultRunTaskを呼ぶようになってて、Compile/run/mainClassにplay.core.server.DevServerStartを設定している。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/sbt-plugin/src/main/scala/play/sbt/run/PlayRun.scala#L49..L50 playRunTaskを呼んでる。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/sbt-plugin/src/main/scala/play/sbt/run/PlayRun.scala#L79..L96 devModeServerの定義で先程のmainClassを使いつつ、Reloaderを使ってサーバを起動できるようにする。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/run-support/src/main/scala/play/runsupport/Reloader.scala#L226..L235 portの設定有無によって、先程渡したmainClassのmainDevHttpModeとかを使って立ち上げる。つまりデフォルトではplay.core.server.DevServerStart.mainDevHttpModeかな。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/play-server/src/main/scala/play/core/server/DevServerStart.scala#L39..L50 mainDevを呼ぶだけ。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/play-server/src/main/scala/play/core/server/DevServerStart.scala#L52..L58 あとはこのへんで頑張ってサーバを起動してますね。

こんな感じの流れっぽい。

compileやdistしたときのmain関数はどこか

sbt runは開発サーバが起動したが、compileした時は違うはず。調べてみるとこちらもPlaySettingsにかかれていた。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/sbt-plugin/src/main/scala/play/sbt/PlaySettings.scala#L152..L153 Compile/mainClassにplay.core.server.ProdServerStartが設定されている。

https://github.com/playframework/playframework/blob/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/framework/src/play-server/src/main/scala/play/core/server/ProdServerStart.scala#L20..L26 普通にmain関数が定義されている。

こういう感じなので、https://www.playframework.com/documentation/aa66958fa46b573bbf7cd8b92a92568c64c15cd4/Deploying にかかれているようにdistやuniversal:packageZipTarballを実行して得られた実行ファイルを起動すると、ProdServerStartのmainから実行が始まるという雰囲気。

まとめ

playframeworkのsbt pluginを有効にしたときの、runやcompileの挙動周りを調べた。奥がだいぶ深かった。ちょっと知識が深まった気がする。