日々常々

ふつうのプログラマがあたりまえにしたいこと。

コードを人に寄せたい(という題の書きかけ)

ブログの下書きを眺めてたら書きかけで飽きたっぽいのがみつかったので供養しておきます。


もう二年前になりますが、「コードをどまんなかに(該当スライド)」でこんな風に人とコンピュータとコードの関係を話しました。(なんかこれのブログ書いてないな……)

ここから少し掘り下げて、コードをソースコードバイトコードに、コンピュータをコンパイラとランタイムに分わけてみます。 「わけるとわかる」とか言いますが、わけるとこんな図が描けます。

ソースコードは人とコンパイラに関係します。 バイトコードコンパイラとランタイムに関係します。 関係に依存方向を入れてみたのがこの図です。

ちなみに言語仕様とかは意図的に省いています。主眼じゃないものを取り除いて強調したいものを強調するのがモデリングだと思ってる。

irof.hateblo.jp

言語仕様入れるとこんな感じ?言語仕様とコンパイラは相互依存になるかもだけど。どちらにしても今回の話には関係ないです。

元の図に戻って、ソースコードから人に依存させているのは、人の読み書きのしやすさに依存して欲しいから。(読み書きのしやすさと文脈だとIDEとかが間に入ったりするんだけど、それも別の話。) ソースコードコンパイラには依存せず、コンパイラソースコードをなんとかしてバイトコードにする感じです。

せっかくこんな構造になってるんだから、ソースコードは全力で人に依存していいはずなんです。 にもかかわらず、ソースコードにはコンパイラ都合が入り過ぎていたり、なんならランタイムの都合も入っていたりします。 それらが入ってしまうと可読性や記述性が損なわれるのは当然。人の方向を向いていないわけですから。

で、一つの解にDSLがあったりするわけで、外部DSLだと言語仕様への依存を外せる。


2024-05-13T19:17追記

ここから話広がりそうなんだけど、なんか終わってんだよねぇ。ブログのタイムスタンプによると最終更新が 2020-06-26 23:25:56 らしい。

なるほど。捨てたつもりが捨てきれてなかっただけっぽい。下書きに残してるなんて未練がましいので晒してやる。

いつか気が向いたら書き直すよ。いつか。

2021年のJavaとnullの話

ブログの下書きを眺めてたら長文がみつかったので供養しておきます。 TODOがDOされるかはわかりません。心の目で見てください。


Javaには null があって、この子がいない世界がいいんですけど、いなくなったりはしてくれないので、仕方がありません。いい感じに付き合っていきましょうねーって話を書きます。関連エントリとかスライドはこの辺。

以降は長文ですが、この手のは実務でいくつかの条件を満たした上での話になります。 なるべく書いたつもりですが、無意識に前提としているものはスコーンと抜けているでしょうし、まるっきり同じ条件でも「こちらの方がいい」という選択肢もあっていいと思います。その辺の話は実務でやりたいなーって思ってます。

本稿は「Javaの話」として書いてます。「他の言語にすればいい」とかは異なる話です。

私の基本的な考え

NullPointerException(以降NPE)は起こるべきところで起こって良い。ダサかろうと予期しない null が混入した際に発生する適切な例外はNPEであり、他の何かで取り繕う必要はない。

予期しない nullnull チェックで誤魔化さない。null チェックして null を返すようなものは延命措置にしかならないし、そんな延命してる間にどんどん傷は深くなる。フェイルファスト。

そのような null は混入しうる境界で検疫し、丁寧に取り除く。実装は冗長で良い。下手に格好良く null を回避できると、一律それを行うような思考停止を招きかねない。 null は必要なところでだけで、泥臭くダサく目立つ形で取り扱う。

もう少し詳しい話

(なんかどっかで書いたと思うんだけどなぁ、と思いながら)

null が混入するタイミングはそれなりに限定的であり、予測可能です。

  • 自身で null を明示的に使用した場合。変数へ代入したり、メソッドの引数や戻り値にすることで伝染する。比較での使用は問題ない。
  • 参照型フィールドのデフォルト値。
  • フレームワークに設定される引数。
  • ライブラリのメソッドの戻り値。

このうち前者2つは自身の実装の話です。フィールドのデフォルト値は完全コンストラクタに拘れば、自身が混入させるのは null を明示的に記述した場合に限定されます。コンストラクタ引数に null が勝手に突っ込まれるのは、後のフレームワークやライブラリの話。

脱線: ちなみに JIGバイトコードから null を見つけて警告しています。自身のコードによる混入チェッカー。作者の null 嫌いがわかりますね(ただの自己紹介)。

自身で制御しづらいのは後者の2つですが、フレームワークやライブラリの実装都合との接点で null は片付けてしまうように設計します。 最近のフレームワークやライブラリは null の代わりに Optional を選べるものも増えてきましたが、これは別に解ではありません。てか Optional であっても考慮しなきゃいけないことには代わりないので、可能であれば null もemptyな Optional も取り除き、予期しない値であればここで素直に例外を発生させます。予期できる null であれば、適切な取り扱いをこの境界で決定し、コアである処理には持ち込まないようにします。

<<<TODO 例示>>>

null は言語都合や実装都合で混入するものなので、コアなところに null は入ってきて欲しくないわけです。Javaという言語上どこにでも入りうる null をそのままにしておくと、本当にやりたいことが null の考慮に圧迫されます。考えなければならないことを減らすが設計の基本だと思っているので、安全な領域を定めて境界で弾く。弾き損ねたものは素直にNPEのフィードバックを受け取る。そんな感じです。

nullセーフな演算子

たとえばGroovyには ?. があります。 null だった場合も気にせずにメソッドが呼び出すかのように記述でき、 nullの場合は呼び出されずにnullが返る便利さんですね。他の言語でも似たような機能は結構見ると思います。局所的にはシンプルなコードが記述できます。

これは少なくとも私がJavaで業務処理を書く時には存在していて欲しくないものです。 道具が悪いのではなく使い方の問題ではあるんですが、「hoge.method() と書けるものであっても hoge?.method() と書いておけば安全」のような思考停止を招くからです。一律やるのは避けたとしても、「NPEが発生したら .?. に書き換える」ようなことが起こります。私は意志が弱いので、その引力に負けない自信はありません。そしてきっと後悔する。

これは「強力な機能が設計の歪みを覆い隠す」パターンだと思っています。 null が混入しなくなった場合に除去するフォースも働きません。そしてそのコードがある限り、 null は混入しうるのか?と疑心暗鬼になります。1箇所あるんだから、他でも入ってきたりするんじゃ……と。そんなノイズを入れながらコードを読み書きしたくないんだ。

でももし言語機能として持ってたら、特定の文脈(ライブラリとかフレームワーク作ってる時とか)では喜んで使うと思う。便利は便利だからねぇ。

JEP 358: Helpful NullPointerExceptions

2021年風味を混ぜておきます。 Java14で入ったJEP 358は、今年9月リリース予定であり、しばらくのスタンダードになると思われるJava17で使用できます。 これは何かというと、メソッドをチェーンしている場合などの途中でNPEが発生した場合、どれが null かを教えてくれる機能です。 以下は 16.0.0-librca での実行例です。

jshell> class A { Object obj;}
|  次を作成しました: クラス A

jshell> var a = new A()
a ==> A@5f184fc6

jshell> a.obj.toString()
|  例外java.lang.NullPointerException: Cannot invoke "Object.toString()" because "REPL.$JShell$12.a.obj" is null
|        at (#3:1)

jshell>

Java13以前で a.obj.toString() のようなコードでNPEが発生した場合、スタックトレースには行番号しか表示されず、anull なのか objnull なのかわかりませんでした。そのためNPEの解析では一時変数で受けてみたり、aa.obj それぞれに対して null チェックを行ってみるなどの足掻きが必要でした。デバッガなどを使用しない場合、コードを変えないと解析が困難なこともあり(少なくともスタックトレースから瞬時に判断はつかない)、NPEが嫌煙される一因だったかと思います。

Java14以降ではObjects#requireNonNull(Object) メソッドや自身で null チェックしての例外送出より、そのままNPEを起こした方が解析しやすいまであります。予期しないNPEには備えるんじゃなく、予期しないnullを教えてくれるNPEを素直に受け止めるのがいいと思います。

nullを許容する場合

ライブラリが要求するAPIになっている場合と、閉じたスコープで使用する特殊値です。

クラスに閉じたフィールドで、最も軽量に扱える特殊値はおそらく null であり、それ以外の値を使用するのはコストが上回ります。 ただその null が外部に流出しないように設計/制御する必要はありますが、制御可能な範囲において null 以外を頑張って使用するよりかは扱いやすいです。これは単に null が言語に組み込まれた特殊値であるが故で、使えるから使うと言うスタンスです。決して「 null が最適解」ではないので、誤解されぬよう。。。

まとめ

NPEがダサいって気持ちはわからなくはないですが、NPE以外の予期しない実行時例外もどうせダサいです。NPEじゃなくす努力なんて要らないんじゃないかなって。

あー null 無くならないかなー。

おまけ(2024-05-12T0:03)

投稿したらサムネに出た画像をおまけでつけておきます。

たぶん <<<TODO 例示>>> でこうやって棲み分けるんだーみたいなことを書こうとしてたんだと思います。 ファイル名が 20210421013620.png とかから察するに、あれがアレで。もう覚えてない。 Miroで書いてるっぽいからどこかのボードに残骸はあるんだろけど……

わからないことの調べかたを考えてみる(2018年版)

ブログの下書きを眺めてたら長文がみつかったので供養しておきます。


開発では、何かを調べることに多くの時間が費やされます。私の調べ物のやりかたや、他の人のを見て思っていることを書いてみます。

前提条件

  • 調べ物が苦手
  • 何か効率が悪い気がする
  • 調べ物に時間がかかる
  • 間違った情報に振り回されている
  • 情報が間違っているのかやり方が間違っているのかわからない

と言った方には役に立つかもしれません。 自分で調べ物ができるかた、そのやり方に疑問のない方には何の足しにもなりません。 あと私のやり方と、私から見える人の私からの見え方でしかないので、他のコンテキストは知りません。

また、ここでの「調べる」は初めて使用するツールの使い方など、未知領域の課題解決を行うためにインターネット等を検索することを指します。 実際は検索する前に意識/無意識にかかわらず課題の切り分けを行い、問題領域によって手段を変えているはずです。

調べ物のレベル

レベル感をふわっと書いてみます。

  1. 何を調べたらいいかわからない
    • 何がわからないかわからない、どう言うキーワードで調べればいいかわからない。思ったものが全く出てこず、解決しない。稀に何とかなったりするけれど、ほんと稀。
  2. 検索上位にでてきたものをそのまま試す
    • 短時間でたどり着くこともあるが、打率は低い。動かないときは「調べた通りにやったけれど、なぜか動かなかった。」でゲームセット。
  3. 検索で出てきたものを手当たり次第に試す
    • うまくいかないときは永久にたどり着かないが、当人は「いつかたどり着く」と思っていたりする。そのため「時間が足りない」と言うが、どれくらい時間があればたどり着くかの予想もできない。
  4. 検索結果をさらに自分の条件に照らし合わせてフィルタリングして試す
    • それなりの精度と質で解決できたりする。しかしなぜそれでいいかの説明ができることは稀。「出典はWikipedia」程度の信頼性。
  5. 公式ドキュメントの手順に従う
    • ドキュメントのGettingStartedなど、手順的なドキュメントがなければお手上げだったりする。
  6. 公式ドキュメントから設定を理解した上で調整する
    • だいたいの問題はクリアできる。逃れられない問題として、ドキュメントの質に依存する。記載されている箇所が見つけにくかったり、そもそもドキュメントが使い物にならないプロダクトも稀によくある。
  7. ソースコードを当たって解決する
    • (コードが使える場合)解決できる問題なら解決できないほうがおかしくなる。しかしドキュメントされていないトリッキーな解決をしてしまう可能性もある。機能を損傷したり、片手落ちな設定をしてしまったりすることもある。
  8. コンセプトを踏まえて解決する
    • 「こういうコンセプトで作られているから」から導かれる解決。「そう言うことに使うものではない」と捨てる判断も早い。ドキュメントはもちろん、経験や歴史を把握していると可能になる。かもしれない。
  9. サポートに問い合わせる
    • 金の弾丸。サポートの人に教えることになることになったり、言う通りにしたら環境が壊れたとかも稀によくあるので、万能ではない。

だいたい前段階はクリアしているので、その手段でなくても前段で解決できたりします。分水嶺は5、公式ドキュメントを読めるか。これが冒頭のツイートのラインです。4までくればそれなりの精度と質で解決できてしまうので、その辺りで満足してしまっているのも多そうな感じがしています。

Qiitaが一部の人に嫌われる理由

特定サービスを出して申し訳ない。別にQiitaに限ったことではありませんが、検索時に

調べ物をしている人を見ていると、高確率でQiitaを参照しています。検索上位にくるのはサービスの努力も大きいでしょうが、同様の問題で困った方が書いていたりするので、近い語彙なのでヒットしやすいと言うのもあるのかなと思います。

Qiitaの記事が玉石混交であると言う事実はありますが、それは他のQAサービスやブログ、情報サイトでも変わりません。 なので問題点はそこではなく レイアウトが統一されていることによる区別のつきづらさ に由来すると思います。 これは「ソースはWikipedia」と言うのが揶揄の対象となるのと近いのですが、Qiitaも同様に「Qiitaに載ってた」と言うような場合に問題になります。 つまり、記事の質は個人に由来するサービスの特性に対して、情報を判定する精度が個人に由来していないのです。

私も調べ物でQiitaを参照することは多いので、非常に助けられています。なので「Qiitaを見てはいけない」とまで言うつもりはないです。 ちなみに、私がQiitaを参照するときは「誰が書いているか」を気にしていることが多いです。

Qiitaのコンセプトを確認してみる

qiita.com

公式ドキュメントを見るべき理由

解決したときの根拠の説明ができるかが重要です。もし解決しても、なぜそれでいいのかを説明できないのであれば、それは「たまたま」でしかありません。対面していた問題は「たまたま」で解決しちゃっていいものでしょうか。再発した場合に何か対処法はあるでしょうか。他のところに影響は与えないでしょうか。などなどあります。公式ドキュメントに記載のある方法ならば、注意すべきことがあるならば記載されていることも多いです。

また、解決できない場合もドキュメントに記載がないのであれば、ある程度「仕方ない」と思えます。そう言うものを使っていると言う事実を受け止めましょう。「ドキュメントが使い物にならないもの」とわかって使うならば、アプローチの仕方も変わってきます。その判断のためにもドキュメントを参照するべきなのです。

もし、ドキュメントに記載されている通りにやってうまくいかないのであれば、バグの可能性もあります。課題管理システムにアクセスできるなら、課題登録されていないかを検索してみましょう。次バージョンのリリースノートに載っているかもしれません。ともかく、記載の通りにやっているのに動かないのであればサポート問い合わせを検討するのがいいでしょう。OSSなら「Use the Soruce, Luke」もありです。

もう一つの理由は、互換性です。ドキュメントに記載されているものは互換性が保たれる可能性が高いです。ソフトウェアはバージョンアップするものなので、互換性は重要です。ドキュメントされているものは、バージョンアップの方法もドキュメントされることが期待できます。ソースコードから得たトリッキーな解、たとえば内部APIを触ったものなどに互換性は期待できません。更新コストを抑えるためにも、提供元が予想できる使い方をすることは重要なのです。

常に公式ドキュメントを読むか

そうとも限りません。ブログやQiita、StackOverflowなどが検索上位に現れることも多くあります。これを除外するフィルタリングを常に検索エンジンで行うのは骨でしょうし、私はやったことはありません。

公式ドキュメントはピンポイントで私の問題に答えてくれるものではないため、(その瞬間に限って言えば)多くのノイズを含みます。常に公式ドキュメントだけを参照するのは時間がかかります。無限に時間があるならそれでもいいかもしれませんが、私の時間は無限ではないのです。そのため解決したい問題の性質によっては、公式ドキュメントを当たらないこともあります。

前段で「たまたま」と言いましたが、たまたまも悪いものではありません。公式ドキュメントではないにせよ同等の信頼性があると判断できる場合だとか、明確な検証方法がある場合だとか、まあ色々言えますが、ともかく。たまたまで解決できていい問題は、たまたまで解決してしまっても良いのです。ドツボにはまりそうになったら、落ち着いて公式ドキュメントを読めばよいだけです。

私の調べ方

  1. アクセスしやすい公式ドキュメントを参照する
    • Javadocコメントなどに記載されているものであれば、高精度な情報に最速でアクセスできる。検索する必要もない。
  2. 検索結果に一通り目を通す
    • タイトルだけでも。だいたいGoogle検索結果の1ページだけで、たまに2ページ目もみる。
  3. 信頼性の高い情報を試す
    • (後述)
  4. 信頼性の高い情報で得たキーワードで公式ドキュメントを検索する
    • 最初から全部舐めるのは厳しいので。

記事の信頼性判断

検索結果は玉石混交。とは言え砂漠の中で砂金


ここで終わってました。 何書こうとしてたんですかね、2018年の私……

あ、今だと「AIに投げかけてみる」ってのも出てきますね。AIの使い方の話になってきて、これはこれで一大トピックだし、それを書き始めるといつまでもこの下書きが成仏できないのでここまでにしときます。