こんにちは、okoshiです。
前回の私の記事は大変たくさんの方に読んでいただいて大変感謝しております。
今回はカッとしてシュッとやった一例を紹介させていただきます。 私の担当していたfreeeプロジェクト管理で、Git操作時にhuskyを用いて小さな課題を解決したというストーリーです。
ご注意
本ページでは、freeeプロジェクト管理でhuskyを採用してみたという話であることと、導入当時とバージョンが変わってしまっているため本ページではインストール方法や使い方までは解説しません。ご了承ください。
小さな課題とは
freeeプロジェクト管理ではバックエンドにRubyを採用していますが、使用するエディターはメンバーによって違い、Vimを使う方もいればRubyMineを使っている方もいます。 メンバーの中にVisualStudio Codeを利用している方もいて、その方はVisualStudio CodeのプラグインでRubocopを自動で実行するようにしていました。 Rubocopとは静的コード解析をする機能を提供するものですが、動作が非常に重く開発速度に支障が出てしまっていました。
我々のチームでは、画面を共有してモブプログラミングをすることが多いのですが明らかに動作が重く、なんとかしなければチームの開発能力が落ちてしまう懸念がありました。 エディタを変更することも考えましたが、Vimの操作になれてもらうことや、RubyMineで解決できるかは検証が必要(PCのスペックの問題かもしれないため)であり早々に諦めました。
CIツールを使用しているので、実行中に指摘がされるためVisualStudioコードで使用しているプラグインを使用しないということも考えました。しかしCIから指摘されるのを待つことになり、数分間作業を待つか、別の作業をしながら待ったとしてもコンテキストスイッチが負担になるという別の課題が発生します。
ローカル環境で、自動でRubocopを実行するにはどうしたらいいか、ということが課題でした。
過去の事例から思いついたアイデア
長くエンジニアをやっていると、過去の事例がサッと頭に浮かびます。 その過去にあったことというのは、マネージドサービスとして使っているCIツールを大人数で使っていたため渋滞していたり、費用がかかり過ぎているという課題があり、ワークロードを減らしたいという思いから、静的コード解析程度のことはローカルで実行しようとなったことです。
その対処方法はこうでした。
- 全員Gitを使っているのでGitフックを使ってコミット時にチェックするようにしよう。
- huskyというnpmライブラリーを使えばGitフック時にプログラムを実行できるので、それを利用して実現しよう。
このアイデアは私が出したものではないですが、このアイデアを出してくれた人もまた別の現場で経験したことから採用したものでした。
この経験から、Gitコミット時であれば自動でRubocopを実行できるので課題を解決できるし、しかもすぐに対応できると思い、シュッとやってみることにしました。
なぜhuskyなのか
なぜそのままhuskyを採用するに至ったかといえば、先述の通り使った経験があったというのもありますが、有名なOSSであるという理由もあります。 有名なOSSであるということは評価が高いとか、よく保守されているだろうから今後も安全に使えるだろうと考えることができます。
huskyは他のメンバーが使い始めるための考慮もされており、yarn install
するときに同時にhuskyをインストールするため、導入してみたはいいものの、ある人は使っているけどある人は使っていないといったことが起きにくいように考えられています。とてもありがたいですね。
メンバーから上がった懸念
huskyによってGitでコミットするときにpre-commitをフックし、lint-stagedというnpmライブラリーを実行し、lint-stagedがRubocopを実行するという仕組みをとっています。
導入することをメンバーに相談したところ、コミット時にプログラムを実行するということは、それだけ時間がかかるようになるのではないかという懸念メンバーから上がりました。実際10秒程度はかかることがあります。
しかし、実行時は何が起こっているのかを視覚的に確認できるので対して遅く感じないことがわかっていたので、実際に作業をしている操作を見せることで受け入れてもらうことができました。
lint-stagedというのはhuskyとは別にインストールするnpmライブラリーなのですが、huskyとの相性がよくいっしょに使われることが多いライブラリーです。
どんな静的コード解析を行わせているか
ここで、我々がどんな静的コード解析を行わせているかを紹介しましょう。 package.jsonの一部を公開します。
"*.{rb,rake,ruby}": [ "bundle exec rubocop --force-exclusion -a", // ① "bundle exec rubocop --force-exclusion -a --only Style/FrozenStringLiteralComment,Layout/EmptyLineAfterMagicComment", // ② "bundle exec rubocop --force-exclusion -P" // ③ ]
①〜③の行で3回もRubocopを実行していますが、すべて違うことを行っています。
先述の通り、コミット時の実行時間の懸念を意識して、--force-exclusion
をオプションとして指定しています。これは、.rubocop.yml
でファイルを除外(AllCops.Exclude等)していても解析だけはしてしまうので--force-exclusion
によってその解析もしないようにするものです。
①では、Rubocopが修正可能なコードは修正しています。例えばコードが始まる桁が2桁目からなのに、ある桁だけ3桁目から始まっているような場合に2桁目に移動させるといった感じです。
②では、Rubyのコードの最上部に# frozen_string_literal: true
とその下の行に空行を挿入しています。
③では、Rubocopによる静的コード解析を実行しています。-P
オプションによって複数のファイルで並行実行を行っています。-P
と-a
は同時指定ができないので①と②では指定していません。
最後に
業務を遂行中に発生した課題から、それの解決に至るまでの思考プロセス、導入にあたってのメンバーのケアを簡単に紹介させていただきました。 今回紹介したのは非常に小さな課題ですが、freeeでは必要なことならメンバーがサッと動いてこういった改善を行っています。
そして、これを実現するにあたってとってもうれしいことがありました。実は”どんな静的コード解析を行わせているか”の②は、①と③を導入後に他のメンバーがこの仕組みを応用しより使いやすく改善してくれたものです。
更に今、別のメンバーからはフロントエンドサイドのLintも入れるかどうかという話も上がっており今後も進化しつづけそうな雰囲気がでてきています。
小さな課題の解決がメンバーの刺激になって継続的な改善につながっていく。こんなチームにいることができて最高だなぁと感じています。