Quantcast
Channel: Kengo's blog
Viewing all 157 articles
Browse latest View live

JJUG CCC 2021 FallでLT参加してきました

$
0
0

JJUG CCC 2021 FallにてJSR305に代わる静的解析用標準アノテーションの策定を目指す活動について紹介しました。スライドは以下からご覧いただけます:

speakerdeck.com

動画は後日JJUG公式から公開されるようです。公開されましたらこちらの記事からリンクします。

以下、単に感想です。

静的解析用アノテーションが多数存在する件

この17年間で生まれた静的解析アノテーション、数はかなりあるんですが歴史等まとまっているものではないので、Maven Centralにデプロイされた日をベースに整理していました。スライド草案に記載した歴史は以下のように非常に長いものになりました:

f:id:eller:20211121203045p:plain
関連アノテーションの歴史

地味に困ったのがJSR305に関する意思決定がJCPのサイトで公開されていないことです。

JCPからリンクされているGoogle Groupを見ても「なぜ活動が止まったか」はわかりません。ここでの活動自体が非活発になったので。近年のJEPがチケットやメーリングリストで管理され透明性が高いのは、こうした反省を踏まえたものなのかもとか思いました。

わかりやすい動画を作成したい

私の動画はmacOSのスクリーンキャプチャ機能で録画しただけのものです。解像度を変えるためにQuickResを購入しましたが、ほかはマイクのようなハードウェア含め工夫をしていません。

が、今回みなさんの動画を見ていると、画面隅に顔を映している方がいらして、しかもその方が気持ちわかりやすかったりするんですよね。デジタル表現の工夫や技法は重要になっていますし、きちんと学びたいなと思いました。まぁ動画編集の時間が作れるかは別の問題ですけど……(今回も体調不良で急遽幼稚園を休んだ子供の面倒を見ながらの録画だった)

JJUG CCCでの過去発表へのリンク


WIP: Gradleの機能でどこまでビルド性能が改善するのか

$
0
0

Gradleの --parallelJUnit並列実行、Configuration Cacheなどがどの程度ビルド性能を改善するのか、いくつかのOSSで実測してみた。利用した機能の概要は以下オフィシャルサイトを参照のこと。

docs.gradle.org

SonarQubeの事例

バージョン 9.0.1.46107リリース後の masterブランチで実践。進捗などは以下のIssueで管理している:

github.com

もともとGradle 6.8.2を使っていたが、1,092ものbuild deprecationsが報告されておりGradle 7に直接アップグレードすることができない状態。Gradle 7ではいくつかのTaskのaccessorがLazy ConfigurationのためのAPIで置き換えられており、移行が必要。また依存管理に非推奨となっているcompiledefaultを使っており、implementationruntimeClasspathJava Library Pluginが提供するapiなどへの移行が必要。

最大の難関は com.github.hierynomus.licenseプラグインcom.github.johnrengelman.shadowプラグインが古く、数多くの警告が出ているというもの。タスクの入出力が不明瞭なためキャッシュも効いていない。またプラグインの最新版がGradle 6をサポートしていないため、これらのアップグレードはGradleの7へのアップグレードと同時に実行する必要があった。

f:id:eller:20211020102235p:plain
手を加える前のBuild scan結果。プラグイン由来の警告が多い。

改善結果

21分半かかっていたフルビルドが16分弱(73.3%)で終わるようになった。

f:id:eller:20211204093213p:plain
中央値で比較して73.3%に短縮

Parallel buildはビルド時間を1分程度短縮する効果があったが、GitHub ActionsのHosted Runnerは2コアしかないためデフォルトでは2並列にしかならない。サブプロジェクトが多いなら --max-workersなどの設定でワーカー数を増やす方が良い。

f:id:eller:20211020104306p:plain
4並列でビルドしたときのタイムライン

テストの並列実行は残念ながらビルド性能の改善に繋がらなかった。Parallel buildでCPUを使い切っており性能が出なかったためと思われる。実際サブプロジェクトひとつのテストを実施してみると、テストの並列実行を有効化したほうが性能が出る。サーバが必要なBuild Cacheは試していないが、これを有効化することでフルビルドの必要性が下がるため、テスト並列実行の重要性も上がると期待される。

SpotBugs

実験中。Antから無理やり移したタスクがEclipse plugin周辺で残っているので、まず buildSrcにビルドロジックを移動させるところからやる必要がある。 Eclipse関連の依存をMaven Centralからダウンロードするようにしたいが、以下の課題が未解決。

www.eclipse.org

Gradleプラグインのメジャーアップデートにおいて、古いGradleへのサポートをいかにして切るか

$
0
0

spotbugs-gradle-plugin v5をリリースしました。beta1のリリースから約3ヶ月間かかっています。

github.com

Gradleプラグイン開発はややマイナーな取り組みだと思うので、Gradleプラグイン開発のメジャーアップデートがどういうものだったのかをちょっと紹介します。

なぜ後方互換を壊す必要があったのか

spotbugsプラグイン固有の理由はもちろんあります。デフォルトの設定がうまくなかった、リソースリークの解消にはデフォルトの動作モードを変える必要があった、などです。これに加えてGradleプラグインならではの理由があります。

古いGradleへのサポートを切るため

Gradle 7をサポートするには、Gradle 7で削除されたGradle APIへの依存を切る必要があります。この場合、移行先Gradle APIが存在するGradleのバージョンが最低限サポートできるバージョンになります。例えば今回削除された compile configurationの代替となるimplementation, api configurationsはGradle 3.4で導入されているので、プラグインがこれらのconfigurationに依存する場合は3.4が最低限サポートできるバージョンになります。

新しいGradleで導入された機能を使うため

Gradleは新しい機能を継続的に取り入れており、中にはJavaプロジェクト開発の体験を大きく変えるものも存在します。依存管理やキャッシュの改善、差分コンパイルの実現によるビルド時間の短縮が良い例ですね。Gradle社としてはこうした改善に命運を賭けているようで、Developer Productivity Engineering (DPE)という造語を作って強く押し出しています。

gradle.com

そしてこうした機能を前提にプラグイン開発を行う場合、自然と古いGradleに対するサポートを切る必要が出てきます。 例えば今回入れたJava toolchain対応はGradle 6.7で導入され、7.0以降で安定しています。これがv5とv6のサポートを切る主な動機となっています。

コードを簡潔に保つため

とはいえ動的にGradleVersionを調べるなどしてプラグインの動作を切り替えることで、古いGradleをサポートしつつ新しい機能を使うことも可能です。例えば今回のリリースでもGradle 7.1で取り入れられた JavaPluginExtensionを使いつつGradle 7.0もサポートしています。

github.com

しかしこの方法はコードが複雑化する上、概念レベルでの変更への対応が難しいため、そこまでコストを掛けて互換を保つ判断はあまりしないのではと思います。 むしろこうした変更をパッチアップデートで行うspotlessのようなプラグインも存在します。

ユーザのスムーズな移行のため、Gradleのメジャーバージョンを複数サポートするバージョンを提供することは重要ですが、同様に新しいGradleメジャーバージョンに集中して管理しやすいリポジトリを保つこともまた必要です。Gradleの場合コミュニティの進歩が速く、ユーザもそれを前提に頻繁なアップデートをある程度覚悟していますので、プラグイン側が過度に古いGradleのサポートに固執する必要はないとも思われます。

まとめ

Gradleプラグインのメジャーアップデートにおいて、古いGradleへのサポートはコードを簡潔に保つためにも早めに切ってしまうべきです。マイナーアップデートやパッチアップデートで古いGradleへのサポートを切るプラグインもあり、それがコミュニティに受け入れられているくらいですので、古いGradleをサポートすることよりも新しいGradleをサポートするための素早い継続的デプロイを実現することにコストを割いたほうが良いでしょう。

なおGradleプロジェクトにおける素早い継続的デプロイには、semantic-releaseの活用がおすすめです。私が実装しメンテナンスしているgradle-semantic-releaseについては以下の記事で紹介しています。

blog.kengo-toda.jp

2021年のOSS活動状況まとめ

$
0
0

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

概要:昨年比30%増

GitHubのプロファイルページによると今年のpublic contributionsは1,865で、昨年が1,440だったので約30%増です。commit 63%のpull requests 12%なので、引き続き手を動かしてコードを書けたと思います。

f:id:eller:20211220102614p:plain
my GitHub profile (2021/Dec/20)

主なリリースはspotbugs-gradle-plugin 4.6.1~5.0.3SpotBugs 4.2.1~4.5.2gradle-semantic-release-plugin 1.4.14~1.6.0actions-setup-docker-compose 1.0.0~1.0.3でした。

SpotBugs周辺の開発

一番大きい貢献はsonar-findbugsのメンテナを引き渡したことだと思います。このプロダクトを開発する過程においてSonarQubeと距離を置きたいと思うようになったため、先日のv4.0.5リリースから新しいメンテナ2名にPRのレビューやマージ、リリースプロセスの扱いなど全般を引き渡しました。活発なプロダクトの引き渡しはこれが初めてだったのですが、いまのところうまく回っているようで安心しています。

またSpotBugsコアにおいてSARIFレポートのGitHub Code Scanning API対応(v4.4.1)と複数レポートの出力(v4.5)とを完了したため、人間向けのHTMLレポートと機械向けのXMLやSARIFレポートとを同時生成するという普遍的なニーズに応えられるようにできました。これユーザには地味に便利な更新なんじゃないかと思います。

なお偽陽性偽陰性は3つ修正しました。これらも歴史が長くユーザ基盤のあるプロダクトとしては望まれるものでしょう。

発展的内容としてはGraalVMのnative-imageでパフォーマンスが向上するか試しています。残念ながら優位な差は出ませんでしたが、picocliで作ったCLIツールのネイティブイメージを作るための必要な知見が溜まったのは良かったです。

GitHub Actions周りの開発

actions/setup-java v2.3.0dependency cacheを実装しました。JavaプロジェクトをGitHub Actionsでビルドしている人なら、この機能による高速化とWorkflow定義の短縮化の恩恵を受けているのではないでしょうか。名前こそ出ませんでしたがGitHub公式ブログで紹介されたのが嬉しかったです。

また5月にはwrapper-validation-actionの安定性を向上させています。このActionは通信断でわりとすぐ落ちていたので、この変更の恩恵を受けている開発チームはけっこうあるんじゃないでしょうか。

GitHub Actionsについてはブログ記事も多く投稿しました。Dependabot周りで変更が多かったため、Dependabotが作るPRでいかに(SonarQubeのために)Secretsを使うか、を探求していた気がします。

なおreadthedocs-action公式のPRレビュー機能が充分に実装されたため、そろそろ不要かなという気がしています。

プログラミング言語

2021年はGroovyとJavaをKotlinで置き換えた年になりました。Javaはもちろん良い言語で、自分の強みの大きな部分を構成する言語です。と同時に変化の速い言語環境に身を置くことはアンテナを高く保つために有用ですし、Gradleビルドスクリプトに静的型付けを持ち込めるという利点は大きいと判断しています。並列実行性能を上げる技術基盤としての価値も期待していますが、そちらはまだ検証段階に至れていません。

似た理由でPowerful Command-Line Applications in GoをベースにGolangのキャッチアップもはじめましたが、こちらは2022年の挑戦になりそうです。

また自分がとても尊敬しているstatic program analysisの講義をしているAarhus Universityが新しくFlix言語を開発中、ということを知ってキャッチアップしています。PureとImpureを明示的に扱うというアプローチが面白いです。なおPRはひとつ送りましたが、残念ながらinvalidでした。最新のリリースではなく、デフォルトブランチでの挙動を確認すべきでした。
Flixはまだツールチェインに乏しいのでflix-gradle-pluginを実装してみましたが、言語標準のPackagerに手を入れないと依存管理周りは良いものにならなさそうなため、最近はScalaを読んでいます。

その他

Vert.xでReactiveなドキュメントビルダーのPoCも書いたのですが、こちらは技術的に無理は無さそうだけど言うほどパフォーマンスに寄与しなさそうという結論を出しました。外部サービスにビルドを委ねる大型プロジェクトでは意義があるかも?ですが、新ツールを書き直す程ではないかなと。
ReactorやVert.xのようなReactiveにプログラムを動作させるための取り組みには、引き続き関心を払っていこうと思います。

Sponsorを募集しています

昨年2月からGitHub Sponsorsを始めています。特にSpotBugsないしそのGradle プラグインをご利用の皆さま、ご支援のほどよろしくご検討ください。 SpotBugsのような古く自分ではあまり使わないプロダクトの保守を続けるにあたって、ユーザーや開発者からのわかりやすく明示的な支持があるとありがたいです。

github.com

継続的に支援するほどでは……という場合、ぜひこちらのGitHub Discussionsにて「誰が、どこで、どのように使っているか」を共有していただけるだけでも助かります。よろしくお願いします。

github.com

JavaScript Actionsをnode16で動かすようにする

$
0
0

この記事はCI/CD Advent Calendar 2021に参加しています。


先日GitHub ActionsがNodeJS v12のみならずv16でも動くようになりました。

github.com

今まではNodeJS v12しかサポートされていませんでしたが、このv12は来年の4月でサポートが切れます。速やかにv16に移行したほうが良さそうですね。必要な作業は actions.ymlruns.usingを書き換えるだけではありますが、他に必要になるであろう作業もいくつか紹介します。

package.jsonengines.nodeを更新

package.jsonで実行に利用するNodeJSのバージョンを指定していた場合、それを更新する必要があります。16.13.1が現在の最新バージョンなので、望ましい設定は以下のようになるでしょう:

"engines": {"node": "^16.13.1"
  }

tsconfig.jsonを更新

NodeJSを12から16に上げると、ES2019ではなくES2021を使えるようになります。TypeScript公式の推奨設定を参考に、compilerOptionsを更新します:

{"compilerOptions": {"lib": ["ES2021"],
    "module": "commonjs",
    "target": "ES2021"
  }}

TypeScriptを更新

ES2021をサポートしていないTypeScriptを使っている場合、TypeScript自体の更新が必要になります。 npm add -D typescript@^4.5.4などとして更新しましょう。

なおTypeScript 4.4で、catchされたオブジェクトの型をanyからunknownに変更されています。トップレベルでエラーをキャッチして core.setFailed()を呼んでいる場合は修正が必要です:

-     core.setFailed(error.message)+     if (error instanceof Error) {+       core.setFailed(error)+     } else {+       core.setFailed(JSON.stringify(error))+     }

ESLintを更新

ESLintの現時点での最新は8.5.0です。私が更新した多くのリポジトリではいくつかのプラグインも導入していましたが、そのような場合は npm add -D eslint@^8.5.0 @typescript-eslint/parser@^5.8.0 eslint-plugin-github@^4.3.5 eslint-plugin-jest@^25.3.0などとして同時にアップデートすることが良いと思います。

.eslintrc.jsonの更新はESLintのリリースノートに載っているマイグレーションガイド(例えば3.0.0用マイグレーション)に従っても良いですが、特に設定にこだわりがないならば actions/typescript-actionのデフォルトブランチから最新の推奨設定を引っ張ってくる方が時間は短縮できます。

GitHub Actions WorkflowでNodeJS v16を使う

GitHub hosted runnersでworkflowを実行し、かつactions/setup-nodeを使っていない場合、今月10日ごろから既にv16を使ったビルドが走っています。特に対応不要です。

actions/setup-nodeを使っている場合は、node-versionを変更する必要があります。ただ個人的には .nvmrc.node-versionに使うバージョンを書いておいて、GitHub Actionsではその値を読むのがメンテ箇所が減るためおすすめです。setup-nodev2.5.0からnode-version-file設定がサポートされています

  - uses: actions/setup-node@v2
    with:
      node-version-file: '.nvmrc'

まとめ

以上です。GitHubJavaScript ActionはいつNodeJSのバージョンを上げるんだろう……と不安になっていた方々、やっと来ましたのでバンバン上げていきましょう。私が実際に作成した変更をいくつか並べておきますので、ご参考まで。

--add-exportsをMaven/Gradleで使う

$
0
0

--add-exportsなんてオプションは使わないに越したことはないのですが、依存先ライブラリの都合でどうしても必要という私のような人のためのメモ。

ポイントは javacだけでなく javadocないし java(テスト実行)コマンドに対するオプション提供も必要という点です。Gradle用のサンプルプロジェクトMaven用のサンプルプロジェクトもあります。

コンパイラ用の設定

注意点として、--add-exportsオプションは --releaseオプションと同時に使えません

Maven3

<build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.9.0</version><configuration><source>11</source><target>11</target><encoding>${project.build.sourceEncoding}</encoding><forceJavacCompilerUse>true</forceJavacCompilerUse><showWarnings>true</showWarnings><compilerArgs><arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg></compilerArgs>

Gradle

// build.gradle.ktsval exportsArgs = listOf(
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
    "--add-exports",
    "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
)

tasks.withType<JavaCompile>() {
    sourceCompatibility = "11"
    targetCompatibility = "11"
    options.compilerArgs.addAll(exportsArgs)
}

javadoc用の設定

いろいろとオプションを指定する手法が提供されていますが、以下のように<additionalOptions>を使う方法での動作を確認しました。内部的にはOptionファイルを経由してjavadocコマンドのオプションを設定しています。

Maven

<build><plugins><plugin><artifactId>maven-javadoc-plugin</artifactId><version>3.3.1</version><configuration><additionalOptions><arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg><arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg></additionalOptions></configuration></plugin>

Gradle

一気にややこしくなります。 options.addStringOption()では動作しないので、Maven同様にOptionファイルを経由しての指定が望ましいです。

// build.gradle.ktsval addExportsFile = file("$buildDir/tmp/javadoc/add-exports.txt")
val createJavadocOptionFile by tasks.registering {
    outputs.file(addExportsFile)
    doLast {
        addExportsFile.printWriter().use { writer ->
            exportsArgs.chunked(2).forEach {
                writer.println("${it[0]}=${it[1]}")
            }
        }
    }
}
tasks {
    withType<Javadoc> {
        dependsOn(createJavadocOptionFile)
        options.optionFiles(addExportsFile)
    }
}

テスト実行

Maven

surefireプラグインJVMをforkするなら、forkするJVM側にオプションを設定します。そうではない場合、以下のような .mvn/jvm.configファイルでMavenを実行するJVM自体に設定を行います。

--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED

Gradle

jvmArgsプロパティを経由して設定します。このプロパティは標準でnullなので、ビルドスクリプトの他の箇所で設定していないなら .addAll()ではなく =を使って設定します。

// build.gradle.kts
tasks {
    test {
        useJUnit()
        jvmArgs = exportsArgs
    }

Gradle/Kotlinで開発する私的ベストプラクティス2022

$
0
0

こちらのエントリーが素敵だなと思ったので、最近書いてるKotlinプロジェクトのベストプラクティスをまとめてみます。一部はJavaプロジェクトにおいても利用できるはずです。

zenn.dev

基本方針

  • 参加障壁を下げる。OSSプロジェクトでもプロプライエタリ・ソフトウェアプロジェクトでも、新しい開発者が参加するコストを下げることには大きな意義がある。
  • 環境差異を吸収する。javaにPATHが通ってさえいればOSに関係なくビルドが通るようにする。
  • プロジェクト固有ルールを作らない。Conventional CommitsやKeep a changelogなど、ひろく世に使われているルールを採用する。

Gradleを設定する

Spotlessを使う

コードのフォーマットはformatterに任せて人間は細かいことを考えない、というのが不特定多数が参加するソフトウェアプロジェクトのあるべき姿だと考えています。ここを妙にこだわるとエディタ縛りだとかタブ幅だとかのいわゆる”地雷”の多い話題を避けて通れませんし、プロジェクト固有のルールができて敷居が高くなり保守コストが高くなるという課題もあります。

逆に言えば、フォーマットをツールに任せれば、人間はエディタやOSの選択の自由を享受できます。

Gradleの場合、最もシームレスに使えるツールはSpotlessでしょう。MarkdownとKotlin、Kotlin Buildscriptすべてのフォーマットをこのツールで管理できます。

github.com

pre-merge buildでは spotlessCheckタスクを使ってフォーマットを確認します。このタスクは通常 checkタスクから依存されていますので、深いことを考えずに ./gradlew buildすれば充分です。

Git hookを導入する

commit時にSpotlessを自動実行するには、git hookを使います。git hookの設定にはghooksを使っていたのですが、脆弱性対応含む更新が止まっており*1使いにくい状態です。後述するsemantic-releaseを使うのであれば、NodeJSにPATHが通っている前提でhuskyを使っても良いでしょう。

JVM toolchainを使う

現時点でJavaには8,11,17という3つのLTSリリースがあります。誰でも簡単にビルドできるプロジェクトを作るためには、どのバージョンにJAVA_HOME環境変数が通っていても問題なくビルドできるべきです。このためのアプローチには「Java 8で動くようにする」と「JVM toolchainを使う」の2択があります。

既にJava 8をサポートしていないGoogle Errorproneやサポート終了を予告しているApache Camelのようなツールもありますので、JVM toolchainを使って「JAVA_HOMEがJava 8を指していても常にJava 11あるいは17を使ってビルドする」方が現時点でしょう。最新のKotlinプラグインならtoolchainの利用は容易です:

kotlin {
    jvmToolchain {
        (thisas JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(17))
    }
}

.gitattributesを書く

GitHubを使っている場合、人間に差分をレビューさせたくないファイルがあれば linguist-generated=trueを指定しておきます。

./gradlew initで生成されるファイルを参考に、Windows向けの改行コード設定 *.bat text eol=crlfを加えても良いでしょう。

*.bat text eol=crlf
gradlew linguist-generated=true
gradlew.bat linguist-generated=true

settings.gradle.ktsを書く

公式ドキュメントで言及されているとおり、設定ファイルをプロジェクトルートに置くことが強く推奨されています。 ./gradlew initでプロジェクトを作っていれば自動的に作成されているはずです。

最低限rootProject.nameを設定しておきます。これでどのようなフォルダ名が使われていても同じプロジェクト名になります。 またビルドスキャンをする予定があるなら、このタイミングで設定をしておくと良いでしょう。

plugins { id("com.gradle.enterprise") version "3.8.1" }

rootProject.name = "foo-bar"

gradleEnterprise {
  buildScan {
    termsOfServiceUrl = "https://gradle.com/terms-of-service"
    termsOfServiceAgree = "yes"
  }
}

gradle.propertiesを書く

公式ドキュメントをもとに設定しておきます。Java併用時に google-java-formatを使う場合、少なくとも Java 17では--add-exportsの指定が必要です。

org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError -Xmx1G --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
org.gradle.parallel=true

ドキュメンテーションコメントにはDokkaを使う

私はあまり語れるほど使っていないのですが、KotlinプロジェクトではKDocをドキュメンテーションコメントに使い、KDocからの文書生成にDokkaを使います。設定も単純なので特に迷うことなく使えるはずです。

github.com

buildタスクに必要なタスクすべてを実行させる

Gradleプロジェクトでは基本的に、 ./gradlew buildによってプロジェクトのビルドと検証に必要なタスクがすべて実行されるべきです。assembleタスクによって成果物が作られ、 checkタスクによってすべてのテストや検証が実行される状態を保つことで、これを実現できます。これらのタスクの役割は ライフサイクルタスクとしてドキュメントに説明されています。

ただSpotlessを使っている場合、手元では spotlessApplyも実行したいがCI環境ではspotlessApplyを実行させたくない、というケースがあるかもしれません。

これにはDSLによりCI環境の場合に分岐する、CI環境では -xオプションにより特定タスクをスキップする、手元で ./gradlew spotlessApplyを明示的に実行させる、などの手法があります。プロジェクト固有ルールを作らないという方針と、標準では buildタスクが spotlessApplyタスクに依存していない現状を踏まえると、手元で ./gradlew spotlessApplyを明示的に実行するのが良いと考えています。

とはいえ人間に「push前に./gradlew spotlessApplyを実行してね」というプロジェクト固有ルールを作りたくはないので、前述のGit hookに実行させる方針を採ることになるでしょう。

GitHub Actionsを設定する

ごく一般的なワークフロー設定は以下のようになります。 なおWindowsでビルドする場合は、--no-daemonオプションでGradle daemonを無効化する必要があります

jobs:
  build:
    strategy:
      matrix:
        os: ['windows-latest', 'ubuntu-latest']
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: gradle/wrapper-validation-action@v1
      - run: ./gradlew build --no-daemon
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: reports (${{ matrix.os }})
          path: build/reports

Dependabotを設定する

現時点では buildSrcプロジェクトの依存は更新対象に入っていません。以下のように明示的に指定する必要があります。

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "build"
  - package-ecosystem: "gradle"
    directory: "/"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "fix"
  - package-ecosystem: "gradle"
    directory: "/buildSrc"
    schedule:
      interval: "daily"
    commit-message:
      prefix: "build"

逆に言えば、ビルドに用いるツールの依存を buildSrcプロジェクトに集めることで、ビルドに用いるツールとプロジェクトそのものの依存とを分けて扱うことができます。上記設定ではプロジェクトそのものの依存にのみ fix接頭辞を用いることで、semantic-releaseによるリリースを発火させています。

semantic-release を使う

以下の記事で以前紹介したので詳細は省きます。なお今なら拙作GradleプラグインがあるのでGradleでもsemantic-releaseのフル機能を用いて開発できます。

blog.kengo-toda.jp

PATHにNode最新のLTSが通っている状態にする必要がありますが、これはnvmやasdfを使うことになるでしょう。最新のactions/setup-node@v2はnode-version-fileでファイルからNodeのバージョンを読み込めます:

- uses: actions/setup-node@v2
  with:
    node-version-file: '.nvmrc'
    cache: 'npm'
- run: |
     npm ci
     npx semantic-release

以上です。なおJava用ではありますが、ビルドの細かい話やGitHub Actionsの使い方については以下の本にも書いてますのでご参考まで。

zenn.dev

*1:PRは作ったんですが見てもらえてなさそう

リリース自動化の嬉しみとその手法

$
0
0

DevOpsやCIOps、GitOpsなどを通じて生産性向上を突き詰めていくと、コンパイルやテストだけではなくリリースまで自動したくなってきます。リリースには必要な作業が多く、また頻度も高くないため毎回思い出したり間違えたりが発生するためです。

特に変更内容をまとめて文書化する作業は、利用者に対する影響度もその煩雑さも高いため、自動化できれば文書の品質向上やリリース頻度の向上に大きく貢献できます。本記事では、筆者がNode/Java界隈でよく見るリリース自動化手法について紹介することで、リリース自動化の敷居を下げたいと思います。

なお本記事で言う「リリース」は、jarファイルやコンテナイメージなどビルドの成果物をリポジトリGitHub Releasesにアップロードして他プロジェクトやデプロイ環境で利用できるようにすることを指しています。環境に対する「デプロイ」や、エンドユーザへの公開を意味する「リリース」とは区別します。

自動化の前に

1. Changelogの要件を検討する

ソフトウェアのリリース時にその変更内容について説明する文書のことを、ChangelogまたはRelease Noteと呼びます。組織によってはChangelogとRelease Noteに異なる意味を持たせることもありますが、複数コミュニティから自動化手法を紹介する関係上、この記事では区別しません。

ChangelogにはKeep a Changelogというひろく知られた書式があります。自動化手法にはこの書式を意識したものも多いので、特に困らなければこれを採用します。

OSSでありがちなChangelogの保管場所としては、プロジェクトルートに CHANGELOG.mdなどのテキストファイルとして配置するか、GitHub Releasesの本文に記載する方法があります。Dependabotなどの依存管理手法はGitHub Releasesを参照して変更内容をユーザーに伝えるので、選択できるのであればOSSでなくともGitHub Releasesを使用することが望ましいでしょう。もちろんテキストファイルと併用しても構いませんし、GitHub PagesやWiki、チャットツールといった他の手法での公開も検討できます。

なおJava界隈ではChangelogに成果物のチェックサムを併記することもあります。これは利用者が成果物の整合性を検証する際に役立ちます。

自動化手法を選択する

1. Release Drafter

以下の記事にて紹介されているため詳細は割愛します。特徴はPRを使って開発しているプロジェクトであれば言語に依存せずほとんどのプロジェクトで採用できることでしょう。Maven開発のような非常に歴史の長いプロジェクトでも採用が検討されているようです

zenn.dev

2. GitHub ReleasesのRelease Note自動生成機能

GitHub Releases自体にも、Pull RequestをもとにRelease Noteを生成する機能が備わっています。 特に設定せずに導入が可能なため、とりあえず使ってみるには便利です。

docs.github.com

ラベルを使ってPRを分類するため、ラベルをPRに抜け漏れなく貼る運用が欲しくなるでしょう。actions/labelerのようなラベル管理を自動化する仕組みをあわせて検討すると良いかもしれません。

想定される利用手法は主に2つです:

  1. GitHub ReleasesからGUIを使ってリリースする手法。Releases作成時にGUI上に生成されるRelease Noteを目視確認できるため、安心して導入できるでしょう。Releasesのドラフトを作成→GitHub Actionsを発火し成果物をビルド→成果物をReleasesにアップロード→Releasesを公開 という流れです。
  2. CLIを使って自動化する手法。リリースプロセスから人手を廃するのに適しています。GitHub Actionsを発火し成果物をビルド→Releasesのドラフトをタグに対して作成、成果物をアップロード→Releasesを公開 という流れです。GitHub Actionsの発火にはタグ、あるいはリリースブランチへのpushを使うことが多いのではないでしょうか。

懸念があるとすれば、GitHubに対するロックインでしょうか。他の自動化手法と比べてバージョンが自動で決定されないのも特徴ですが、これはSemVer以外のバージョニングポリシー(例えばCalVer)を採用しやすいというメリットだと取ることも可能です。

3. semantic-release

SemVer2Conventional Commitsの利用を前提として、コミットメッセージをもとにリリースを自動実行する仕組みです。デフォルトブランチやリリースブランチに対するすべてのpushを契機としてリリースを行います。

semantic-release.gitbook.io

運用に柔軟性を持たせつつも、極力自動化し人の手を入れさせないための工夫が要所に見受けられるのが特徴です。なんせ、使っているロゴがこれです:

f:id:eller:20220216093216p:plain
「人間に作業させるとロクなことにならん」とか言ってそう

例えば運用には各開発者がConventional Commitsに従う必要がありますが、commitizenを使うことで導入障壁を下げることもできますし、commitlintを使うことでコミット時にコミットメッセージ書式の検証を行うこともできますし、semantic-pull-requestsを使うことでコミットメッセージが書式に従っていない場合にGitHub Checksを失敗させることもできます。またPRをsquash mergeすることで、コミットメッセージの決定をマージ時まで遅延することもできます。

またプラグイン機構による拡張も可能ですし、Shareable configurationsを使えば複数リポジトリをまたぐプロジェクトにも一貫した設定を行えますので、ある程度大きな規模の組織でも運用しやすいかもしれません。

利用方法はビルドのワークフローに npx semantic-releaseを埋め込むだけです。ブランチ名などの情報からリリースを行うべき状況だとsemantic-releaseが判断したら、Changelog生成やリリースが自動的に実行されます。リリースには npm publish./gradlew publishなどのすでにコミュニティで利用されている手法が利用されるため、既存のリリース手順を再利用できます。

導入における主な課題は2つ。monorepoがbuilt-inではサポートされていないことと、Node.JS最新のLTSを必要とすることです。semantic-release自体はCI環境で実行するものなので開発者の手元にNode.JSを入れる必要はないのですが、前述のcommitizenやcommitlint, huskyといった関連ツールもほぼNode.JSコミュニティによって管理保守されているため、Node.JSを入れる判断をすることもあるでしょう。そのためNode.JS以外の環境を対象に開発しているプロジェクトではプロジェクトセットアップが若干複雑化するかもしれません。

なお似たものにstandard-versionrelease-itがあります。私は中の人を尊敬しスポンサーしているので、semantic-release推しです。spotbugs-gradle-pluginなど複数のOSSプロジェクトで使っていて、貢献受け入れやリリースを含め問題なく便利に回せています。

4. changesets

Atlassian発のmonorepoに特化した仕組みです。Node.JSを使ったmonorepoを開発しているのであれば検討しても良いかと思いますが、私はまだ試せていません。既存ユースケースもstandard-versionやsemantic-releaseと比べると1桁少ないです。

github.com

5. JReleaser

Node.JSではなくJVMを用いて動く仕組みです。他の仕組みと比べてまだ若いですが、既にMavenやGradleのサポートも用意されています。

jreleaser.org

f:id:eller:20220216093124p:plain
https://jreleaser.org/guide/latest/index.htmlより引用

Gradle用のクイックスタートを見た感じでは、 maven-publishプラグインではなく自前でリリース用の設定を持つようです。ここが ./gradlew publishnpm publish用の設定が完成されたプロジェクトに外付けする semantic-releaseとは大きく思想が異なる点です。コミュニティの進歩にJReleaserが自前でついていく必要があるため、保守コストが高くなる選択だと言えます。個人的には期待しつつもちょっと様子見です。

リリース自動化の果てに

1. 手動作業が残る部分

Changelog以外の文書は引き続き手動で作成する必要があります。例えば以下のようなものです:

  • エンドユーザ向けに変更内容を説明する文書
  • メジャーリリース時のマイグレーションガイド
  • マニュアル、プレス、その他

TwitterやSlackなどでの更新通知は自動化が可能ですが、もしブログ記事やメール通知のような手の込んだ文章を作成しているのであれば、それも残るでしょう。

ただこうした文書や通知はパッチリリース時にはあまり作らないはずで、パッチリリースの高速化・安定化・高頻度化は自動化によって充分に実現できると期待できます。

2. リリース自動化に向いたプロジェクト構成

CIやリリース自動化を推し進めると、ビルドやリリースに手作業が必要なプロジェクト構成はやりにくくなります。たとえば依存ライブラリを手でダウンロードしないとビルドできないとか、バージョン番号を手で書き換える必要があるとかです。

依存ライブラリについては、幸いJava界隈ではMaven Centralからほとんどのライブラリをダウンロードできます。昔では考えられなかったライブラリ、例えばOracle JDBC Driverもありますので、一度探してみるといいでしょう。
Maven Centralやその他のパブリックリポジトリに置いてないライブラリを使う場合は、自前でMaven Private Repositoryを管理してそこに置くことになるでしょう。これはNode.JSにおいても同様です。

バージョン番号の更新は、自動的に更新できるようにする必要があります。今回紹介した仕組みでサポートされているケースもありますし、Maven Release Pluginなどの機能を使うこともできます。ビルドツール設定以外のファイル、例えば META-INF/MANIFEST.MFBundle-Versionなどは手動ではなくビルドツールが自動で生成するようにしましょう。

3. リリース自動化に向いたブランチ戦略

リリース自動化は多くの場合、デフォルトブランチやリリースブランチが「常時リリース可能」であることを前提としています。 これは極力従うことが好ましいでしょう。

もしブランチが常時リリース可能でなかったら、リリース作業前に「リリース可能かどうか」を人間が検証する必要性が出てしまうためです。 もともと「自動化により人間の関与を減らしリリースの安定性と頻度を増やす」ことを目的に自動化しているのですから、ここに人間による作業を入れてしまうのは本末転倒です。少なくともリリース可能性検証のプロセスを自動化して、マージ前ビルドないしリリースビルドで自動的に検証されるようにするべきでしょう。

注意点として「マージしたらリリースされてしまう」ことを「完成するまでマージするべきではない」と受け止めない事が必要です。これはトピックブランチの寿命は短いほうが開発効率に良い影響があるためです。リリースできないとわかっている変更を公開することは避けつつ高頻度に変更をマージするために、Feature Toggleを使うなどの工夫が必要になるかもしれません。

まとめ

本記事では、筆者がNode/Java界隈でよく見るリリース自動化手法について紹介しました。

自動化手法特徴注意点
Release DrafterPRを使っていれば言語やビルドツールに関係なく利用可能。リリースビルド用のワークフローを別に用意する必要がある。
GitHub ReleasesのRelease Note自動生成機能設定不要。ラベルをもとにPRを分類してRelease Noteに反映。バージョン番号を自分で決める必要がある。Release Note生成だけで、リリース作業自体は別に実行が必要。
semantic-release柔軟な拡張性と徹底した自動化を両立。実行にNode.JS最新のLTSが必要。標準ではmonorepoに非対応。
changesets標準でmonorepoに対応。Node.JSプロジェクト用。
JReleaserMavenやGradleといったビルドツールと統合。JVM言語プロジェクト用。

リリース自動化はリリースの安定性と頻度を増やせる強力な仕組みです。ものによってはChangelogやコミットコメント、ブランチ戦略にリリース検証可能性検討の自動化といったものまで見直しをかける必要がありますが、それらも開発効率や生産性に寄与すると考えられているものがほとんどなので、開発体験向上のため検討してみてはいかがでしょうか。


「非nullのint配列」をアノテーションで表すのは `@NonNull int[]` ではない

$
0
0

正解は int @NonNull []です。な、なんだってー!

本当です。Java言語仕様書にも記載がありますが、配列を修飾する場合は []の手前にアノテーションを書く必要があります。JVM仕様書に記載の例のほうがわかりやすいかもしれません:

@Foo String[][]   // Annotates the class type String
String @Foo [][]  // Annotates the array type String[][]
String[] @Foo []  // Annotates the array type String[]

組み合わせて考えると、「要素も配列自体も非nullのString配列」は @NonNull String @NonNull []になります。コレクションは @NonNull List<@NonNull String>みたいにわかりやすいんですけどね。JavaのRecordでは配列を使わないほうが良いという話の時にも思いましたが、Javaは配列周りに非直感的な挙動が多い気がします。

なお配列だけでなく内部クラスでも同様で、パラメータが非nullの内部クラスを要求することをアノテーションで表現する場合は @NonNull Outer.Inner paramではなく Outer. @NonNull Inner param内部クラス名の手前にアノテーションを書く必要があります。これTwitterで11名にアンケートご協力いただいた限りでは、正答を答えられたのは1名だけという難問でした。

他にアノテーション周りで驚く機能としては、 thisを修飾する方法も提供されています。receiver parameterと言います。Pythonのように第1引数にthisを書く形です:

class MyClass {
  void method(@Foo MyClass this, String param) {
    // ...
  }
}

いずれもJava8(2014年)からあった機能ですが、自分は今日まで知りませんでした。クラスファイルパーサを書くことが無ければ、このまま気づかなかったかもしれないです。Java、奥が深い。

オブジェクト指向か関数型か、という話題に私達はどう接するべきか

$
0
0

私がコードを書くときには「オブジェクト指向でいくか、それとも関数型か?」みたいなことはほとんど気にしていません。特にオブジェクト指向については人によって定義から違うこともままあるため、この手の議論がとても遠回りになることも多いと感じます。

ただきしださんのLT資料を拝見して、もしかしたらまだ需要があるのかなということで、この話題にどう接するべきか考えていることを書いてみます。

どう書くべきかはコンテキスト次第

結論から書くと、どのようにコードを書くべきかはチームや解決したい課題、利用言語や既存資産などのコンテキストによって変わります。 ので「何がオワコンでこれからは何が来る」みたいな議論は、チーム内という限られたスコープでのみ有効なはずです。 チームよりも広い場で議論する場合は、「どういったコンテキストにおいてどのような書き方をするか」のように若干抽象的なテーマが適切でしょう。

言い換えると、コードの書き方において絶対善や絶対悪は存在しないはずと考えています。例えばバッチ処理ないしウェブアプリケーションでは、複数スレッドから同一データを共有することで性能を高めるため、メソッドの戻り値をキャッシュしたりメモ化を施したりするかもしれません。このためにはデータが不変であると便利でしょう。 しかしこうした実装に登場するデータすべてが不変であるべきかというとそうではなく、むしろ可変データによって性能や可読性が向上することだってあるはずです。

私の経験した範囲でいうと、Repository内部で扱うデータを完全に不変にした結果コピーコンストラクタやシリアライズ・デシリアライズが頻出する読みにくいコードになったことがあります。 今ならライブラリの力を借りてビルダーを実装するなどもっとうまくできる気もしますが、単体テストによる品質担保を厚めにしつつ可変データを導入する手もあったはずでした。

トレンドの書き方が良いソフトウェアを届けるのに必須ということはない

OSSから例を出すと、OpenJDKやKotlin, Gradleといった著名なプロジェクトで使われているObjectWeb ASMはオブジェクト指向で書かれており、継承や配列といった今ならまず採用を避けるであろう書き方も頻出しています。また異なる意味を持つ intStringも多く登場し、「この文字列はクラス名だっけティスクリプタだっけ?」といった注意を払いながらコードを読む必要があります。一部ではTypeTypePathみたいな型が用意されていて取り回すこともできますが、そのAPIはカプセル化やTellDontAskといった近年プログラマが慣れ親しんだものとは程遠いものです。

この面ではObjectWeb ASMは「プログラマの認知負荷を下げる」トレンドからは大きく離れていると言えます。

ですが、ObjectWeb ASMは事実上オワコンでなく、JVMエキスパートからの支持を集めて止まないわけです。 加速したJavaのバージョンアップにも速やかに追随し、コミュニティからの貢献を受け付けて修正をデリバリするとともに、リファクタリングや性能改善も行っています。今日SonarQubeを見たところではカバレッジ96.6%でした。まさに「質とスピード」を地で行くプロジェクトです。 *1

この面ではObjectWeb ASMは「ユーザに高い品質とかけがえのない価値を継続的に提供する」ソフトウェアの理想像に限りなく近いと言えます。 だいぶ極端な例ではありますが、トレンドの書き方が良いソフトウェアを届けるのに必須ということはないことを説明する良い事例だと思います。

曖昧な定義や由来を明らかにすることが重要

何がいいかはコンテキストによるのでコンテキストを明らかにしないまま議論をするのはやめましょう、というのが私の主張ではありますが、コンテキストを限定せずとも行うべき重要な議論・問題提起はあります。不明瞭な定義や由来に補足をしてただすものがそうです。言葉が曖昧だと議論が噛み合わず、建設的な議論になりません。例えばまさに今日読んだ Value Objectについて整理しよう - Software Transactional Memoはまさにこの貢献をするもので、とても勉強になりました。

定義や由来を明確にすることは、コンテキストが明らかなチームにおいても重要です。 例えばオブジェクト指向だと 2021年の「オブジェクト指向」を考えるで指摘されるように、様々な定義が想起されます。 今話しているオブジェクト指向が何を意味しているのか、議論に参加する各々がきちんとすり合わせる必要があります。

まとめ

コードの書き方は結局、チームが望む働きを実現する道具のひとつです。チームの中で合意が取れているか、コードの理解と変更が容易か、APIや性能が利用者にも受け入れられているか……そういった要件に目を向けるべきです。オブジェクト指向や関数型も私達の道具箱に入っている道具のひとつとして、それぞれ尊重して理解につとめていきたいです。

*1:またAPIの認知負荷が高いのも、言い換えれば他のことに特化していると言えます。これは想像ですが、クラスファイルとプリミティブとの架け橋に徹し高い性能を実現することが設計の主目的かなと感じます。認知負荷については、各利用者が自分のコンテキストに最適化されたAPIを設計しそれでObjectWeb ASMを包むことで解決できます。ObjectWeb ASMはその用途が幅広いため、いたずらに抽象化してしまうと特定ユーザにとって使いにくいものになるでしょう。今のVisitorベースとTreeベースのAPIを提供するくらいがちょうどいいという判断かもしれません。

退職エントリ

$
0
0

14年勤めたソフトウェアベンダーを今月末で退職します。私が入社したころは新卒が3年で辞めるという話があって、漠然と自分も似たような感じになるのかもと思っていたので、まさかここまで長く在籍することになるとは想像していませんでした。お世話になった皆様、ありがとうございました。

職場近影(2018年1月)

一生に何度もあるイベントではないので、14年前に立てた入社目的を満足できたのかと、14年を経て自分の何が変わったのかを書いてみます。

私は誰?

手広く働いてきたジェネラリスト寄りのITエンジニアです。研究開発、性能改善、製品開発、要件発掘、品質保証、テクニカルライター、OSPO、セキュリティ、SREなどを色々やってきました。「何やってる人なんです?」と言われてうまく説明できた試しがありません。

OSSプロジェクトではクラスファイル解析ツールSpotBugsSLF4J向け静的解析ツールのメンテナ、actions/setup-javaのdependency cacheの実装もしています。

入社理由は満足できたのか

私が今の会社に入社した理由は3つありました。そしてそれらはこの14年間を通じて満足できたと思います。

  1. ユーザ数が多く、得られるフィードバックの質と量が期待できた
  2. 製品を持ち、それが社会に与える影響が大きいと思えた
  3. 経営陣が提唱する理屈・哲学に納得・共感できた

私は何がやりたいのか、あるいは何がやりたくないのかという話 - Kengo's blog

1について。ソフトウェア提供の形がパッケージソフトウェアからウェブアプリケーションに広がり、リーン開発手法の浸透やクラウドの普及、可観測性(Observability)技術などを通じてソフトウェア開発者が得られるフィードバックは大きく変化しています。一方でソフトウェアの向こうに人がいるのは変わらない事実であり、多くの顧客を抱えつつも個別の顧客と深く関わる機会もあるBtoBビジネスを経験できたのはとても幸運でした。顧客とのコミュニケーションを通じて得た経験は、アルゴリズムやデータ構造に関する知識のように今後のキャリアを長く支えてくれると思います。

2について。ソフトウェアには短いライフサイクルを持つものもありますが、私は自分が思っていた以上に長いライフサイクルを持つものが好きみたいです。レガシーと呼ばれるシステムもそうですし、FindBugsみたいなOSSもそうなんですが、長い実績を持つシステムに手を入れて品質を向上し長持ちさせるための工夫を考えて実行するのが性に合っていました。その点では請負開発やコンサルティングではなく、製品を抱えて育てるパッケージソフトウェアはとても適していたと思います。社会的課題の解決に貢献できている実感も定性的定量的に得られ、長期的にモチベーションを保つ支えになりました。

3について。創業経営者から色々学べたのは事実ですが、上司や同僚からも多くのことを学びました。これは当時の自分の想像を大きく越えた体験でしたし、「組織は人」という価値観を醸成するには充分すぎる体験でした。駐在員も経験し、世代や文化や価値観の違いに起因するコミュニケーションの難しさにも幾度となく直面しましたが、この多様性が多様な市場やコミュニティとの交流において価値を生むこともバズワードではなく体感として理解できた気がします。魚座の星占いでよく「清濁併せ呑む」ってワードが出てくるんですが、アレが組織の競争力と魅力の源泉として必要になる時代なのかなとか思います。

仕事に対する解像度が上がった話

この14年で趣味プログラマからプロプログラマ、そしてプロ開発者へと自己認識が変化したと感じています。

まず趣味プログラマからプロプログラマに自分の意識を変えることに、のべ2年はかかった気がします。プログラミングにおけるアマチュアとプロの大きな違いとしては例外処理や保守性、運用容易性への意識がよく語られます。私の場合は、コーディングをする際に機械だけではなく人にも目を向ける必要があるのだという気付きが大きなきっかけとなりました。コードを勝手にフォーマットしてコミットして迷惑を掛けるとか、コメント内容が古いシステムの発掘をするとか、なんでこんなログがここにあるんだと過去の自分に苛立つとか、めちゃくちゃ性能保守両面が考えられている実装に出会って感激するとか、そういう経験が大切でした。一応社会人になる前から他者のコードを読んだり自分のコードを公開したりはしていたはずなんですが、仕事として成果にコミットすることが私には学習効率を上げるために必要だったのでしょうか。フリーソフトなら「気に入らなきゃ使わなくていいよ」と言えてしまいますし、機能性や互換性よりも書き手の手間を減らすことに注力しがちだった気もします。

そして保守性や運用容易性に気を配るプロプログラマになれてさらに数年、自分の仕事がプログラミングでもシステム構築でもシステム運用でもない、課題解決なのだということに改めて向き合う精神的余裕が出てきました。業務におけるマネジメントの割合が増えてきたこととも無関係ではないと思います。

システム実装はミケランジェロが言う「余分なものを取り除くだけで理想の像が現れる」プロセスとは大きく異なります。似たシステムでもチームが違えば理想も変わりますし、要件が同じでも使える資源や技術が変われば自然と適用する技法も変わります。特に重要な特徴は、チームや資源、技術といったこれらの条件がすべて「時間」を変数としていること、つまり「常に正しい正解」が存在しないということでしょう。以前紹介したトリさんのスライドがとても参考になります。

よって「どのようなシステムを目指すのか」「何が余分なものなのか」をチームで議論することは依然重要ではありますが、それ以上に「よし!私達はこれで行く!プランBはこれ、プランCはこれ!いっちょやってみっか!」という自信と勢い、間違ったときに即時修正するための評価判断手段こそが必要です。そしてこれらは目標設定と戦略と迅速性によって、すなわちリーンな組織と情報公開(組織の可観測性)そして迅速に価値を届ける開発体制によって生み出されます。

この体制の実現はマネジメントとかリーダーシップとかソフトウェアエンジニアリングとかが噛み合うとてもおもしろい課題領域なのですが、この「課題と現状をすべて卓上に並べて明らかにし即時対応する」働き方はとても疲れるのですよね。GitLabのHandbookとかAmazonのWorking BackwardsとかGoogleのデザインドキュメントとか、コミュニケーションを円滑化かつ非同期化する手法はいろいろ知られているのですが、この「疲れる」ことに対する心理的抵抗感をいかに下げるか・チームとして越えるか、言い換えるといかに他のチームひいては顧客の信頼を獲得するかが大切なのだな、そしてプログラマやマネジメントとしての知識や技術そして自分自身の生き様が信頼獲得の武器になり得るのだなということが見えて、初めて「プロの開発者」になれた気がします。

まとめ

技術による社会課題の解決にこだわる会社に新卒入社して多くの学習の場を経験できたことが、自分の人生の大きな財産になりました。 関係各位におかれましては、至らぬところの多い私にお付き合いいただき、ありがとうございました。 新しい職場においても、自らの成長と社会課題の解決に向けて工夫して参ります。

13年ぶりにストレングスファインダーをやった

$
0
0

ストレングスファインダー、今はクリフトンストレングス(CliftonStrengths)と呼んでいるそうですが、13年前に新卒入社したときも本を買ってテストを受けたことがありました。

当時は慎重さ・戦略性・規律性・内省・収集心が強みだという結果が出ていました。「あらゆる道のりには、危険や困難が待ち受けていると考えている。日課や秩序正しい計画に従うことを好み、決定や選択を行う時に細心の注意を払う。あらゆる種類の情報を蓄積したり自分の頭の中で考えるのが好きで、知的な討論が好き。」ということで雑に言うと石橋叩いて計画するタイプだったんですね。

さて新しく入社した会社がクリフトンストレングスをまた受けさせてくれました。今回強みとして出た資質は「学習欲・最上志向・収集心・アレンジ・原点思考」でした。内省は7位、慎重さは15位、戦略性は16位、規律性はなんと27位に落ちています。この変化について考えてみたら人生経験がわりとダイレクトに反映されてるかなと思ったので書いてみます。

資質変化の裏側にある人生経験

計画から経験主義へ

「秩序正しい計画に従う」規律性と「決定や選択を行う時に細心の注意を払」う慎重さが落ちて「結果よりも学習すること自体に意義を見出」す学習欲と「一度作り上げた構成にこだわらず、作り変えることをいとわない柔軟性を備える」アレンジが浮上したのは、業務での意思決定において経験主義を採用することが増えたことと関係がありそうです。つまりスクラムの採用と不確実性への理解です。

少なくとも学生時代には、私は「世の中の問いには答えがある」と考えていた節があります。理想のプログラム、理想の情報システム、理想のマネジメント、理想の自分があり、それを探し出してその具体化についてのみ考慮すればよいという考えです。これが正ならば、細心の注意を払って作った秩序正しい計画に従って行動することが最も効率的に理想を実現する方法のはずでした。

実際には答えのない問い、あるいは答えが変わりゆく問いもあります。理想も現状も移ろうので、戦略も継続的に更新しなくてはなりません。そのためには失敗というプロセスから学ぶ準備と姿勢、状況の変化に対応した新しい戦略に作り替えるための柔軟性が必要になります。これを身につけられた13年だったのかもしれません。

歴史・書籍・研究から学ぶ

「過去を調べることにより、現在を理解」する原点思考と「一度作り上げた構成にこだわらない」アレンジが浮上したのは、もともと高かった「あらゆる種類の情報を蓄積」収集心が歴史・書籍・研究から学ぶスタイルとうまくかみ合ったからだと考えています。なお「物事の理由と原因を追求」する分析思考が6位に入ってきたのも関係あるかなと思っています。

歴史・書籍・研究から学ぶというのは、今目の前にある課題や疑問に対して自分の頭で考えるだけではなく、自分の外に情報や発想を求めるということ、自分の向き合っている問題領域について先人が何を考え行動してきたのかを知ることです。

例えばビジネスで出会う課題のいくつかについては、書籍にすでに情報がまとまっていたりします:

また組織がどのように失敗してきたか、だけでも以下のように様々な書籍が出ています:

自分の頭で考えることも重要なのですが、考える材料が揃わないうちに直感ベースで突き進んでしまうと既知の落とし穴に容易にハマるのが人間です。例えばITエンジニアの業務の範疇では、FlickrのDevOpsGoogleのError budgetを知っているのと知らないのとではシステム運用に関する発想が大きく変わるはずと思っています。

単語を知り概念に名をつけるだけでも、考察の幅が大きく広がります。意義や新規性のある意見を持つためにも、まず先例や類似事例について学ぶことは重要ですし。人間や組織がどう考え行動するのかを知ることで人生の意外なところで役立てられるのかなとも思います。

組織とは人だ、ではマネジメントには何ができるのか

内省よりの話が続いたので対外的なところ、マネジメントについて着目してみます。チームとの関係性の築き方に影響しそうなのは、2位の「個人やグループの改善を促す方法として長所に着目」する最上志向と、8位の「各人のユニークな資質に関心を持ちます。異なるタイプの人たちの集団をまとめ、生産性の高いチームを作ることに長け」る個別化です。

実際に生産性高いチームを作れていたかは他の方に評価を譲るとして、尖った個性的な人材をまとめて開発チームを作ること自体は好きでした。残念ながら私は自分大好き人間なので、傾聴すべき場面で自分語りをしたり自分流を押し付けてしまうなどの問題行動もあったのですが。他者の強みを知ること、強みを伸ばすこと、強みが摩擦で損なわれないようにすること、チームの凹みをカバーすることには時間を割き関心を払ってきたと思います。

異なるタイプの人たちの集団をまとめるのに重要なのは、マネジメントの期待を明確にすることだと考えています。最低限の要求を明文化し示すことで、それだけ守れば自由にやっていいのだというあそびが生まれるためです。期待とはアウトプットかもしれませんし、企業文化かもしれませんし、レゾンデートル(存在理由)かもしれません。MVV(Mission, Vision, Value)だとちょっと粗すぎるので、四半期か半年レベルの目標に落とし込む必要があると思います。

課題だと思っていること

経験主義が重要な考え方だとは言え、慎重さが必要なくなったわけではありません。目標を見直し続けるためにどういったデータを残すべきなのか、残したデータをどのように分析するか、失敗したときにどう戦略を切り替えるか、といった細かな内容を「とりあえずやってみて失敗する」前に準備しておかなくてはなりません。

また規律性も同様で、画一的な働き方が不要になった今でも残すべき規律はあります。ITエンジニアで言うならばアジャイルのセレモニーのような、働き方のリズムをつくる仕組みは従来どおり実行していく必要があると考えています。これによって透明性が確保されることで、経験主義が回り始めるからです。

これらはもともと気にできていた部分ですが最近あまりできていないのかなと思ったので、改めて見直していきたいと考えています。

まとめ

日々学習すべきなのは目の前の課題の”正解”ではなく課題解決の基礎体力であること、そのためには過去や外に目を向ける必要があること、マネジメントとして長所に目を注目して期待を伝えることの3点が大切だと学んだ13年でした。

一方で慎重さや規律性も重要なので、かつての強みを手放すことなく活用するべく見直しをかけていきます。

GitHub Actions 最近のやらかし一覧(2022年夏)

$
0
0

2020年のやらかし一覧に続いて、最近のやらかしも残しておきます。

PRがマージされたときだけPR番号を取得しそこねる

PR番号を取得するのに GITHUB_REFを使いがちですが、マージされたときだけはマージ先ブランチ名が入ってきてしまうので完全ではありません。

# badon:pull_request:types:[ opened, synchronize, reopened, closed ] # closedイベントも拾いたいjobs:bad-case:runs-on: ubuntu-latest
      steps:- run: |
            PR_NUMBER=$(echo $GITHUB_REF | sed -e 's/[^0-9]//g')
            echo "PR番号は${PR_NUMBER}です" # merge時には空文字が入ってしまう

pull_requestイベントにちゃんと numberが入っているので、これを使用すればOKです。

# goodon:pull_request:types:[ opened, synchronize, reopened, closed ]jobs:good-case:runs-on: ubuntu-latest
      steps:- run: |
            echo "PR番号は${PR_NUMBER}です"env:PR_NUMBER: ${{ github.event.pull_request.number }}

自分が配布するActionでsemantic tagsを提供しないほうが良いと思っていた

GitHub公式のセキュリティガイドに、クリエイターが信用できるときだけタグに依存して良い=通常はフル長コミットSHAを使って依存せよ、と書いてあります。ので自分を信用するやつなんておらんやろの精神で v1v1.2のようなsemantic-tagは提供してきませんでした。

ところが同じ公式ドキュメントで、semantic tagsを提供してねと推奨しているんですね。

Add a workflow that triggers when a release is published or edited. Configure the workflow to ensure semantic tags are in place. You can use an action like JasonEtco/build-and-tag-action to compile and bundle the JavaScript and metadata file and force push semantic major, minor, and patch tags. For an example, see this workflow. For more information about semantic tags, see "About semantic versioning."

有名企業でもやらかす世界線で個人開発者を信用するのはやめたほうが良いとは今でも思っていますが、信用するしないはユーザが決めるものなので、Action提供者としては選択肢を残してあげるほうが良さそうです。

Gradleのjvm-test-suiteプラグインがテスト周りの定型コードを排除するのに便利そう

$
0
0

Gradle v7.5の時点ではまだIncubating段階の機能ではあるのですが、Gradleの新しいプラグイン jvm-test-suiteがいい感じなので紹介します。

docs.gradle.org

解きたい課題:サブモジュールや統合テストが出てくるととたんに面倒になるビルドスクリプト

Gradleは設定をDSLで記述するので基本的には何でもありなのですが、やはり定形コード(boilerplate)は少ないほうがビルドスクリプトの見通しも良くなります。もちろんGradleは「設定より規約(Convention over Configuration)」の考えを持っているため、ある程度は空気を読んでSourceSetやTaskを自動的に生成してくれます。しかしテスト周りにおいてはこうした自動生成は十分ではなく、次に挙げるような課題がありました:

  1. サブプロジェクト全てに対して実行したタスクのレポートを統合するのが面倒。ここでレポートとは単体テスト実行結果、ないしJaCoCoによるカバレッジ測定結果などを指す。
  2. スモークテストや統合テストといった単体テスト以外のテストを実行する際に、SourceSetやConfiguration(依存管理)、Taskなどを自分で書かなくてはならない

例えばJaCoCoのカバレッジ測定結果をSonarQubeに渡すために、SpotBugsプロジェクトでは以下のようなタスクを自分で書いて実行しています。一部はプロジェクト固有の課題を解決するためのコードですが、複雑さが伝わればOKです:

task jacocoRootReport(type: JacocoReport) {
  description = 'Merge all coverage reports before submit to SonarQube'def reportTasks = project.getTasksByName("jacocoTestReport", true).minus(rootProject.jacocoTestReport)
  dependsOn reportTasks

  executionData.setFrom reportTasks.executionData
  sourceDirectories.setFrom reportTasks.sourceDirectories

  // Only enable class directories related to non-test project
  classDirectories.setFrom files(reportTasks.classDirectories).filter {
    !it.toString().contains("-test") && !it.toString().contains("Test") && !it.toString().contains("junit")
  }

  reports {
    // JaCoCo SonarQube plugin needs a XML file to parse// https://docs.sonarqube.org/display/PLUG/JaCoCo+Plugin
    xml.required = true
  }
}

jvm-test-suiteプラグインの新規性

jvm-test-suiteプラグインは java-libraryなど既存のプラグインを導入しているプロジェクトであれば既に有効化されています。既存のプラグイン達とのシームレスな統合を前提に開発されていると言っていいでしょう。

また別種のテスト追加やレポートの統合機能もこれに統合されており、例えば前述のJaCoCoレポート統合は test-report-aggregationプラグインによって提供されますが、 jvm-test-suiteプラグインの提供するレールに乗っていれば testCodeCoverageReportというTaskが自動的に生成されます。先述の定形コードがまるっと削除できるわけです。

後述するSourceSetやConfiguraitonの自動生成と合わせて、多くの定形コードを排除するのに役立ちそうです。

jvm-test-suiteプラグインの提供するレール

jvm-test-suiteプラグインを適用する上で注意すべきはひとつ、テストの実行に必要な情報を testing Extensionに集約することです。

詳しい説明は公式サイトに譲りますが、例として spotbugs-gradle-pluginで試している設定は以下のようなものになりました:

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter()
            dependencies {
                implementation(gradleTestKit()) // Gradle v7.6+ が必要。 https://github.com/gradle/gradle/issues/19849
            }
            targets {
                all {
                    testTask.configure {
                        maxParallelForks = Runtime.getRuntime().availableProcessors()
                    }
                }
            }
        }
        val functionalTest by registering(JvmTestSuite::class) {
            useSpock()
            testType.set(TestSuiteType.FUNCTIONAL_TEST)
            targets {
                all {
                    testTask.configure {
                        description = "Runs the functional tests."
                    }
                }
            }
        }
    }
}

これだけで functionalTest Taskと、その実行に必要なSourceSetとConfiguraitonが自動的に生成されます。useJUnitJupiter(), useJUnit(), useSpock(), useTestNG()など依存やバージョンを一括管理してくれる便利メソッドもある(Gradle 7.6からuseKotlinTest()も増えそう)ため、今後は testImplemenentationなどのConfiguraitonを自分で設定する機会も激減しそうです。

まとめ

jvm-test-suiteプラグインを利用することで、ちょっと複雑なGradleプロジェクトでは頻出だったテスト周りの定形コードの多くを省くことができます。 サンプルプロジェクトがGradle公式から提供されているので、興味のある方は確認してみてください!

github.com

医療向け基幹システムを提供するスタートアップに入社しました

$
0
0

前回の退職エントリに続けての入社エントリです。

2022年7月1日より株式会社ヘンリーにSREとして入社しています。Java→Kotlin、AWS→GCP、Ansible→Terraformなど技術的には大きな変化を伴う転職でしたが、SREとしての観点や課題解決プロセスへの習熟、そして持ち前のひとがらのよさ(なぜか変換できない)などのポータブルなアビリティに助けられて2ヶ月生きています。このエントリーでは入社の目的と2ヶ月働いてみての転職の感想を残しておきます。

目標

新卒入社のころと違って自分なりの働き方やプロフェッショナルとしての在り方があるためそこまで厳密な目標は定めなくていいのかなと思う反面、振り返りや内省が成長の糧になることも確かなので、3つほど定めています。

1. 顧客と開発現場から学び、製品とチームを継続的に改善する

さすがに14年も経つと、ある程度はITエンジニアとしての基礎や専門性に自信がついてきます。その実態は、自信を持たないといけないという焦りかもしれませんが。。。

ともかく、継続的に仮説を検証して製品やチームに反映させることを通じて成長を積み重ねることや、その実行のための各種棚卸しや優先度付け、情報を整理したうえでの議論などはある程度可搬性のある技術として身についた気がしています。また昔「ソフトウェアを育てるにはユーザが多いほうがいい」と考えていたのとは違うやり方が多数あるということもSaaSやリーンソフトウェア開発手法の文脈で見てきたので、あまり規模にこだわることは本質的ではないのだなという気づきも得ました。ので前回の目標1を単純化して、継続的な学習の実行を新しい目標としています。

2. 製品を通じて社会の課題を解決する

今回転職活動を通じて多くの会社さんと話す機会を作っていただいたのですが、その中には全くときめかないところがいくつかありました。大きく分けると2パターンあり、自らを破壊的イノベーターと位置づけながら「なぜ新技術によってレガシーのやり方を駆逐できるのか」を説明できないところ、「新しいアプローチによって顧客事業を助けられる」と売上機会を説明するだけで「どのような社会的課題を解決するのか」を語ってくれないところには魅力を感じませんでした。

これは新卒で入った会社がブレークスルーによる社会的課題の解決にこだわっていたことの影響だと分析しています。新卒で入る会社が発想や働き方に強く影響するというのは本当なのかもしれません。いまの私は「新しい技術」だけでも「新しいアプローチ」だけでもない、「新しい技術で新しいアプローチを実現することで難しい社会的課題を解決する」働き方がしたいようです。

また同様の理由から、コンサルや請負開発のような自社で製品を持たない働き方を本業にするイメージは湧きませんでした。CD最適化や静的解析、ビルドツールといったスキルだけ見ると、むしろ多数のプロジェクトを実装して渡る働き方はマッチしているとは思っているんですけどね。Canは合ってもWantが合わないという感じなのでしょうか。

3. ドメインの課題を解決する手段とプロセスを理解する

これは俗に言う「チャレンジ目標」です。

私はいままでたくさんの部署や役割を転々とさせていただいて、製品開発だけでなく保守や研究開発、管理統制といった経験を積むことができました。また製品種別だけでもBtoB、BtoC、BtoE、社内ツールと多岐にわたる経験ができました。

が、一方でひとつのドメインに長く留まって業務課題を解決することはしてきませんでした。よく言えば多彩な観点からソフトウェア開発を支えられる人材ではあるのですが、前述したとおり社会的課題の解決に価値を感じている以上、ソフトウェア開発の手前つまり「ドメインの課題を噛み砕いて実行可能な計画に落とし込む」スキルを自分で持っておきたい気持ちもありました。

このスキルを得ることはソフトウェア開発者としての成長にもつながると思っています。ドメイン駆動開発の話です。出典を失念したのですが「昔は言語の表現力や計算機の性能が不足していたのでGoFのようなデザインパターン技法が注目された。現代では多くの障害が取り除かれた結果、変化するソフトウェアによる課題解決という核心に資するドメイン駆動デザインパターンが注目されている」という表現を読んで非常に納得できたことがあり、以降システム設計や保守に加えてドメイン駆動についても学んでいます。

今回の転職で「ドメインの課題を噛み砕いて実行可能な計画に落とし込む」ことを学び、観察し、実践する機会を多く得られそうなので、業務を通じて理解を深めていきたいと考えています。

入社2ヶ月時点での所感など

1. 異業界スタートアップで働いてみて

大企業向け基幹システムを提供するメガベンチャーから医療機関向け基幹システムを提供するスタートアップに転職したわけですが、業務内容自体は意外と変わりませんでした。少なくともベンチャー・スタートアップでは、自分で課題を見つけて周囲を巻き込んで解決していく「問題解決の核」としての働き方はかなりポータブルなんだなと感じます。

業界が変わって大きく違ったのは顧客の求めるものでしょうか。前職だとROI(情報投資効率)がひとつの大きな指標だったのですが、医療機関の電子カルテやレセプトコンピュータ(レセコン)だとまた違った話になるようです。またユーザがかなり多様なため、業務想定やユーザ体験設計などの難度が一段上がった気がします。

2. OSS開発ないし専門性と直接の関係がない役割へ転職して

静的解析関連のOSSを長年やっていることもあり静的解析サービスの企業はよく検討していたのですが、ビジネスモデルの問題かどうしても魅力的なポジションがなく、ここは早い段階で諦めていました。 そのため静的解析と直接の関係がない会社に入ったことは特に気にしていません。

またヘンリーはOSSを積極的にはやっていませんが、OSS経験があることは評価してくれていましたし、実際ここ2ヶ月でgradle-gcs-build-cache, tfmigrate, tfaction, gcs-proxy-cloud-runといくつかの貢献を業務として行えました。のでこれも気にせず正解だったと思います、まぁ前職でも言うほど業務ではOSSやってなかったですし。

3. 駐在員としての経験がフルリモートにマッチしているかも

オンライン会議前にアジェンダをまとめておくとか、とにかくドキュメントに落とすとか、カレンダーに不在予定含めすべてのスケジュールを入れておくとか、ソフトだが回りくどくない表現をするとか、Working Out Loudするとか。駐在員として本社の人とどうコミュニケーションするかを突き詰めた結果としてのコミュニケーションスキルはフルリモートで遺憾なく発揮されています。

ちょっと面白いのが「戸田さんはエンジニア以外にもやってることがわかりやすい」「コミュニケーションがいちいち工夫されていて、フルリモートのはずなのに存在感がすごい」というフィードバックをいただくことですね。工夫って……Slack書いてるだけだが?というのは置いといて、もはや意識的にやっていることではなかったので新鮮でした。

VPoEお墨付き↑の存在感🤗

まとめ

とりあえず2ヶ月を走りきり、今後もやれそうな感覚を掴めています。やるべきこと・やれることが山積みの状態ですが、健康に気をつけつつベストを尽くし、挑戦を続けていきます。

ちなみに今回の転職はLaprasさん経由でした。今週インタビューも掲載いただいておりますので、よろしければご覧ください:

note.lapras.com

あとド定番で恐縮ですがWe're hiringなので、戸田が選んだチームや社会的課題ってどんなのだ、というのが気になる方は採用情報も見ていただけると嬉しいです:

jobs.henry-app.jp


2022年のOSS活動状況まとめ

$
0
0

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

概要:OSS活動は減った

GitHubのプロファイルページによると今年は4,513 contributionsでした。ただこれはprivate repoでの活動を含む値で、これを除くと1,263 contributionsと昨年や一昨年よりも少ない数値になりました。7月に転職してから新しい仕事にキャッチアップしていてあまり時間が作れていないのもありますが、Javaを書かなくなっていよいよSpotBugsを使わなくなったことも大きいです。

Private Repoの貢献を含まない値
Private Repoの貢献を含む値

新しい貢献領域を探さないとなとは思っていますが、ここ6年間でOSS活動から受けたベネフィットはあんま無いなというのが正直な感想なので、焦らず食指が動く領域に出会うのを待つのもいいかなという気持ちもあります。

とはいえこういう焦りも無いことはない(出典 まんがタイムきらら編集部

その他

ひきつづき自分が困った時に関連GitHub ActionにPRを出していました。

2022年に試した開発ワークフロー関係の機能やツール

$
0
0

数えてみたら意外と数あったのでまとめます。

release-please

Google謹製のリリース自動化ツール。monorepo対応のRelease Drafterという感じですが、リリースはDraft Releaseの安定版への昇格ではなく、PRのマージによって行います。PRでリリースするという点ではgit-pr-releaseぽいですが、ブランチは main だけでリリースブランチは無い感じ。changesetsよりはとっつきやすい印象です。

github.com

例えば↓のようなワークフローを用意すれば、モジュールごとにGitHub Releaseを作成するためのPRを自動作成できます。 初期セットアップでJSONファイルを2つ作る必要があるのが若干面倒ですが、それさえ越えてしまえば考えることは少なさそうです。

# .github/workflows/release-please.ymlon:push:branches:- main

name: release-please
jobs:release-please:runs-on: ubuntu-latest
    outputs: # workspaces/module_1 ディレクトリにモジュールがある想定module_1: ${{ steps.release.outputs['workspaces/module_1--release_created'] }}
    steps:- uses: google-github-actions/release-please-action@v3
        id: release
        with:command: manifest
          token: ${{ github.token }}
          default-branch: main
          monorepo-tags:truemodule_1:needs: release-please
    if: needs.release-please.outputs.module_1
    runs-on: ubuntu-latest
    steps: # リリース用の設定

なおrelease-please以外のリリース自動化ツールについても以前まとめてますのでよろしければ。

tfaction

めちゃくちゃ便利ですね。自分としてはTerraform使うなら必携という感じです。安全安心を計算能力で買う感じのアプローチでActions代がちょっとかさむ印象はありますが、ワークフローを工夫すれば改善できる余地がある気もしています。

suzuki-shunsuke.github.io

スクリプトの塊なので全体像の把握は難しいですが、設定を展開する部分と処理を実行する部分に別れているのだとわかればやりやすいです。GCP民はコントリビュートチャンスいろいろありそう。

version catalog (Gradle)

複数プロジェクトで利用されている依存をまとめて管理するためのファイル。GradleにもTomlが来るとはねぇという感じですが、Yamlより書きやすいのは間違いないので特に問題はないです。Kotlinのようなプログラミング言語で書く必要性はなかったと思いますし。

docs.gradle.org

例えばbuildSrcとメインのプロジェクトの双方でspotlessを使うとDependabotがPRを2つ作っちゃう問題があるのですが、version catalogを使えばきれいに解決できます。現時点ではDependabot自体がversion catalogに対応していないという問題はあるので、Renovateに切り替える必要はありますが。

あとKotlin DSLだとextra propertyやプロジェクトプロパティでの依存バージョン管理をするのがやりにくいので、Gradle 8.0以降でKotlin DSLが標準になることを踏まえても今後version catalogがメジャーになるんだと思います。

composite action (GitHub Actions)

何度か出てくる似たような処理を抽象化してinputsで処理を分けられるようにするというやつ。 複数環境にデプロイするようなワークフローとか、ちょっと凝った初期化処理が必要なワークフローとかで使いました。

https://docs.github.com/en/actions/creating-actions/creating-a-composite-action

reusable workflow (GitHub Actions)

何度か出てくる似たような処理を抽象化してinputsで処理を分けられるようにするというやつ、その2。 自分のケースだとまだ使い所がないというか、Environmentsと合わせると便利なんだろうなという理解で止まっている。

https://docs.github.com/en/actions/using-workflows/reusing-workflows

--mount=type=cache (Docker)

MavenやGradle、aptなどのインストール処理はキャッシュがあると早い。が、コンテナ環境にローカルの ~/.m2/repositoriesとかをCOPYするのはハードルが高い。というときに使いやすそう。

docs.docker.com

ただローカルでは恩恵を受けやすいのだろうけど、Circle CIではまだって感じらしい。ここを頑張るよりはコンテナの外でjarを作ってCOPYする方がキャッシュ容易性という点ではきれいだし、CI環境が宣言的にメンテされてればビルドの再現性という点でも充分なので、自分で使うことはあんまりないんじゃないかという気がしている。

--mount=type=secret (Docker)

GCPのApplication Default Credentialをコンテナに渡したい、というときに使えると教えてもらったやつ。 コンテナの中で ./gradlew buildするときにGCSをGradle Remote Build Cacheとして利用したい、みたいなときに使った。

docs.docker.com

これもやはりコンテナの外でjarを作るやり方なら不要だけど、 type=cacheよりは使いやすそう。

パッケージなメガベンチャーから医療機関向けサービスを提供するスタートアップに転職して6ヶ月して感じたこと

$
0
0

いよいよ試用期間が終わりまして、ドメイン知識はともかく同僚の働き方はだいぶ掴めてきた気がします。6ヶ月何をやっていたかは会社の方のブログに書いたので、こちらでは感じたことを書いておきます。なお入社2ヶ月時点での所感を別の記事に記載しています

文脈

  • 人事給与,会計や購買管理といったERPをメインとしたパッケージベンダーから、医療機関向けERPを提供するスタートアップに転職した
  • SaaSを製品ラインナップに加えようという活動に従事した経験があるので、パッケージとウェブサービスと両方それなりに知っているつもりだった
  • 製品開発、製品運用、プロジェクトマネジメントないしピープルマネジメントはわりとわかっているつもりだった

ウェブサービスとパッケージはやはり大きく違うという話

ウェブサービスのほうがサポートバージョンを絞って効率よく開発できる、動作環境を掌握できるので細かいサポートが不要になるといった違いはまぁ知っていたとおりでした。古いミドルウェアやOS、明らかに少ないメモリみたいなパッケージあるあるに心を砕く必要性が少なくなったのはとてもやりやすいです。

ログやメトリクスを採ることのハードルが下がったのもやりやすく、仮設が検証しやすいということは即機能の成長速度に反映されるわけで、製品づくりのハードルも大きく下がったと思います。

一方で各お客様固有の環境は引き続き存在しています。医療機関はその内部に複雑なネットワーク構造を持っていることが多いようで、お時間のある方はぜひ事例がいくつか載っている月刊新医療 2022年11月号を見ていただきたいのですが。こういった多様かつ複雑なユーザ事情を踏まえてサービスを提供するのは難しいなと思う機会が多いです。

しかも連携先システムが多い!既存システムはもちろん、オンライン資格確認や電子処方箋のような新システムもどんどん入ってくるわけで、これらの考慮がいるという点で純粋なウェブアプリケーションではありません。こうした固有環境を織り込んでのサービス信頼性向上は、パッケージ屋としての経験が活かしやすい部分かもしれません。

医療機関の情報セキュリティ基準はだいぶ明確

医療機関といえばランサムウェアに狙われている印象がありますが、国から出ている情報セキュリティ関係のガイドラインはわりと明確です:

  1. 医療情報システムの安全管理に関するガイドライン 第5.2版(令和4年3月)
  2. 医療情報を取り扱う情報システム・サービスの提供事業者における安全管理ガイドライン

明確なら実行も簡単というわけではもちろん無いのですが、ゴールや判断基準が明確であればベンダーとしてのお客様とのコミュニケーションはやりやすい部分があると思っています。

ここで面白いのが、この「3省2ガイドライン」と呼ばれる2つのガイドラインはそれぞれ「医療機関」と「医療情報システムを開発/運用する情報処理事業者」とを対象にしている点です。つまり3省2ガイドラインに準拠した医療を提供するには、医療機関とベンダーの双方が継続的に協力しないといけないということですね。 情報セキュリティ周りには取得して何が守られるんだっけみたいな認証とか違反が明らかになっても剥奪されない認定とかが跋扈している印象がどうしても拭えないのですが(個人の感想です)、このある種の緊張感あるガイドラインは結構好きです。

ちなみに医療機関向けセキュリティ教育支援ポータルサイトも整備されてきていて、国も然るべき手をきちんと考えて打っているのだなと思わされます。数年後は医療機関の情報セキュリティに対する印象も大きく変わっているかもしれませんし、微力ながら変えるための一翼を担えればと思います。

mhlw-training.saj.or.jp

採用が活発

組織は人、人は採用からということで採用にめっちゃ力を入れているわけですが、まさか入ってすぐここまで駆り出していただけるとはという驚きがありました。日常的な採用プロセスはもちろん、インタビュー記事だけでも2回も出していただいています:

実際JVMやバイトコードの酸いも甘いもじっくり10年超見てきた自分がSREやってますというのはサーバサイドKotlinを推す企業の採用戦略としては非常に正しいと思うので、今後も貢献できればとは思っております。実際やりたい・やるべき施策も大量にバックログに積んであって、当面は忙しそうだなという感じです。

あとは社内外の勉強会も活発で、自分が直接関わったものだけでも6回くらいやりましたね。会社ブログに情報が出ているのは以下です:

おかげさまで他社様とのコラボレーション企画も多く、こういうフットワークの軽さと顔の広さはすごくいいなと思っています。VPoEの弛まぬ尽力の賜ですね。自分自身も他者に声をかけられる・かけていただける開発者でありたいものです。

シニアだけ採用する != 教育不要

さすがにGitコマンドの使い方とかテストコードを書こうねみたいな意識付けとかは要らないんですが、シニアだから教育不要ということは全く無くて、むしろシニアだからこそ新たな学習機会を求めるのですよね。自分のバックグラウンドは他の社員と比べてやっぱり異色なので、ドメイン知識や技術を学びつつ自分からも還元できればと思っています。

とりあえずKotlin Fest 2022で話したようなDetektの話とか、結構突っ込んで研究してきたGradleの話とかは役立てていただけているように思います。特にGradleやGitHub Actionsはちょっとした工夫でぐんとビルド時間が短縮されるので、開発体験の向上に貢献できているかなと。15-20分かかっていたワークフローを12-15分くらいに短縮して、でも5分くらいにしたいよねっていう話をしているのが今です。頑張ります。

それはそれとして、才能ある新入社員と継続的に話して成長を支援することを続けてきた自覚はあるので、そのうち新卒採用もできると嬉しいです。新卒で入ってきてくださった方のほうが自分よりもめっちゃ優秀、みたいな体験があるとやはり緊張感出ますし、お客様の課題を解決するのとはまた違った達成感もありますし。

肩書に助けられてたんだなぁという話

前職では上司と駐在員という肩書があったので、新しく入ってきた”部下”との関係構築が比較的やりやすかったように思います。一方で新しい職場はフルフラットなのでこうしたバフやバイアスが存在せず、日頃から関係を構築しておかないというべきことも言いにくい(言えない空気はないんだけど「お前何様だよ?」って自分で思っちゃう)のだなという新鮮な発見がありました。自分が相手の”上司”であるという責任感にけっこうバフもらってたんですね。

6ヶ月あればさすがに言い方がわかってきて、相手が誰であろうと自分の弱さや専門性をどんどん出していこう感は出てきているのですが、「人間を信用しないで!」とか「KILL ALL HUMANS」とか叫んでるとさすがに自分でも「おっ、サイコパスか?」とは思いますね。

技術的ディスアドバンテージの埋め方

TerraformとGolang、GCPは入社してからのキャッチアップでした。GCPはQwiklabsがあるのでまだ楽でしたが、Terraform周りは自分がまだあまり読めないGolangで書かれていてけっこう難しく、大きめの失敗をいくつか経験しました。副業で入ってきていただいてる詳しい方に何度もサポートいただいて助かりました。

当たり前なんですが、詳しい方にどんどん聞く、わからないことをわからないと叫ぶことはとても大事だなと思います。Working Out Loud風の働き方は今後も継続していきたいです。

まとめ

土地勘がついてきてどういった貢献ができるのか・誰に聞けばわかるのかが見えてきた感じです。組織の規模や形が変わったことで自分の動き方も変える必要が出てきたところと、今までの強みが活かせるところとを理解して動いていきたいです。

Maven4の動向メモ

$
0
0

Maven 4は2年近く前から開発されていて、最近alphaバージョンがリリースされています。自分がMavenを使うことは殆ど無いとは思うのですが、傾向だけでも把握しておきたくてリリースノートやメーリングリストに潜ってみました。

ざっと見た感じ、Mavenでマルチモジュールビルドをしている人や、Maven向けにプラグインや拡張を提供している人は動向を追ったほうが良さそうです。既にGradleに移行した人はあまり気にしなくて良いでしょう。

マルチモジュール周りの改善

以下のサイトで解説されています。他にも親POMのバージョンを記入しなくて済むとか、依存解決周りの改善が多そうです。

maarten.mulders.it

プロジェクト定義に変更を加えつつ、古いMavenからも使えるようにしたい

MNG-6656動画で基本設計が説明されているようです。POM 4.0.0の配布を継続しつつ、ビルド時には新しいプロジェクトオブジェクトモデルを使いたいようですね。ただXSDファイルはまだ https://maven.apache.org/xsd/では公開されてなさそうでした。

Gradleが配布時にPOM 4.0.0に加えてメタデータを添付しているのと逆で、POM 4.0.0の定義に収まるように情報を削ぎ落として互換性を確保する感じと理解しました。POM 4.0.0という枠を維持するということは、例えばGradleがcompileをapiとimplementationに分けてABIの概念を持ち込んだような破壊的な変更はあまり考えていなさそうですが、少なくとも新機能を入れることはやりやすくなるでしょう。

Maven Wrapper

4.0で wrapperライフサイクルが導入され、 mvn wrapper:wrapperではなく mvn wrapperで実行できるようになるようです。 Maven Wrapper 3.1.1 のドキュメントに以下の記載がありました:

wrapper:wrapper is the default goal, invoked during the wrapper phase in Maven 4. It downloads and unpacks the maven-wrapper distribution,

ちなみに「3.7.0から入る」という古い情報もありますが、リリースノートによると3.7.0は廃止になって3.7の新機能は4.0に入れることになったそうです。

とはいえMaven 3には wrapperライフサイクルが無いだけで wrapperプラグインの公式化は完了しています。よって mvn wrapper:wrapperがMaven3でも既に通りますので、実用上の問題はないと思います。

古い”API”のサポート中止

Maven 3.0 またはこれよりも古いバージョンのAPIを利用したプラグインのサポートをやめたい、という話が出ているようです。

github.com

個人的にはMavenにAPIなんて今までなかったよねという話がちょっと面白かったです。maven-plugin-apiというパッケージは存在するのですが、ちょっと複雑なことをするとすぐに実装詳細であるmaven-coreに依存する必要性が出てくるという話ですね。

実際alphaリリースには既存のプラグインが動かなくなる問題も報告されていて、publicなクラスを積極的に削除しているのは間違いないようです。プラグインや拡張を提供している人は、alphaはともかくbetaとかRelease Candidateとかが出てきたタイミングで動作確認をしたほうが良いかもしれません。

Cloud RunがImage not foundと言ったときの原因事例集

$
0
0

Cloud Runはとても便利なのですが、エラーメッセージがわかりにくいことがあります。特にデプロイしたときに遭遇するこちらのエラーが厄介です:

ERROR: (gcloud.run.deploy) Image 'asia-northeast1-docker.pkg.dev/foo/bar/baz' not found.

コンテナを引っ張ってこれなかったときは原因が何であれこのメッセージだけ残してデプロイが失敗するので、自分で切り分けなければなりません。ここ数ヶ月で複数の原因に遭遇したので記録を残します。

指定したタグを持つコンテナがない

docker buildはしたけど docker pushはしてなかったケース。見つからなかったのは指定したタグを持つイメージなのですが、エラーにはアクセス先レジストリそのものが見つからなかったかのように出てくるので要注意です。

Cloud Run Service Agentに権限がない

公式にはこちらのページで説明されています

project-aのCloud Runから他のプロジェクトproject-bにあるArtifact RegistryやContainer Registryからコンテナを引っ張ってくるときに、project-aのCloud Run Service Agentに適切な権限がないと失敗します。なおCloud Run Service Agentは service-PROJECT_NUMBER@serverless-robot-prod.iam.gserviceaccount.comという名前を持っています。

Artifact RegistryであればArtifact Registry Readerロールが、Container RegistryであればStorage Object Viewerロールが必要になります。

イメージのマニフェストがおかしい

docker/build-push-actionを導入したときに遭遇しました。docker/setup-buildx-actionと組み合わせると application/vnd.oci.image.index.v1+json media typeを持つイメージにタグが打たれるようになり、これをCloud Runは扱えないようです。イメージが使えるものだということは docker runで確認したし、公式によるとOCI image formatはサポートしているようなのですが。

docker/build-push-action単体だと docker build時と同様に application/vnd.docker.distribution.manifest.v2+json media typeを持つイメージにタグが打たれるため、Cloud Runでも扱えるイメージができます。

2023/01/23更新

こちらはbuildx 0.10の挙動変更による影響でした。

github.com

対策としては docker/build-push-actionの設定に provenance: falseを追加することが推奨されています。

Viewing all 157 articles
Browse latest View live