Kengo's blog

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

JJUG CCC 2021 Springでバイトコードの話をしました

ということでJava8〜16におけるバイトコード生成の変化について、先日開催されたJJUG CCCで喋ってきました。 動画をYoutubeにて配信していただいているので、よろしければご覧ください:

youtu.be

資料はSpeakerdeckにあります。ハイパーリンクを埋めているところは、PDFを落としてもらえれば追えるはずです:

speakerdeck.com

マイクロベンチマークGitHubに置いてあります。みんなも手元でレッツJMHだ:

github.com

なお最後の方に触れたJEP396については、掘り下げたセッションがあったようです:

youtu.be

運営の皆様、いつも素敵なイベントを開催いただきありがとうございます!

InclusiveなOSS開発体制を作る意味で、GFWの影響をざっくり知ってもらえるとありがたい

TL;DR

2021年5月時点では raw.githubusercontent.comgcr.ioはGFW内からアクセスできません。 開発しているOSSを広いユーザに利用してもらいたい場合は、こうしたドメインへの強い依存は避けることを検討しても良いかもしれません。

中国インターネットから見たOSS開発体制

中国本土でOSS開発をしていると、以下のようにバッジが壊れているGitHubリポジトリによく遭遇します:

f:id:eller:20210520191050p:plain
バッジが表示されないGitHubリポジトリの図

これはバッジが使っているドメイン raw.githubusercontent.com にアクセスできないことから生じます。バッジなら大して影響ないのですが、開発体制構築がこれに依存していると厄介です。 例えばYarn v2のインストール手順は、JSONファイルを raw.githubusercontent.com からダウンロードするためにうまく実行できません。このためGitHub Issuesでユーザ側による回避策の適用が推奨されています:

github.com

GitHubが中国本土からアクセス可能なのは有名な話だと思いますが、それでもこうした一部ドメインへの制限があったり、Web画面にもたまにアクセスできなかったりもします。

同様に gcr.io もアクセスできません。Jibがかつて標準で利用していたベースイメージであるDistrolessのダウンロードが行えません。ただJibが利用するベースイメージはv3からDocker Hubに置いてあるAdoptopenjdkに変わったので、だいぶ使いやすくなりました。

試してませんがStackoverflowの投稿によるとfirestoreも一癖あるっぽいですね。

どのような回避・緩和策があるのか

GFW内からもアクセス可能なドメインを使う

バッジやJSONファイルを配布する場合、2021年5月時点では shields.ioGitHub Pages、Netlifyなどで代用が可能です。 おそらく最も運用が軽い対策になると思います。

2021年5月26日追記:GitHub Releasesに添付されたファイルもダウンロード可能でした。

ミラーサイトを用意する

Mavenやnpmなどのパッケージダウンロードが遅い場合は、中国国内に大手が提供するミラーサイトが利用できます。通信自体は可能なので必須ではありませんが、開発体験を向上できる場合があります。

maven.aliyun.com github.com

このように、ミラーを用意してそちらを使ってもらう手は一応あります。ただ個人的にはXcodeGhostの1件があったので、ミラーの利用をコミュニティとして推奨するのは避けたいなぁとは思っています。少なくとも公式サイトからチェックサムを確認できるようにする必要があるかと。なおGradleはv6から依存のチェックサムを自動的に検証できるようになりました。

hostsを書き換えてもらう

以下のように、中国語ブログでよく紹介されているのがこれです。詳細は割愛。

www.cnblogs.com

まとめ:そんなに気を使わなくても良いかも

こういうことを気にできると確かにInclusiveOSS開発体制の構築には貢献すると思いますが、今見たらant-designも普通に raw.githubusercontent.com を使っていたので、気にしなくても問題ないレベルかもしれません。

JavaのRecordでは配列を使わないほうが良いという話

配列使うとmutableになるから使うべきではない、というのに加えて。生成される hashCode()equals(Object), toString() が配列を考慮しない実装になっているため、JavaのRecordでは配列を使わないほうが良いようです。

検証コード

生成される hashCode()equals(Object) の挙動を検証するコードです。

gist.github.com

なぜこうなるのか

ObjectMethods クラスによって生成されるコードを見ると、 Arrays.hashCode(int[]) ではなくint[].hashCode()が、Arrays.equals(int[], int[]) ではなく int[].equals(Object) が使われているようです。これは考慮漏れというわけではなく、ObjectMethodsクラスのjavadocに明記されている仕様です。core-libs-devメーリスに質問を投げて確認しました

実際の実装はObjectMethodsクラスのコードを読むか、以下の記事を参照ください。

alidg.me

望ましい対策

Recordでは配列の代わりに不変コレクションを利用するのが望ましいと思われます。Java 9からimmutable listを作るfactory methodが導入されています。また不変であることを型として示したいならば、GuavaのImmutableListを使う手もあります。

Recordクラスがmutableになるリスクを受容した上で配列を使う場合は、hashCode()equals(Object), toString() を自動生成に頼るのではなくハードコードすることで、この問題を回避可能です。大抵のIDEにはこれらのコードを自動的に生成する機能が備わっているので、利用すると良いでしょう。

オンラインコミュニティに参加してるまとめ

自分のキャリアを考えたときに、FOSSエンジニアであることの比重が高いわりにプレゼンスないんだよね……ということに課題を感じたので改善策を考えてみた。LTやセッションを増やしたりcontributeするプロジェクトを増やしたりするのも必要だけど、他にも継続的にオンラインの知り合いを増やせる・議論の機会を得る手段がほしいと思い、オンラインコミュニティにいくつか参加し始めているところ。

参加コミュニティ

Codacy Community

OSSの静的解析ツールを複数組み合わせて統一されたUIで解析結果を伝えるSaaS、のコミュニティ。Twitterで見かけてDiscourseに参加。

SonarQubeやLGTMのような自社解析エンジンは持っていないが、CEOが静的解析で修士とった人だったし社員ぽい人もSpotBugsにPR送ってくる。のでマニアが集まるのではと期待してDiscourseに静的解析を学ぶ良い方法について話題を投げたけど反応薄いのが今。

SonarSource Community

前から参加してたDiscourseをより頻繁に見に行くようにした。私はコミュニティ担当の方とどうも反りが合わないようで、PRで話が噛み合わないとか返信を何週間も待たされたりとか、どうも見る気になれないのだけど。

どこにも買収されてない&自社で技術を持っている数少ない企業だと思うので、今後も注目していきたい。

Gradle Community

Slackがあるのに最近気づいて参加。Discourseでもいいけど、Configuration CacheとかWorker APIとかわりと新しめの話題について質問するにはSlackのほうが反応早いかもしれない。

GradleはGitHub Issuesでコミュニティ発のプラグインの機能について俯瞰状況をまとめてたり活発なユーザを称えるFellowship Programを初めていたりと周りを巻き込もうという意気を感じる。のはいいんだけどYoutubeチャンネルのノリがよくわからない。アイスブレイクのつもりなんだろうけどバラエティ番組みたいになっていたりする。

Write the Docs

コミュニティの蓄積がすごい。ツールの話はもちろんテクニカル面についてもまとまってそう。L10nについては特にまとまっていないので、Slackチャネルでおすすめのツールを聞いてみている。ドキュメンテーションツールもそうだが翻訳メモリなどについても話していきたい。

API the Docsというオンラインカンファレンスが近いようなので覗いてみるつもり。

SIGPX

DX: Developer Experience (開発体験)は重要だ - Islands in the byte stream で知り、PXは静的解析ツールとの親和性高いのではと思って第8回勉強会に以下のスライドで参加。静的コード解析そのものについて扱っている方はいらっしゃらなかったが、IDE周りというかMagpieBridgeLSPについて掘り下げて話すと良いかもと感じた。

speakerdeck.com

Java-jp Discord

ゆるゆる参加している。

twitter.com

コミュニティの更新を追う方法

モバイルアプリで追えるようにして、通勤中などのスキマ時間にぱぱっと済ませられるようにしている。

Discourse

公式モバイルアプリがあり、複数サイトを横断的に管理できる。今まではメール通知に頼っていたけどこちらのほうが早い。

Slack

Slackアプリが複数ワークスペース管理をサポートしている。各ワークスペースに同一のメールアドレスでサインインするようにすれば特別な配慮は不要。

Discord

こちらもSlackと同じ。

まとめ

はじめたばかりなのでまとまらない。情報発信は得意な方なはずなので継続していきたい。

静的解析ツールまとめ2021年春

静的解析ツールで飯を食えないか定期的に気になるタチなので有償サービスなど調べている。のをまとめる。主にJava/JavaScript周り。

Name Download Star 開発企業など
SpotBugs 482k/mo 2.2k なし
PMD - 3.3k なし
Stylelint 10m/mo 8.5k なし、Open Collectiveはある
SonarQube - 5.6k SonarSource
Codacy - - Codacy
CodeClimate - - CodeClimate
LGTM - - GitHub

有償サービス展開している企業は、どこも(国境を超えた)リモートでの就業はやってない。

選外

coveralls
カバレッジ出すSaaS。他のSaaS機能で代替できることが多そう。
snyk
依存に含まれる脆弱性を見つけるSaaS。DependabotやSonatypeと競合?

外国人永久居留証を取得しました

外国人永久居留証の申請条件を満たしたので、2020年9月25日付で外国人永久居留証を申請し、半年後の2021年2月25日に受け取りました。

どういうカードなのか

俗に言う中国版グリーンカードですが、だいたい以下の恩恵があるようです。

  1. 就労許可・居留許可を継続的に取り直す必要性がなくなる。有効期限は10年。毎年3ヶ月は中国に滞在する必要あり。
  2. 投資に制限がなくなる、らしい。
  3. 行政手続で段階的に使えるようになる、中国人レーンや住宅公共積立金を使えるようになるなどの計画があるらしい
  4. コロナ禍環境下においても入境が可能とされている(2020年11月27日現在)。無ければ入境できないというわけではない。

正直なところ、現状あまりメリットはありません。オンラインサービスで身份证代わりに使えるわけでもないですし、銀行や行政での手続にも引き続きパスポートが必要だという話も聞きます。2020年から鉄道もパスポートで乗れるようになっているので、会社に就労許可・居留許可の更新を代行してもらえる駐在員としてはさほど利点はない感じです。

ただコロナ禍環境下でも入境がしやすいはずという期待と、ICチップが入っているのでいずれ第二代身份证のシステムの環に入れるのではないかという期待、また会社に頼らず家庭環境を維持できるという生活の安定性向上の観点から申請に臨みました。

これから申請する人へのアドバイス

上海での行政手続というと、情報が錯綜しているとか担当者によって言うことが違うというイメージでしたが、今は既に上海公安出入境管理のサイトで申請に必要な書類や手続が明確化されているため、大きな混乱はありませんでした。いくつか情報を補足すると:

  1. 上海総領事館で発行する婚姻証明書単体では証明として不適とのことで、婚姻証明書の発行に使った戸籍謄本のコピー2部とその翻訳文を合わせて提出する必要がありました。窓口で翻訳すると手数料100元。
  2. 上海総領事館経由で発行する無犯罪証明は自分では開封できないのですが、翻訳文もまた必要なため、申請当日に窓口で開封してもらいその場で業者に翻訳を依頼する必要がありました。手数料150元。
  3. 手続きはサイトに記載のとおり、学林路の窓口で行う必要がありました。以前は民生路だったようですが移転しています。
  4. 手数料1,800元は、申請時ではなく居留証受け取り当日に現金で支払う必要がありました。
  5. 写真は白背景の印刷したものを2枚のみ、データでの提出はありませんでした。当日窓口でも別に撮影があります。
  6. 「有效签证(或居留证件)」の原本とコピー提出をもとめられますが、居留許可カードのことではなく、パスポートに貼られたビザをコピーする必要があります。
  7. カードに中文姓名を併記するか英文姓名のみにするか選択できました。
  8. 申請時にバーコード付きの書類を渡されます。受取など後続の手続きにはこの書類の持参が必要です。

申請のクリティカル・パスは無犯罪証明です。申請から届くまできっちり2ヶ月かかりました。また婚姻証明のために日本から戸籍謄本を取り寄せる必要もあります。この2つを早めに開始するべきでしょう。

健康診断は2週間前から申込可能、1週間強で受取可能。公証は申請してから1週間で受取可能。銀行での預金凍結は即日でできます。户口本の名称変更(英文に訂正する必要あり)も、配偶者の戸籍がある場所の公安で即日でできました。配偶者の協力が必須なのは住居に関する公証と、户口本の名称変更、そして申請当日に身分証やパスポートを預かるのみです。配偶者の勤め先の名前も控えておくと良いと思います。

参考

Gradle Plugin実装の基本

Gradleは動きが早いのでTIPSを文書化する意味があまりないのですが、全体像をざっくりつかめるだけでも変化を追うには有用のはず。 そのうち体裁まとめてZennに置いたほうがいい気がしてますが、一旦走り書きということで。

Gradleの考え方

設定と実行の分離

キャッシュを効かせるためにも、Configuration Cacheとかの文脈で、設定フェーズと実行フェーズは分けて考えることを推奨する動きがあるっぽい。 実行フェーズに設定を変えるなとか、afterEvaluate { ... } に過度に依存した設定を書くなとかを気にすればいい。

設定の遅延

設定より規約

能力と慣習の分離

登場人物

プラグイン実装に必要な登場人物は、だいたい3つほど頭に入れれば良いと思われる。

  1. Plugin
  2. Extension
  3. Task

他には依存を扱うConfigurationとかProjectにメソッドやプロパティを追加するためのConventionなどもある。ConventionはExtensionで代替すべきという情報もCommunity Forumにはあったので置いといて良いかも。

Plugin

プラグインを適用した際、真っ先にインスタンスが作成されるクラス。このPluginによってまず必要な初期化処理を行う。Gradleのバージョン確認もここ。 基本的にはファイル作成やタスク作成といった重い処理は行うべきではない。適用しただけで重くなるようなプラグインになってしまうので。

また"能力と慣習の分離"の観点から、機能を提供する部分をbase pluginとして慣習・規約を扱う部分から独立して定義することが望ましい。 BasePlugin, ReportingBasePluginなど標準で用意されたbase pluginもあるので、適宜 project.pluginManager.apply(BasePlugin) などとして利用できる。

Extension

Property-like fieldsを持つPOJO。 ExtensionもPlugin同様、インスタンス生成コストは抑える方が良い。これはプラグインの提供する機能(タスク)を使わない場合でもExtensionは作成されることが多いため。

Extensionではプロジェクト内で実行されるTaskすべてに共通の設定を行うことになるはず。

Task

主にDefaultTaskを拡張Property-like fieldsを持つ。入力と出力を宣言することで、キャッシュ可能にできる。

実行のコストは重くして良い。不要なTaskは生成されない・実行されないようにビルドスクリプトを書くことがユーザには推奨されている。 同じプロジェクトに属するTaskは同時実行されない=後続Taskをブロックするので、重い処理はWorker APIに逃がすことも検討する。

lazy config をどう実現するか

PropertyFileCollection に代表されるProperty-likeなクラスを活用する。Extensionのプロパティのconventionをデフォルト値に、TaskのプロパティのconventionをExtensionのプロパティにする。ちょっと古いプラグインだとconvention mappingというのを使っているがinternal APIなので無視で良い。

// ExtensionはPOJOにする。生成コストを低く保つ。
// 抽象クラスにすることでProperty-likeフィールドの生成処理を省略できる(ドキュメント未確認)
abstract class MyExtension {
  abstract Property<String> getFoo()
}

// TaskもProperty-likeフィールドを持つ。@Inputや@Output,@Internalなどの修飾子を使ってキャッシュ可能にする
// やはり抽象クラスにすることでProperty-likeフィールドの生成処理を省略できる
@CacheableTask
abstract class MyTask extends DefaultTask {
  @Input
  abstract Property<String> getFoo()

  @TaskAction
  void run() {
    print this.foo.map { "The value of foo property is ${it}" } .get()
  }
}

// base pluginはextensionの登録やGradleのバージョン確認など、機能実行の前準備のみ実行。規約(慣習)は扱わない。
class MyBasePlugin implements Plugin<Project> {
  @Override
  public void apply(Project project) {
    def extension = project.extensions.create("myExtension", MyExtension)
    project.tasks.withType(MyTask).configureEach {
      // 規約に関係ないデフォルト値はここで設定して良いはず
      it.foo.convention(extension.foo)
    }
  }
}

// こちらのpluginでは規約(慣習)を扱う。これはsourceSetごとにTaskを作る例
class MyPlugin implements Plugin<Project> {
  @Override
  public void apply(Project project) {
    project.pluginManager.apply(MyBasePlugin)

    project.plugins.withId('java-base') {
      def convention = project.convention.getPlugin(JavaPluginConvention)
      convention.sourceSets.all {
        def name = it.getTaskName("foo", null)
        project.tasks.register(name, MyTask) {
          // 必要な設定を行う
        }
      }
    }
  }
}

VerificationTask のようなインタフェースもあるので適宜実装すると、他プラグインとの整合性を取りやすい。

ドキュメントに目を通しても迷ったら