oinume journal

Scratchpad of what I learned

2023年に読みたい本

最近読書量が減ってきたので、今年は読書を頑張ろう!と思ったのでまずは読みたい本をリストアップしてみる。

プロを目指す人のためのTypeScript入門

仕事でTypeScript書いてるんだけど、型システムが難しすぎて詰まっているので、体系的に理解したい。

データ指向アプリケーションデザイン

分厚いけど良書だと噂の本。

モダン・ソフトウェアエンジニアリング

これも良書だと噂なので。

A Philosophy of Software Design, 2nd Edition

いい加減読み終わりたい...

詳解 システム・パフォーマンス 第2版

めちゃくちゃ評判がいい本なので。

LIFESPAN(ライフスパン): 老いなき世界

最近老いが気になってきたので...

AlfredからRaycastに移行した

最近はRaycastが巷で流行っているので、自分もAlfredから乗り換えてみた。Alfredはv5への移行をずっと保留にしていてタイミング的にも良かったというのが一番大きい。ちなみに自分はAlfredの有料課金のライトユーザーで、以下の機能を使っていた。

  1. 通常のランチャー機能
  2. HotKey - よく使うアプリをHotKeyとして登録
  3. Clipboard history - クリップボードの履歴
  4. ブラウザのブックマーク検索

ライトユーザーなので、とてもスムーズに移行できた。

また、Raycastの拡張機能でいいなと思ったのは

  1. Screenshotの検索とコピーが簡単にできる
  2. Emojiの検索が簡単にできる
  3. カレンダー連携すると直近のMeeting URLにRaycastから飛べる
    • なので、「あ、Meetingだ!」って思ったらRaycastを開いておけばいい、という体験が良い
  4. Quicklinkの機能で簡単に指定のURLに飛ぶリンクが作成できる

の2点。とりあえず色んな拡張がStoreにあるので、有用そうなものを日々探してインストールしている。まだ使い始めて3日ぐらいだけど、そろそろAlfredはアンインストールしようかなと思うぐらい馴染んでいる。

OpenTelemetryとhttptrace.ClientTraceを使ってHTTPリクエストのlatencyを可視化する

この記事は OpenCensusとhttptrace.ClientTraceを使ってHTTPリクエストのlatencyを可視化する - oinume journal のOpenTelemetry版。OpenTelemetryについては OpenTelemetryとは何か、そしてなぜそれが計装器の未来なのか? | New Relic を見てもらうのが手っ取り早くて、httptrace.ClientTraceについては先のブログを見てもらえればと。

OpenTelemetry Tracing + httptrace.ClientTrace

早速本題に入ってしまうと、以下のようなコードを書くことでhttp.ClientでHTTPリクエストを送った際のlatencyを可視化することが可能になる。

以下のように otelhttptrace.NewClientTraceで生成したClientTraceをctxにセットし、そのctxをhttp.Requestにセットするだけで良い。

clientTrace := otelhttptrace.NewClientTrace(ctx)
ctx = httptrace.WithClientTrace(ctx, clientTrace)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
...

Jaeger UIでの確認

https://github.com/oinume/go-http-client-trace-sample/ のリポジトリをcloneしてdocker-compose up -dするとJaegerが立ち上げることができる。

$ git clone https://github.com/oinume/go-http-client-trace-sample
$ docker-compose up -d
$ go run ./examples/otelhttp_client_trace/main.go

のように、Jaegerを立ち上げてサンプルコードを実行するとJaegerのexporterにトレースの情報が送られる。そのトレースの情報は http://localhost:16686/search にブラウザでアクセスすると見ることができる。以下のスクショを見てもらうとわかるように、http.getconnhttp.dnshttp.tlsなど、httptrace.ClientTraceのコールバックメソッドごとにSpanが作成され、それがどのぐらい時間がかかっているのかがわかるようになっている。

OpenCensusとOpenTelemetryの違い

httptrace.ClientTraceに関わるところで、OpenCensusとOpenTelemetryで微妙に挙動が違うところがあったので、メモしておく。

OpenCensusの場合はデフォルトではhttp.getconnhttp.dnsなどの情報がSpanにならないが、OpenTelemetryの場合はそれぞれSpanになっている。この挙動で注意した方が良いのは、例えばGCPのCloud Traceだとスパン単位で従量課金されるので、場合によっては金額が大きくなってしまうかもしれない。しかし、以下のようにWithoutSubSpansを指定することでSpanを生成しないようにすることができる。

clientTrace := otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans())

最後に

OpenTelemetryのTracingについては各言語のSDKも揃ってきて、クラウドベンダーの対応もされてきているので、そろそろ実戦投入していこうと思った。

Chapter 5 - Information Hiding (and Leakage) / A Philosophy of Software Design

第5章はInformation Hiding (and Leakage)というタイトル。いいモジュールを作るには情報の隠蔽(カプセル化)を行うことが大事だよ、という話。

5.1 Information hiding

  • Deep Moduleを作る上で最も大事なのは情報の隠蔽
  • モジュールを実装する上で必要なメカニズムをinterfaceとして表には出さないこと
    • ex)
      • B-treeにどういう情報を保存するか
      • ファイルの中身が物理的なディスクのブロックのどこに存在するかを認識する方法
      • TCPプロトコルの実装の詳細
      • マルチコアCPUでのスレッドの実装
      • JSONをどうやってパースしているか
  • 情報の隠蔽は2つの方法で複雑性を減らす
    • 情報の隠蔽はモジュールのインターフェースをシンプルにする
      • 具体例) B-tree classを開発者が使うときに、理想的なノードの広がりやツリー内部の均衡化については、利用者は気にしなくて良い
    • 情報の隠蔽はシステムの発展を容易にする
      • 情報がモジュール外には隠蔽されている=(モジュール外からみて)その情報への依存性がなくなっているので、その情報に関する内部的な設計の変更の影響はモジュール内部のみである
        • 例えば、TCPでネットワーク混雑時の制御についてプロトコルの仕様が新しく追加されても、TCPのデータの送受信のコードには影響がないはずである

5.2 Information leakage

  • 直訳すると情報の漏洩。情報の隠蔽の反対の概念
  • これはモジュール間の依存を作り出してしまう
  • 情報の漏洩はインターフェースだけで起こるものではない
    • 例えば特定のファイルフォーマットを扱う2つのクラスがあり、片方がファイルを読み込み、もう片方が書き込みをするような場合。ファイルのフォーマットが変更された時は両方のクラスを修正する必要がある。
  • 情報の漏洩もまたred flag(危険信号)
  • 情報の漏洩をどうやって防ぐか?
    • 先ほどの2つのクラスの例であれば、クラスが小さくて密接に関連しているのであればマージして1つにしてしまうのが良い
    • 情報をpull(取得)してカプセル化するクラスを新しく作る、という方法もある。
      • Another possible approach is to pull the information out of all of the affected classes and create a new class that encapsulates just that information. However, this approach will be effective only if you can find a simple interface that abstracts away from the details; if the new class exposes most of the knowledge through its interface, then it won’t provide much value (you’ve simply replaced back-door leakage with leakage through an interface).

5.3 Temporal decomposition

  • 時間による分解
  • Temporal decompositionの考え方で「ファイルを読み込んで、その内容を変更して、新しいバージョンを書き出す」というアプリケーションを考えると、3つのクラスに分解される。
    • ファイルを読むクラスと新しいバージョンを書き出すクラスはそれぞれファイルのフォーマットを知っている必要があるので、ここで情報の漏洩が発生する
    • 情報の漏洩を防ぐには、ファイルを読むクラスと書き込むクラスは同じものにする必要がある
  • 時間による処理の順番でクラスを分解するのではなく、そのクラスが行う処理が知識として何を持っているべきか(先程の例だとファイルのフォーマット)に着目してクラスを分解するべき

5.4  Example: HTTP server

  • 生徒がHTTPプロトコルの実装をした際のデザインの意思決定について、実際の例を交えて考察
  • HTTPプロトコル説明の図

5.5 Example: too many classes

  • 生徒がよくやってしまう間違いとしては、たくさんのShallow Classに分割してそのクラス間での情報漏洩をもたらしてしまうこと
    • 例) ネットワークコネクションからHTTPリクエストを読み込んでstringに変換するクラスを作り、別のクラスでそのstringをパースする
      • Temporal Decompositionのダメな例
      • 例えばContent-Length headerでbodyの長さが指定されているので、Bodyの長さを計算するにはまずContent-Lengthをパースしないといけない
        • stringになったリクエストをパースする時にBodyの長さがわからないとパースできない
        • コードの具体例がなかったのでちょっとここはよくわからなかった
      • 結果として、2つのクラスに分割してもそれぞれがHTTPリクエストの構造を知っていないとダメな実装になっている
      • さらに呼び出し側も2つのクラスの使い方を知っている必要が出てくるので、呼び出し側に余計な認知負荷がかかる
    • よって、上の例ではリクエストを読み込むクラスとパースするクラスは一つにマージした方が良い
      • HTTPリクエストをパースという処理を1クラスに集約することで情報の漏洩が発生しない
      • クラスの数を減らすことで利用者から見たインターフェースはシンプルになる

5.6 Example: HTTP parameter handling

  • HTTP requestがサーバーに来ると、サーバーはリクエストの情報を参照する必要がある
  • 例えば、以前のFigure 5.1で言うとphoto_idというリクエストパラメーター
    • パラメーターはURLのQuery Stringとして送られてくることもあれば、ボディで送られてくることもある
    • Query Stringの場合はURLEncodeされているが、処理する時にはこれはデコードされている必要がある
  • 著者の生徒の大半は以下の良い情報の隠蔽を行なっていた
    • パラメータはQuery StringまたはBody経由で送信されてくるが、それを利用する人にはどちらから来ているかは重要ではないのでマージしている
    • 2つ目の良いところは、パラメーターのデコードについて利用者が意識しなくて済むようにしていること
  • ただし、生徒が作るモジュールは以下の点でShallow Moduleだった
    • HTTPRequestというクラスを作ってその中に Map<String, String> getParams() というメソッドを定義していた
    public Map<String, String> getParams() {
      return this.params;
    }
  • Map<String, String> という内部のデータ型を利用者に公開するのではなく、String getParam(String name) という形にした方が、仮に内部のデータ構造を変えたいときに利用者に影響を与えずに済む
  • パフォーマンスチューニングのために内部のデータ構造を変更することもあるので、なるべく内部のデータ構造は利用者からは見えないようにした方が良い
  • あとはこのMapを利用者側で変更できないようにImmutableにする必要があるなどの問題もある

5.7  Example: defaults in HTTP responses

  • HTTP Responseに関する、生徒たちがよくやる不適切なデフォルト値について説明
  • HTTP ResponseのHTTPのバージョンに関して
    • (生徒の)あるチームはメソッドの呼び出し元にHTTPのバージョンを指定させるようにしていた
    • しかし、HTTPのバージョンはリクエストで指定されているものと一致しているべき
    • なので、リクエストのHTTPバージョンをそのままレスポンスのバージョンとすることが望ましい
    • 呼び出し元がHTTPのバージョンを指定すると、このHTTPのライブラリと呼び出し元の間で情報の漏洩が発生してしまう
  • HTTPレスポンスに必要なものとしてDateヘッダーもある
    • これもHTTPライブラリが気の利いたデフォルト値を提供すると良い
    • もし呼び出し元が何かしらの理由でDateヘッダーをオーバーライドしたい場合は、専用のメソッドを用意する
    • クラス or モジュールはできる限りいい感じのデフォルト値を用意する
    • P.26のJavaのIOバッファリングは、この点でよくない例となっている
      • IOライブラリのほとんどのユーザーはバッファリングするのでそれがデフォルトになっているべきだが、Javaの標準ライブラリではバッファリングのために別のクラスを使ってラップする必要がある
    FileInputStream fileStream = new FileInputStream(fileName);
    BufferedInputStream bufferedStream = new BuffferedInputStream(fileStream);
  • Red Flag: Overexposure(過度の露出)
    • よく使われるAPIが、たまにしか使われない機能もユーザーに覚えさせるようにしてしまうと、認知負荷を増大させてしまう(意味がよくわからなかった)

5.10 Conclusion

  • 情報の隠蔽とDeep Modulesは密接に関連している
  • モジュールがたくさんの情報を隠蔽することで、インタフェースをシンプルにしつつ多くの機能が提供される
  • システムをモジュールに分解するときに、実行時に行われる処理の順番を意識しないようにすること
    • 以前に説明したTemporal decompositionが発生し情報の漏洩やShallow modulesの問題を引き起こす
    • モジュールが知っているべき知識を考慮してそれを各モジュールがカプセル化することで、Deep modulesを作ることができる

2022年の抱負

もうすっかり年が明けてしまって1月末ですが、今年の目標というか抱負を書いておこうかなと思います。

健康第一

最近は風邪をひいたりすると仕事でもプライベートでも明らかに悪影響が出るので、健康第一で生きたい。その意味で以下を引き続き習慣として頑張る。

  • 筋トレ
  • 有酸素運動

有酸素運動はジョギングや早歩きの散歩だと膝が痛くなってしまい、色々と試した結果、マンションの階段昇降が一番コスパ良いかつ膝へのダメージが少ないということが昨年分かったので、1日20分 / 週4を目安に頑張る。

GraphQL+Next.jsでWebアプリを作れるようになる

昨年は趣味プロダクトのReact Hooks + React Query化が終わったので、今年はGraphQL+Next.jsを導入しつつ学びたい。具体的には以下を使っていきたい。

  • Next.js
  • GraphQL
    • gqlgen
    • apollo-client

英語

  • iKnowを週5
  • Camblyが良さそうなのでトライ
  • TOEICを年に2回受ける

をやる。DMM英会話はやめてしまったけど、値段的にはCamblyが良さそうなので試してみる。

アウトプット

1ヶ月に1つはちゃんとした記事を書く。昨年に引き続き継続は力なり、ということで。

というわけで

2022年もやっていきだ!

f:id:oinume:20220130100849p:plain