Kengo's blog

Technical articles about original projects, JVM, Static Analysis and JavaScript.

GitHub Actions 最近のやらかし一覧

FOSS開発で細かいやらかしを積み上げてきたのでまとめる。

テストの失敗原因レポートをartifactとしてアップロードしそこねる

actions/upload-artifactを使ってテストレポートをartifactとしてアップロードする際、以下の書き方だと失敗する。

# bad
    - run: |
      ./gradlew test --no-daemon --stacktrace
    - uses: actions/upload-artifact@v2
      with:
        name: reports
        path: build/reports

これはテストが失敗した時点で後続のstepsが実行されなくなるため。明示的に失敗時でもアップロードされるように指示する必要がある。

# bad
    - run: |
      ./gradlew test --no-daemon --stacktrace
    - uses: actions/upload-artifact@v2
      if: always() # this config is necessary to upload reports in case of build failure
      with:
        name: reports
        path: build/reports

forkからのPRではGITHUB_TOKENを除くsecretsを参照できない

secretsの存在を前提にしているコードがあると、forkからPRをもらったときにビルドが通らなくなる。

# bad
  - name: Decrypt file
    env:
      GPG_SECRET_PASSPHRASE: ${{ secrets.GPG_SECRET_PASSPHRASE }}
    run: |
      gpg --quiet --batch --yes --decrypt --passphrase="$GPG_SECRET_PASSPHRASE" --output decrypted encrypted
      # -> gpg: missing argument for option "--passphrase="

後述する方法でsecretsが空かどうか確認する必要がある。

if では $VAR で環境変数を参照できない

if で書くのはbashじゃくてexpressionなので、runに書くのと同じノリでやると失敗する。

# bad
    - name: Run SonarQube Scanner
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_LOGIN: ${{ secrets.SONAR_LOGIN }}
      if: ${{ $SONAR_LOGIN != "" }}
      run: |
        ./gradlew sonarqube -Dsonar.login=$SONAR_LOGIN --no-daemon

正しくはenvコンテキストを参照する(なお${{ .. }} で囲むのは必須ではない)か、

# good
    - name: Run SonarQube Scanner
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_LOGIN: ${{ secrets.SONAR_LOGIN }}
      if: env.SONAR_LOGIN != ""
      run: |
        ./gradlew sonarqube -Dsonar.login=$SONAR_LOGIN --no-daemon

bashで統一して読みやすくしたいなら run の中で判定する。

# good
    - name: Run SonarQube Scanner
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_LOGIN: ${{ secrets.SONAR_LOGIN }}
      run: |
        if [ "$SONAR_LOGIN" != "" ]; then
          ./gradlew sonarqube -Dsonar.login=$SONAR_LOGIN --no-daemon
        fi

gradleで環境変数があるときだけ特定タスクを実行する場合は、こういう書き方もできるらしい。シンプル。

    - name: Run SonarQube Scanner
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_LOGIN: ${{ secrets.SONAR_LOGIN }}
      run: |
        ./gradlew spotlessCheck build smoketest ${SONAR_LOGIN:+sonarqube} --no-daemon -Dsonar.login=$SONAR_LOGIN

最近キャッチアップしているもの 2020-05

いろいろ手を出しすぎてごちゃごちゃしてきているので、頭の中を整理する目的でここに書き出す。

reproducible build

Mavenのメーリスで話題に出ることがあり知った。特別新しい概念ではないけどある種のunlearningであり、ビルド職人は見といて損ないやつ。

reproducible-builds.org

端的に言うと、誰がどこでビルドしても同じアーティファクトができるようにしようという話。実はJavaのビルドツールでアーティファクト(主に.jarファイル)を作ると、そのビルド結果は常にバイナリ等価とは限らない。ビルドした日付がMETA-INFに入ったり、同梱されたファイルはすべて同じなのにZIPに突っ込む順番が違ったりと、ビルドした時刻や環境によって異なる結果が生まれることがある。詳細はDZoneの記事を参照。

なぜこれが着目されているかは公式サイトに書かれているが、つまるところ「アーティファクトソースコードの対応」を検証する術として期待されている。
従来は「本当にこれが公式に配布されているファイルかどうか」はchecksumや署名で確認できていたが、それ以前にバージョン v1.0.0VCSv1.0.0 タグから本当にビルドされたのかを検証することができなかった。ので例えばビルドとリリースのプロセスを攻撃することで、悪意あるコードが含まれた v1.0.0 がリリースされる可能性を検証するコストが高かった(CHANGELOGやRelease Notesではなくバイトコードを読む必要がある)。これがreproducible-buildにより、手元で v1.0.0 をチェックアウトしてビルドした結果と配布物を突き合わせることで攻撃の可能性をまず検証できるようになる。

これがunlearningだと考える理由は、ビルドツールのデフォルト設定だと達成されないから。つまりコミュニティで可搬性のあるプラクティスと考えられてきた常識を疑い変えていくフェーズに今はある。まぁ落ち着いて考えればビルドした日付をアーティファクトに埋める必要性など皆無なわけだが、昔は「このJenkinsジョブが作った.jarだ」ということを明確化するためにファイル指紋に加えてユーザ名やらホスト名やらいろいろMETA-INFに組み込んでいたような気がする。

なおMavenだとプラグインが提供されているので、これをプロジェクトに適用すればよい。Gradleのもあるけど活発ではないので、カバーされていない問題もあるかもしれない。Gradle公式には特にまとまった情報はなく、依存先のバージョン固定に関するドキュメントにさらっと書いてあるくらい。

visual testing (GUI regression testing)

Seleniumを使ったGUIテストについては従来からやってきたが、主なテスト対象はアプリの挙動だった。これに加えてUIの崩れについてもテスト対象とする動きがある。JS界隈・CSS界隈は動きが速く、それでいて多様な環境で動作しなければならないということで、互換性維持の難度がもともと高い。これがUIとなると解像度についても考える必要性があり、余計に複雑化する。例えば最新のブラウザでベンダプレフィックスがサポートされなくなったり、flexboxの挙動が変わったりしたときに、レガシーブラウザでの挙動を変えること無く新しいコードに実装を置き換えていく必要があるが、これを手動でやっていると大変。

visual testing自体は7年前に試行錯誤しているブログがあるくらい仕組みは簡単で、スクリーンショットを撮っておいて保存、次回実行時に比較するというもの。なおまだ名前が定着していないらしく、visual testingとかGUI regression testingとか、みんな好きに呼んでいる。SaaSベンダーやFOSSプロジェクトの比較をするならawesome-regression-testingが役に立つ。

Seleniumを使ったGUIテストが安定かつ高速に回せる環境がすでにあれば、導入自体は難しくない。ブラウザやOSのベータ版でテストを回す仕組みとか、失敗したテストだけを再実行する仕組みとか、テストを複数ノードに分散して実行する仕組みとかがあれば強みを活かしやすい。percyのペーパーによるとUIの変化が確認されたケースの96%はapproveされたようなので、visual testingが失敗したら無条件にマージさせない的なPR運用ではなく、開発者に判断材料を提供する仕組みとして運用する必要性がありそう。

ここまで書いてみて、なぜここ最近でSaaS/PaaS界隈が盛り上がってきたのかがよくわかっていないことに気づいた。BrowserStackのリリースノートによると実デバイスによる機能を提供したのが2019年2月AWS Device Farmに至ってはSeleniumテストの実行をサポートしたのが2020年1月ということで、最近の動きであることは間違いなさそう。自分の直面している課題に最適なのは疑い無いので構わないが。

cross-functional team

従来の「組織の役割を単純化して相互連携によって業務を進める組織設計」の課題を解決する手法としての「単一チームに複数の役割をつめこむ組織設計」と認識している。チームができる意思決定を増やし、組織の壁を超える頻度を減らし、ビジネスのagilityを上げることを目的とする。縦割り(silos)の弊害を解消するために全員をひとまとめにしようというアイデアと考えるとわかりやすいが、チームリーダーの責務が大きく変わるので導入は慎重にしたほうがいい。

cross-functional teamの背景にはcontinuous deployment, DevOps, servant management, capability modelといったトレンドがあると理解している。「マネジメントが理想を描いてメンバーを従える」独裁型と違い、「ミッションを明確化し、専門家がミッション達成のために必要なもの全てを提供するために腐心する」調整型のマネジメントになる。従来型の組織ではチームの長はメンバーと専門性を共有していることが多かったが、cross-functionalチームではそうではないので、独裁のしようがない。これがcross-functional teamとservant managementそしてcapability modelが切り離せないと自分が感じる理由。ただ今まさにaccelerateを読んでいるので、この辺の意見は今後変わるかもしれない。

まだよくわかってないのがチームの小ささ(two-pizzas team)とバス係数の両立。cross-functionalとは言えどもチームは充分小さく活発に議論ができなければならないはずで、例えばDX Criteriaでは5-12名の規模を目安としている。バス係数を考慮し各roleを2名以上とすると、どんなに頑張っても入れられるrole数は2−4個に制限されるはずで、専門性の細分化が進むシステム開発において企画から運用まですべてのプロセスで必要になるすべての専門性をチームに入れることは不可能だとわかる。のでcross-functional teamを採用する組織においても、ある程度の縦割りは残るだろう。その「残し方」として著名なのはSREだが、他にもセキュリティやUXなど組織横断的に統制をかけてやるべき活動については専門のチームが残ると予想する。その際はcross-functional teamとは異なる解決策が必要で、それがSREではerror budgetになる。silosの壁を壊すには「同じ問題を解決する1つのチームなんだ!」的精神論ではなくて、仕組みまで落とし込むことが肝要。

ではどういったroleをcross-functional teamに入れられるかというと、すぐに思いつくのがテスト周りの責務。テストの知見を持ったエンジニアを企画段階から参画させやすくなり、手戻りを減らし保守性を向上すると期待できる。testabilityに配慮したコードかどうかをPRレビューで見てもらえるし、組織としてPOVがひとつ増えることのメリットが大きいはず。Opsも同様。

ちょっと脱線するけどテストはビルドエンジニアのキャリアパスとしてもわりとアリだと思っていて、自動テストの重要性がどんどん上がっている昨今、テストをどうCD pipelineに組み込むか・高速に終えるか・安く実行するかというビルドエンジニアならではの貢献ポイントが多々あるように見受けられる。もちろん別々のroleとしてチームに配置してもいいんだけど、GoogleのTEの例にあるように、1人が知識を持ち合わせてその掛け算で問題解決を図るのは十分可能なレベルだと思う。

Product Management

Strategic Management, Visionary Leadership, Project Management (PjM)とやってきて、いざProduct Management (PdM)に首を突っ込んでいるのだが。今のところは「プロダクトを形作る組織の全体像を把握して、課題に優先度を付けて解決する」という解釈しか持てていない。

すえなみさんのツイートに共感した。

PjMとPdMに限らず、最近はアジャイルウォーターフォールにも差が対して無いんじゃないかみたいな気持ちになってきていて、学習の振り子が振り切った感じがする。そのうち逆側に振れると思うけど(比較対象表とか書き出すやつ)。

考察:Reactive Workflowが生まれた背景とその狙い

人に説明するのがスムーズにできなさそうなので、理論武装というか順序立てて話すためにこの記事をまとめる。

対象

TL;DR

  1. 状況やベストプラクティスが目まぐるしく変わる現代において、すぐに変化できるソフトウェアを保つこと・ヒトの手をできるだけ空けることが重要。
  2. かつてIaaSがAPIを提供し環境管理の多くを自動化したように、各種サービスがAPIやWebhookを通じてDevelopment Workflowの多くを自動化してきている。
  3. 多くの視点や知見を活かすcross functionalチームによる共同開発を支えるために、コラボレーションを助ける仕組みも必要。

前置き:課題解決手法としての生産性向上

我々はなぜ生産性向上を目指すのか? それは生産性が十分ではないという課題意識や危機感を持っているからだろう。では、なぜ課題意識を持ったのか? 機能の実装が進まないとか、バグが多いとか、手戻りが多いとか、本質的でない業務が多いとか、現場によって多くの理由があると思われる。そしてそれを深堀りする前に他者とざっくり課題意識を共有するための言葉として「生産性を向上しないとね」といった表現が用いられる。 言い換えれば、生産性向上というのは現場の抱える課題に対する抽象的な解決策である。ので「生産性を向上するには」などと議論しても、地に足をつけた議論にはならない。「なぜ生産性が低いのか」を掘り下げて、その解決策を見出す必要がある。

ここで重要なのは「なぜ低いのか」は比較対象を知らなければ説明できないことだ。それが他のチームが実際に回している開発なのか、スケジュールから逆算した「理想生産性」なのかは課題ではない。そうした「あるべき姿」が共有されてはじめて比較ができ、KPI(モノサシ)が生まれ、議論できるようになる。これは開発手法や組織、プロセスなど、どこに課題がある場合でも同じだ。

これはこのブログで繰り返し出てきているproblem solvingの考え方であり、真新しいものではない。単に課題の設定と共有をきちんとしようという話である。課題が設定されてはじめて、課題解決の進捗や施策の妥当性を評価できる。Development Workflowに手を入れる前に、本当に必要なものがそれで手に入るのか確認する必要がある。

Development Workflow

私はDevelopment Workflowは自分の専門というわけではなく、またウォッチャーを自任できるほど時間をかけてトレンドを追ってもない。いちエンジニアの理解であり何かを代表するものではないことを、はじめに改めて強調しておく。

またここでDevelopment WorkflowというのはCI, CD, DevOpsといった文脈で出てくるパイプライン処理、特に確認や承認や手動テストという非自動化処理をも含めた一連の流れを指している。GitHub ActionsCircle CIでは単にWorkflowと呼んでいるが、ここでは業務向けワークフローシステムと区別する目的でDevelopment Workflowと呼ぶ。Development Workflowの「終点」は成果物のデプロイだけでなく、チームへのフィードバック=学習機会の提供であることも多い。

かつてDevelopment Workflowはどのようなものだったか

自分が10年前にどういうDevelopment Workflowを組んでいたかというと、概ね以下の通りだったように記憶している(便宜上HudsonをJenkins*1と表記):

  • trunk, branches, tagsを備えたSubversionによるコードのバージョン管理(ブランチ切り替えコストが高い)
  • 設定画面を利用したJenkinsジョブの作成・設定
  • ブランチを指定して実行するJenkinsジョブを開発者に対する公開
  • プラグインによるJenkinsの機能拡張
  • Mavenの親pomファイルを利用したプロジェクト管理
  • LANに閉じた開発環境
  • 環境構築や開発作業の手順書をWiki等でメンテ(.jarファイルの手動配置、テスト手順、IDE設定方法など)

自動化されているのはジョブと呼ばれる単発作業のみで、いつどのように実行するかは主にヒトに委ねられていた。出荷作業をするにはこのジョブをこういったパラメータで実行すること、という手順書が必要だったのである。VCS上での変更を知るにはpollingを必要としていたし、他にもマスターサーバの責務は多くある程度の性能を要した。また結果をヒトに通知する手法はメールが主体だったので、何かを強制することは難しかった。

当時はまだビルド職人という言葉があったが、それは成果物の作成を特定の環境や個人でしか行えなかったり、ジョブ実行時間が非常に長かったりしたことを反映している。実際ビルド環境を可搬にすることはまだ難しく、また手順書が必要な現場も多かったのではと推測される。

近年Development Workflow周りで見られる変化

逆に、当時に無くここ10年で開発され普及したものを思いつく限り列記する:

  • ブランチを利用しやすいVCS(Git)
  • 画面による設定ではなく、VCSによって管理されたファイルよる設定
  • CIパイプライン
  • インクリメンタルビルド
  • 複数ノードにおけるテストのparallel実行
  • プロジェクトごとに利用するJDKを切り替える仕組み
    • rvm はあったようなので、他言語には実行環境をプロジェクトローカルのファイルで指定する仕組みはあった?
    • 開発環境にはあらかじめ複数のJDKを入れておくことで、Toolchainを使って使うものを指定する仕組みはあった(どの程度普及していたのかは不明)
  • DisposableでReproducibleな、頻繁に壊して作りなおせる環境
    • Vagrant Sahara pluginが出たのが2011年
    • Boxenが出たのが2012年
    • Dockerが出たのが2013年
    • ChefとPuppet, EC2にAMIは10年前既にあったので、自前でコード書いて実現する手はあったと思う(自分はやった記憶がない)
  • CLIから実行できる、設定がVCSで共有可能で環境非依存なコードフォーマッター
    • gofmt, rustfmt, Prettier, Spotlessなど
    • コードフォーマットが個人の環境・設定に依存する必要性をなくし、差分レビューを容易にした
  • チケット中心主義の緩和
    • PRで変更を提案し、その中で議論・修正できるようになった
    • 不具合も再現するテストをPRで送り、同一PR内で修正しマージできる(チケットによる報告も残ってはいる)
    • GitHub Releasesなどでタグに対して情報を残せるのでリリース作業もチケット不要
    • チケットを作って議論を尽くしてからコードを書くケースはもちろん残っているが、関連作業の記録・集約のためにまずチケットを作るというフローはほぼ不要になった
    • チケット管理システムの責務から変更の記録が外れ、要件の記録に注力できるようになった
  • 他システムとの連携を前提としたAPIやWebHook、アクセストーク
  • 運用を肩代わりし本質に注力させてくれるSaaS
  • ベンダ間で統一されたインタフェース(WebDriver, JS, CSSなど)

多様にわたるが、その多くがビルド時間の短縮や環境管理を楽にすること、システムを”繋ぐ”ことに貢献するものである。

10年前と比較して、どのようなニーズの変化があったのか

技術の進展というのはあとから振り返ると「なんで今までなかったんだっけ?」と感じるものが多いが、Development Workflowを構成するサービスの成長にも同じことを感じる。 例えば10年前はヒトに使われることを前提にGUIを通じて提供していた機能の多くが、サービスから使われることを前提にWebAPIやVCSで管理されたファイルによって使えるようになった。しかしGUICUIどちらが技術的難易度が高いかと言うと、一般的にGUIだろう。GUIを提供できるがCUIはできない、というケースはあまりないはずだ。

ではなぜ10年前はGUIを中心としていたのか?それは(特にJenkinsについては)利用者に対するハードルを下げた面もあると思うが、手実行することがあまり問題にならなかった、設定や運用をオープンにし誰でも触れるようにするモチベーションが高くなかったことが要因として挙げられる。もっと言えば、当時は1日に数回のビルドで十分にビジネスを回せたのだ。DevOpsの草分けとなったFlickrの発表がちょうど10年ちょっと前*2だし、おそらく当時はまだ多くの組織で「開発と運用の対立」「出荷前検証の重視」が残っていたはずだ。Flickrを始めとした多くの組織で、インフラのコード化や共有されたVCSリポジトリがいかに生産性へ貢献するか、注目された頃とも考えられる。

つまり10年前と今の大きな違いは、開発における試行錯誤の速度だ。自動テスト、継続的デプロイ、カオスエンジニアリング、コンテナなどの様々な技術が生まれたことで、ソフトウェアがより素早く変化するものになっている。またソフトウェア開発の現場に経験主義の考えが強く根付いていることや、スタートアップとソフトウェアの相互の結びつきから、ソフトウェアが素早く変化できることがビジネスで有利に働くと強く信じられるようにもなっている。DevOpsやSREが生まれた背景もそれを裏付けているように思う。

速度が違うとは、どういうことか。例えば10年前の私に「Gradle Pluginの開発でテストに使うGradleのバージョンを増やしたい、なぜこんなことをJenkins管理者にお願いしなければならないのか」と言ったところで、「え、メール一本だし別にいいじゃん、何気にしてるの?」と不思議がられるに違いない。今はこれもPRひとつで実現可能だが、当時はそうではなかった。
他の例では、Mavenのバグを回避するためにバージョンを上げるとして、Jenkins管理者にお願いしてCI環境のバージョンを上げるだけでなく、各開発者の使っているMavenのバージョンまで上げるにはメールでバージョンを上げるよう依頼しつつmaven-enforcer-pluginで意図しないバージョンでビルドした場合に落ちるようにする必要があるだろう。さらに複数プロジェクトで実施するにはすべてのプロジェクトのpom.xmlを書き換えるか、あらかじめ親pomを作成して各プロジェクトに依存させ、maven-enforcer-pluginの設定を親pomに加えてprivate maven repositoryにdeployしてから各プロジェクトのpom.xmlを書き換える必要がある。文が長くてわかりにくいが、とりあえず面倒なプロセスが必要ということが伝われば良い。これもMaven Wrapperを使うようにすればPRひとつで済む。ミドルウェアについても同様で、Dockerfileなりdocker-compose.ymlなりAnsibleなり、何らかのファイルを少し変えるだけで意図したバージョンが確実に使われるようにできる。

こうして製品ないし開発環境を変えることがPRひとつでできるようになっただけでなく、マージ前のプロセスの簡素化・短縮化も図られるようになった。Tracでチケットを作って手元で対応するパッチを作ってReview Boardでレビューして問題なければパッチを適用するという複数のシステムを行き来する方法から、検証済みマージやPull Requestといったひとつのシステムで完結する方法へと変化した。またPre-merge build自体の実行時間も各種技術によって短縮されたし、ミドルウェアを使った統合テストも使い捨て環境を作りやすくなったので実行したいときに他のテストとの衝突を気にせず実行できるようになった。気にすることが減るというのは、Pre-merge buildでいろんなことを自動的に確認・検証したいというニーズを満たす上で必要だ。統合テストを回して互換性が壊れていないことを確認したいのに、DBが他のテストで使われているので待つ必要がある、なんて事態は避けたいのだ。

10年前と今の「生産性」を比べたときに、この開発プロセスの違いは大きな差を生むに違いない。

自動化、有機的連携、そしてリアクティブワークフロー

別の表現をすると、10年前は省力化のために作業の自動化が推奨され、それで充分に競争力を生んでいた。その後、自動化された作業を有機的に繋ぎ合わせるようになり、CIパイプラインが登場した*3。その後でWebAPIやWebHook等の整備により、PRやcron以外でもパイプラインを自動発火することが増え、リリース作業やデプロイ、ドキュメント整備など多様な使い方をされるようになったと言えるだろう。 近年は脆弱性対応の観点から、ライブラリのリリースや脆弱性の公開をイベントとして使えるようになってきている*4が、これも従来なら開発者が各ベンダからの情報をRSSSNS等を使ってウォッチする必要があった。

さてジョブの並列実行・並行実行ならびに有機的連帯についてはPipelineやWorkflowという用語が存在するが、このヒトの指示による実行ではなくイベントに応じた実行を意識した組み方については特に固有名詞がないように思う。イベントを受け取りステートレスな要素をつなぎ合わせて実行する様がリアクティブシステムの考え方に近いことから、ここではリアクティブワークフロー(Reactive Workflow)と呼ぶ。

なぜリアクティブワークフローが好ましいのか、その説明はReactive Manifestoが流用可能だ。使いたいときにすぐ使え(Responsible)、ジョブがステートレスなため壊れても再実行でほぼ対処でき(Resilient)、ビジネス上の要請あるいは技術的な必要性から生まれる突発的な負荷に対応する(Elastic)。また各ジョブの入出力がコミットハッシュやパラメータなど不変なものがほとんどであることと、ワークフロー基盤がサーバーレスアーキテクチャで構成されることが多いことから、メッセージ駆動のメリットも享受できると思われる。

おそらくユーザ(開発者)としてはResponsibleであることが最も重要だ。JenkinsのマスターやエージェントをLAN内に構築していた(サーバーレスでなかった)ころは、資源をうまく使うように夜間に定例処理を回したり週末にテストを回したりといった工夫が必要だった。これはリアクティブワークフローを組み、資源の管理をSaaSあるいはIaaSに押し付けることで、必要なときに必要なだけ実行し即結果を受け取れるようになった。Elasticであることも合わさって、開発者が本質的でない気遣いを排し(ビルド並列化などによって)開発速度を上げることに貢献できるわけだ。

こうしてみると、コンテナ(ジョブはコンテナ内で走ることがある)やサーバーレスといった新技術によって最近のDevlopment Workflowも支えられているのだなと感じる。

コラボレーションの重要性

近年の開発手法、例えばSREやDevOps, Lean, cross-functional teamについて紐解くと、個人の役割を明確化・細分化すると同時に組織のサイロ化を防ぐことに多大な関心を持っていることが伺える。著名なのがError Budgetで、Reliabilityの実現を目的としたSREと多様なデプロイを多数行う(ことによるプロダクトの改善)を目的とした開発という一見相反する行動原理を持つ役割をうまく「同じ課題を解決するチーム」へと仕立て上げ、サイロ化を防いでいる。詳しい話は書籍に譲り割愛する:

他にもプロダクトマネジメントトライアングルでも、立場が異なるヒトをいかに連帯させ機能させるかという関心ごとについて説いているが、その議論の中心はProductという誰もが共有しているモノにある(と私は解釈した)。デザイナーは開発者の生み出す価値をユーザーに届けるためにどうデザインすべきか、経営は開発資源を効率的に使うためにどうマネジメントすべきか、開発者はユーザに製品の魅力を伝えるためにどうコミュニティを築くべきなのか。そのすべてのコラボレーションが製品というOutputを通じてOutcomeを生み出す。開発、ビジネス、ユーザのどれを取っても独立しておらず、すべてがProductや他のロールと繋がっている。プロダクトマネジメントはヒトのコラボレーションを認めてはじめてできるものだ。

もうひとつ、面白い観点にロックスターエンジニアというやつがある。有能なエンジニアはそうでないエンジニアの10倍、あるいは27倍の生産性を持っているというold termだ。これもたぶん昔は説得力のある概念だったのだろうが、近年では全く使われなくなっている。代わりにtechnology raderでは10x teamという表現を紹介していて、素晴らしいoutputが素晴らしいチームによって生まれるとしている。これはGoogleが研究で明らかにしたチームやマネジメントの重要性によっても肯定されていると言えよう。

つまり近年のソフトウェア開発の現場では、個人を育てること以上にチームを育てることに関心を持っている。個人を育て組織を開発することで、ビジネス上の競争力を効率的に高めていけると信じているわけだ。 チームを育てるには様々な手法があるだろうが、成長が内省によって生まれることを考えればBlameless Postmortemのような事実と向き合い理性的な議論を通じてより良い姿を模索する活動には一定の価値があると思われる。失敗時に限らず日頃から反省の機会を持つこと、Scrumで言うSprint Retrospectiveのような定例イベントも助けになるだろう。

さてこの手のイベントをやってみるとわかるのが、準備のコストが高いことだ。Postmortemのためには事実をリアルタイムに記録しなければならないし、ベロシティを測るには日々チケット消化の状況を記録する必要がある。自分とは違う専門性を持つチームメイトに向けて資料を整理することもあるし、わかりやすくするためにグラフを整理することもあるだろう。定期的なイベントの準備をすべて人力でやっていては、継続的にコストを払うことになる。

Development Workflowはこのコストを下げることができる。人手を使って証跡を残していた運用を、自動的にログを残す運用に変えられる。ChatOpsやGitOpsが最もイメージしやすいだろう。 また自動化やSaaSなどによって、相手の専門性に合わせたビューやファイル形式で情報を提供するコストも下げられる。例えばデザイナの要請を受けてCSSを変更したときに、それが既存ページにどういった影響を及ぼすか、percyのようなGUI regression testingが整っていればスクリーンショットを取ること無くPR一本で説明できる。

まとめ

とりとめのないままに書き下した結果、ひどいことになってしまったが。

結局は「cross-functional teamとして高速度で成果を出す」という時代の要請に自動化やコンテナ、サーバーレスといった技術が応えた形がいまのリアクティブワークフローなのだと思う。機械学習や業務改革の文脈で「機械ができることは機械にやらせて、ヒトはヒトにしかできないことをやろう」と言われるが、開発プロセスにそれを適用したものがこれだ。 事実と向き合い考えることこそが、ヒトにしかできないことだ。事実(ログ)の集約や解析、テストやビルドといった開発作業、ライブラリの更新や脆弱性報告の精査といった定例作業はすべてリアクティブワークフローにやらせる。私達は上がってきたデータをもとに考え、議論し、内省し、立てた仮説をもとに次のPRを作る。Gitにpushすれば機会が変更の妥当性をまず検証してくれるので、ヒトは既知の問題が無いことを前提に議論を尽くすことができる。タブ文字かスペースか、括弧の位置はどこにするかなんて議論をPRでする時代ではもう無いのだ。

もちろんここで議論したのはある種の理想形で、すべてのプロジェクトがこれを満たせるとは限らない。私が見ているFOSSプロジェクトにも、まだ手動で色々とやらなければリリースすらできないものもある。また10年前でも既にリアクティブワークフローを回していたところもあるかもしれない。

*1:このエントリではJenkinsが「古いWorkflowシステム」の代名詞として使われている節があるが、それは単に筆者個人の経験がそうだっただけで、Jenkinsというプロダクトがレガシーというわけではない。Cloudbees Flowなどを参照

*2:この点で、本考察はここ10年ではなく13〜15年とかで切ったほうが多くを学べるのかもしれないが、その頃は私が技術的情報収集をしていなかったのでよくわからない

*3:時期はよくわからないが後発のJenkins 2.0が2016年4月なので2014〜2015くらいか?

*4:GitHubこれとか、試してないけどSonatypeのこれとかjFrogのこれとか

SpotBugs 4.0.0がリリース&貢献者募集のお知らせ

.classファイル向け静的解析ツールであるSpotBugsのバージョン4.0.0がリリースされました。 FindBugsとの互換性を極力保っていた3.1系と異なり、いくつかのAPIが変わったり機能が削られていたりします。 特にプラグイン開発者は、SpotBugs 3.1 から 4.0 への移行ガイドを確認するようお願いします。 一応ユーザに使われていない機能を調べて削ったので、一般ユーザに対する影響は限定的なはずです。

なおJava 9以降にはまだ対応していません。コミュニティで議論された4.0でやりたいことには含まれていたのですが、貢献がありませんでした。 ObjectWeb ASMやApache BCELの更新に追随しているのでコア機能は動作しますが、標準DetectorのうちNullness周りをはじめとしたいくつかのDetectorが動作しないことが知られています。のでJava9移行のプロジェクトでもSpotBugsの利用が必要な場合は、オプションで利用するDetectorを絞ってください。詳細は以下のissueを確認すると良いでしょう:

なお最近はIDEAプラグインの実装に動きがあるので、SpotBugsを使いたいIDEAユーザはwatchすると良いかもしれません。 Gradleプラグインもフルスクラッチで書き直し中で、正式リリースも近いのでGradleユーザは是非試してみてほしいです。

SpotBugsは貢献者を求めています

さて本日、サイボウズさんのブログでOSSに貢献する利点や手法が紹介されています:

そしてSpotBugsは知名度もあり、本家のFindBugsよりも使われているプロダクトとなりましたが、まだ貢献者が少ない状態です。コードが複雑なのは難点ですが、貢献を始めるには良いプロジェクトかもしれません!ぜひ覗いてみてください!!!

チームには私の他に、issueでの返信やコードレビューを担う人、Eclipse PluginやMaven Pluginに特化した人がいますが、間に落ちている問題もJava9対応に限らず多くあります。また直接コードに貢献しなくても、使い方や利用状況をブログやSNSで発信するだけでも他のユーザの参考になります。どういう形でも構わないので、ぜひ検討してみてください。コミュニティの言語は英語ですが、日本語のわかる私を貢献の取っ掛かりに利用していただいても構わないです。Twitterやメールで連絡ください。

なお継続的に貢献いただいた方には、Organization参加要請を出していきます。実績としても、2019年には2名をOrganizationに招待しています。お邪魔でなければ受けていただければ幸いです。一見リリースが少ない不活発なプロダクトではありますが、チーム自体はissueだけでなくGitHub Teamを通じてコミュニケーションを取っていますので、招待や議論は問題なく行えます。よろしくお願いします。

あわせて読みたい

WIP: 2020年やりたいこと50

2020年やりたいこと100 - ややめもを見て良い取り組みだと思ったので書いてみたのですが、100も出てこないのでとりあえず半分でお茶を濁す作戦。

健康(1~10)

  1. 週2回の筋トレを継続
  2. 平日は10,000歩以上歩く
  3. 体重を72kg±2程度に抑える
  4. 24時までに寝る
  5. 平日のコーヒーをやめる
  6. 水やお茶を1日2リットル以上継続して採る
  7. 朝ごはんの菓子パン依存率を減らす、食パン+野菜や肉などで代替する
  8. 皮膚のプロアクティブ治療を継続する
  9. 暗いところでスマホで読書する習慣をなくす、視力低下を防ぐ
  10. 平日の昼寝を継続する

育児(11~20)

  1. 息子氏がひらがなを読めるようにする
  2. 息子氏を新幹線に乗せる
  3. 息子氏の小学校を決める
  4. 息子氏の中学校以降を検討する
  5. 絵本を継続して読みきかせる
  6. 毎朝30秒のハグを息子氏が嫌がらない限り継続する
  7. 幼稚園への登園中に日本語で息子氏にどんどん話しかける
  8. 息子氏を動物園や水族館、スポーツの試合に連れて行く
  9. 息子氏の友人一家と遊ぶ
  10. 日本語を話す息子氏の友人を作る

家庭(21~30)

  1. 家族と中国国内旅行に行く
  2. 家族と日本国内旅行に行く
  3. 音声認識コントローラを導入する
  4. 無駄を省き効率化を進めることで、平日夜の時間を創出する
  5. 平日朝の忙しさを緩和する(≒息子氏をきちんと起こす、息子氏の朝食や着替えをきちんと済ませさせる)
  6. 投資や副業などで収入を増やす
  7. 映画や美術館、新しいショッピングモールなどに行く習慣を作る
  8. そのための日常的な情報収集を行う(WeChatなど)
  9. 実家との継続的な連絡を取る
  10. スケジュールや心持ちに余裕を持つ、息子氏の突発的な風邪などを常に想定に入れる

趣味(31~40)

  1. Skebでらきすけさんにライチュウを描いてもらう
  2. SpotBugs Gradle plugin v2をSpotBugs Organization下でリリース
  3. SpotBugs 4.0.0安定版をリリース
  4. Sphinxに3つ貢献する(docker imageとか)
  5. n月刊ラムダノートに寄稿する
  6. JavaプロジェクトをひとつKotlinで書き直す
  7. VRゴーグルを導入する
  8. 週1冊の本を読む
  9. GitHub sponsorsに登録する
  10. ブログ記事を6つ書く

業務(41〜50)

  1. 部署の売上目標をつくり達成する
  2. 部署の利益目標をつくり達成する
  3. 部署の技術目標をつくり達成する
  4. 個人の技術目標を策定・達成する
  5. 職場のモニタを新調する
  6. 職場のポインティングデバイスを新調する
  7. 日本の技術イベント登壇
  8. 上海の技術イベント登壇
  9. 3回ほど社内技術勉強会で公開セッションをする
  10. M5StickCで職場用ガジェットを作る

2019年のFOSS活動状況まとめ

昨年のに引き続きFOSS活動状況をまとめます。2019年12月30日時点の情報です。

概要:昨年比22%増

GitHubのプロファイルページによると今年のpublic contributionsは1,166で、昨年が950だったので約22%増です。commit 61%のpull requests 21%なので、けっこう手を動かしてコードを書けたと思います。業務でのpublic contributionsはゼロなので、すべて趣味開発です。

f:id:eller:20191230233035p:plain
Contributions at GitHub in 2019

今年新規に作成したのは主に gradle-semantic-release-plugin, open-the-sesamespotbugs-gradle-plugin-v2 です。gradle-semantic-release-pluginについては3月に紹介記事も書いています。

7〜9月の Contributions が減っているのは勤務先における異動による影響です。これがなかったら1,500くらい行けたかもしれません。

SpotBugs周りの開発

2019年もSpotBugsが一番活発な開発でした。コア開発58 commitsと昨年比で減っていますが、SonarQube pluginGradle pluginは横ばいという感じです。

f:id:eller:20191230235144p:plain
Contributions for spotbugs/spotbugs in 2019

特に現Gradle PluginがGradle本体からのforkなため、internal APIに依存していたりAndroid開発へのケアが抜けていたりという課題が多かったので、半年ほどかけて準備をしスクラッチで書き直しています。Gradle Pluginポータルにはデプロイしたので、承認されしだい利用可能となるはずです。

今年の目玉はSpotBugsのダウンロード数がFindBugsのそれを上回ったことですね。あれから苦節3年弱、継続は力なりを体現した感じです。リリースもそうですがマニュアルサイトの作成と管理も結構関われているので、嬉しく思います。

その他

ひとつプロジェクトに関わらせていただいているのですが、まだ公開できる状態ではなさそうです。あまり積極的に貢献できていないとはいえ意義のあるプロジェクトですので、機が熟したらいろいろと紹介していければと思います。

JavaウェブアプリプロジェクトにJavaScript/TypeScriptなどの静的アセットをどう配置するか

以前のJavaウェブアプリ開発では、JavaScriptをはじめとした静的アセットはsrc/main/webappディレクトリに配置するのが普通だった。そこに置くことでmaven-war-pluginのようなビルドシステムが.warファイルの中に突っ込んでくれる。この挙動は今でも変わらないが、src/main/webappディレクトリに静的アセットを直接置くにはいくつかの問題がある:

  1. TypeScriptコンパイラやBabelのような、静的アセットに事前処理を施す手法が普通になった。
    • src/main/webappに処理後のリソースを置くようにもできるが、mvn cleanなどで処理後のリソースが削除されるようにする手間を考えるとtargetディレクトリ直下にあるwebappDirectoryに置くのが無難と思われる。
    • よってsrc/main/webappには事前処理を必要としないアセットのみを置くことになるが、次に挙げる理由からそれも必要なくなってきている。
  2. フロントエンド開発が複雑化し、サーバサイドと同一プロジェクト・モジュールで開発することが難しくなった。
    • ここで言うフロントエンドはサーバからのレスポンスを受け取ってユーザ向けにインタフェースを提供する部分。HTML5アプリだったりモバイルアプリだったりする。
    • フロントエンド開発で使うビルドツールやライブラリ、development workflowは必ずしもJavaのそれと一致しない。例えばブラウザやOSのプレビュー版が出たときにフロントエンドだけテストを回すといったことができればより高速に検証できる。またJavaScriptライブラリはJavaライブラリ以上にこまめなバージョンアップが必要となるケースが多い(脆弱性対応とか、OSアップデート追随とか)ため、フロントエンドを独立に更新していけるプロジェクト体制を整えることが必要になる。
    • frontend-maven-pluginを使ってJavaのビルドツールに統合することは可能だが、そもそも開発者が異なるケースではあまり意味がない。OpenAPIやgRPC、GraphQLなどのインタフェース定義をフロントエンドとサーバサイドの間に入れて独立に開発していく体制を組むほうが良い。
  3. 静的アセットはTomcatのようなサーブレットコンテナではなく、Amazon S3のようなストレージサービスやCDNによって配信することが好ましい場合が多い。
    • 例えばGoogle Cloudのドキュメントでは、アプリによる静的アセットの配信をstraightforwardだが欠点のある手法として紹介している。

この問題を解決するために、大きく分けて3つの手法を紹介する。

1. 同一プロジェクト内で2つのビルドツールを併用する

サーバサイド開発にはMaven/Gradleを、フロントエンド開発にはnpm/Yarnを利用するが、プロジェクトは分割しない手法。

この手法1.を採る場合、アセットは .war.jar に同梱する形が最もやりやすい。例えばSpringだと同梱されているリソースをStatic Resourcesとして配信可能。懸念されるサーブレットコンテナの負荷低減は、HTTPレスポンスのキャッシュやCDNによって実現することになる。

1.1. サーバサイドをMaven/Gradleで、フロントエンドをMaven/Gradleで包んだnpm/Yarnで開発するケース

サーバサイドとフロントエンドを同一のビルドライフサイクルに乗せるため、frontend-maven-plugin/frontend-gradle-pluginを使って、Maven/Gradleからnpm/Yarnを実行することになる。フロントエンド開発の複雑さがあまりない場合、Java開発者がフロントエンド開発も兼ねる場合に重宝する。例えばsrc/main/typescriptにTypeScriptを、src/main/sassにSassを置くような体制。

プロジェクトルートにpom.xmlsettings.gradleを置いてフロントエンド用モジュールをMavenのサブモジュール/Gradleのサブプロジェクトとして扱うこともできるが、そこまでやるならば次に挙げる手法2で充分であろう。

なおフロントエンドの成果物をMaven Repositoryにzipしてデプロイし、他プロジェクトから利用するということを以前試したことがあったのだが、zip/unzipを多用し性能が出ないのでおすすめしない。普通にファイルコピーで済ませるために、利用者(サーバサイド)と同一プロジェクト(Gitリポジトリ)に置くようにしたほうが良い。

1.2. サーバサイドをYarnで包んだMaven/Gradleで、フロントエンドをYarnで開発するケース

逆にYarnを主、Maven/Gradleを従とする手法。サーバサイドが小さいときは使えるかもしれないが、そこまでするなら手法3が素直。

2. サーバサイドとフロントエンドでプロジェクトを分ける

各モジュールの開発技法において、自由度を確保することを意識した手法。それぞれ異なる開発者が請け負う場合にはこういった形を取ることが多いのでは。Gitリポジトリを分けるかmonorepoにするかという点も考慮が必要だが、自分の開発経験(小型中心)だと分割の必要性を感じたケースは無いので、ここではmonorepoという前提をおいて考える。またフロントエンド=ウェブアプリとし、モバイルアプリ開発については触れない。

サーバサイドとフロントエンドのつなぎ目には、OpenAPIなどのAPI定義手法を利用する。サーバサイドとフロントエンドを突合する部分(CD、プロビジョニングなど)が複雑化する可能性はある(例えばspring-boot-vue-exampleではserverとclientのデプロイにスクリプトを使っている)が、多くの場合でさほど問題ないのでは。プロジェクト構成例は以下の通り:

interface/
  openapi.yml

server/
  src/
  pom.xml // 最終成果物は .war, executable .jar あるいはコンテナ

frontend/
  src/
  package.json // 最終成果物は .zip あるいはコンテナ

docker-compose.yml // あるいはTerraformとかCloudFormationとか?
README.md

この場合、フロントエンドの成果物はサーバサイドの成果物に含めない。またフロントエンド開発にサーバサイドの挙動をmockする必要があるが、Open APIならmock serverを利用できるし、その他のAPI定義手法でも似たものがあるはず。

3. サーバサイドをNode.js化するという選択肢

これはタイトルに全く沿わない手法であるが、真面目にありがちな話だと感じている。つまりJavaJavaScriptという異なる言語を同一プロジェクトで使用することが複雑さを生むならば、その原因を取り除けないだろうか?ということである。

そもそもNode.jsが発展しTypeScriptのような型を使った開発も可能な現代において、サーバサイドとフロントエンドで異なる言語を使うモチベーションがどの程度あるだろうか?

サーバサイドをNode.jsにすれば、プロジェクトの複雑さを除くことができる。性能に直結する非同期処理もCompletableFutureRxJava、spring-webfluxで使われるReactorに比べればJavaScriptasync/awaitの方が簡潔になるし、何よりJavaScriptは言語自身が「メインスレッドをブロックしない」ことを前提に設計されているので、うっかりblocking処理をサーバ内に書くことを避けやすい。 もちろんJavaの方がやりやすいこともたくさんある。またJVMの進展は非常に目覚ましいものがある。運用の知見など積み直しになるものもあるため即置き換えとは行かないし、する必要もない。結局道具は適材適所なので、Javaエンジニアだと自分を狭く定義するのではなくて、JavaもNode.jsもTypeScriptも学んで使いこなせばいいし、KotlinやDartのような新しい言語とそのコミュニティにも目を向けていければ良いのだと思う。

まとめ

小型のプロジェクトでは手法1.1、専任のフロントエンド開発者がいる場合は手法2を使うことをまず検討すると良いだろう。