この文書ではプログラミング環境(言語、開発ツール等)について考えます。既存の様々な概念の理解及び自分で考えたアイデア(項目名に「_案」とついたもの)を書いています。あくまで私の認識を記述するもので、知識として正しい保証はありません。
さらに、この文書は私がTenyuプロジェクトのソフトウェア構成(どの言語、フレームワークを使うか等)の判断をする時に、あらゆる潜在的な可能性をも考慮して最も優れた判断をしようとして考えた諸々の着眼点や解釈、可能性などです。
もし世界が最も優れたアイデアを採用して正しく成長していくと信じるなら、さらに自分が各技術について正しい認識を持てれば、現時点でメジャーなものが後に廃れるだろうとかマイナーなものが流行るだろうといった予想ができます。
その他雑多なメモも含まれます。
例えば、新しいコンピューターやOSの出現を促すために世界的にさらにクロスプラットフォームを追求すべきだという考えが強くあり、その考えが判断に影響しています。この文書はその観点が強く出ています。特に重要なアイデアはfat_java_案または実行時依存性解決です。
この文書はTenyuLicenseで公開されます。 用途制限:本ライセンスはp2pソフトウェアまたは創作活動のプラットフォームを作成する目的ではない場合にのみ適用される。
- 概要
- License
- 目次
- バーチャルプラットフォーム_語の追加
- バーチャルプラットフォームのランタイムが持つべき特徴_案
- バーチャルプラットフォームなソフトウェアが充実した場合にもたらされるもの_予想
- JITの強み_解釈
- 仮想環境の強み_解釈
- 仮想環境言語とシステムプログラミング言語_案
- fat_java_案
- JNI用実行時コンパイル_案
- C言語は代替されるか_比較_考察
- 言語とは何か_解釈
- 何が言語の趨勢を決めるか_解釈
- 各言語への一般的期待_解釈
- インタプリタ_解釈
- WebAssemblyとJVMが今後衝突する可能性_メモ
- 仮想デスクトップによるreproducible_builds証明_案
- 自己完結型の否定_批判
- Java8_OS側JDKの必要性_解釈
- Glb_紹介
- Glbはオブジェクトであるべきか_考察
- Glbに依存したクラスを他プロジェクトから使う_検討
- 最高のプログラミング言語は_考察_案_解釈
- 私の使用言語
- C言語の特徴_解釈
- GraalVM_メモ
- WebAssembly_HTML5_解釈
- Web_解釈
- 実行時依存性解決_案
- Jigsaw_解釈
- 世界的なソースコード検索システム_案
- 私が空想する少し未来の理想的なプログラミング環境_案
- Lombokの使用範囲_考察_解釈
- プログラミングの本質的進歩とは_解釈
- 汎用プログラミング言語でビルド処理を書く_案_経験
- 起動処理をJavaで書く_自己再起動_案
- プログラミング言語によるビルド処理が必要になった場面_経験
- P2P_PKI_メモ
- Smalltalk_解釈
- オブジェクトとは_説明_解釈
- クラスとは_説明
- 継承構造_案_解釈
- 継承より構成_比較_考察
- 問題は継承ではなく抽象的設計そのもので、しかもそれは本質的難しさではないか_仮説
- NPEとOptional_解釈
- 例外処理_経験
- リリース番号_案
- ソースコード公開確認API_案
- セーフランチャー_案
- 3Dゲームにおける演出用サブ物理空間_案
- オンラインゲームの物理演算_案
- ソフトウェアプロジェクトの死_問題
- ネットにアクセスするコンピューターの水準を底上げする_問題_解釈
- 創発的なソフトウェア_解釈_案_思想
- 自由ソフトウェア_コピーレフト_オープンソース_TenyuLicense_比較
- GNUとLinuxとUnix_解釈
- リベラル_思想
- staticを使うべき場面_経験
- NVMe_SSD_予想
- デシリアライズ用コンストラクタ_案
- OSは多様化するか_予想
- LLVMとJDK_考察_案_メモ
- プログラムの統一_プログラマーの均質化_案
- JPMS_解釈
- アドインのセキュリティ_学習
- プログラムとデータファイルの配置_案_解釈
- 1プロセスで多数のJavaアプリを動かす_案
- Javaの複雑化_愚痴
- JavaFX_学習_経験
- OS_解釈_予想
- peopleware_解釈
- Tenyutalk_案
- IPアドレスからレイテンシを予測する_案
- Tenyu基盤ソフトウェアのモデルクラス_案
- プログラマー、デベロッパー、ソフトウェアエンジニア_解釈
- ReactiveX_学習
- モナド_学習
- var_代替案
- クラス設計の限界_解釈
- zstandard
バーチャルプラットフォームは仮想環境にのみ依存しているという条件を指し、その条件を満たしたソフトウェアはその仮想環境さえ提供されればどんな環境でも動作するので、その意味で未知の(将来的に出現する)プラットフォームでも動作させやすい(ランタイムさえ提供されれば動く)性質があります。バーチャルプラットフォームなアプリは特定の仕様を満たしたランタイム(仮想環境、JDK等)を要求し、他の種類のプラットフォーム(Windows+x86, Linux+ARM等)とバイナリ互換は通常ありません。ランタイムが環境の違いを吸収します。pure javaという概念に近いですが、java以外でもバーチャルプラットフォームなソフトウェアはありうるのでバーチャルプラットフォームという語が適切です。バーチャルプラットフォームなソフトウェアは環境毎のネイティブコードに依存している(それが無いと動作しない)ソフトウェアは該当しません(定義から当然)。
類似した既存の語であるクロスプラットフォームは現在存在する複数のプラットフォームで動作するというだけで条件を満たします。ソース互換しかない場合や、プラットフォーム毎のネイティブコードを同梱してプラットフォームに応じて使い分けている場合もクロスプラットフォームと言われる場合があります。クロスプラットフォームなソフトウェアは未知の環境で動作させれません。
類似した既存の語であるバイナリ互換はプラットフォームAとBにおいてバイナリ互換という捉え方になります。さらに、プラットフォーム毎のネイティブコードを同梱してプラットフォームに応じて使い分けている場合でも定義上バイナリ互換だと言える可能性があります。
だからバイナリ互換も未知の環境で動作させやすいという性質を意味しません。例えばABIの共通規格化などを通して広い範囲でバイナリ互換が達成されても、将来的に出現するプラットフォームがその共通規格に参加しない場合動作しません。
類似した既存の語であるplatform independentは、プログラムの場合、たとえpure javaでもJDKというある種のプラットフォームに依存している点で不正確です。bmpのファイルならplatform independentと言っても良いと思います。
このように、私はクロスプラットフォームやバイナリ互換という語に昔から微妙に不満があり、バーチャルプラットフォームという語を勝手に追加しました。
さらに、バーチャルプラットフォームなソフトウェアを増やすべきという思想を持っています。バーチャルプラットフォームなソフトウェアはコンピューターやOSの設計の変化に強く、言い換えればコンピューターやOSの設計の変化を促します。例えば誰かが新しいプラットフォーム(命令セットやOS)を作るとします。新しいプラットフォームはソフトウェア資産において古いプラットフォームに負ける可能性が高いですが、もしバーチャルプラットフォームなソフトウェア資産が大量にあれば、ランタイムさえ実装すればソフトウェア資産の面で追いつけます。
javaはある程度そのような事を実現しうるプラットフォームですが、一部のjavaライブラリはネイティブコードを含んでいて新しいプラットフォームがJDKを実装しても動作しません。 典型的にはそのようなソフトウェアはwindows, linux, macなどのメジャー環境毎にネイティブコードを持ち、環境に応じて使い分けています。 後述するfat java案はそのネイティブコード同梱問題を解決します。
ネイティブコードを含んでいてもバーチャルプラットフォームと言っていい場合もあります。高性能化のために環境別のネイティブコードを含んでいて、しかし対応していない環境ではバイトコードで動作させるというアイデアが実用化されています(例えばNettyとjmonkeyで見かけました)。
実際に新たなプラットフォームが流行する兆候がいくつかあります。例えばARMやRisc-Vです。
ARM版JDKはx86版よりもある種のテストにおいて高性能というデータ。
https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO18-JavaOnARM-ja.pdf
しかもARMの中でも随時ハードウェア実装された関数が増加しているようで、そのようなものがプログラムの再ビルド、再インストール無しでJITで活用されます。
バーチャルプラットフォームなプログラムはプラットフォームの革新を阻害しないという点で創発的だと言えます。
ここでランタイムは仮想環境を実装したソフトウェア(JDK等)。
- 中間コードでアプリをバイナリ互換にする。CPUやOSの変化からアプリケーションを守る。
- 第三者による実装がライセンス上可能である事。新しいプラットフォームを作る人がそのプラットフォーム版のランタイムを自由に作成して配布できる事。
- 新しいプラットフォームでランタイムを技術的に作成しやすい事。
- 実行時環境に応じた最適化(JIT)。例えば標準APIをハードウェアアクセラレーションできるコンピューターがあった場合、それを活用できること。
- 標準APIの強化等で全アプリを完全にバーチャルプラットフォームにする。
- デスクトップアプリ及びp2pをサポートする。dockerに対する優位性。
- ISAの多様化。RISC-V等
- OSの多様化または移行(特にLinux等FOSS系OSへの)
ハードウェアに新機能の実装を促せる可能性がある。例えば32bitから64bitへの移行の時、C++アプリよりJavaアプリの方が高速な事例があったようです。Javaはランタイムを更新するだけでCPUの新機能を活用できるのに対して、C++アプリはアプリ毎に再ビルドと再インストールが必要です。 つまりJITと中間コードというアイデアは本質的にプラットフォームの進歩を促していて創発的だと言えます。
仮想環境の例はJDKや.NETです。 一般にそのようなものはプロセス仮想機械と呼ばれていますが、私はアプリ側から見た印象で仮想環境と呼んでいます。極論すれば仮想環境はプロセス概念と関連付ける必要性がありません。極論ですがもしかしたら1プロセスの中に多数の仮想環境が作られるかもしれないし、多数のプロセスが協調して1つの仮想環境を作るかもしれないし、仮想環境がハードウェア実装されたり、カーネルの概念設計が全く変わってプロセスという概念が用いられなくなるかもしれませんが、どうなろうとも仮想環境のみに依存したソフトウェア資産は仮想環境が実現されたなら動作すべきです。
良く言われる仮想環境の強み
- 実行ファイルのバイナリ互換
- サンドボックス適性。機械語プログラムでサンドボックスをやるにはVMware等システム仮想機械が必要です。
- (中間コード+JITという設計が採用される事が多いので)実行時情報を活用した最適化
私は他にも重要な特徴がある事に気付きました。仮想環境はアプレット適性が高い。 C++等のネイティブコンパイル言語と比較した場合、仮想環境言語はアプレットやアドインを安全かつ省メモリかつ高速に実行できます。 アプレットとはアプリ上で実行されるアプリです。アプレットの例はJavaアプレットやFlashやwasmです。 Webブラウザの考察の方でも書きましたが、例えばもしWebブラウザがJavaベースで作られていたらJavaアプレットの起動は高速だったはずです。WebブラウザがC++ベースで作られている時点でJavaアプレットに限らずWebページ上で実行されるプログラムのために仮想環境を起動する必要があります。その仮想環境が高機能であるほど起動時間が問題になります。WebブラウザをC++で作るという判断がアプレットに適していません。ホストアプリとアプレットが同じランタイム上で実行される方が省メモリになります。アプレットやアドインに対応したアプリは仮想環境言語で作られるべきです。
かつてJavaによるWEBブラウザが存在したようです。HotJava
アプレットを高速起動できるというメリットが存在したはずですが、当時Java自体が低速で普及しなかったようです。
仮想環境言語とシステムプログラミング言語という組み合わせは究極的にも有力だと思っています。 仮想環境言語(Java等)はあらゆる環境で共通の機能(仮想環境が提供する機能)を使用できて、フリースタンディングできません。 システムプログラミング言語(C等)は(一部の環境にしかない)環境固有機能を使用できたり、フリースタンディングできます。この関係は相補的です。
システムプログラミング言語はコンピュータに対するほぼ完全な制御性を持つという特徴から仮想環境の欠点を補える。仮想環境言語はどの環境でも出来る事しかできません。だから仮想環境は1つシステムプログラミング言語を想定し連携すべきです。
このアイデアはコンピュータをプログラミングするという問題に対する根本的な解決のフレームワークです。
多種多様なプラットフォーム(CPU,OS)が作られる事を想定すると、一部の低水準プログラムはシステムプログラミング言語で環境毎に作り、大抵のアプリは仮想環境言語で作り、アプリ資産が全種類のプラットフォームで共用される事で新しいプラットフォームを作りやすくなり(アプリ資産の面で追いつけるから)、このサイクルは加速しやすいからです。
JDKにAPIを追加しまくれば、それらはネイティブ実装されるので、ユーザーライブラリがネイティブコードを同梱する必要性がほぼなくなるだろうというアイデア。 ネイティブコードを同梱しているライブラリを広く調査して、JDKにそれらネイティブコードを代替するAPIを実装する。 そのJDKの肥大化によってほぼすべてのライブラリをバーチャルプラットフォーム(pure java)にしようというアイデア。
"新しいコンピューターアーキテクチャを作る人がJDKさえ実装できればほとんどのJavaアプリが動く"という状況を作るのが理想。
このアイデアの良いところはJava世界でどんなネイティブAPIが必要とされているかが既存のライブラリによって洗い出されている事にある。 性能的にもメリットがあるはずで、JNIでネイティブコードと連携するとオーバーヘッドがあるが、JDKに実装する事でそれを無くせるはず。
ただしLLVM+JDKというアイデアでもpure javaが促進される。性能のためにJNIを通してネイティブコードを利用する事が無くなるから。
JNI用のソースコードをJarに含めて実行時コンパイルする事でソースコード記述時点で存在しない環境にも対応しようというアイデアです。 とはいえ、JDKが最終的にJNIに期待するのは環境固有機能の使用だろうと思います。性能目的でのJNI使用は多数実在していますが、JNIはオーバーヘッドがあり性能問題への理想的な解決策ではありません。性能問題はJITとfat java案で解消できるはずだから、最終的には環境固有機能(特に特殊なハードウェア機能)の利用がJNIの役割になるはずです。私はこのアイデアをバーチャルプラットフォームの改善になるだろうかと思い書き始めましたが、不要かもしれません。環境固有機能を呼び出すコードはどう努力しようともその意味によって環境依存します。
C言語で書かれたプログラムは脆弱性が多かったり保守性が低いので代替が望まれます。しかしCはあらゆる言語の中で最も膨大かつ基礎的な既存資産を持ち代替は困難です。
一方で、Cへトランスパイルできる言語というアイデアは優れています。 何か他の言語を作り、そこからCにトランスパイルできるようにすれば、即座にほぼ全ての環境でその言語は機械語へコンパイル可能になります。さらに今後出現する未知の環境においてもCコンパイラは恐らく作成されるので、未知の環境でも機械語へコンパイル可能になります。
- Cコードは機械語と単純に対応づくので機械語にコンパイルできるならCコードにトランスパイルできる。
- Cコンパイラの普遍性からCへのトランスパイラは価値が高い。
- 環境非依存のコードを作成し、Cへのトランスパイルのタイミングで環境依存させれる。
- より大規模開発に適した言語仕様を提供できる。
ではCへトランスパイルできる言語(Nim等)がCを置き換えるか? Cの主な領域はOS、デバドラ、組み込みであり、そのような領域ではCの言語仕様がハードウェア(CPU)を詳細に制御するために適しています。書いたことしか起きない、実行時間が予測可能である事が重要とされます。抽象的な言語はその点で逆効果とみなされるようです。特に抽象的なデータ構造は(Cの配列やポインタを活用した処理のように)ハードウェア性能をストレートに引き出すようにトランスパイルするのが難しいようです。
Cは(ノイマン型の)CPUを単純にモデリングした言語で、CPUという概念が変わらない限り変わりそうにありません。 https://www.quora.com/Do-you-think-C-programming-language-will-die-out-in-the-future/answer/Jan-Christian-Meyer
他に一部組み込み等で実行ファイルが小さい事や省メモリである事が要求されるようです。Cはこれらの条件をすべてクリアできます。
AIによるデバッグというアイデアもあるようです。しかしこれはCが他の言語で置き換えられるわけではありません。
他にCoq等の証明支援システムを通じて安全なCコードを作成しようというアイデアもあるようです(まだ調査できていない)。
Rustが少しCの代替として期待を受けましたが、フリースタンディングな領域ではポインタ変数を定数や変数等で初期化する場合があり、そのようなものは静的にポインタの安全性を判定できないのでunsafeであり、unsafeなコードが増えるようではRustを使う意味がどの程度あるのかという事になります。しかしMSがC言語の代替としてRust系のアイデアを検討したようです。
かつてC++もCを置き換える事が期待されていたようですが、実際置き換えていません。C++の学習コストの高さ、言語仕様の複雑さから生じるプログラマーの不均質等も問題になります。
言語は処理系や言語仕様や開発ツールも含み、どの部分もバージョンアップされていきます。それを構成する全部分が更新されてもそれ自体が存在します。
言語と言語仕様は良く混同されています。例えば「言語(言語仕様)で性能が決まるわけではない」という言説がたまにありますが、ランタイムやコンパイラをも含んで言語と呼ぶべきなので、言語は性能に影響すると言えます。実際「どの言語(ランタイムやコンパイラ等を含む)が最速か?」というような話題がたまにあります。言語によってはランタイムやコンパイラが複数ある場合もありますが、それも含めて様々な事を念頭においた文章を書くしかないと思います。
いくつかの言語は既存の言語よりスマートにコードを書けるなど言語仕様上の細かなメリットを主張し既存の言語を代替するかのように登場しました。例えばkotlinはbetter javaとして登場しました。C#はJavaを、C++はCを、RustはC++を置き換えるかと思われました。
いずれも置き換えられる事はありませんでした。
多くの専門家が新興言語の方が優れていると説明してもなかなか代替は起きません。なぜか?
- 既存言語もバージョンアップで言語仕様を改良できるから。
- 細かな改良でいちいち言語を乗り換えていたらキリがないから。
- 僅かなメリットのために学習コストをかけるのが嫌。
- 抱えているプロジェクトが既存言語で書かれている。
- 既存言語の方がライブラリや解説記事等が豊富だから。
- 自分のプログラミングライフにおいて1つはメジャー言語をやらないとプログラミングを体験できたと言えるのか疑問だから。
- 新興言語は方向性がブレそう。
1について。例えばRustがメモリ安全性を謡って現れました(2010年)が、少ししてC++もunique_ptrとshared_ptrによって同等の安全性を達成しました(2011年)。
2について。エコシステムが軽率な理由で言語を乗り換えるなら小さなエコシステムが乱立する事になりどのエコシステムも十分なソフトウェア資産を持たなくなりライブラリの量やプログラマーの調達に苦労するようになります。
各言語はそのソフトウェアエコシステムから何らかの期待を受けています。 Javaエコシステム、PHPエコシステム、Cエコシステムなど、それぞれ言語への期待が違います。クロスプラットフォーム、ライトウェイト、フリースタンディングなど。
言語はエコシステムからの期待に応じてアップデートされます。例えばFortranは非常に古い言語で、科学技術計算の期待に応え続けてきたと思いますが、時代によって具体的な要求は様々だったようです。言語はエコシステムからの期待に応えるためにアップデートされ、変化し続けます。
言語は一部の期待を捨て去る場合があります。例えばJava AppletはJava11で廃止されました。これまでいくつものAPIが廃止されました。Python 3.0は後方互換性を捨てました。このようなリスクを考えるとある時点で自分のプロジェクトに必要な機能がすべてあるというだけでは言語を採用するのに不十分で、その言語が長期的にどのような方向性で変化していくのかを考える必要があります。
言語の趨勢は複雑な社会的動態の結果で決まり予測不可能な面がありますが、一貫した法則を見出すなら、新しい期待を創出した言語は使われ続け、僅かな改善を施しただけの後発言語に負けないという印象があります。
Javaはクロスプラットフォームという期待を受けています。公式の処理系や言語仕様だけでなく、Java周辺のエコシステムが作るライブラリはクロスプラットフォームである事が多く、そうでないなら明記されるだろうと思えます。 ライブラリの豊富さやJDKの質についても期待されます。恐らくJavaが流行した理由は仮想機械+中間コード+JITという構成で高度なクロスプラットフォームを実現するという独自の期待を得た事、そして期待に応え続けた事です。
C言語はコンピューターの完全な制御とそこそこの移植性とコンパイラを書きやすい事が期待されます。
C#は後発言語としてbetter javaであること、MSの構想で良くサポートされたりMSの開発力によって高水準が維持されるだろうと期待されます。
PHPは手早くウェブサイトを作れる事を期待されていました。PHP: Hypertext Preprocessor この名前はそれが何であるかを示しています。それは当時独自の期待だったと思います。 PHPはいくつかのDBを標準でサポートしています。例えばmysql_query()という関数が定義されていました。それはとてもPHPらしいことで、汎用性を重視するプログラミング言語ではAPIの関数名にmysqlという言葉が出てくる事はありません。 昔PHP対応したレンタルサーバがたくさんあり誰でも簡単に動的なWEBサイトを作れました。そのようなエコシステムもPHPらしいことです。
JavaScriptはWEBフロントエンドが期待されます。近年ではサーバサイドでも使われます。 ちなみにJSやTSはWebAssemblyを通じて参入してくる他言語によって殺されるか?言語への期待に基づいて考えればされないと言えます。他の言語のエコシステムはWebフロントエンドのために成長する意欲をほとんど持っていない。
Python。 Webスクレイピング、データ加工や分類や集計、バッチプログラムなど、業務の細かな場面で使われる利用者数が限定的なアプリがPythonで作られている。 特にPythonは機械学習で使われますが実際機械学習を処理しているのはC++で作られた外部ライブラリです。 Python+ネイティブコードという多言語連携でPythonのちょっとしたアプリを作る適性と高速な機械学習を組み合わせる事に利点があるのだろうと思います。
昔インタプリタがスクリプトを実行時環境で解釈する事でクロスプラットフォーム性が高まるという観点がありましたが、中間コード(Javaバイトコード等)を実行時環境でJITするという方式は上位互換であるように思います。
-
WebAssembly
- セキュリティ◎
- 性能◎
- クロスプラットフォーム〇
- 汎用目的△?
- 多言語〇
- ブラウザはより厳しいセキュリティが要求され、機能は制限されます。
セキュリティは計測不能な対象で、ブラウザ毎にセキュリティに関する見解が異なる可能性があり、ブラウザ間の互換性に限界が出ます。
汎用目的はWASIプロジェクト次第なので?つき。
-
JVMアプリ
- セキュリティ〇
- 性能◎
- クロスプラットフォーム◎
- 汎用目的◎
- 多言語〇
- WEBのようにネットからコードをDLして実行する普及した仕組みがありません。ただしSecurityManager、AccessController、カスタムPermission等を使ってWEBのようなネットからのコードのDL及びサンドボックス実行するシステムを構築可能です。
このような機能があればreproducible buildsを証明できそうです。 githubが仮想デスクトップを提供し、開発者がそこでビルド手順を実行し、 その模様を記録し、作成されたバイナリを入れるフォルダがありそれがgithubのリリースページに置かれる。 その操作の模様と結果をgithub上に表示する。
Java9+では自己完結型が推奨されるようです。自己完結型はバイナリ互換性がありません。VM+中間コードという構成はバイナリ互換という貴重な性質を達成できるにも関わらず。
一応Java 11以降でもOS側JDKはあって、Liberica JDKがJavaFX同梱版のJDKやMSIインストーラーを提供しています。 しかし現在のJavaでは自己完結型が推奨されているという印象があります。
現状Java9+はあまり使われていません。多くのプロジェクトがJava8に留まっています。
https://snyk.io/blog/jvm-ecosystem-report-2018/
少し前のデータですが、Java9+全体でJava7未満です。Java8には到底及びません。
こうなっている理由はいくつか考えられます。
- Java9-10が短期サポートしかない。Java11を待っている。
- 単に時間が足りてない。いずれJava9+にする。
- Java9+への移行作業が難しい。使用しているライブラリがJigsawに対応していない等。
- 自己完結型を求めていない。
https://qiita.com/kazumura/items/50f041054572ceffe994(B) JPMS(Jigsaw)の否定
2019後半に至ってもjava8が最も使用されています。
https://www.i-programmer.info/news/80-java/13451-java-8-remains-dominant.html
JavaユーザーはLTSバージョンを利用する傾向があり、次のLTSはjava17(2021)ですが、java8はjava17より長期サポートされるという話があり最も長期サポートされるのはjava8です。
https://codezine.jp/article/detail/11259
JPMSは自己完結型を推進する仕組みで、javaへのバイナリ互換の期待を裏切る方向性です。
しかし、Javaの方針にバイナリ互換が入っていると言えるのか微妙です。 https://en.wikipedia.org/wiki/Java_(programming_language)#Principles
It must be architecture-neutral and portable.
portableはバイナリ互換を意味する場合もあるようですが、ソースコードレベルの移植性を意味する場合もあるようです。
https://en.wikipedia.org/wiki/Software_portability
Building executable programs for different platforms from source code ; this is what is usually understood by " porting ".
自己完結型が主流となってもJavaの基本方針からの逸脱は無いと言えるかもしれません。
corretto/corretto-8#108
私はJava環境がこのようになることを期待しています。
2つのJavaが共存すべきだと思います。OSサイドのJava8JRE+自己完結型の最新JRE。JigsawはJava9+を自己完結型にするのに適している。下位互換性は、OSのJava8JREによって維持されます。自己完結型のアプリケーションは、JREの脆弱性を残す可能性が高くなります。OS側のJREが存在する必要があります。下位互換性のためにはJava8でなければなりません。いくつかの有名なプロジェクトはJava8からJava9+への移行に苦労したり失敗したりしている。私の考えが正しければ、Java8のjreインストーラとアンインストーラは将来永久に必要になると思います。
Java9+は後方互換性を簡単に捨てるようになったと思います。 バイナリ互換のクロスプラットフォームも失いました。
それで私は2つのJavaが併存する事を主張しました。
多くのJDKがJavaFXを同梱していなかったり自己完結型を前提にしつつありますが、 Liberica JDKはJavaFXを同梱していてOS側JDKとして適しています。
Java8からJava9+への移行問題はJavaがこける可能性の1つです。他にもリリースサイクルが短くなっていますがその結果としてJavaプログラマーの均質性が低下し、可読性が低下する恐れがあります。
TenyuはGlbという特殊なクラスを使用しています。
https://github.com/lifeinwild/tenyu/blob/master/src/main/java/glb/Glb.java
私のおぼろげな記憶によれば、これは昔某社で見かけたアイデアであり私の発想ではありません。 Glbは(悪名高い)グローバル変数を管理し、インスタンスが作られずstaticメンバーで構成され、とても簡単です。
Glbの利便性は
Glb.get
とタイプするとUtilクラスとかLoggerとかDBとかその他様々なシステムの要素にアクセスできることです。
もう1つの利便性はGlb.set系インターフェースを通じてテスト用オブジェクトをセットして、システム全体をテスト用に切り替えれる事です。
Glbはシングルトンを改良したようなものです。シングルトンは何か特殊な仕組みを用意しない限りテスト用に切り替えれないという問題がありましたが、Glbにそれはありません。
グローバル変数への批判は非常に強いのでGlbは批判されそうですが、実際どうだったか。
- テストは非常にしやすい。セッターでテスト用インスタンスをセットするだけでシステム全体をテスト用にできます。 テスト用クラスは本番用クラスを継承して一部メンバーをオーバーライドするだけです。
- Glbはトランザクションが必要な場合があります。 その領域はSoftware Transaction Memoryと言われているようです。 ClojureがSTMを実装していますが性能が悪いようです。
- Glbは最も素直な構文で扱えます。複雑で高度な概念は何もありません。
- アプリのあらゆるクラスがGlbに依存する可能性があるので、もし一部のクラスを他のプロジェクトと共有する事を考えた場合、問題になります。後述。
- Glb的アイデアを推し進めるとプロセスが1個のオブジェクトであるように思えてくる。staticメンバーはクラスローダー内で一意ですが、Glbのstaticメンバーはそのクラスローダー空間を1個のオブジェクトとみなした場合のメンバー変数である、というような。そしてクラスのメンバー変数がそのクラスのメソッドにおいて標準知識であるように、Glbのstaticメンバーはそのプログラムで標準知識になる。
- Glbをデフォルトパッケージに置いて、かつそのstaticインターフェースについて共通規格化すると、Glbに依存したクラスを他プロジェクトで再利用可能になる。しかし現在のJavaの仕様ではデフォルトパッケージに置かれたクラスを他のパッケージでインポートできない。そこで現在glb.Glbになっています。Glbに依存したクラスを他プロジェクトで再利用するというアイデアはまだ実証できていません。
Glb的アイデアを主張している人が他にいないか探しましたが、こんな記事を見つけました。
https://www.ibm.com/developerworks/jp/webservices/library/co-single.html
この記事のToolBoxはGlbに非常に似ています。しかしこの記事はToolBoxをシングルトンであると説明していますが、Glbはインスタンスがゼロでシングルトンではありません。
Glbはいくつかsetup系メソッドを持ち、setup系メソッドを通じてメンバーを初期化します。Glbのメンバーの初期化はシステム全体をセットアップする事を意味します。例えばテストのためにシステム全体をセットアップできます。
現在の実装ではGlbは純粋にstaticでありインスタンス化されません。
もしGlb自体がオブジェクトだと、Glbインスタンスをメソッドの引数に入力しローカル変数として扱う場合が出てきます。
恐らくそれは問題を引き起こします。
Glbと名付けられたものがLocalになる事は意味的な間違いがあります。
この問題はまだ検討中です。
Glbベースで設計されたクラスを他プロジェクトから利用しようとすると、依存しているGlbインターフェースが見つからないせいで動作しない可能性があります。
Glbはglb.Glbという名前で作成されるので、同じパッケージに自前のGlbクラスを置いて同じ名前のインターフェースを持たせる事で動作させれる場合もあるかもしれませんが、オリジナルのインターフェースの実装がまた他の様々なものに依存している場合、同等の動作を実現するのが難しくなります。
とはいえ、LoggerやUtilなどは比較的依存性が限られているのでこのアイデアで十分対応できそうです。対応できなそうなのはアプリ性が強いGlbメンバーです。
多くの場合、プロジェクト間でのクラスの共有はあるプロジェクトがデータを作成して他のプロジェクトはただそれをデータとして読み取るだけです。この場合クラスの全機能が使える必要は無く、ゲッター以外要りません。ほとんどのアクセサはGlbに依存しません。
Javaの実行において、依存先クラスのロードはそれが必要になるまで行われません。
つまりゲッターを使うだけならGlb関連の依存関係を解決する必要はありません。
とはいえ問題が出る場合もあります。メンバー変数の初期化にGlbインターフェースを使用していてデシリアライズの場面でその依存先クラスの定義が無かったら、デシリアライズできません。これはデシリアライズがまずフィールドを初期化(デフォルトコンストラクタ)した後にシリアライズデータから情報が設定されるからです(Kryo5 rc1で確認)。つまりフィールドに2回値が設定されているような面があります。もしフィールドの初期化でstaticメソッドが呼び出されるようなコードになっていると、デシリアライズの時にそれが呼び出されます。この場合ただゲッターを利用したいだけでも動作しません(デシリアライズすら成功しません)。
このような事をかなり多くの人が思うだろうと思います。 「私(あるいは企業)の長期的なプログラミングライフに適した最高のプログラミング言語は?」
この問題の本質は何か?
- 学習時間を無駄にしたくない。長期サポートされる開発環境が理想です。言語によってGC等処理系の前提の違い、言語仕様の違い、周辺のライブラリやフレームワーク等の違いがある。最新の話題をキャッチアップしていく必要もある。だから言語習得は学習コストがかかります。他の言語への乗り換えはそれまでの学習時間を無駄にする可能性があります。「その都度ぐぐって使うから何も事前に習熟しておく必要が無い」という主張はある程度正しいものの限界があります。運よくいつも全ての必要な情報を検索で見つけられるとか、見つけた情報をすぐに理解して活用できるかという点で楽観し過ぎです。「その時々の問題を解決できる最も簡単な言語を選ぶ」というスタンスは、長期的に様々な問題を扱っていく場合、毎回異なる言語を選ぶ可能性があり、それなら最初学習コストを払ってでも本格的な言語を覚えて一貫して1つの言語で解決する方が結果的に楽かもしれません。
- できるだけ可能性が広いスキルセットが欲しい。その方が何かを作ってみようと思い立つ可能性が高くなります。あるいは、やってみようと思った事を何でもやれるようになります。
- 世界のソフトウェアエコシステム全体で重複したソフトウェアを作らないように。もし最高のプログラミング言語が明らかになり世界がそれに統一されたら重複したソフトウェアを作る労力を省けます。現状、各言語毎にほとんど同じ内容のライブラリ等が作られています。
- コンピューターをプログラミングするという問題に対する解決のフレームワークはどんなものか。つまりプログラミングとは根本的にどんな問題で、どんな枠組みで解決されるものなのか。例えば仮想環境言語+システムプログラミング言語というアイデアはその一種です。つまりその都度自分の状況に応じて適切な言語を考えるのではなく、プログラミングとは何かという事に対して汎用的な解決の枠組みを見出せたなら、ただ1つの最高のプログラミング言語または最高の言語の組み合わせが見つかるはずです。
だから最高のプログラミング言語を考える必要があります。さらに開発ツールやフレームワーク等も含めてプログラミング環境全般を考える必要があります。
一般論として、やろうとしている事に応じて選択する事が重要だと言われます。 ではどんな事にどんな言語が適しているか?そしてその理由は?
どんな領域があるか?主に使用される言語は?
- OS:C
- デバドラ:C, C++
- 機械語汎用アプリ:C, C++,
- スパコン:Fotran, C, C++
- 仮想機械汎用アプリ。バイナリ互換:Java, C#
- スクリプト汎用アプリ、ちょっとしたツール。バイナリ互換:Python
- Webフロントエンド:JavaScript, TypeScript
- 組み込み。実質、デバドラまたは汎用アプリ:C, C++, Java
どんな理由でそうなるか?なぜ1言語で全てをやらないか?
多くの言語でクラス概念が使用されるが、クラス概念が適さない領域がある。
例えばゲームはクラス設計が適しません。interfaceすら安定しないほどの予測不可能な概念の変化が生じる対象において、クラス設計(概念のモデリング)は通用しません。
さらに、ハードウェアに近い領域では抽象的設計の上ではコードがどのようなハードウェアの動作を引き起こすのかイメージしづらく好まれないようです。極度に省メモリが要求されるプログラム(一部組み込み)も抽象的設計と相性が悪い面があります。
主要OSはほとんどCで書かれているようです。Linuxは一度C++に移行しようとしてまたCに戻ったそうです。Cはクラス概念を持っていません。
クラス設計が適した領域とは? ほとんどのアプリ。設計を作り込んでいけばやがて安定するだろうと思えるような設計対象。アイデアが変化しにくい対象。ハードウェアの動作を詳細にイメージする必要が無い場合。
クラス概念がほとんどの領域で猛威を振るっていますが、それにも関わらず構造体程度の概念しかないCが主要言語であり続けます。つまり1言語による完全統一は実現しそうにありません。
一方でC言語はほとんどのアプリ開発に適しません。FFI(JNI等)で部分的な関数をCで書くことは多々ありますが、アプリ全体をCで書くことはあまりないと思います。gitはCで書かれていますが恐らくそれはCで大規模プログラムを書くという特殊スキルを伸ばした人が作ったからです。
JDK系言語(Java等)や.NET系言語のようなOSのAPIを隠蔽して仮想環境をアプリに提供している言語はOSやデバドラの作成に使えず使用領域が制限される一方、プラットフォームの変化に強い等独特の強みがあります。さらにOSやデバドラといった領域を捨てている事でGCを標準採用する判断が可能になります。GCが発生させるラグはハードウェアに近い領域ではしばしば問題になります。GCがプログラミングにもたらす利益は非常に大きい。例えば近年注目されているRustという言語は(GC無しでの)メモリ安全性が主な評価理由ですが、仮想環境言語はGCや配列境界チェック等を通じてメモリ安全性を標準的に達成しています。
つまり領域毎に使用言語が異なるという状況は今後も変わりそうにありません。あらゆる領域を扱う事を望むなら1言語ではなく複数言語の組み合わせを考える事になります。全体利益のために世界でどんなプログラミング言語が流行すべきかという問題は、特定の1言語が答えになることはないという事です。
言語によってプログラマーに標準的に期待できる事が違う。
- フリースタンディングなコードを書くスキルを最も期待できるのはCプログラマーです。
- C++プログラマーは抽象的設計と低水準操作を両方扱います。扱う概念が多岐に渡るのでC++プログラマーは均質性が低くなります。
- Javaプログラマーはクラス設計に慣れていて他のパラダイムでコードを書きません。Javaプログラマーは均質です。
なぜあらゆる言語機構を搭載した言語はダメか? あらゆる言語機構を搭載した最強万能言語があったとして、学習コストが高すぎるのでその言語をマスターしている人は限られ部分的にしか使えない人がたくさんいる状況になるし、人によって設計方針がバラバラになります。現実的に可読性が高まらない事を意味します。さらに前述したように言語が使用される領域を制限する事でGCを標準採用できるなど可読性と利便性を両立できる場合があります。
問題を多言語連携で解決するというアイデア(FFI)もあります。Pythonが機械学習ライブラリをネイティブコードに頼っているように。あるいはJavaがJNIでネイティブコードを利用するように。つまり1つの大きな問題を複数のプロジェクトに分けて、プロジェクト毎に言語を選ぶということです。 多言語連携の長所は1プロジェクト内では一貫性があるコードが記述される事です。 多言語連携の弱点はFFI部分で新たに留意する事が出てくることです。例えばJNIではJava起動オプションでGCを選択できますが、GCによってJNIの性能がアプリによって致命的なほど変わる場合があるようです。
性能や大規模開発適性を求めない場合がある。利用者数や利用頻度が限定的な、業務上必要とされるちょっとしたツールをスクリプト言語で作りたいという需要がある。Pythonが支配的な領域。
このような考えで「なぜ1言語で全てをやらないか?」について私は納得しました。 しかしまだ問題は続きます。最高の1言語を特定できないなら、最高の言語の組み合わせは何か? 仮想環境言語とシステムプログラミング言語_案が有力そうに思えます。 そして現状最高のシステムプログラミング言語はCです。対抗馬はRustですが代替は無さそうです。 仮想環境言語はJDK系か.NET系になります。wasmというのも出てきたようです。
それで、私はJava+Cが有力だと思いました。 ところがC++が恐ろしい可能性を持っている事に気付きました。 私の中で最終的にC++またはJava+Cという候補が残りました。
- C++
C++は1言語路線の最有力候補。
学習コストが高い。
Qtでソースコードレベルのクロスプラットフォームも。
1つ恐ろしい可能性がある。C++はWebAssemblyでWebフロントエンドにも乗り込めるし、WASIプロジェクトの結果次第ではVMすら手に入れてバイナリ互換のクロスプラットフォームすら手に入れる。そこまでいくとJava+Cの対応可能範囲に追いつく。
多くの重要なプログラムがC++で作られている。JDKや .NETや主要ブラウザ等。 - Java+C
それぞれが単純な言語で大抵の問題を片方で解決できて、ステップアップしやすい事、ソースコードの一貫性を保ちやすい事、そして連携させる事でほぼすべての問題に対応できる事からこれが最高のプログラミング言語(の組み合わせ)であると考える。相補的。
いずれも長年最上位のメジャー言語。
仮想環境言語+システムプログラミング言語というアイデア。
KotlinのライブラリをJavaから利用するなど他のJVM系言語のソフトウェア資産を利用できる。
null安全ではないが静的解析ツールで補える。
GWTやteavm等でwebフロントエンド開発にも使えるようです。しかしJSより先にJavaアプレットがあった事を考えるとJavaはこの領域で後退しました。
個人的な予想では、JavaベースのP2PソフトウェアがWWWを置き換える可能性があります。実際どうなるかは分かりません。もしそうなるとWebフロントエンドという概念自体が無くなります。
LLVMベースのJITでC++並の性能に到達できる。
C++とJava+Cの比較についていくつか思ったことを書いておきます。
- JDK自体は(一部モジュールを除けば)javaで実装できない。
- Webブラウザのようなアプレットを扱うホストアプリは仮想環境言語で書いた方がアプレットの起動が高速になり総合的に省メモリになります。
- GCのラグが許されないソフトウェアがある。しかしZGCで大幅に改善する見込みがあります。
- 特殊なハードウェアを多用するシステムではJNI連携が多発するので、Javaは適さないか何かソリューションが必要。
当初私はプログラミング言語の未来についてこう考えていました。 バーチャルプラットフォームなソフトウェアが増える事によってプラットフォームの進歩が激化したり多様化すればC++等のネイティブコンパイル言語のソフトウェア資産はついてこれないと。 ソースコードレベルではCトランスパイラとかLLVMがあればついていけますが、実行バイナリがバーチャルプラットフォームではないので、再ビルドと再インストールの手間があるから徐々に使われなくなっていきます。 例えばCPU命令セットが急激に多様化した場合、PCを買い替える時アプリを移行できるかということです。アプリがビルドされる時点で存在していなかったCPU命令セットには対応できません。しかしバーチャルプラットフォームソフトウェアはランタイムの更新だけで対応できます。 だから長期的にはJDK系や.NET系のソフトウェアが利用者を増やすのだろうと思っていました。 しかしWASIの結果次第でC++アプリもバーチャルプラットフォームになるので、この話は成立しなくなり、最高のプログラミング言語が何であるかという問題は再びはっきりしなくなります。
ちなみに私はWebフロントエンドではTypeScriptが優れてるんだろうと思ってますが、そもそもWebの行方に疑問を持っているので含めませんでした。 TypeScriptが優れているんだろうと考えた理由は、人気であることと、このデータを見つけたからです。 https://web.cs.ucdavis.edu/~filkov/papers/lang_github.pdf
TypeScript−0.43 TypeScript −1.32 (0.40)∗∗ −2.15 (0.98)∗ −1.34 (0.41)∗∗ −0.34 (0.07)∗∗∗ https://findy-code.io/engineer-lab/github-programming-language-ranking
あるいは新たな言語が出現したり、(Javaがラムダを採用して関数型の側面を持ったように)既存の言語が大きく変容する可能性もあります。
私はCとJavaを主にやってきました。その他いくつかの言語を少しだけ触りました。
Cを学んだのはコンピューターに対する完全な制御性(なんでも作れる事)と性能を求めたからです。
Cで中規模システムを完成させた後、私はより本格的な言語を学びたいと思っていました。当時の私のイメージと感覚によれば、Javaがそれであると感じていました。
それで私がJavaを学び始めた頃、同僚のエンジニアがKotlinを私に勧めてきました。その人はあるプログラミング言語の作者で説得力がありました。実際、当時から今日まで様々な専門家がKotlinはbetter Javaであると評価しています。別の同僚は「なんでJavaなんかやるんや」と私に突っかかってきました。彼はメジャーな言語を嫌っていました。私が少しC#をやっていたこともあってC#をやれと言ってきました。私はそれでもJavaを選択しました。理由を言語化するのは難しいですが、当時の私は「C#はオープンじゃない(MS周辺のエコシステムを想定している)」というようなことを言っていました。Kotlinをやらなかったのは、当時まだ極めてマイナーであったこと、学習情報があまりに乏しかった事等があります。Kotlinについて最後まで悩み、明確な理由は難しいですがJavaを選びました。
幸いにも今日CとJavaは人気言語の1,2位です。
https://www.tiobe.com/tiobe-index/
- Cコンパイラの普遍性。最大の特徴。CコンパイラはCPUが存在するほぼすべての環境に存在し、存在しなかったとしても作成するのが最も容易です。
- トランスパイル容易。C言語は機械語と単純に対応づくことからトランスパイルのターゲットにしやすい。
- コンピューターに対する完全な制御性。書いたことしか起きないし、何でもできる。ハードウェアに近い開発で良く使われる。主要OSやデバイスドライバは主にCで作成される。
- 高性能。高級言語の中でFortranが最速と言われているが、Cもほぼ同等の性能が出る。
- アプリ開発では他言語から呼び出される関数(FFI)の実装で使われる事が多い
- 脆弱性が多い。https://resources.whitesourcesoftware.com/blog-whitesource/is-one-language-more-secure
- lliを内蔵している。LLVM IRは中間コードといってもクロスプラットフォーム性を期待できない。
https://www.graalvm.org/docs/reference-manual/languages/llvm/Note: LLVM bitcode is platform dependent. The program must be compiled to bitcode for the appropriate platform.
- ネイティブコードの性能が少し落ちるらしい。
https://hackernoon.com/why-the-java-community-should-embrace-graalvm-abd3ea9121b5?gi=11182f32c16b
ここのNativeが0.85 - JVMワールドではなくGraalVMワールドになりそう。例えばGraalVMを前提としたbitcodeを含むライブラリが作られる可能性があるが、通常のJVMで実行できないはず。それはもはやJVM系ライブラリではない。
- 2019年5月時点でWindows版が無い。
- Native-Imageでネイティブコードを生成できるらしい。しかし完全対応ではない。
- polyglot
- EEの方が性能が良いらしい
アセンブリはクロスプラットフォームではないが、クロスプラットフォームであるべきwebのアセンブリとは何を意味しているか?
WEBブラウザにもともとJS用仮想機械があって、それが中間コードwasmを実行できるようになる。かつて排除されたJavaアプレットとほとんど同じに思える。
検索するとそれについて述べている記事が見つかった。
https://words.steveklabnik.com/is-webassembly-the-return-of-java-applets-flash
JavaアプレットはHTMLやCSS等Web技術と統合的でなかった、と。でもJavaFXはCSS対応している。
さらにJVMとの違いとしてメモリを手動管理できる。よりシビアな性能のため。でもWebページ上でそんな壮大なゲームや科学技術計算をやるか?GC言語の方が開発効率が良いと言われてるから、GC無しで性能を求めるようなプロジェクトは開発効率を犠牲にしてでも性能を求めてるわけで、あまりWeb上でやるものと思えない。
コア開発者にその質問をぶつけた人が居ました。
WebAssembly/design#960
jvmや.netはセキュリティ上の問題があった、と。
でもそれはエンジニアリングの問題のはずで、結局のところやはりJDKや.NETと被るもので、ただ自分達の方が高品質なエンジニアリングができるという事だろう。
WebAssemblyをブラウザ外で使おうという動き:WASIもあるようです。日本語訳
WASIは完全に.NETやJDKと被ります。WASIはGC無しでメモリを直接管理できるという特徴から高性能を謡い、ある程度人気を集めたら結局GCを標準採用しているのだろうと思います。 いろいろプログラミング言語について調べてきて、GCは仮想環境が使用されるような領域(java等が使用される領域)ではほぼ必須という印象がある。そして、WASIがGCを標準採用したら.NETやJDKと比べて何も新たな特徴を持たないと思います。つまり高性能を謡えなくなる。
ちなみに.NETやJDKの性能はネイティブコードと比べて低いと言われますが、LLVMベースのJDKのようにC++並の性能に到達する方法があります。
迷走する巨大エコシステム。 JS、HTML5、WebAssembly、しかもWASIでブラウザ外へ。WebでAAAゲームや科学技術計算、P2Pまでやろうとしている。
WebがJDKや.NETを無視してwasmやwasiで独自のランタイムを作った事は世界的利益のためにソフトウェアプロジェクトが連携できない事を示している。それこそが世界のソフトウェアエコシステムの課題なのでは? 要するにソフトウェアエコシステムに世界的な統合性が無い。
今JSが支配している領域、あるいは今後WebAssemblyがやろうとしている領域は、かつてJavaアプレットがありました。しかし起動が遅いという事で不評で、排除されました。しかしJavaアプレットの起動はブラウザ自体がJDKベースで作られていたら高速だったはずです。私は、Javaアプレットの排除は間違いでありブラウザ自体をJDKベースで作るべきだったと思います。その観点からWebAssemblyを見ると、JDKと同じものを作ってJavaアプレットと同じようなものを実現しようとしているように思います。
今WebAssembyプロジェクトがあるということは、Javaアプレットを排除した判断が間違いだった事を意味しています。
私の邪推によればWebAssemblyは以下のシナリオを辿ります。
- プログラミング界隈で最も活発なエコシステムを持つJSからスタートして注目される。
- Webブラウザで採用されて注目される。
- ブラウザ外へ進出する(WASI)。もしかしたらWebブラウザ自体がWebAssemblyで作られる。Webページ用途はオマケとなり、WasmAppletと呼ばれるようになる。
- 最初のうちだけGCを持たせずメモリを手動管理する事で優れたベンチマークが多数発表されるのを待ち、高性能というイメージを持たせる事に成功したらGCオプションを実装する。
- GCはほとんどの開発でメリットがある偉大な判断の1つであり、アプリケーションプログラマーはほとんどのアプリを開発効率を理由にGCつきで書く。
- JDKと同等の能力を持ち、そのシェアを奪う。
- 革命的な最新技術であり何か素晴らしいものへと成長するとみなされていたWebAssemblyの最終形態が既存技術(JDK)の模倣と言い換えに過ぎなかった事に気付くが、その頃にはJDKは死んでいる。プログラミング言語が技術的なものではなくいかにバズらせるかで決まるものになり、技術系の人々が離れ徐々に衰退する。
WebAssemblyはどこでこのシナリオから外れるのか?WebAssemblyやWASIの新しい発明や思想が分からない限り私は懐疑的です。
- codebase(jar等)にpom.xmlを標準搭載する。このアイデアは実行時依存性解決でなくてもコンパイル時に依存性解決(jarの同梱)をする場合でも単にjar hellの解決策として有効。
- 実行時にpom.xmlを通じて依存性解決して実行時環境にmavenリポジトリを作成する
- 実行時にpom.xmlを通じてcodebase毎に異なるバージョンのライブラリを使用する。codebase単位で各パッケージ名がどのバージョンのどのライブラリのものを参照するか決まる。というようにJDKを改修する。この改修はpom.xmlが無い場合従来通りのclass検索が行われるだけなので互換性に問題を生じさせないようにできるはず。
ビルド時に依存性解決するだけではなく、配布物(jar)を極小化しつつ実行時にエンドユーザー環境で依存性解決しなおそうというアイデアです。リポジトリをエンドユーザー環境に構築すれば配布物がライブラリを同梱しなくなり小さくなります。
Googleのような圧倒的なインフラを持っているところ(あるいはP2Pプラットフォーム)が、Maven Centralやjcenterのミラーを世界中のエンドユーザーに提供し、エンドユーザー環境でローカルリポジトリを構築する。そしてアプリケーションはpom.xml等に依存関係を記述し、pom.xmlをjarに同梱し、実行時にエンドユーザー環境で依存性解決する。なお現状Maven Central等にエンドユーザーがアクセスする事はサーバへの虐待とみなされ、実行時依存性解決を実現するには桁違いのインフラが必要です。 エンドユーザー環境でローカルリポジトリが構築されると、他アプリによってDLされたライブラリを再DLしたり同梱する必要がありません。
従来、アプリケーションは他のアプリケーションと同じライブラリを同梱している可能性があり、エンドユーザーは同じライブラリを複数のアプリを通してDLしていました。
しかし実行時依存性解決ではライブラリを重複して配布する事が激減します。実行時依存性解決ならアプリをいくつインストールしてもライブラリはファイルシステム上で共通です。ライブラリの重複した配布が無くなるので、プログラム全体のファイルサイズは小さくなります。ファイルのキャッシュが効く場面が増えるし、近年ストレージの変化はHDDからSSD、さらにNVMe SSDやoptaneなど、ストレージ容量が小さくなる傾向があります。
最大のメリットはアプリ開発者がライブラリのアップデートの手間を無くせる事です。ライブラリにおいてsemantic versioningあるいはそれに準じるバージョニングルールが守られれば脆弱性やバグが解決された最新版が自動的に使用されます。そして実行時依存性解決では実行時に毎回最新のライブラリが使用されます。
実際の所、多くのアプリがライブラリを最新版にしておらず、多数の脆弱性やバグが放置されていると思います。この構想ではアプリ開発者が開発を辞めてしまってもライブラリのアップデートが適用されます。
このアイデアは世界で実行されるソフトウェアの品質を飛躍的に高める可能性があります。一方で、主要ライブラリに後方互換性の問題が生じると多くのアプリが一斉に動作不可能になります。さらに、常時起動するアプリのアップデートの仕組みを考える必要があります。アプリの動作に問題が生じた時に、最後に正しく動作したライブラリの組み合わせで起動する等リカバリー処理を扱う必要がありそうです。
このアイデアをライブラリだけでなくJDKやそのモジュールに広げる事も可能そうです。
このアイデアをさらに進めるとソフトウェアの包括的なアップデートシステムが構想できそうな気がします。
- 実行時依存性解決
- 起動中のプロセスのアップデートシステム
- アップデート失敗や互換性問題のリカバリーシステム
- SemantecVersioningによる最新版のライブラリの自動適用
自己完結型を推奨し、実行ファイルを最小化してクラウド適性を高めるための技術。JDKのモジュール性を高めるとも良く言われる。
しかし自己完結型は多くのアプリにおいてJDKのバグを残す可能性が高い。 世界にばらまかれた自己完結型アプリはいずれ更新されなくなり、JDKの良く知られた脆弱性を残したままになる。 非自己完結型なら、OS側JDKを更新するだけで全てのアプリがJDKのバグを修正できます。 自己完結型はアプリのバイナリ互換性を失わせる。バイナリ互換は仮想機械の利点です。 多くの場合、自己完結型を作るならそのプロジェクトはC++を使うべきです。
ライブラリの世界的な利用状況をモニタリングするというアイデア。 githubやMavenセントラル等、膨大なオープンソースのソフトウェアを管理するプラットフォームが提供する。 IDEはメソッドの呼び出し元を検索する等できるが、 そのような機能をオンラインで膨大なソフトウェアに跨って提供する。 世界の状況に対応したエンジニアリング、膨大な実装サンプルの検索が可能になる。
- OS側JDK
- APIを強化しfat java案やJNI用実行時コンパイル案を採用しバーチャルプラットフォームを追求したJDK
- 実行時依存性解決
- 完全にバーチャルプラットフォームな豊富なライブラリ
- reproducible buildsや開発者の信頼性評価や創作性の相互評価などをサポートする創作のプラットフォーム
EqualsAndHashCodeアノテーションのみ使用したいと思いました。メンバー変数を修正した時にequalsとhashcodeの再作成をし忘れると根深いバグを作るからです。ボイラープレートの排除が目的ではありません。コンストラクタやアクセサはコンパイルエラーで気付きそうです。
しかし、Lombokを使用する事自体が問題を起こすかもしれないので、私は結局Lombokの使用に懐疑的です。
Lombokの一部のアノテーションがJavaに標準採用されたらもちろん使うと思います。Lombokがやっている事は言語仕様レベルで試みられるべきことで、サードパーティのツールとして試みられている事が疑問です。
言語仕様、開発ツール等が以下のように改善すること。
- 最も困難な開発においてプログラマーを助ける。簡単な開発をさらに簡単にする必要はない。
- 気付きにくいバグを減らす。
- 解決困難なバグを減らす。
- プログラマーを騙さない。
普通ビルド処理はant等で書かれます。私は昔からそれが疑問でした。それで私はJavaでビルド処理を書いた事がありますが簡単でした。commons-ioが便利です。 最近Gradleというものもでてきて複雑なビルド処理ができるようです。しかし私はやはりGradleの必要性が分かりません。 ビルド処理を統一的に記述したいから?統一的に記述すると可読性が高まりますが、Gradleの言語を強制されてしまいます。アプリと同じ言語を使えた方が可読性が高いです。
antやgradleの必要性は分かりませんが、mavenのpom.xmlの「プロジェクトのメタデータを宣言的に記述する」というアイデアは優れています。 そのアイデアは1プロジェクトのビルド処理の範疇を超えて、世界的なプロジェクトの整理や連携に影響します。
ちなみにもしビルド処理をJava等で書くとして、pom.xmlの情報やmavenの機能をプログラムから使用したい場合maven aetherというライブラリがあります。
さらに、プログラミングではテストデータのセットアップ(大量のランダムなデータを格納したDBをセットアップする等)が必要になることがあります。 それもある種のビルド処理に分類されると思いますが、アプリケーションコードと同じ文脈(ライブラリ、言語等)で記述するのが妥当です。
Javaアプリは起動時のVM引数に条件がある場合があり、起動スクリプトが用いられます。 pure javaを謡っているアプリ(netbeans等)でも起動スクリプトや起動用exeファイルを使っていて、pure javaを破壊しています。javaでは環境毎に起動用プログラムを作成する事が標準的な方法として推奨されています。
しかしpure javaで起動処理ができます。拡張子.jarのJavaアプリが可能です。
この簡単なプログラムは、期待しているVM引数(ここではSerialGCの指定)があるかチェックして無ければ適切なVM引数で自身を再起動します。
/**
* Restart itself with the expected VM arguments.
* That means you can have Java app extension of .jar.
*
* Pure Java startup process.
* Alternative to startup scripts.
* With this idea, you can eliminate the startup script with a very
* simple program. Since startup scripts are environment dependent,
* it is more cross-platform to be able to write startup operations
* in pure java.
*
* 適切なJVM引数をつけて自身を起動しなおせるかテスト
* @param args
*/
public static void main(String[] args) {
String expectedGcName = "Copy";
boolean reboot = true;
String rebootedArg = "rebooted";
//再起動ならこれ以上再起動しない
for (String arg : args) {
if (arg.equals(rebootedArg)) {
System.out.println("don't reboot because already rebooted");
reboot = false;
}
}
System.out.println(Arrays.toString(args) + System.lineSeparator()
+ ManagementFactory.getRuntimeMXBean().getInputArguments());
//https://stackoverflow.com/questions/39929758/ps-marksweep-is-which-garbage-collector
List<GarbageCollectorMXBean> gcs = ManagementFactory
.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gc : gcs) {
System.out.println(gc.getName() + System.lineSeparator());
if (gc.getName().equals(expectedGcName)) {
reboot = false;
}
}
try {
if (reboot) {
System.out.println("reboot itself");
Process p = Runtime.getRuntime()
.exec("java -XX:+UseSerialGC -jar JavaFXTest.jar "
+ rebootedArg);
try {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (final Exception e) {
e.printStackTrace();
}
System.out.println("exit");
System.exit(0);
} else {
System.out.println("report: executed by correct gc");
}
} catch (Exception e) {
e.printStackTrace();
}
launch(args);//javafx
}
ビルド直前にフォルダやファイルのコピーをしたい事は良くあります。 あるプロジェクトでsrcフォルダにシンボリックリンクを使っていましたが、 OracleJDKから他のJDKへの移行でシンボリックリンクの扱いが変わったようでビルドに失敗するようになりました。 試した範囲ではcorrettoとlibericaはsrcフォルダ中のシンボリックリンクに対応していませんでした。 それで結局Javaでビルド処理プログラムを作成して必要時にフォルダごとコピーするような方法で解決しました。
シンボリックリンクが解決してくれるのはフォルダやファイルに関係する問題だけであり、 あらゆる複雑なビルド処理を想定した場合、Javaでビルド処理を書く方が対応力が高いです。
あとシンボリックリンクは1つ面倒な事がありました。
a/b/c.txt
a/b/d/
a/e/
フォルダa配下のファイルのうち大部分は別フォルダと同じものを使いたいがc.txtだけ自前のを使いたいとなった場合。
a,bをシンボリックリンクにすることはできません。
d,eフォルダをシンボリックリンクにする事はできます。
もし「c.txtだけ自前のを使いたい」という事情が無ければaをシンボリックリンクにするだけで全て解決します。
つまりただその1ファイルの事情が生じただけで細かくシンボリックリンクを作成する必要が生じます。
そのような細かい管理が生じるので、プログラミング言語でフォルダコピー等のビルド処理を書いた方が楽な方法が見つかります。
もともとPKIとか公開鍵証明書とかSSLというものがある中でTenyu基盤ソフトウェアは独自の公開鍵システムを作っています。どんな違い(存在意義)があるのかという事を長らく考えていました。
そのあたりを考えていた時、Tenyu基盤ソフトウェアのことをP2P PKIと言っていました。
従来のPKIはどの現実の組織と通信しているかを確認するという観点に立っていたと思います。
それに対しP2P PKIはどの現実の組織かを確認するためではなく、ネット上の主体を識別するためにあり、P2P PKIにおける信用はネット上の活動を通じて創造され、現実の組織の信用を流用しません。
P2P PKIはネットユーザーの信用の数値化や現在のIPアドレスやP2Pネットワークで共有されたファイルやDB等を外部アプリに提供できます。この点が重要だと思ったのは、P2P PKIが行うネット上の主体の識別とは即ちユーザーDBを作る事であり、そのユーザーDBは様々なシステムに提供され共用されます。 P2P PKIはサーバーモードを設定できて、エンドユーザー環境とサーバー環境両方をサポートします。 そしてインターネットを新たな信用を創出する場にする。それはそこに新たな経済を創出しうるという事です。
Smalltalk界隈はやたら独創的な概念が出てくるように見えて、実際の所Java等と大差ないOOPでREPLしながらシステムの状態(オブジェクトの状態や利用可能なクラス等)を覗けるという感じです。違いはIDEの機能をプログラムから呼び出せるなど統合的であることです。Smalltalkのメッセージングはメソッド呼び出しとして素直に理解できます。処理結果がTranscriptとかWorkspaceで文字列として表示される事は結果として返されたオブジェクトがtoString()的なメソッドを実装していて表示されているんだと理解できるし、オブジェクトの状態を確認する事はIDEのデバッグ機能のようなものだと理解できるし、基本的には多くのプログラマーが理解している事と大差ありません。
理解
- メッセージングはメソッド呼び出し
- VMやIDEが統合されている。だからSmalltalkは単なるプログラミング言語(ツール)ではなく包括的なプラットフォームという印象が強い。この観点を進めたもの
- REPL
- IDEの機能もプログラムを通じて呼び出せる。
- 環境構築が簡単(全て統合されている)、IDEでGUIベースでREPLで簡単かつ楽し気。
疑問
- オブジェクトをコンピューターのメタファーとして捉える事が具体的に何をもたらしているか?
- メソッド呼び出しをメッセージングと呼んでみる事が具体的に何をもたらしているか?
SmalltalkのクラスライブラリにおけるOOPのデザインパターンが他の言語のクラスライブラリ等に影響を与えたという話は正しいんだろうと思いますが、コンピューターのメタファーとしてのオブジェクト等の概念は怪しいです。
I realized that the cell/whole-computer metaphor would get rid of data,
このような示唆は面白いものの、Smalltalk内にはそれを実現しているようなものは特に何もないように思います。細胞は活動していて、即ちそれ自体が固有のスレッドを持っているようなもので、極度の並行計算を前提としたコンピュータとプログラミング言語が無ければこのようなアイデアは意味を持たないと思います。オブジェクトは他所から来たスレッドが走るだけです。例えば膨大なAIがありAIが所有するオブジェクトを自動操作するようなシステムが考えられます。そのようなシステムは曖昧で適応的になります。
プログラミングの世界では概念は徹底的に抽象化されたり分離され、汎用的な部品やツールとなります。そのような世界においてSmalltalkの面白さは本来分離していいもの(VM、IDE、プログラミング言語)をむしろ統合することによって何かをやろうとしたことです。さらにそのような統合システムにおいてREPLを行い柔軟なユーザー動態を想起させた事です。例えばもしSmalltalkのREPLがオンライン対応(ネット越しの他ユーザーにメッセージを送れる)すれば、大勢のユーザーがREPLを通してコミュニケーションするようなシステムが構想できます。
関連:クラスとは
OOPという言葉で指定される意味には、コンピュータのメタファーという恐らくまだ具体化されていない概念、構造化プログラミング、プロトタイプがあります。
-
アランケイのオブジェクト指向。 コンピューターのメタファーとしてのオブジェクト、という事が良く語られます。しかし恐らくその意味でのオブジェクトは具体的には実現されていません。ではSmalltalkとは何だったのかという事になりますが、恐らくSmalltalkはその後OSやJVMやIDEやクラスライブラリに影響を与えたのだろうと思います。アランケイが語るメッセージングやコンピュータのメタファーとしてのオブジェクトという独創的概念がJavaやC++に影響を与えたと思えません。
-
JavaやC++で用いられるオブジェクト指向。 実はこの意味のOOPは構造化プログラミングです。
https://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/EWD268.html私はもともと構造化プログラミングとはサブルーチン等の事を言うのかと思ってましたが、そうではなくクラスベースのOOPを言っているんだと気付いたのはこの記事を読んだことがきっかけです。調べたところこのあたりの文章を投稿したのはMonadaisukiという人のようです。 https://ja.wikipedia.org/wiki/%E6%A7%8B%E9%80%A0%E5%8C%96%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0
>抽象データとその上で動作する抽象化文の共同詳細化ここの4.1から私の解釈で説明します。
In the refinement of an abstract program
抽象的プログラムの洗練において
(i.e. composed from abstract statements operating on abstract data structures)
つまり抽象(他クラスを型とした)データ構造(メンバー変数群)の上で動作する抽象文(メソッド)によって構成される
we observe the phenomenon of "joint refinement".
連動的洗練現象を観察した。
For abstract data structures of a given type
型名に応じた抽象データ構造のために
a certain representation is chosen in terms of new (perhaps still rather abstract) data structures.
特定の表現(同じ意味を達成するデータ構造でも複数の設計方法がありそのうちの1つの設計)が選択されます(たぶんまだ抽象的な=メンバー変数がプリミティブ型じゃなくクラス型)新しいデータ構造(メンバー変数群)として。
The immediate consequence of this design decision is
その設計判断の即時の結果として
that the abstract statements operating upon the original abstract data structure
もともとあった抽象データ構造(メンバー変数群)に依存していた抽象文(メソッド)は
have to be redefined
再定義されなければならない
in terms of algorithmic refinements operating upon the new data structures in terms of which it was decided to represent the original abstract data structure.
元のデータ構造を代替すると判断された新しいデータ構造(メンバー変数群)で機能するようにアルゴリズム的洗練として。
Such a joint refinement of data structure and associated statements
そのようなデータ構造(メンバー変数群)と関連付けられた文(メソッド)の連動的洗練は
should be an isolated unit of the program text:
プログラムの独立したユニット(=クラス)でなければならない:
it embodies the immediate consequences of an (independent) design decision
それは設計判断の直接の結果を具体化し
and is as such the natural unit of interchange for program modification.
プログラムの変更における自然な交換単位です。
It is an example of what I have grown into calling "a pearl".
それは私が真珠(クラス)と呼ぶようになったものの一例(まだinterface等があるがここではクラスのことしか説明していない)です。joint refinementが生じる範囲を1個のユニット(クラス)にしてプログラミングしろ、ということです。
さらに、Simulaは構造化プログラミングに分類されていますが、既にクラスを扱っていました。
https://ja.wikipedia.org/wiki/Simula
>クラス(class)の構文と対象(object、オブジェクト)の概念を初めて導入した言語である
今日のOOPに影響を与えたのはダイクストラまたはSimulaの構造化プログラミングです。 -
プロトタイプベースのオブジェクト
これは構造化プログラミングのオブジェクトと全く異なり、クラス群という抽象的知識がありません。その利点は恐らくスクリプトのインタープリタにおいてオーバーヘッドが小さくなることです。WEBページでスクリプトを実行する時にクラスローディングの手間が無い。
エドガーダイクストラは「データ構造を変更した時そのデータ構造に依存していたアルゴリズムを修正するという連動的洗練(joint refinement)現象を観察した」と述べました。クラスという概念は言語によって様々に実装されていますが、共通の本質は連動的洗練が波及しやすい範囲を1ユニットにまとめるものです。
何かデータ構造(メンバー変数群)があったとします。
class Person{
private int age;//年齢 0-100程度
public int getAge(){
return age;
}
}
ではデータ構造を変更(連動的洗練のトリガー)してみます。
class Person{
private int birthYear;//生年 1900-2100程度
public int getAge(){
return birthYear;//IDEの機能でageから置換された。birthYearはageとデータの意味が異なるので修正が必要
}
}
getAge()はpublicであり他のコードから利用されている可能性があり、今後も維持しなければならない(そうしないと連動的洗練がクラス外へと波及する)ので、正しく動作するように修正します。
class Person{
private int birthYear;//生年 1900-2020程度
public int getAge(){
//現在の年を取得
int year = Calendar.getInstance().get(Calendar.YEAR);
//生年から年齢への変換
return year - birthYear;
}
}
データ構造(age)を変更した事でアルゴリズム(getAge)が修正されました。これが連動的洗練joint refinementです。
そして、getAge()インターフェースが維持されているので、他のクラスへの影響(=連動的洗練の波及)は一切ありません。即ちこの一連の修正はクラス機構の成功例と言えます。冒頭に述べたクラスの本質を再確認してください。
このケースからなぜpublicやprivateといったアクセス修飾子が必要か分かります。(リフレクションという例外を除けば)privateなものは外部からアクセスされている可能性がないので変更しやすく、publicなものは外部に対する約束であり継続的な保守が必要で変更しにくい(しかしIDEが補助してくれる)です。
もちろん今回の例は極めて単純で、実際のプログラミングではデータ構造の大幅な変更とそれに伴うアルゴリズムの大幅な変更が生じます。場合によってクラス自体の削除や新しいクラスの設置などクラスの構成を覆さなければならない事もあります。メンバー変数の変更はもしそのオブジェクトが永続化されているとそのメンバー変数がprivateでも難しくなります。つまりクラス機構があってもまだ問題は残り、あらゆる問題が円滑に解決されるわけではありませんが、クラスを通じて情報処理システムを考えるという方法論(クラスベースのOOP)は有力です。実際、現代の情報処理システムはクラスによる設計が主流です。
このようなクラスの本質とクラス設計が主流となっている状況は、プログラミングがアップデートの繰り返しであり、アップデートのしやすさ(保守性)のためにコストをかける事が重要である事を示唆します。
ある種のクラス設計はそれが対象とする問題領域における理論構築であるようにも思えます。
さらにクラスには継承やinterface等の言語機構があり、メソッドの引数の型等で抽象的な型指定が可能になります。
継承は使いどころが難しいです。本番用クラスを継承したテストクラスを作り一部メソッドをオーバーライドしてシステムの動作をテスト用に変更するような使い方は安全(プロジェクトの頓挫に繋がるような困難をもたらさない)で便利です。 設計中には仮想メソッドを多用した抽象クラス等はうまくモデリングできているという印象を受けますが、それでも継承はプロジェクトが失敗する原因になりえます。関連:継承構造
システムはモデルを中心としてその周囲にGUIやDB(ストア)や通信があるものとして捉えれます。 Tenyu基盤ソフトウェアはモデル毎に<モデル名>Guiとか<モデル名>Storeというクラスが作られていて、Guiやストアクラスはモデルに沿った継承構造を持ちます。 それらのクラスの総称型引数にはモデルクラスが現れます。 継承ベースの設計はそのようにしてシステム全体が継承構造に支配されるに至ります。
そのような巨大な継承構造を設計する事は難しく、成功しても設計が崩壊するリスクが付きまといます。
一方で一度継承構造を認識しさえすればシステムを理解する事は簡単になります。 システム全体が継承構造に沿った一貫したパターンで作成されている事が認識できるからです。 継承構造はアプリケーション固有のある種のフレームワークです。(とはいえSpring Frameworkみたいな通常のフレームワークとは全く違う)
プログラミング技術は要件や仕様の変更に耐えられるよう発達しました。 しかし継承構造を肯定する立場においては、そのような要求でシステムが作成されるというよりも、むしろ作成されてしまった継承構造こそがそのシステムの本質であるとしてその継承構造でやれる事を探すという発想の転換をすべきかもしれません。そう考えたくなるほど徹底した抽象化と再利用があり、そして継承構造の変更は困難です。
私は、色々調べましたが、継承も委譲(構成、合成)もどちらも問題を抱えていて万能な設計戦略は無いと思いました。 一方で継承構造によってシステム全体を一貫して認識できることに魅力を感じています。
洗練された継承構造を作るべきという事も考えました。
Glbと継承構造でほとんどの情報処理システムを一貫して設計・認識する事ができるはずです。
構成(合成)=compositionとして使っています。 私はこの問題について色々考えましたが、言語仕様で想定されている分継承の方が良い気がします。
継承を一般的に構成に置き換えようとすると子クラスによる仮想メソッドの実装及びそれによる親クラスの動作変更を構成でどう代替するか?が問題になります。 最も有力なアイデアは、部品側(構成の要素となるクラス)がコンストラクタでラムダを受け取るような設計パターンです。
class CompositionalA{
private Function<Long, Boolean> f;
public CompositionalA(Function<Long, Boolean> f){
this.f = f;
}
}
class Holder{
private CompositionalA a;
public Holder(){
a = new CompositionalA(l -> true);
}
}
このような設計は仮想メソッドによる親クラス(上の例ではCompositionalA)の動作変更をラムダで代替しています。 しかしこのような設計はユーザーコードの独自の仕組みで、一方で継承は言語仕様でサポートされた仕組みです。ラムダをシリアライズしてからデシリアライズするまでにクラスがバージョンアップされてコードが変わっていた場合等問題が出そうです。
他にHolderをCompositionalAに渡して相互依存させる事で仮想メソッド以上の事を達成するというアイデアもありえますが、複雑なユーザーコードによる仕組みになったりインターフェースを大量作成する事になったり、良い設計と思えません。構成をするならラムダ方式が妥当です。しかしシリアライズの問題からやはり問題が出そうです。
継承を徹底すると継承構造という一貫した設計が見出せます。
構成の問題点
- 構成で継承を代替しようとすると仮想メソッド相当の事を実現するためにユーザーコードによる独自の仕組みが生じる。可読性や設計の発展性(シリアライズ等)に問題が生じる。
- 構成で多態性を実現するにはインターフェースを作成する必要がある。高階層な抽象クラス群を置き換える場合、構成の組み合わせが自由になるという点を活用したいなら、抽象クラスの組み合わせの数だけインターフェースを作成する必要がある。
継承の問題点
- 概念の変化によって継承構造全体が崩れうる。
継承への注意喚起が行われた事は正しいと思います。プログラムが保守困難になる原因の一部は継承で、プログラマーは継承を警戒すべきです。しかし使わない事が正しいのかは疑問です。
さらに継承も構成も問題があるけど避けられない説という事について書きました。この観点からは、実は警戒すべきは抽象化や多態性であり、設計に成功すれば便利だけど失敗率が高かったり時間がかかると捉えます。
継承の設計変更への柔軟性の低さは短所であると同時に長期的にそのような設計が通用するというモデリング上の記述ができている面もある。言い換えれば変化しない抽象概念がある場合継承は有力です。
もし全子孫クラスが絶対に持つべき状態があるならそれは継承で与えるべきです。 例えば、抽象クラスにidフィールドを置けば全子孫クラスはそれを持ちます。しかしinterfaceでgetId()があってもその全実装クラスを見渡した時一部のクラスはもしかしたらオンラインアクセスしてどこかからidを持ってくるかもしれず、状態としてidを持っているかは分かりません。クラス群がどのような状態を持つかという制約が設計上確定できる場合、インターフェースでそれはできません。
他にも継承は本番用クラスを継承してテストクラスを作るとか、フレームワークが提供する抽象クラスを実装するだけでアプリを作れたりとか、非常に便利な場合があります。
他に構成を実現する手段としてインナークラスを検討しましたが、他クラスのインナークラスを自クラスのインナークラスであるかのように扱う事ができないと無理そうです。
関連項目
もし「論理的思考が嫌だ」とか「メソッドやクラスがたくさんあって管理するのが嫌だ」と言っている人が居たら、その人にはプログラミングを諦めてもらうしか無いのではないか?存在するしかない本質的難しさを拒否するなら。私が「本質的難しさ」と言っているのはそのような拒否できない難しさです。 そして継承の問題等抽象的設計の困難を指摘する立場はそのようなものではないか?ということです。
継承はその多態性による利便性の分だけ設計が難しい。再利用は良いものだと言われますが、再利用されるほど設計は熟慮が必要になり、設計の更新は難しくなります。
なぜnullチェックをそんなに嫌う必要があるのかなという記事です。
例外とtry catchを扱うJavaにおいて、nullはほとんど常に正常系の意味のある値になります。正常系の意味のあるnullならnullチェックがボイラープレートじゃない。
正常系のnullとは、例えばMap#put()、get()、remove()は場合によってnullを返しますが、そのnullは例外系を意味せず、該当するデータが無かった等の正常系の意味を返しています。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Map.html#put-K-V-
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Map.html#get-java.lang.Object-
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Map.html#remove-java.lang.Object-
例外をcatchしたならthrow eと書いて例外を伝達できるので、catch句でわざわざnullを返すなら、それはthrow eを選択しなかったという点で正常系としてnullを返しています。つまりそのメソッドの仕様としてnullが何か意味を持っている場合だろうと思います。
try{
}catch(Exception e){
return null;//正常系 throw eなら例外系
}
Optionalが何やらNPEからプログラマーを守ってくれるという話がありますが、自分はこの意見に近く、ほとんど使う必要が無いと思いました。モナドについて調査した後でもそう思います。
https://www.quora.com/Why-use-Optional-in-Java-8-instead-of-traditional-null-pointer-checks/answer/Alan-Mellor
パイプライン的記述をする場合Optionalは役立ちますが、Javaでそれをやってもただ少し書き方が違うだけで大した利益があるように思いません。
https://devlog.atlas.jp/2019/04/24/2679
モナド前提の言語ならMaybeモナド(Optional)とかパイプラインは便利なのかもしれませんが、Javaで便利かという問題です。
Optionalのパイプライン(ラムダの連続)は各処理の返値のnullが一貫して例外系を意味する事で成立します。
o.map(f).map(f).map(f)というラムダの連続において返値がnullであることは常に例外系を意味し次のラムダが呼び出されません。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html#map-java.util.function.Function-
こんな仕様だとむしろ無駄にnullを返すようになりそうです。
それに、この動作は例外を投げる事やtry catchでも実現できます。
Javaでは例外が発生したなら例外を投げれるので、例外系としてのnullを無くせます。
正常系のnullに対して(必要な)ヌルチェックを忘れた場合、インターフェースの仕様を把握していなかったということです。 何かインターフェースを呼び出すときnull以外に0や空文字列等も特別な意味を持つ場合があるし、そもそもインターフェースの仕様を調べずに利用するのは無理があります。
関連:例外処理
自分のやり方
- catch句でログを残す
- 失敗して良い処理ではtry catchを必ずやって、例外が生じたらcatchしてその後の処理をする。このような場合のみ例外の存在意義を感じる。
- DBのトランザクションをやるときはtry catch。catch句ではロールバックやトランザクションの破棄等をする。設計の基本として、ソフトウェアの堅牢性のため、あらゆる持続的状態(ファイル、DB、グローバル変数等)はトランザクションで更新されるべきで、できるだけ重要なデータはDBに入れる(性能の問題等でこの規則を守らない場合もある)。DBの更新が失敗したらロールバックする。関連:Software Transactional Memory
- DBからデータを取得するメソッドでもしDBが壊れていた場合、ログを出力してnullを返す。nullはデータが無い場合の正常系の値。DBが壊れているという例外系の文脈を呼び出し元に返さずそれを見つけたcatchブロックで必要な処理を行う。そのメソッドの役割はデータを取得する事で、データが見つからなかったならその理由(DBが壊れている)によらずデータが見つからなかったことを意味するnullを返す。もしDBの回復処理が可能なら別スレッドで処理する。
- 通信もtry catch。例外的文脈が生じると多くの場合対処の方法が無いが、通信に関しては再送処理とか別のところに問い合わせるなどの回復的対応がありうる。
- 失敗してはいけない処理で例外が生じたら、fatalでログを残す。この場合の回復方法は基本的に無い。開発者に自動的にログをメールするというアイデアを考えたことがある。
- 仕様レベルで各データの健全性が繰り返し確保されるようにする。他のシステムから正しい情報を取得する、壊れたデータを破棄して再作成する等。
tenyuで用いているリリース番号という概念は、 ソフトウェアをリリースするたびに世界のどこかでそのバージョンでDBが構築されるというイメージに基づいている。 公開しない限りどれほど大幅な変更が行われても問題にならない。 ”リリース”がソフトウェアエコシステムに配慮すべき単位を作る。
githubやbitbucketは登録されたプログラムの全リリースバイナリについてそのハッシュ値を記録する。 そして入力されたハッシュ値が登録済みか、つまりソースコードが公開されているかを判定できるようなapiを提供する。
ソースコード公開確認や作者信用数値を確認するランチャー。 ランチャーソフトウェアで起動直前にソースコードが公開されているかチェックする。 ソフトウェアのセキュリティに寄与する可能性がある。
髪の毛やスカートなどひらひらしたものは物理演算した方が開発効率や見栄えにおいて優位性がある。 しかし主にボーンアニメーションで行われているようだ。 物理演算は重いので慎重に使わないとゲームに適さないが、髪の毛やスカート等もっぱら演出用の物理演算であれば広範囲の物理的な相互作用を無視してもいいので、うまく並列処理できるはず。髪の毛やスカートのひらひらは純粋に演出的でありゲームロジックや対戦における有利不利に影響しないので、1キャラクター内での純粋に演出的な物理演算という事で処理できる。
現在CPUはRyzenによってコア数が急増しつつあるので状況に適している。 技術的イメージは演出用サブ物理空間を作り1キャラクター限定で演算し、多数のオブジェクトの相互作用を扱うゲームロジック用物理空間と分ける。 演出用サブ物理空間はキャラクター毎の空間で、その中で髪の毛やスカート等のひらひらを物理演算する。キャラクターの体の動きや装備品の動き等の影響は受けるが、他のキャラクターの影響は受けない。 つまり髪の毛やスカートは他のキャラクター等と衝突しない。自キャラ内での衝突や速度による変化等が影響する。 一方で、ゲームロジック用のベース物理空間では、キャラクターは1個の球体など単純な図形として扱われ、他のキャラクターや物体等との広範囲の衝突判定が行われる。
モデルの形状によってゲーム上の有利不利が生じると対戦ゲームでは問題がある。だから対戦ゲームにおいて当たり判定は全キャラクター共通の単純な形状(球体等)であることが望ましい。しかし演出用の物理演算(髪や服の処理)はゲームの有利不利と関係ないので複雑な当たり判定を持っていい。
とはいえ衝突部位に応じて変化する運動学的モーションは演出上魅力的なので、対戦上の有利不利のためにあくまでキャラクターはベース物理空間上で単純な球体の衝突範囲を持つべきだが、その球体の衝突箇所がそのキャラクターのサブ物理空間に通知されて演出だけ運動学的になる、キャラクターの速度はベース物理空間で決まる、というのはよさそうだ。このアイデアは性能と演出のバランスが取れている。
このようなアイデアは実証しなければあまり意味が無いが、実証できていない。たぶんそのうちやる。 これはゲームロジック用物理空間の話。 一般にオンラインゲームで物理演算は難しいと言われている。
問題
- キャラクターが位置補正を受けてワープするのがプレイヤーにとってストレス
- 僅かな位置の違いで当たったか外れたかが変わり、当たった場合吹っ飛ばされるが吹っ飛ばされる角度も僅かな位置の違いで変わるので、物理演算によって僅かな違いが大きな違いに繋がる。
解決策
- 自キャラの位置と速度に関して常に自分の主張を通せて、他プレイヤーやホスト等からの情報によって自キャラの位置や速度が上書きされない。つまり自キャラがワープする事は無い。他キャラ、他オブジェクトはワープする。
- キャラクターの当たり判定(物理演算上の形状)を球形にする。角を無くす事で衝突後の速度のずれを最小にする。
- HPなどは、ノードに序列をつけて上の序列からのメッセージを採用する。ホスト概念がある場合、ホストノードは最上位。
- 自キャラの位置と速度について他プレイヤーに定期的に送信して同期する。
- 自キャラの近くにいるプレイヤーには高頻度で同期する。遠くにいるプレイヤーには頻度を低下させる。
- 定期的な同期の他にイベントベースの同期を併用する。例えば位置や速度の急変があったら全プレイヤーに送信する。
- 自分が主張を通せる物理演算対象のオブジェクト数を空間内で5程度と想定。位置と速度は1オブジェクトにつき40バイト程度で送信できる。10人対戦(自分を除いて9人)を想定。1秒あたり60回同期できればラグを感じないと予想。5 * 40 * 9 * 60 = 108KB/s が送受信量になる。最近の回線なら問題ないはず。
このアイデアはクライアント優先の場合があるのでチートが容易になっているが、Tenyuの場合リプレイファイルや紹介制ユーザー登録などがチート対策になるので問題無い。
人気言語ではライブラリが豊富にあるとよく言われます。 しかし大事な事は生きたプロジェクトがどれだけあるかです。 死んだプロジェクトは検索を妨害するし、プロジェクトの死亡率が高いとライブラリ選定における比較検討を難しくします。
日本のスマホは回線代に端末代が含められている。 そしてユーザーは定期的に端末を無料で最新のものへ交換する。 そのモデルは端末の水準を保つために優れています。個人任せにしていたらいつまでも古い端末を使い続ける人が出てくるからです。
なぜ古い端末を使っていてはいけないか?
- スマホはある程度新しい端末じゃないと最新のOSがインストールできません。古いOSは脆弱性が放置されます。
- コンピューターはたびたびハードウェアレベルの脆弱性が発見されますが、リコールされません。
だから定期的に新しいコンピューターに買い替えさせるモデルは正しいと思います。 端末の定期的な買い替えを強制できるビジネスモデルはコンピューターセキュリティにとって有益です。
セキュリティだけでなく、極端に古く低スペックなコンピューターをソフトウェア開発者が想定しなくてよくなるので、ソフトウェア開発が楽になります。
一方でPCの世界は水準がばらばらでいつまでも古いPCを使っている人がたくさんいます。PCにおいても水準を保つ工夫が必要です。
オープンソースや自由ソフトウェア等FOSSが注目される本質的理由はそれら概念がソフトウェアを世界の共用資産にする事になり、ソフトウェアエコシステムにおいて世界的な創発的現象に繋がるからです。 私はソフトウェアのための世界で1つの共同資本という言葉を使う場合がありますが、FOSSの充実は共同資本の成長の1つです。 FOSSの価値観はソフトウェアの生産性にとって最も理想的です。
オープンソースや自由ソフトウェアは世界の共用資産を目指す性格のソフトウェアです。 TenyuLicenseのソフトウェアもFOSSです。
私はソフトウェアやオンラインサービスについて創発的と言う場合があります。 ソフトウェアが創発的であるかは、世界の共用資産である事を目指す性格があるか、ソフトウェアの最大の生産性を目指す性格があるか、創発的現象に寄与するか等が基準になります。 ソースコードが非公開のオンラインサービスでも創発的とみなせる場合があるかもしれません。 つまり、ソースコードを公開したり自由なライセンスにする事は創発的であろうとする一手段に過ぎず、必須の条件ではないという事です。
オープンソースと自由ソフトウェアは作者の経済的利益の達成方法について十分に説明していない。 Tenyuの構想では、ソフトウェアの自由な相互利用と作者の経済的利益を両立できる。 さらに、誰がどの程度貢献しているかデジタルに記述され、その情報が公開される。
GNUはUnixを模倣するために始まりました。 LinuxはMinixを参考にして作られました。 MinixはUnix系OSです。 そのような事実に対して、GNUやLinuxはパクリであると批判する意見を僅かに見た事があります。
Tenyuの構想である作者権やTenyuLicenseは私の創作に対する思想を示したものですが、その観点からすると、僅かでも真似をしたらダメというわけではなく、独自の創作性が十分に入っていて、かつ参考にした他の創作物を明示していればほとんどの場合問題ありません。
GNU,Linux
- 成果物が無料で公開され世界的な創発的現象を引き起こしました。
- 社会を騙そうとしていません。何を参考にしたか明示しました。
- 独創的な創作活動です。
そのようなプロジェクトを否定した場合、100%独自の創作物のみが肯定される事になり、非現実的です。
リベラルの本質は世界をより創発的にする事です。 良く寛容さと訳されますが、寛容さは場合によって世界をより創発的にすると思いますが、リベラルとされる行動全般を説明しません。 OSS、途上国の成長を助ける事、子どもの貧困を解決する事、ネットで様々な学習情報が提供されている事などは世界をより創発的にします。 何が世界をより創発的にするかは明白に判断できる場合もありますが、難しい場合もあります。
私が遭遇したstaticを使うべき場面について。
- Glbのメンバー変数及びアクセサ
- interfaceに付随した定数。 interfaceは「仕様を記述している」という意識が強いが、例えば連番のIDで最初のIDが何番か(通例0か1)、特殊な意味を持つIDが何か、-1,-2,-3等が特殊なオブジェクトに設定される場合など、と言った仕様についてinterfaceにstaticに定義している。
- オブジェクトをDBに登録するための一連のDB操作シーケンス。 複数のストアに一連の操作をするコードはどこに書くか?私の場合、そのような一連の操作の必要性はモデルによって生じていたので、モデルクラスにstaticメソッドとして定義した。DBがグローバル状態の一種であるからこの設計が妥当だと思った。
- 複数コンストラクタ。 Tenyu基盤ソフトウェアに含められているIDListクラスのListを返すコンストラクタ的な静的メソッド等。
NVMeを使ってもゲームのロードが高速化しないという話がありますが、恐らくCPUのコア数が不足しているか、ゲームがアセットを並列にロードしていません。 アセットは大抵の場合圧縮されていたり、ゲームエンジンにとってネイティブではない形式で保存されているので、変換処理が必要になり、アセットを読み込む時にCPU負荷が生じます。 今後CPUのコア数が増えると思うのでNVMeはゲームでも有力なはずです。
Kryoなどシリアライザはコンストラクタを呼び出さないとフィールドを初期化できない。 そのせいで、初期化処理が走った後に再度メンバー変数に値が設定されていて二度手間になっている面がある。 デシリアライズ用のコンストラクタを暗黙的に備えて、一部の初期化処理をシリアライザに委譲すれば解決するはず。
OSはデバドラの移植が難しい事によって守られている。Linuxディストリの多様化やWinからの大規模な移行等はありうるかもしれない。
Linux等FOSS系OSは新しいハードウェアプラットフォーム(例えばAndroidスマホ)でカスタマイズされて使用される場合がある。 その意味での多様化はありうる。
java bytecodeをLLVMを通してJITしたりネイティブコードにできるはずです。
AzulのfalconやRoboVMが既にこのアイデアを実現し良い結果(C++並の性能)を得たらしい。
他にSharkというプロジェクトがLLVMによるJITを実現したようだ。 https://icedtea.classpath.org/wiki/ZeroSharkFaq
LLVMに最適化を任せる事でJDKの開発者はCPUアーキテクチャ固有の最適化について悩む必要が無くなる。
理想的にはOSがLLVMを標準搭載すべきだろう。 LLVM(というよりJIT系のアイデア)とOSを統合的に考えると最適化された実行バイナリをキャッシュする仕組みが考えられる。
プログラムは統一的に記述されたほうが可読性が高まり良いだろうというアイデア。 性能やセキュリティの違いがあってやっているなら良いが、無駄な不均質化の場合もある。 システムの設計パターンも統一した方が良い。Glbを使うかDIフレームワークを使うか等。 ライブラリも統一された方が良い。
私はJPMSに強い疑問を持っています。結局私の中にあるJPMSへの疑問は、自己完結型を推奨する事がJVMのメリットを失わせるのではないかという事と、MavenとJDKを統合するようなアイデアと比べてJPMSがどの程度合理的なのかということです。
-
jar hellを解決するものと良く言われていますが、jar hellを解決しません。module-info.javaの依存性の記述においてバージョンを指定しないのでjar hellを解決できる可能性はありません。 例えばこの記事がそれについて言及している。
https://blog.codefx.org/java/dev/will-there-be-module-hell/
Moduleは依存性解決を対象としていない。
http://openjdk.java.net/projects/jigsaw/goals-reqs/03#versioningMultiple versions — It is not necessary to support more than one version of a module within a single configuration.
-
セキュリティに寄与するかのような記述もありますがそんな意味は無さそうです。
https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO17-Modules.pdf攻撃者がアクセスできるクラスが少なくなるため、プラットフォームのセキュリティが向上します
ここでいうセキュリティの向上はほとんど宣伝文句です。アクセスできるクラスが少し減ってもほとんどセキュリティを向上させないと思います。ほとんどの文脈で必要とされるファイルシステムAPIやDBや通信APIがあればほとんどすべての攻撃が可能だからです。
-
自己完結型のため最小の実行ファイルを作成する事に寄与します。これによって組み込み適性が高まります。自己完結型の否定
-
module-info.javaによるカプセル化で保守性に貢献します。しかし、--add-opens等でmoduleでカプセル化されたクラスにアクセスできるようなので、どの程度保守性の改善が期待できるのか疑問です。
-
コンパイル時だけでなく実行時にも定義されたアクセスルールが満たされているか判定できます。しかしJPMSはセキュリティソリューションではないと考えると、これが何に寄与するか良く分かりません。
-
モジュールをカプセル化して想定外のアクセスを防止できるという話を聞くとアドインのセキュリティ(サンドボックス)に寄与しそうな気がしますが、そのような文脈ではインターフェース単位やさらにはアクション(read, write等)単位での細かい権限設定が必要であり、JPMSのパッケージ単位での設定は粗すぎるのでアドインのためには役に立ちません。
-
JPMSの採用(Java9+への移行)ペースは非常に遅い。多くのプロジェクトはjava8に留まっています。
2019 83%
https://www.jetbrains.com/lp/devecosystem-2019/java/
2018 84%
https://www.jetbrains.com/research/devecosystem-2018/java/
さらに、Java9+へ移行したライブラリやアプリにおいてもどの程度JPMSに対応しているか差があるようです。
JPMSがjar hellを解決すると主張する仕組み。jigsaw layers
https://www.slideshare.net/nikitalipsky94/escaping-the-jar-hell-with-jigsaw-layers-gee-con
- 21.JPMSは最初のドラフトではversion指定があったが削除された。
- 50.例えversionを指定してもjar hellは解決されない。
- 89.Jigsaw Layersが紹介されている。クラスローダーの委任モデルと似ているが、さらに子レイヤは親レイヤに対してサービスとして機能を提供する。こうする事でサービスの実装(どのバージョンのライブラリを使うか)は隠蔽され、バージョン競合問題は1レイヤ内で閉じる。
- 111.サーブレットコンテナは普通モジュールについて何も知らない。つまりjigsaw layersに対応していない。これに対して「フォークして改修しろ」と答えている。Jigsaw Layersによるjar hellの解決は設計とコーディングのコストが生じる。
このサンプルコードが分かりやすかった。
https://github.com/torstenwerner/java-9-no-jar-hell/blob/master/src/main/java/com/app/Main.java
- サービスを定義する必要がある。
- 恐らくこの仕組みはサーブレットコンテナとアプリの間のようなある程度大きな枠組みにおいてのみ有効であり、あらゆるライブラリの利用シーンにおいてバージョン競合問題(jar hellの一種)を解決できるわけではない。
Jigsaw Layersはjar hellが極端に複雑化しないようにするもので無くせるわけではない。 通常のライブラリの利用場面でJigsaw Layersをいちいち使うという事は現実的ではない。サービスの定義が肥大化したり定義回数が増加する。
やはりcodebaseにpom.xmlを標準搭載するのが素直だと思う。そうすればjar単位で異なるバージョンのライブラリを使える。
アドイン(アプレットという場合も)のセキュリティ(サンドボックス)を実現する方法。その他Javaのセキュリティ
関連:
- Javaセキュリティアーキテクチャ
https://docs.oracle.com/javase/jp/1.3/guide/security/spec/security-spec.doc1.html
「codebaseへの権限付与」と「スレッドのこれまでの経路上の最小権限で動作する」と「doPrivileged」がポイント。 https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/doprivileged.htmlリソースにアクセスしようとする場合は常に、その地点に至るまでに実行スレッドがたどってきたすべてのコードが、そのリソース・アクセスのためのアクセス権を持っている必要があります。
- ファイル操作などシステム系処理の権限
- カスタム権限
カスタム権限にメンバー変数を持たせてPermission#implies()をオーバーライドすれば大抵の要求に対応出来ると思う(AccessController#checkPermission()の引数に状態を持たせてPermission#implies()で利用できる)。- コードベースに付与されたPermissionが要求されたPermissionの子クラスだった場合、許可できるか?
>BasicPermissionで簡単なサンプルを書いてみたができなかった。クラスが異なるとimplies()が呼び出されない。
- コードベースに付与されたPermissionが要求されたPermissionの子クラスだった場合、許可できるか?
- 権限付与
ポリシーファイルまたはSecurityManagerを継承したカスタムクラスを作りメソッドをオーバーライドする。あるいはPolicy.setPolicy()でprogramaticallyに権限付与できる。codebaseに対する権限付与は、/*を使う事でその先の全classまたはjarファイルに権限付与できる。 - (自分の)メソッド単位でのアクセス拒否。(少なくともJava8では)JDKのクラスがそうしているように各メソッドの内部でSecurityManagerでチェックするコードを書く。アノテーションで簡単に出来そうな気がするがそういう書き方が紹介されていないあたりたぶんできない。
- オブジェクト単位でのアクセス拒否。GuardedObject
- スレッドまたはスレッドグループ単位での権限付与。どうやらスレッドまたはスレッドグループ単位の権限付与はない。SecurityManager#checkAccessはスレッド優先度の変更やユーザースレッドの作成の権限があるかを調べているだけで、スレッドに任意の(例えばファイル操作)権限があるかを調べるというものではない。恐らくこのメソッドはオーバーライド用に定義されている。しかしJavaのセキュリティアーキテクチャは、そのスレッドの経由クラスに応じてスレッドに権限付与されているという捉え方ができるかもしれない。
- jar単位での権限付与
基本的にjarに権限付与して、そのjar内のクラスはそのjarに与えられた権限を得て、さらにスレッドがクラスを経由するたびに全経由クラスの共通権限で動作する。 https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/doprivileged.html
リソースにアクセスしようとする場合は常に、その地点に至るまでに実行スレッドがたどってきたすべてのコードが、そのリソース・アクセスのためのアクセス権を持っている必要があります。
つまりJavaでは各クラス毎に権限が付与されているが、加えて、スレッドの経路上の全クラスでその権限が付与されている必要がある。
ただしdoPrivilegedを使うと、経由クラスを無視してそのクラスに本来与えられた権限だけでアクセス拒否を判定する。
細かな調整はSecurityManagerを継承したクラスを作り一部メソッドをオーバーライドして対応できるようだが、性能が悪化しやすいので注意する必要がある。
Javaではクラスローダー毎に静的フィールドが作られるが、ではクラスローダー毎にSecurityManager(Systemのstaticフィールドに置かれている)をセットできるか?
クラスローダーの委任モデルによって、上位のローダーでロードされるSystemクラスは子ローダーでロードしえない(即ち上位ローダーに委任される事を避けられない)ので、そのフィールドsecurity(getSecurityManager()の返値)がクラスローダー毎に作られるという事はない。
https://coderanch.com/t/385932/java/ClassLoader-Static-variables
コードベースに付与されたPermissionが要求されたPermissionの子クラスだった場合、許可できるか?
できない。その子クラスのimpliesが呼び出される事すらないので型チェックの時点で対象外になっていると思われる。
このようなコードで実行時に(起動コマンドでの指定やJDKフォルダ内のポリシーファイル無しで)任意のポリシーファイルを読み込めた。
//load policy at runtime
String policyFileAbsolutePath = MainClass.class.getClassLoader().getResource("test.policy").getPath();
System.setProperty("java.security.policy", policyFileAbsolutePath);
Policy.getPolicy().refresh();
System.setSecurityManager(new SecurityManager());
実行時にjar別に権限を与えたりcodeBaseを相対パスで指定する事もできる。
アプリはファイルを作成する場合がありますが、どこに作成するかいくつかパターンがあります。
-
コマンドライン引数で出力先を指定するタイプ。
通常、アプリとそのアプリが作成するファイルは分離されます。 例えばmkdirコマンドはその実行ファイルの位置と作成されるディレクトリがファイルシステム上で全く異なります。mkdirのようなプログラムは(アプリケーション開発者が作成したとしても)OSの機能とみなしうるもの(パイプが有効に機能するもの)です。 -
ドキュメントフォルダやC:\Users<user>に出力するタイプ。
user.homeは全OSで扱われている概念ですが、ドキュメントフォルダはOSのアーキテクチャに依存しプラットフォーム間での互換性がありません。 -
GUI上でユーザーに出力先フォルダを設定させるタイプ。
ユーザーのファイル管理の負担が大きくなります。 -
そのプログラムと同じフォルダに出力するタイプ。
私はこの方法が一部のアプリで有力ではないかと考えています。- この方法はOSのフォルダアーキテクチャに依存しません。
- アプリを他の環境に移行する場合1フォルダのコピーだけでアプリも関連データもまとめて移せます。
- アプリがシステムの様々なところに勝手にデータを作成せずアンインストールが1フォルダの削除だけで済みます。
- 異なるバージョンのプログラムを使用する場合でも他のバージョンのデータファイルと混同する可能性がありません。
- ファイル権限がアプリインストール位置以下の権限だけで済みます。これはある種のセキュリティアーキテクチャを想起させます。例えば、ある種類のアプリは、インストール位置以下のファイルまたは<user.home>/以下のファイルしか読み書きできないなど。
- このアイデアはドキュメントフォルダと比べるとファイルの種類別で探す事に苦労しますが(ドキュメントフォルダにはビデオやピクチャ等のフォルダがある)、OSは拡張子で全ファイルから高速に検索できるべきです。
1プロセス上で多数のJavaアプリを同時実行すると起動時間を極小化できて省メモリになりJITも効率が良い。
しかし各アプリは独自のOSレベルのプロセスを持てないので互換性がないものが出るかもしれない。現実的にはアドイン(アプレット)を扱えるアプリになるか。
言語は単純であるべきだが、複雑化する一方だ。 Javaはエコシステムが巨大で歴史が長いせいもあるだろうがどの領域にも複数の選択肢がある。
- JavaエコシステムのGUIフレームワークは主なものに限定してもAWT, Swing, SWT, JavaFXと4種類あり、公式かつ最新のJavaFXがランタイムから分離されていて、古いSwingが同梱されている。
- サードパーティライブラリも乱立している。しかも1つの領域に利用者数が多いライブラリが複数ある。
- 継承か構成か?構成が推奨されるが相互参照が生じ、ユーザーコードによる構成にも問題を感じる。
このような状況はJavaソースコードを不均質化させていく。 世界的なJavaソースコードの統一的記述が達成された方が可読性やプログラマーの均質性が高まる。
Javaエコシステムの複雑さのせいで、私はTenyuプロジェクトのソフトウェア構成の判断において色々な比較検討をさせられた。 実際の使用経験無く評判や概要に基づいて判断した。いちいち詳細に調べていられない。
Netty vs Grizzly 私はNettyを選んだ。Nettyはベンチマーク結果が良かったり実績が豊富だった。
libgdx vs jmonkeyengine vs JavaFX javafxはゲーム用にも使える。実際にFXGL等のライブラリがある。他にもマイナーなものがたくさんある。
scene builder vs FXML vs code JavaFXをGUIに使うとしても様々な手段がある。自分はscene builderを少し使っていたが、直接コードで書いた方が良いと判断した。
何にせよ選択肢が多い上に簡単に優劣をつけられないからJavaソースコードの不均質化及びJavaプログラマーの不均質化の原因になる。せっかくの巨大エコシステムが無駄どころか逆効果になっているように感じる。
使いたいライブラリがjava9+に対応していなかったり、別のライブラリはjava9+を要求してきたり、 まちまちだったりすると判断に困ります。更新停止した古いjava8版のライブラリを使う事になります。
まず、JavaFXに関して様々な問題を認識したにも関わらず、なぜか私はJavaFXが好きです。
- SceneBuilderを使っているとできないことがたくさんある。FXMLも不要。利用を試みて時間を無駄にした。
- 直接Javaコードを書くのが最も楽で適切な設計(Tenyu基盤ソフトウェアのSubmitButtonFuncなど)に到達できる。
- プロパティバインドしようとするとモデル側にjavafx.beans系のクラスが出てきてモデルとビューの分離に問題が生じる。利用を試みて時間を無駄にした。
- プロパティバインドを使ってもDBからモデルデータを読み出すシステムの場合ほとんど役に立たない。値の更新はDB上で行われそれを再読出ししなければならない。永続化されずリアルタイムに変更されるモデルデータでのみバインドは有効。DBまで一貫した仕組みを提供しなければコーディング量を減らせない。
- Swingに対して特に利点がない。強いて言えばラムダ前提で設計されているのは利点か。
- GUIをキーボードショートカットのみで操作できるようにしようとしたらずいぶん苦労した。
- JavaFXが牽引力を持っていないという記事。https://www.codenameone.com/blog/should-oracle-spring-clean-javafx.html
- その記事のコメントにおいてJavaFXで問題が生じるとプロセスを落とすしかないがSwingだったらアプリが回復するというようなコメントがある。
- JavaFXのGUIコードを書いている時に何度かセグメンテーション違反に遭遇した。
Windowsを使い続ける理由は既存のソフトウェア資産が豊富だからで、もしバーチャルプラットフォームなソフトウェアが増加するなら、脱Windowsが可能になる。 さらに、私はLinuxデスクトップ等FOSS系OSへの大規模な移行がありうると思っている。Tenyuの相互評価フローネットワークという構想はFOSSを収益化できる可能性があり、もしその構想が実現するならFOSS界隈に大きな変化が起きる。
https://www.wired.com/2015/04/microsoft-open-source-windows-definitely-possible/
Windowsをオープンソースにするという話があるが、そうするくらいならソースコード非公開のまま無料化した方が良い。ソースコードの公開はセキュリティリスクを増大させる懸念があり、非公開の方がFOSS系OSに対する差別化になる。
FOSS系OS
- ソースコード公開
- 無料
- カスタマイズ可能
- ソフトウェアのみ
Windows
- ソースコード非公開
- 有料
- カスタマイズ不可
- ソフトウェアのみ
Mac OS X
- ソースコード非公開
- 有料?ハードウェアとセット
- カスタマイズ不可
- ハードウェアとセット。動作保証しやすい。
その他新しいマイクロカーネルOS。HarmonyOSやFuchsiaなどがある。
FOSS系OSはJDKを標準搭載しJavaアプリをメインに想定する戦略が有力かもしれない。そうする事で全FOSS系OSがアプリ資産を共有できて、FOSS系の弱さであるアプリ資産の乏しさをカバーできる。
web2.0とも言われていました。ちゃんと意味のある言葉です。 WWW上の様々なアプリ、例えばyoutube, twitter, blogいずれにおいても、実のところ「様々なユーザーが活動してそれぞれが自分のデータを登録してサーバー上にDBを作っている」わけで、そのようなものをpeoplewareと呼びます。(https://japan.cnet.com/article/20361105/2/)
主なサイトが結局peoplewareであるなら、P2Pでうまく代替できるはずです。自分のデータを自分のPCに登録して自分で配信すればいいのです。それをサポートするP2Pソフトウェアがあればいい。
集計は、モデルデータがmachine readableかつ公開されていれば可能です。
ダイナブックやSmalltalkについて調べていて、メッセージングがネット上の他のユーザーにも届き、他のユーザーが持つオブジェクトが応答するなら、そのようなネットワークが広がるなら、そしてリアルタイムに自分の環境に新しいオブジェクトを追加したりREPL的に交流したら、そしてそのようなプラットフォームをWWWを代替するものとして捉えたら面白いのではないかと思いました。WWW上で行われている事はほとんどpeoplewareだからP2Pでうまく代替できそうです。この構想ではユーザー間でやり取りされるのはHTMLではなくオブジェクトなので高度なセマンテックがあります。
Tenyu基盤ソフトウェアはTenyutalkというシステムを搭載し、Tenyu基盤ソフトウェア上で様々なアプレットを実行する事が可能です。その表現力はWWW上のあらゆるコンテンツを代替するのに十分であり、しかもアプレットはTenyu基盤ソフトウェアのユーザーDB等を参照できて簡単に高度なシステムを実現できます。
C/Sのサーバーに人々のコンテンツを集める(=peopleware)のではなく、コンテンツ作者が自分のPCで直接発信します。さらにTenyutalkはP2Pネットワークによる公開日時証明も実装し、創作物を発表するプラットフォームとして必要な全ての機能を実装します。
新しいアプレットを誰でも作れて、P2Pネットワークに流して、基盤またはアプレットによるDBが作られて、どのアプレットも基盤や他のアプレットが作ったDB(どのアプレットにアクセス権を与えるか設定可能)を参照できます。これを達成するためJavaセキュリティアーキテクチャが重要になります。
tenyutalk
- ユーザー中心コンピューティング
- ネットユーザーの活動を包括的にサポートする統合システム
- 人間がプログラムと同じものを見る。smalltalkのclass browserやinspectorのような
Tenyutalkはネットユーザーが自分のPCでサーバを実行し自分のコンテンツを自ら発信するシステムです。ただし他のノードがコンテンツをキャッシュして代わりに配信するなどしてオフライン時にもコンテンツを提供できたり、分散処理によって大規模なアクセスにも耐えれるようにします。 さらにTenyu基盤ソフトウェアのモデルクラスも重要です。
もともとTenyu基盤ソフトウェアの一部の機能が"セキュリティ要件がほとんどないが高性能なP2P機能"を必要としていて、それを単純に実装するのではなくより汎用的なシステムを構想した結果、WWWの置き換えにもなりうるような汎用的なプラットフォームが可能そうな気がしました。
さらにおおむねWWWを代替しうる仕様を持つ事も要件に入れます。それを想定した基本設計があります。javaオブジェクトならセマンテックが完全にありmachine redableで、WWWに対して技術的に優位性を持ちえます。
私がこのアイデアを思いついた当初本当に有望なのかを再考したとき思ったのは、これは新しいプログラミング環境の創出であるという事です。このような思想が進んだ先でネット上のあらゆるモデルがmachine readableとなりオープンに共有されている状況を想像できます。そしてこの構想が完全に実現された場合、これはSmalltalkを進化させた、世界に存在する全てのpeoplewareのDBを誰もが利用できる新しい統合プログラミング環境という事になります。
geoip2というIPアドレスから地域を特定するライブラリがあるが、そのようなライブラリが距離計算機能を備えれるはず。 もしその機能があるといくつかのIPアドレスの候補から最も低レイテンシなIPアドレスを選択できるので、P2Pネットワークで効率的な情報拡散ができる。
私はユーザー中心コンピューティングにおいて一貫して通用する抽象的設計の確立に取り組み、Model、AdministratedObject、IndividualityObject等の一列の抽象クラスの系統を設計しました。これらの設計はネット上での人々の活動全般において一貫して適用しうると考えています。
それらモデルクラスを前提としたModelGuiクラスやModelStoreクラスがあります。このような設計を継承構造と呼んでいます。
ModelGui(一時期ObjectGuiと呼んでいた)クラスはsbfというアイデアがあり、GUIコードの再利用を実現し、従来のWebフロントエンド等と比べて技術的に優れたアイデアです。モデルに対してただ一度GUIコードを実装すれば様々なGUIからそのコードが再利用されます。
この話題はいろいろな人によって語られていますが、自分は昔から以下の理解で使用しています。
- プログラマー。主体的にプログラミングに向かう人。組織内では経営のためという意識が弱い傾向があり下っ端(=ただのコーダー)になる事が多い。日本のIT業界でプログラマーと呼ばれている人のほとんどが実はソフトウェアエンジニアでありプログラマーではない。
- ソフトウェアエンジニア。組織に所属し、その組織の経営のため価値ある存在となるべくプログラミングに向かう人。ソフトウェアエンジニアにとってプログラミングは稼ぐ手段であり、スキルセットを市場動向や所属組織の求めに応じて選択している。
- デベロッパー。エンジニアのうち新しい領域を開拓している人。
https://blog.kyanny.me/entry/20100520/1274285790
この記事は私の理解とほぼ同じです。
https://ericsink.com/No_Programmers.html
この記事は、少なくともプログラマーとデベロッパーは異なるものであるという観点で書かれています。そしてプログラマーの問題としてドキュメント類を整備しない事が指摘されていますが、プログラマーは組織のためという意識が弱いから引き継ぎなどを意識して仕事しないという事だと解釈できます。企業はデベロッパーを求めるべきと主張しています。
http://codebetter.com/raymondlewallen/2005/02/22/developer-versus-programmer/
この記事は、デベロッパーがプログラマー呼ばわりされる事を嫌う場面が述べられています。プログラマーは組織への親和性が低いというレッテルになりうるからです。
https://weblogs.asp.net/miked/2200_Developers_2200-and-2200_Programmers_2200
この記事は、デベロッパーとプログラマーが異なるものであると主張し、プログラマーが自分の仕事を限定しがちであるという主張は、プログラマーが組織の経営の観点で自身の価値を考えない傾向があるからだと解釈できます。
いずれの記事も私の解釈に符号します。プログラマーという概念がIT業界においてただのコーダーを意味するものとして捉えられがちなのは、プログラマーが組織との親和性が低く、所属組織の経営の観点から自分の価値を考えず、プログラミングに向かう動機が個人的である傾向が強く、結果として組織内で扱いづらく下っ端になるしかないからです。
世界最高のプログラマーは誰か?という事で調べるとプログラマーという概念が見えてきます。 プログラマーの特徴はプログラミングを通じて主体性、独創性、行動力等を発揮した者です。 ここに挙げられた人たちはプログラマーと呼ばれる事はあってもソフトウェアエンジニアとかデベロッパーと呼ばれる事は稀だと思います。
http://reactivex.io/intro.html
古典的なプログラミングがデータ構造やサブルーチンや抽象クラスやインターフェースなどを設計してきたのに対して、RPは非同期処理を設計する。つまりRPは非同期処理の(抽象的な)パターン化を行う。その手段はストリーム(イベント発生源、Observable)をモナドにすることです。
ここのサンプルコードが分かりやすい。
https://qiita.com/rubytomato@github/items/40c2aeabf762cc9465ad
Rxjava解説
https://qiita.com/Yoshi25/items/200e253a9d61df9bb21b
ReactiveXオペレーター
http://reactivex.io/documentation/operators.html
Observableにイベントの種類毎にハンドラとなるラムダを与える。 1つのObservableの定義は1つの小さなシステム(複数のイベント及びそのハンドラ等)を定義している。
さらに、2つのObservableの間に単純な演算が定義される。 例えば2つのObservableをmergeする事が可能だが、mergeの時点でObservableがどのようなイベントをどんな順番で発生させるか未確定でもいい。つまりそのmergeされた2つのObservableは実データ(merge時点で確定していたデータ)としてmergeされたのではなく、モナド的仕組み(モナドの3)によってmergeされたように振る舞う。Observable#map()等でハンドラを修飾していた場合もmerge後に維持される。
非同期イベントシーケンス(Observable)というしばしば対象化されずオブジェクトとして扱われてこなかったようなものが、まるで数値のような取り回しの良い(mergeなど視覚的にイメージしやすい単純な演算が複数ある)ものとして扱われる。これは演算を通じて多種多様なシーケンスを生み出すような、ダイナミックにシーケンスを扱うシステムを想起させる(そのようなシステムの用途は不明だが)。Observableに対するmerge等の各種処理は未確定な将来の値を合成したりそれに応じてイベントハンドラ(ラムダ)の呼び出しタイミングが変わっていく(計算の合成戦略の動的変更)という特殊さがあり、モナド的であるメリットの1つと思う。
近頃はJavaでもモナドを認識しなければならない。という事でモナドの学習がてらJava発モナド行の文章を書く。
自分なりの結論。モナドとは
- 値を格納する箱
- その箱にラムダ(関数オブジェクト、ファンクタ)を与えて処理を進める
- ラムダ呼び出しの前後でモナドの種類(いくつかある)に応じた処理を挟む
- パイプライン記述を可能にするインターフェース(本場の関数型言語ではbind>>=演算子やdo記法で記述する)
3を認識するのが重要かと思う。便宜的に言えば、3によってモナドはラムダへそのモナドの種類に応じた基本的前提(渡される変数がNotNullとか)を提供する。
主にこの記事を参考にする。
https://the.igreque.info/posts/2016/04-monad-in-java.html
モナドはパイプラインを可能にする。
monad.f().f2().f3()
こういうインターフェースの設計はしばしば見るがその全てがモナドというわけではない。
ここにモナドとなる条件が書かれている。
https://the.igreque.info/posts/2016/04-monad-in-java.html#monadLaw
その記事中のinterface Monadは、Javaプログラマー的にはこう読める。
- 指定された型T1をラップする型であること。(後の説明も踏まえてコンテナとか箱のような印象がある)
- T1から別の値を格納したモナド(Monad)へとラムダ(ファンクタ)を通じて変換できること。返値がモナド型なのでパイプラインが可能。
- 渡された値をモナドに格納して返すインターフェースがあること(Return#doNothingReturning)。最初モナドを作る時とかに使う。
モナドは命令型プログラミングの典型的な複数行の処理(直前の行で作成した値を次の行で処理するようなもの)を(Maybeモナドのnull対応のような特性を与えつつ)パイプラインで記述(ラムダの連続)できる事を意味している。
Javaにもいくつかモナドとみなしうるものがある。
- OptionalはNullableモナド(Maybeモナド)
- Observableは非同期イベントシーケンスモナド
モナドは特殊な値(Nullableな値だったり将来的に発生してくるイベントシーケンスだったり)を格納する入れ物。 将来的に発生するイベントシーケンスとかNullかもしれない値とかは、確定した実データと比較した時に特殊な面があるせいでその特殊さに応じたコードが必要になり、そのようなコードがモナド的仕組み(ラムダの前後に挟むモナド側の処理)の中に隠蔽される。
当然ながら入れ物(モナド)よりも中に入っている値に意味があり、その格納値に外部から与えられたラムダをいくつも適用しながら処理を進める。 この、わざわざモナドという箱に値を入れてラムダで中身に対して処理を進めるという回りくどいスタイルが有用らしい。ラムダ呼び出しの前後にモナド側の処理を挟めるから。
Monad monad = Monad.wrap(x);
monad.f(x->...).f2(x->...).f3(x->...)
モナドは良くプログラム可能なセミコロンと言われるが、要するにモナドがラムダを受け取った時そのラムダ呼び出しの前後にモナド側(ラムダを与える側と対比)の処理を挟む事が可能だからだ。そうして多数のラムダが与えられた時、1つのラムダを1行とみなすと、行の間に処理が挟まっているようにみなせる。
値を直接扱うのでなくわざわざモナドに入れて扱う。値を取り出さずにラムダを通じて処理する。するとモナドは格納している値に対するあらゆる処理をフック(ラムダの前後に処理を挟む)できる(参照が他所になければ)。これは格納値が持つ厄介な性質(Nullableとか)を管理するのにとても都合が良く、それ(Nullableとか)に設計的に(非場当たり的に)対処できる。
モナドはある種の値の特殊さの影響が外部に波及する事を防止している。
Javaにモナドを持ち込んだら便利かというと、RxJavaはうまくはまれば便利そうだしダイナミックにイベント発生源(ストリーム)の扱いを変えていくような特殊システムを構想できるが、Optionalはあまり便利じゃなさそうと思う。
むしろ自分はこういう風に書きたい
SomeClass o = new("a");
Integer i = valueOf("1");
代入の左辺のクラスを暗黙的に想定する。
この場合varという新しい予約語を作る必要が無く、ただ従来記述されていたものが省略される。
クラス設計は同種(同じクラス)の全個体(インスタンス)が同じ実装を持つという前提を必要とする。
これに反する例として生物とゲームを挙げれる。
生物の特徴は個体ごとに異なる適応をする事にある。例えば犬や猫などの四足歩行動物が事故で足を一本失って、しかし個体ごとの適応の結果、三本足で走るようになったりする。 これはクラス設計の文脈で言えば「同じCat/Dogクラスのインスタンスなのにwalkメソッドの実装方法が異なる」という状況であり、 そのような「個体ごとの適応(同じ目的を異なる方法で達成する)」がある対象はクラス設計(同種の全個体が同じ実装を持つという前提)が適さない。
ゲームのオブジェクトはしばしば奇抜な発想(物体だったものが一時的に幽体になり衝突判定が無くなるとか)が要求され、属性をほとんど見出せない。だから個体毎に異なる構造を持つという前提で、クラス設計を諦めて、ECS等の個体毎に異なる構造を許す設計が模索される。
apache commons compressがzstandardに対応しているが、zstd-jniに依存している。 このようなライブラリはJDKとサポートするプラットフォームが異なる可能性があり、さらにjniのオーバーヘッドがある。 このようなものはJDKが直接実装すべきだと思う。 しかもzstandardはオプションを調整すればほとんどのケースに対応できる汎用的な圧縮アルゴリズム。