Magnolia Tech

いつもコードのことばかり考えている人のために。

『Learning Go』

今まで簡単なコードリーディングくらいはしてきたけど、自分でちゃんとGoのコードを書いてこなかったなーと思って、とりあえず『Learning Go』を買ってみた。

ざっと一通り書けるようにトピックが網羅されている気がする。

『Programming Scala』と『Scala Cookbook』がScala3に対応して改版されました

O’ReillyScala関係の本がScala3に対応して改版されました。

Scala3で大きな文法や言語機能に大きな変化が有った一方で、ライブラリは必ずしもScala3に対応しきれてはいない、という状況ですが、書籍類は続々とScala3対応版になっていますね。

まだメンテされ続けるScala2のコードも当然残るので、『Programming Scala』はかなり意識的にScala2と、Scala3で異なる部分を丁寧に説明しています(Scala3対応の機能のところには「3」のマークが付いています)。

Scala Cookbook』は言語機能周りはScala3にアップデートされているので、OPTIONAL BRACES形式の文法で書かれている(つまり明示的にブロックのための括弧を書かない)一方で、ライブラリ関係はScala2に合わせているのでブロックのための括弧が書かれています。

なぜデータモデルをきちんと考えないといけないのか

場当たり的なコードを書き続けると、そこら中に個別最適なデータ構造が作られてしまって、気がつくとコードの大部分がデータ構造間の形式に合わせるための詰め替えになっていたりしませんか。

コードからビジネスロジックを読み取りたい時...

  • どんな入力を期待しているか
  • 処理の対象・範囲は何か
  • どんな条件で判断しているか
  • どんな形式に変換しているか
  • どんな単位で処理するか(トランザクションとか)
  • データベースのカラムのうち、何を実際に使っているか
  • どんな条件でデータベースを更新しているか

あたりを読み取りたいので、単なるデータ構造の変換のためのコードが増えてくると上記のようなコードが埋没して読み取りづらくなっていきませんか。

特にデータベースのカラム、不用意にお作法を守らずに参照しているコードが思ってもみなかった影響を与えることが有るので、素早く読み取りたいけど、全カラムを取得して変換して、フィルタして...みたいなコードが続くと実際に影響のあるコードを特定するのにすごく時間がかかってしまう。

ひょっとして我々が今求めているのは、そのデータがどこから来て、どこへ行くのか、出自が追跡可能なデータモデル・機能なのかもしれない(今、思い付いた)。

Rustの所有権という概念もいいけど、エンタープライズ用途だと、追跡可能性(トレーサビリティ)という概念を導入した方がいいのかもしれない。

ディストリビューションごとのJavaのバージョン表記

色んなJavaディストリビューションのバージョン表記を集めてみました

  • 1行目はjdkのバージョンを示している
  • ベンダがLTSを表明しているディストリビューションは「LTS」の表記がつく
  • JREJVMのバージョンはベンダごとに独自のバージョンが付けられる
  • JREJVMのバージョンの後ろに括弧付きでopenJDKのバージョン番号が表示される...ビルド番号や、ディストリビューション固有のバージョン番号が含まれる

1行目のダブルクォーテーションに囲まれた数字を見ておけばOKですね

Oracle JDKのバージョン表示

java version "17.0.1" 2021-10-19 LTS
Java(TM) SE Runtime Environment (build 17.0.1+12-LTS-39)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.1+12-LTS-39, mixed mode, sharing)

Temurinのバージョン表示

openjdk version "11.0.13" 2021-10-19
OpenJDK Runtime Environment Temurin-11.0.13+8 (build 11.0.13+8)
OpenJDK 64-Bit Server VM Temurin-11.0.13+8 (build 11.0.13+8, mixed mode)

UbuntuのopenJDKのバージョン表示

openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.18.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.18.04, mixed mode, sharing)

Microsoft build Javaのバージョン番号

openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment Microsoft-28056 (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Microsoft-28056 (build 17.0.1+12-LTS, mixed mode, sharing)

Azul Zulu Builds openJDKのバージョン番号

openjdk version "17.0.1" 2021-10-19 LTS
OpenJDK Runtime Environment Zulu17.30+15-CA (build 17.0.1+12-LTS)
OpenJDK 64-Bit Server VM Zulu17.30+15-CA (build 17.0.1+12-LTS, mixed mode, sharing)

Javaのバージョン番号の形式を理解する

要約

JDK 8のバージョン番号の読み方

インストーラの表記

  • インストーラを入手する時は、「jdk8u312-b07」というような表記のうち、uの後ろの数字が一番大きなものが最新版
  • -bの後ろの数字はjdk自体のビルド番号であり、jdk自体の開発者向け情報...利用者側は無視してよい

java -versionの出力

  • java -versionでインストール済みのバージョンを確認すると、1行目に「1.8.0_312」とインストーラと異なる表記方法で出力される
  • アンダースコア以降の数字がインストーラuに続く数字と一致する
  • 1.8.0は固定で、今後JDK 8がアップデートされても変わることは無い(uの後ろの数字だけがインクリメントされる)

JDK 10以降のバージョン番号の読み方

インストーラの表記

  • インストーラを入手する時は、「jdk-17.0.1+12」というような表記のうち、+の前の数字を確認する
  • +の後ろの数字はjdk自体のビルド番号であり、jdk自体の開発者向け情報...利用者側は無視してよい
  • 17JDKのメジャーバージョン...JDK xxという表記と一致する
  • 続く二つ目のバージョンの数字は現状0のまま
  • 続く三つ目のバージョン番号の数字が一番大きなものが最新版

java -versionの出力

  • java -versionでインストール済みのバージョンを確認すると、1行目に「17.0.1」とインストーラのバージョン番号からビルド番号を除いたものが出力される

JDK 9

上記二つと異なる表記ルールだが、LTSバージョンではないため割愛


詳細

Javaのバージョン番号の形式は典型的なセマンティックバージョニングでもないし、長い歴史の中で何度も変わっているので分かりづらいところがあります。

例えば2021年11月14日時点で最新バージョンのTemurinのJDK 8のバージョンはダウンロードサイトには「jdk8u312-b07」と表記されています。

adoptium.net

実際にインストールして「java -version」でバージョン番号を確認すると以下のように返ってきます。

openjdk version "1.8.0_312"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (Temurin)(build 25.312-b07, mixed mode)

まず、バージョン番号らしきものが3種類出てきて、どれ一つダウンロードサイトの表記と合っていません。

openJDKベースのでJDKであるTemurinをインストールしたのでおそらくopenjdk versionJDKのバージョン情報のように見えます。しかし、「1.8.0_312」というバージョンの、セマンティックバーョニングでいうメジャーバージョンを示す位置にある「1」や、パッチバージョンを示す位置にある「0」はどこから登場したのでしょうか。

また、同じくTemurinのJDK 17のバージョンは「jdk-17.0.1+12」とダウンロードサイトに表記されています。

adoptium.net

同じようにインストールして「java -version」でバージョン番号を確認すると以下のように返ってきます。

openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12)
OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

少し表記が一致しましたが、今度は最後の「+12」はどこかへ消えてしまいました。JREのバージョン番号には含まれているし、完全一致しています。

この違いは何でしょうか?


OracleJavaのサイトに残っているリリース情報を見てみるとバージョン番号の形式が色々と変わってきたことがわかります。

https://www.java.com/releases/

その最後にバージョン番号の変遷の歴史が載っていました。

f:id:magnoliak:20211114150328p:plain

ここに書かれている「JEP 223」と「JEP 322」がポイントになるようです。それぞれ提案を見てみましょう(JEPは、JDK Enhancement Proposalsの略で、機能拡張の提案文書のこと)。

JEP 223: New Version-String Scheme

JDK 9リリースに向けてバージョン番号の形式を変更する提案が、「JEP 223: New Version-String Scheme」でした。これは採択されてJDK 9から実際に適用されました。

openjdk.java.net

Motivationの項にこう書かれています。

What's the difference between releases named "JDK 7 Update 60", "1.7.0_60", and "JDK 7u60"?

These are just different names for the same release. These differences make it difficult to identify and verify equivalent releases. A simple pointwise comparison of sequences of parsed tokens does not suffice; instead, a fairly sophisticated algorithm is needed. The use of the lower-case 'u' is not an industry standard and is not language-neutral.

冒頭のJDK 8のバージョン番号の件でいえば、「1.8.0_312」と「JDK 8 8u312」は同じものを指すそうです(その後ろに続く「b07」は後述します)。

また、話は前後しますが、uの後ろに続く数字は以下のようなルールで採番されているそうです。

Minor releases containing changes beyond security fixes are multiples of 20. Security releases based on the previous minor release are odd numbers incremented by five, or by six if necessary in order to keep the update number odd.

機能追加の時には20の倍数を付与し、セキュリティ修正をする時は、5か、6を足していく...よく分からないですね。とにかくJDK 8に更新が発生すると「u」に続く数字がインクリメントされ、リリースされることが分かります。とにかくこの数字が一番大きなものを使っておけば大丈夫、という訳です。

ただ、これでは分かりにくい、ということで「JEP 223: New Version-String Scheme」では「$MAJOR.$MINOR.$SECURITY」という一般的なバージョン番号の形式に変更することが提案されました。

メジャーバージョンが上がれば$MAJORがインクリメントされ、バグ修正や実装の改善(GCの拡張など)が有れば$MINORがインクリメントされ、セキュリティ修正が反映されれば$SECURITYがインクリメントされます。ただし、$MINORがインクリメントされても$SECURITYはリセットされずそのままインクリメントされ続けます。これはセキュリティパッチと機能追加を独立してチェックできるようにするための仕組みだそうです。

また、この時点でメジャーバージョンとJDKの番号が同期されるようになり、JDK 9のメジャーバージョンである$MAJORは「1」ではなく、「9」になりました。ここは分かりやすくなった点です。ただ、逆にちょっと分かりづらくなった点として、$SECURITYがゼロになる時は、ゼロと表記するのではなく省略となり、更に$MINOR$SECURITYの両方がゼロの場合は両方とも省略されます。そのため、最初にリリースされた「JDK 9」のバージョンは「9」で、その後にリリースされたセキュリティアップデートは、「9.0.1」というバージョンになりました。ちょっと分かりづらいですね...

しかし、openJDKが半年に一度のメジャーリリースとなり、それに対応した更に新しいバージョン番号の形式であるTime-Based Release VersioningがJEP 322として提案されたため、この形式でのリリースはJDK 9のみとなってしまいました。$MINORはインクリメントされないまま終わりました。

ちなみに、4番目以降の数値はopenJDKの派生リリースを行うベンダが自由に付与して良い、という扱いになっています(JDKディストリビューションベンダが独自のパッチを当てたバージョンをリリースする際に利用する)。

JEP 322: Time-Based Release Versioning

openJDKが半年に一度のリリースサイクルに変更されることに合わせて「JEP 322: Time-Based Release Versioning」という更に新しいバージョン番号の形式が提案され、JDK 10から実際に適用されました。

openjdk.java.net

今度は、「$FEATURE.$INTERIM.$UPDATE.$PATCH」という形式に変わっています。

  • $FEATURE$MAJORと同じ...JDK 17であれば「17」...ただし、JDKが6ヶ月ごとのリリースモデルに変わったため、何が含まれているかに関わらずインクリメントされます
  • $INTERIM$MINORと同じ...バグ修正や、互換性を破壊しない拡張が含まれると定義されています...ただし、6ヶ月ごとのリリースモデルではゼロに固定化されます...将来のリリースモデル変更のための予約となっています
  • $UPDATEは、互換性を破壊しないセキュリティ修正、バグ修正、リグレッション修正が含まれる...従来の$SECURITY$INTERIMが混ざったような定義になっています
  • $PATCHは、緊急リリースのために使われます

$FEATUREは6ヶ月に一度インクリメントされますが、$UPDATEも新しい$FEATUREのリリース後1ヶ月時点、以降は3ヶ月に一度でインクリメントされてリリースが行われます。

また、ビルド番号を示す$BUILDを「+」に続けて付加することができます。ビルド番号は開発中にインクリメントされる番号で、リリースされてしまえば気にする必要はありませんが、リポジトリのtagでは番号表記だったり「ga(general availability)」と表記されていたり、ちょっと分かりづらいですね。冒頭の、「17.0.1+12」であればJDK 17.0.1というところだけを注目しておけばOKです。

17.0.1はビルド番号12がGAリリースなので、17.0.1+12とが17.0.1+gaという表記は同じものを指しています...逆に17.0.1の後ろが11以下のバージョンのソースを開発目的以外で使うべきではない、ということが分かります。このビルド番号の考え方はJEP322が適用される前のJDK 8でも同様に運用されているようです(ただし、ビルドを示す「b」が付加されて「b07」という表記になっているので多少表記方法は異なる)。

あくまでビルド番号なので、バージョンコマンドでopenjdk自体のバージョン表記には含まれていないようです。

JEP 223の定義と変わらず、$PATCH$UPDATE$INTERIMが全てゼロの場合は省略されるので、JDK 17の初期リリース時のバージョンは「17」となり、次のリリースのバージョンは「17.0.1」となりました(長期サポート版にはバージョン番号の後ろに、空白を一つ空けてLTSを付ける...というルールも有るようですが、Oracleのリリース一覧では何故かそれぞれの初期リリースの時にしか付加していないようですね)。

バージョンコマンドで表示される3つのバージョン

冒頭で、バージョンコマンドは3つのバージョンを表示すると書きましたが、一つ目がJDKのバージョン、二つ目がJREとしてのバージョン、三つ目がJVMとしてのバージョンとなっていてビルド番号はJDK 8の頃よりJREJVMにのみ表記する仕様が踏襲されているようです。

openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12)
OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

派生バージョンのバージョン番号

バイナリをリリースしているベンダが独自にパッチを充てた場合、上記のバージョン番号の後ろにディストリビューション独自のバージョン番号を付与して区別します。例えば、ubuntu 18.04のopenJDK 11のパッケージのアップデート情報は以下のように管理されていて、11.0.11+9-0ubuntu2~18.04が、11.0.11+9をUbuntu 18.04へバックポートしたビルドであることが分かります。

http://changelogs.ubuntu.com/changelogs/pool/main/o/openjdk-lts/openjdk-lts_11.0.11+9-0ubuntu2~18.04/changelog

おわりに

バージョン番号の後ろに+に続いて付けられている番号の意味が分からなかったので調べ始めたら、色々なことがわかったのでメモにしてみました。簡単に言うと、無視しておいて良かった、ですね。

次回こそディストリビューションの話をします。

ドキュメントを書いて維持するのが何故こんなに大変なのか

この辺で手を打ってもらえないかって話になるよね

ドキュメントはステークホルダーとのコミュニケーションツールなのに、そのコミュニケーション設計が無いまま「こうあるべき」という議論をしても「オレオレ絶対正しい理論」が誕生するだけだよね。誰が読んでも分かる表現なんて無いわけで。

OpenJDKのメンテナンス状況について

JDKのメンテナンス状況、バイナリの提供状況に関する覚え書き。

OpenJDKの開発サイクル

OpenJDKプロジェクトは、半年に一回JDKのメジャーバージョンをリリースしている。2021年11月時点での最新版としてJDK 17がリリースされている。LinuxWindowsmacOS用のバイナリは、Oracleのサイトから入手可能。

JDK 17.0.1 GA Release

OracleはOpenJDKのバイナリを最新のメジャーバージョンのみ提供する。つまり、JDK 17がリリースされた以降はJDK 16以前のバイナリをOracleは提供しない。アーカイブとして過去のリリースへのリンクは貼られているが、アップデートされていない旨の記載が有る。

JDK 16 Releases

一方で過去のJDKはそれぞれ別のリポジトリにコピーされ、それぞれソースコードのメンテナンスが続けられている。

Main - Main - OpenJDK Wiki

OpenJDK: JDK Updates Project

JDK8, 11, 13, 14, 15, 16のリポジトリはメンテナンスが続いていて、最新のJDKに反映された修正のバックポートされて取り込まれている。

例えば、JDK15のリポジトリのコミットログを見ると随時アップデートされていることが分かる。

JDK 15u - JDK 15u - OpenJDK Wiki

GitHub - openjdk/jdk15u: https://wiki.openjdk.java.net/display/JDKUpdates/JDK+15u

また、その成果が一定間隔でソースコード一式をGeneral-Availability Releasesとして提供している。

Tags · openjdk/jdk15u · GitHub

しかし、あくまでソースコードのみのリリースであるため、バイナリは提供されておらず、使いたい場合は自分でビルドする必要がある。

Oracle以外のベンダはOracleJDKのサポートポリシーに合わせてOpenJDK8, 11, 17のみを長期サポート(LTS)対象としているが、ベンダがその気になればこれらのソースコードを元に、他のJDKの長期サポートを提供することも可能????

HomebrewでインストールされるOpenJDKは独自ビルド

macOSのパッケージ管理ツールであるHomebrewでopenJDKをインストールすると、最新のJDKソースコードから独自にビルドされてインストールされる。2021年11月現在は最新バージョンのJDK 17がインストールされるが、JDKがメジャーバージョンアップすると差し替えられる。

homebrew-core/openjdk.rb at master · Homebrew/homebrew-core · GitHub

macOSのHomebrewには最新のJDK 17以外にもOpenJDK 11をインストールするFormulaが用意されているが、それらもJDK Update Releasesで提供されるソースコードを元にインストール時に独自にビルドされている(OpenJDK 8だけなぜかリンク先がGitHubではなく、Open Source Community Infrastructureになっている)。

homebrew-core/openjdk@11.rb at master · Homebrew/homebrew-core · GitHub

当然だが、独自にビルドされたバイナリなので、サポートなどは提供されない。自分でビルドして、分からなければ知っている人に聞くか、自分で調べるしかない。

なお、JDKをビルドするためには他のバージョンのJDKがインストールされている必要があるためフリーで入手可能なバイナリがダウンロードされるようになっているのが興味深い。

この記事を読むと、以前はOracleが提供するOpenJDKバイナリへのリンクが貼られていた模様。

OpenJDK 11 を Homebrew で macOS にインストールする - Qiita

バイナリの入手方法について

次回に続く...