Kengo's blog

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

SLF4Jの1.8は安定版が出ないので2.0を使おうという話

前回の投稿で、SLF4Jには現在1.7、1.8、2.0の3バージョンがあるという話をしました。

これについてSLF4Jのuser向けメーリングリストで質問をしたところ、1.8の開発中にFluent Logging APIを追加するために2.0へとメジャーバージョンを上げたという旨の回答を得られました。つまり1.8は今後開発されず、安定版も出ないことになります。

[slf4j-user] Will version 1.8 be released in future?

よって現時点では1.8を利用する積極的な理由はあまりないと思われます。ライブラリ開発者としては、JPMS(Jigsaw)あるいはFluent Logging APIが必要なら2.0、そうでないなら1.7を選択することが良いのではないでしょうか。

Javaライブラリを配布する際のログ周りにおける配慮と実践 2020

この記事は、2013年に書いた記事を現状に合わせてアップデートするものです。結論から言うと、当時から id:miyakawa_taku さんがおっしゃっていた「APIは依存に含めて良い」を支持するものです。あるいは無難にバージョン 1.7.30 を使っておきましょう。

blog.kengo-toda.jp

slf4j-apiに1.7, 1.8, 2.0の3バージョンが生まれた

現在slf4j-apiには3つのバージョンが存在します。現在のSLF4Jエコシステムを考える上では、これらの違いを抑える必要があります:

1.7.x
Java 1.5から利用できるバージョンです。安定版にこだわるなら未だこのバージョンを使う必要があります。
1.8.x
JPMS(a.k.a. jigsaw)を採用しServiceLoaderを使ってBindingを呼び出すようになったバージョンです。Java 1.6が必要です。alpha0が出てから3年以上経ちますが、未だbeta4です*1。作業用ブランチも残っていないので、1.8は安定しないまま2.0開発に移ったものと思われます。
2.0.x
このバージョンからはJava 8が必要です。Fluent Logging APIが実装され、ログの書き方の選択肢が増えました。新しい実装はinterfaceのdefault methodを使って実装されているため、slf4j-api単体で変更が完結しており、binding側は変更なく利用できます。

複数バージョンの存在を踏まえ、ライブラリ配布者は何を気にするべきか

前出の情報を紐解くと、3バージョン間に眠る2つの差が見えてきます:

  1. APIの差。slf4j-api 2.0.x には従来存在しなかったメソッドが増えている。これに依存したライブラリのユーザは、slf4j-api 2.0.xを利用しなければ実行時に NoSuchMethodError が出るかもしれない。
  2. binding選択手法の差。slf4j-api 1.7.x と1.8.x以降では実装を決定するための手法が異なるため、apiとbindingのバージョンは揃える必要がある。

このうち2については、アプリケーションをビルドする開発者に責任があります。実行時にCLASSPATHにどのバージョンのAPIとbindingを含めるか、彼らに決定権があるからです。のでライブラリ提供者としては、彼らに選択肢と必要な情報を提供しつつ、1に取り組む必要があります。

考慮すべきはライブラリでFluent Logging APIを使っているかどうか、です。このAPIに依存していないなら、実行時にslf4j-apiのどのバージョンを利用しても問題ありません。

もし使っているなら、その旨をユーザに伝えて実行時にもslf4j-api 2.0.xを使ってもらう必要があります。そしてそのための最もsemanticな方法が、compileスコープで依存することです。ライブラリが2.0.xに依存しているにも関わらずに古いバージョンで依存が上書きされてしまった場合、ビルドツールによっては警告を発してくれます。Gradleの場合はもっと細かな依存指定も可能です。

よって、現時点では slf4j-api には compile スコープで依存することが望ましいと思います。bindingは引き続きprovidedあるいはtestスコープが望ましいでしょう。

どのバージョンを使うべきか

安定版にこだわるなら1.7.xを、JPMSを使いたいなら1.8.xか2.0.xを、Fluent Logging APIを使いたいなら2.0.xを選ぶことになるでしょう。

1.8.xと2.0.xはJPMSに準拠しており、モジュールを利用したビルドを行っているプロジェクトでも安心して使うことができます。ただどちらも安定版が出ていない点には注意が必要です。

2020-08-07 追記: 1.8を選択する積極的な理由がないことを以下の記事に記載しました。 SLF4Jの1.8は安定版が出ないので2.0を使おうという話 - Kengo's blog

マイナーケース:ライブラリとしても実行可能ファイルとしても配布する場合

私がメンテに参加しているSpotBugsは、spotbugs-gradle-pluginやspotbugs-maven-pluginなどから利用されるライブラリとしての性質と、CUIGUIで実行される実行ファイルとしての性質とを持ち合わせています。内部的にはDistribution Pluginで配布用パッケージを作成しています。

この場合、Maven Centralにアップロードする pom.xml にはSLF4J bindingを依存として追加しない一方で、distribution pluginの対象にはSLF4J bindingを追加する必要があります。

runtimeOnly 依存を使うことが素直な解決になるでしょう。configurations.runtimeClasspath を配布用ファイルに入れる形になります。

ただSpotBugsの場合はEclipse用依存を別に管理している(Eclipse用のruntimeClasspathにはSLF4J bindingを入れたくない)ので、以下のようにconfigurationを自前で定義しています

plugins {
    id 'distribution'
}
configurations {
  slf4jBinding
}
dependencies {
  implementation 'org.slf4j:slf4j-api:1.8.0-beta4'
  logBinding ('org.apache.logging.log4j:log4j-slf4j18-impl:2.13.3') {
    exclude group: 'org.slf4j'
  }
}
distributions {
  main {
    contents {
      from ([configurations.compileClasspath, configurations.slf4jBinding]) {
        into 'lib'
        include '**/*.jar'
      }
    }
  }
}

*1:newsページにはbeta5が公開された旨が記載されていますが、Maven Centralには来ていませんし、タグも残っていません

研究などの学術的目的で問い合わせや協力依頼をいただく際の私の対応方針

たまに研究への協力依頼などをいただくのですが、それに対するポリシーをこちらにまとめます。説明用です。

連絡先

Twitterかメールを推奨しています。メールアドレスは私のウェブサイトで紹介しています。

サーベイに対する協力

常識的なボリュームのサーベイであれば協力させていただきます。URLと回答所要時間を共有いただければそれで充分です。

その他の協力

SpotBugsの使い方やbytecode manipulationなどについて、突っ込んだ質問をいただくことがあります。こうしたクローズドな質問はStackoverflowやGitHubで行っているFOSS活動とは一線を画するものですし、対応コストも高いので、基本的にはお受けしません。まずは対応するFOSSコミュニティ、あるいは周囲の指導担当者からの協力を得るようにしてください。ウェブサイトやメーリングリストGitHubプロジェクトなどで質問を行えることが多いはずです。

どうしても必要と思われる場合は、所属機関のメールアドレス(.ac.jpドメインなど)から所属と研究目的、得たい協力について説明するメールを送ってください。この際、指導担当者もCCに入れてください。指導担当者はあなたの指導に何らかのポリシーを持っているはずで、それを逸脱する助力をすることを避けるためです。

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を通じてコミュニケーションを取っていますので、招待や議論は問題なく行えます。よろしくお願いします。

あわせて読みたい