【読書メモ】Linuxで動かしながら学ぶTCP/IPネットワーク入門

定期的にやってくる「特に勉強したいこともないしネットワークのことでも勉強するか」ってシーズンが来た。ネットワークの考え方とか前よりは分かってきたが、実際のところどういう仕組みなのかピンと来ない。ってことで今回は書籍「Linuxで動かしながら学ぶTCP/IPネットワーク入門」を買って動かしながらやってみたメモ。

書籍については、この著者が書いたはてなブログの記事を参照のこと。

blog.amedama.jp

第一章 はじめに

この本では Network Namespace なる技術を使ってネットワークを構築してゆくらしい。普段から Ubuntu ユーザなので環境構築で特に困りそうなことはない。

第二章 TCP/IP とは

この章ではプロトコルの階層構造とか ping コマンドのような基本的な事項について書かれている。さすがにこのあたりはだいたい知っている。ping は裏で ICMP(Internet Control Message Protocol)プロトコルの echo request/reply が使われているらしい、へー。traceroute コマンドの動作原理についても書かれている。IP ヘッダのフィールドである TTL(Time to Live)がルータを通過するごとにデクリメントされる性質を利用しているらしい。なるほど。ルータはルーティングの情報をルーティングテーブルで管理する。宛先の IP アドレスに応じて via(ネクストホップのルータ)や dev(デバイス)が指定される。ふむふむ。

第三章 Network Namespace

この章では network namespace を使いながらルーティングを設定する。手を動かしながらやってみるとルータを介した ping が動くようになる。ネットワークインターフェイス(veth)ごとに名前や IP アドレスを設定する。network namespace ごとにルーティングテーブルを設定する。

今回の場合は、ルーティングテーブルを設定するときに「宛先がどんな IP アドレスのときにどこに転送する」ことを人間が教えてあげる。ルーティングテーブルは局所的なルールを定義するものだが、それを定義する人はネットワークの全体を把握しないといけない。インターネットを構成するルータではそれが難しいので、動的にルーティングを制御する仕組みとして BGP(Border Gateway Protocol)が存在する。なるほど。

第四章 イーサネット

この章はデータリンク層イーサネットについて書かれている。IP のパケットは複数のフレームを経由して目的の IP アドレスまで運ばれる。イーサネットではフレームを運ぶ先は MAC アドレスによって与えられる。IP アドレスから MAC アドレスは ARP(Address Resolution Protocol)プロトコルを用いて解決される。

前の章の IP のルーティングの話と総合して IP による通信の仕組みを整理してみる。まず、目的地点の IP アドレスが与えられる。ルーティングテーブルを参照することで、目的地点の IP アドレスから次に送信すべき IP アドレスを特定する。そして ARP によって、次に送信する IP アドレスから MAC アドレスを特定する。MAC アドレスが分かったのでイーサネットの仕組みでフレームを送信できる。このように、次の IP アドレスの特定と MAC アドレスの特定を繰り返すことで目的地までバケツリレー的にパケットが運ばれる。

後半は、ブリッジを使うことで複数のネットワーク機器を接続できることが書かれている。network namespace を使う場合はブリッジ用のネットワークスペースを作り、そこに type bridge のネットワークインターフェイスを追加する。それを mater とするリンクを追加してゆけばよい。

第五章 トランスポート層プロトコル

この章ではトランスポート層プロトコルとして UDPTCP を触ってみる。この章に至るまでネットワーク層データリンク層と下ってきたが、ここでトランスポート層まで上る。ネットワーク層までのプロトコルでは、どのパケットがどのアプリケーションのためのものか判別できないという課題があった。これに対してトランスポート層プロトコルでは、パケットにポートの情報を持たせることでどのアプリケーションに向けたものであるかを明示する。

UDP ではポートを指定してパケットを送信できる。これは nc コマンドを使って実験できる。nc コマンドは CTF(capture the flag)をやったときに使ったことがある。サーバ側のポート番号は上位のプロトコルによってだいたい固定で、クライアント側のポート番号はランダムに払い出されることが多い。

TCP では UDP とは違ってパケットの損失の検知や順序を保つことができる。よりリッチな機能を提供するといえる。通信の開始時にスリーウェイハンドシェイクを行う。TCP は信頼性が高いのでより上位の HTTP とか SMTP プロトコルでは前提となっている。

第六章 アプリケーション層のプロトコル

この章ではアプリケーション層の HTTP, DNS, DHCP について書かれている。これまでの章の内容を理解していれば、これらのプロトコルTCP/IP を活用したものであることがよく分かる。

第七章 NAT

この章は NAPT について書かれている。NAPT は、プライベート IP からグローバル IP とトランスポート層のポート番号のペアへ変換する技術である。もちろん一対一対応しているので逆方向に変換もできる。実際にはパケットの書き換えを行っている。NAPT って教科書とかに書いてあるので知識として知っていたが、パケットの書き換えをしているイメージはなかった。言われてみればそりゃそうだ。

前半はソースとなる IP アドレスを書き換える Source NAT について実験できる。今までとの違いは iptables にルールを追加する部分にある。具体的には、対象となる IP アドレスの範囲に対して MASQUERADE(マスカレード)を指定する。「マスカレード」って仮面舞踏会って意味なんだよな。なんとオシャレなネーミング。ちなみに iptables は network space ごとに存在している。

後半は Destination NAT について書かれている。いわゆる「ポートを解放する」ことである。サーバとしてリクエストを受け付けるときにグローバル IP アドレスが埋められたパケットがやってくるので、それをプライベート IP アドレスに変換する処理である。これは iptables で DNAT を指定することで実現される。

第八章 ソケットプログラミング

いよいよ最後の章である。といってもソケットを使ったプログラミングをする話なので、ソフトウェア屋さんである自分にとっては API 叩いてるだけに見える。ただし、ソケットを使ったプログラミングは TCP とか UDP のようなトランスポート層プロトコルに従った通信を実現するものであることは意識したい。HTTP とかのアプリケーション層の話はいったん忘れても問題ない。もし HTTP に代わる天才的なアプリケーション層のプロトコルを思いついたら、この章に書いてあるみたいな感じでソケットプログラミングをやってみよう。他方、TCP でも UDP でもないトランスポート層プロトコルを思いついたら、必ずしもソケットを活用できるわけではない。もっと低レイヤな部分から作ろう。

全体的な所感

一言で言うとめっちゃ良い本であった。分かりやすい。読者に TCP/IP のエッセンスを理解させたいという気合いが感じられた。これまでのネットワークの書籍って独りよがりなものが多いというか、事実を述べるだけになっているものが多い印象がある。ネットワーク屋さんは仕様や仕組みを深く理解してこそ力を発揮できるというのはよく理解できるので、そのあたりについて細かく書かれた書籍が重宝されるのは分かる。ただ、初学者がまず理解すべきなのはプロトコルの階層構造の意義とか、階層構造の概念が現実世界でどのように実装されているのかとか、各レイヤーでどういう情報が加わって何を達成できるようになるのかとかだと思う。この書籍はこういうエッセンシャルな部分を無駄なく教えてくれている。

積率母関数の覚え方

統計で出てくる積率母関数って覚えてもすぐに忘れますよね。印象づけて覚えるためにマクローリン展開との関連性をメモる。

覚えたいこと

確率変数 X積率母関数 M_X(t) は以下である。

M_X(t) = E [ e^{tX} ]

この関数を使うと以下のようにして n 次のモーメント E [ X^n ] を計算できる。

E [ X^n ] = \left.\frac{d^n M_X}{dt^n}\right|_{t=0}

印象付けて覚えよう

指数関数  e^xマクローリン展開 は以下である。

e^x = 1 + x + \frac{x^2}{2!}+ \frac{x^3}{3!} + \dots

xtX に置き換えると以下となる。

e^{tX} = 1 + tX + \frac{(tX)^2}{2!}+ \frac{(tX)^3}{3!} + \dots

期待値 E は線形性があるので以下が成り立つ。

E[e^{tX}] = E[1] + E[tX] + E[\frac{(tX)^2}{2!}] + E[\frac{(tX)^3}{3!}] + \dots

定数倍を期待値の外に出すと以下となる。

E[e^{tX}] = E[1] + t E[X] + \frac{t^2}{2!} E[X^2] + \frac{t^3}{3!} E[X^3] + \dots

これが積率母関数 M_X(t) である。

両辺を微分してゆくと以下のようになる。

M_X(t) = E[1] + t E[X] + \frac{t^2}{2!} E[X^2] + \frac{t^3}{3!} E[X^3] + \dots\\
\frac{dM_X}{dt} = E[X] + t E[X^2] + \frac{t^2}{2!} E[X^3] + \frac{t^3}{3!} E[X^4] + \dots\\
\frac{d^2 M_X}{dt^2} = E[X^2] + t E[X^3] + \frac{t^2}{2!} E[X^4] + \frac{t^3}{3!} E[X^5] + \dots\\
\frac{d^n M_X}{dt^n} = E[X^n]  + t E[X^{n+1}] + \dots

このように n 回微分して  t=0 を代入すると右辺は n 次のモーメント  E [ X^n ] だけが残る。

めでたし。

f:id:t-keita:20220224020807p:plain:w0

【読書メモ】Googleのソフトウェアエンジニアリング16, 18章

前回 に続き GoogleSWE 本を読んでメモる。

書籍は以下である。

www.oreilly.co.jp

今回は以下の2つの章を読む。

  • 16章 バージョンコントロールとブランチ管理
  • 18章 ビルドシステムとビルド哲学

16章 バージョンコントロールとブランチ管理

16.1 バージョンコントロールとは何か

内容: バージョンコントロールシステムはさまざまな理由から重要である。
所感: バージョン管理の重要さはよく分かっているので感想は特にない。むしろ無かったら即死する。

内容: バージョンコントロールシステム(Version Control System, VCS)には中央集権的 VCS と分散 VCS がある。中央集権的 VCS(Centralized VCS) は単一の中央リポジトリーがあるモデルである。Subversion がその代表例である。一方で、分散 VCS (Distributed VCS)は中央リポジトリーについての制約が強制されないモデルである。Git がその代表例である。ちなみに Google では分散 VCS を内製してものを使っている。
所感: Google では Git っぽいものを内製しているらしい。知らなかった。Google保有するくらいの巨大なコードベースを中央集権的に管理するとなると Git では力不足らしい。

内容: 中央集権的 VCS ではトランクにコミットされたものが信頼できる現在のバージョン、つまり Source of Truth である。一方で、分散 VCS の概念には Source of Truth は存在しない。しかし実際は、特定のリポジトリーの特定のブランチを Source of Truth として宣言することがややこしさを回避することために重要である。
所感: 分散的な環境において「ここを見れば最新」が明確であることの重要性について。普通の開発だったらプロジェクトごとにルールが決められるだけの話であって特に課題になることはなさそう。Google のコードベースは巨大で多様なので Source of Truth の明示が重要なんだろう。

16.2 ブランチ管理

内容: 進行中の作業(work in progress)はブランチに似ている。
所感: ここの主張がよく分からなかった。あえてブランチを作らなくても VCS がブランチ管理相当のことをやってくれるので、ブランチは特別(崇高)なものじゃないよって言ってる?

内容: 開発(dev)ブランチは特定の機能開発用のブランチであり、トランクの安定性向上を目的として作成される。しかし、dev ブランチは理にかなっていないので、トランクだけを使うトランクベース開発(trunk-based development)にすべき。安定性の問題は CI によってテストをたくさんすれば解決できる。dev ブランチを後でまとめてマージするより楽。
所感: ここは一般的なブランチの使い方を否定する内容になっている。確かに、ブランチを切るとマージ作業がややこしくなるイメージはある。なるほどなぁ。ちなみにリリースブランチはトランクから cherry-pick するだけのものなのであまり害はないらしい。

16.3 Googleでのバージョンコントロール

内容: Google ではほぼすべてのプロジェクトを単一のリポジトリで管理する "モノリポ" を採用している。リポジトリ内の依存関係において、選択すべきバージョンは1つしか存在してはならないという原則がある。選択の余地を作らないことがスケーリングには重要である。
所感: Source of Truth の話と同じことを言っていると理解した。しかし常に最新版へ依存しないといけないってけっこう辛くないか。バージョンを固定したくなりそう。たとえばライブラリの API の呼び出し方が変わったときは、それに合わせてクライアント側の呼び出し部分を変える必要があるってことよな。こういうのは例外的に dev ブランチでもよいってことなんだろうが、本当に例外的なんだろうか。

16.4 モノリポ

内容: Google ではモノリポを採用している。利点は、あらゆるプロジェクトの最新版が常に見えていることやリポジトリを探す手間が小さいことである。一方で、必ずしもそれがどの環境にとっても最適とはいえない。重要なことは選択の機会を無くすことである。最近は Git サブモジュールなど、モノリポを模倣するようなメカニズムがある。
所感: Git submodule って知らなかった。依存するリポジトリをプロジェクトのディレクトリ配下で管理できるらしい。

18章 ビルドシステムとビルド哲学

18.1 ビルドシステムの目的

内容: ビルドとはソースコードを実行可能なバイナリへ変換する行為である。速さと正しさが求められる。ビルドシステムはテストなど自動化の文脈でも有用である。
所感: ビルドシステムの大切さは理解できているつもりなのでまぁそうだよなぁという内容。

18.2 ビルドシステムがないと何が起こるか

内容: コンパイラだけだと依存関係の解決ができない。ビルド用にシェルスクリプトを書いてもスケールしない。
所感: ビルドスクリプトを叩くときは90%くらいコケる気持ちで臨むのでよく分かる。

18.3 現代的ビルドシステム

内容: シェルスクリプトや Ant、Maven などはタスクベースのビルドスクリプトと呼ばれ、任意のタスクを実行できるのが特徴である。それゆえ(安全側に倒す必要があるため)ビルドの並列化が難しい。また、変更したコードのバイナリへの影響を追跡できずインクリメンタルビルドを実現することも難しい。
所感: 個人的には Maven を使うことが多いが大して使いにくいと感じたことはなかった。もっと大規模なシステムをビルドしたり、他人が作ったビルドスクリプトをメンテナンスしたら不便さが分かるのかもしれん。

内容: 上記の課題に対してアーティファクトベースのビルドシステムが有効である。Google で使用している Blaze がその例である。エンジニアはビルドのために what を指定するだけで how は指定しない。エンジニアは限られた命令セットしか使えないが、それがビルドシステムの効率化に寄与している。
所感: アーティファクトベースのビルドシステムを関数型プログラミングに例えているのが分かりやすい。端的に言うと referential transparency(副作用がなく状態を持たない性質)が保たれる操作の組み合わせによってビルドを構成することで、並列計算も値の再利用もできるという感じ。dynamnic programming とかメモ化とかそういう計算。なるほど。ちなみにこっちの方式のデメリットってあるんだっけ?

内容: ビルドの時の依存関係を外部からダウンロードする場合はセキュリティ面での考慮を要する。Bazel では、ダウンロードした外部ファイルのハッシュ値を管理することでバージョンが勝手に変わるのを検知する。新たなバージョンを受け入れる場合はレビューで承認される必要がある。
所感: URL だけでは依存関係を一意に管理しきれないという話だな。なるほど。

内容: 以下の図はリモートキャッシュを用いた分散ビルドの方式である。中央にビルドの出力がキャッシュされている。各ユーザがビルドを実行したとき、すでに欲しいものがキャッシュに存在すればダウンロードしてくる。存在しなければ自らビルドする。

f:id:t-keita:20220127232047p:plain:w600

所感: 共有キャッシュが機能するのはアーティファクトベースのビルドシステムを採用しているからだと理解した。同じ素材からは常に同じ料理が作られるのがポイント。

内容: 続いて以下の図はリモート実行による分散ビルド方式である。中央のビルドマスターがビルドタスクを受け付け、実際のビルド処理はワーカーに投げる。もちろんキャッシュも利用する。

f:id:t-keita:20220127232753p:plain:w600

所感: こちらはアーティファクトベースのビルドシステムの並列可能性がポイント。一般的な master/slave 方式の分散処理だと思う。最近は master と slave って言わないほうがいんだっけ。

内容: そして Google の分散ビルドシステムは以下のような仕組みである。キャッシュをダウンロードしたり、新たなビルドタスクをリモートに投げれるようになっている。

f:id:t-keita:20220127233305p:plain:w600

所感: 結局のところ、ビルドの計算リソースや成果物を会社単位でシェアしているのでモノリポでも上手く運用できているんだな。このくらいの仕組みならオープンソースでも再現できるらしい。本当か。とはいえビルドができて分散処理ができてキャッシュを管理できたらいいのか。

18.4 モジュールと依存関係を扱う

内容: ビルドのターゲットの単位は好きに決めることができる。小さな単位でターゲットを定義していくことが推奨である。各ターゲットの可視性は適切に絞ろう。
所感: ビルドするにあたり考えたこともない課題感だが言ってることは理解できる。Dockerfile から Docker イメージをビルドするときに RUN を細かく分けるか、コマンドを && で連結して1つの RUN にするかみたいな話だな。再計算とストレージのトレードオフ

内容: Blaze では、推移的に依存しているライブラリを直接呼び出すのを禁止している。その分、依存関係の記述量が増えるデメリットがあるが、保守性の向上によるメリットの方が大きいことが経験的に分かっている。
所感: pandas を使いたいときに pandas を依存関係に追加しても numpy は自動的に追加されないみたいな話。Python だったらけっこう面倒くさそうだが、Java とかだったらあまり嫌なケースを思いつかない。

内容: 外部への依存関係の管理には単一バージョンルールが適用される。つまり、外部ライブラリのバージョンを全体として統一する。ダイヤモンド依存関係を回避するのが目的である。一方で、既存のバージョンとの整合性を保つためには推移的な依存関係は自動でダウンロードするわけにはいかない。すべての依存関係のバージョンを明示的に管理する必要がある。
所感: これはかなりスゴイ話。推移的な依存関係って膨大な数になるので、すべての整合性が取れる組み合わせが存在するのかすら疑問。Python の依存関係とかヤバそう。適当に pip を叩きまくるだけで環境が壊れるんだぞ。

全体的な所感

まず、16章「バージョンコントロールとブランチ管理」がすごく読みづらかった。翻訳の問題かと思って英語版を見てみたがそれも読みづらい。3人の著者がいるので章によって読みやすさが違うのか。

今回読んだ2章ともに運用が非効率化する原因を徹底的に潰し、それによって生じる課題はマシンパワーや自動化の仕組みで解決するって感じだな。Source of Truth が不明になることを徹底的に潰すため、モノリポかつトランクベースで開発をしている。これには膨大なコミットを捌く VCS が必要である。そしてビルドがカオスになるのを避けるためアーティファクトベースのビルドシステムを使い、おまけに分散ビルドを実現しているって感じ。

ちなみにモノリポにおけるマイクロサービスってどう開発されるのか気になった。自チームのサービスの API を更新する場合、通信相手のサービス内の呼び出し箇所も一緒に修正してあげるのかな。

必要条件と十分条件の覚え方

必要条件と十分条件ってどっちがどっち向きか分からなくなりますよね。

めっちゃシンプルな覚え方を高校生の時に編み出したので紹介します。

p は q であるための...

 p \rightarrow q

のように 矢印が成り立てば 十分 条件

 p \leftarrow q

のように 矢印が成り立てば 必要 条件

ところでみなさん、右利きですか?左利きですか?

はい、右利きですよね。生活するうえで、

手さえあればだいたいのことは 十分 ですね

ただし、たまには

手も 必要 ですね

おしまい。

matplotlib でフォントを埋め込む方法

matplotlib とかその派生の seaborn で作った図(PDF)を論文に貼りたいが、その図にフォントが埋め込まれないときの対処メモ。焦らず、まずは心を落ち着けよう。

症状

PDF ファイルのフォントの埋め込み状況を確認すると Type3 フォントが埋め込まれてない。フォント名としては DejaVuSans とか。ヤバイ、カメラレディの提出に間に合わない。

対処方法1

matplotlib で図を生成するとき、Type3 フォントではなくTrueType フォントを使うように指定する。

具体的には、図を描画する前に以下の命令を実行する。

matplotlib.rc('pdf', fonttype=42)

これでフォントが埋め込まれた。以下で確認できる。

$ pdffonts demo.pdf 
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
DejaVuSans                           CID TrueType      Identity-H       yes no  yes     24  0
DejaVuSans-Bold                      CID TrueType      Identity-H       yes no  yes     15  0

何をやっているか?

使用した matplotlib.rc 関数の API は以下にある。

matplotlib.org

PDF の設定についてはこのあたりに書かれている。

### PDF backend params
#pdf.compression:    6  # integer from 0 to 9
                        # 0 disables compression (good for debugging)
#pdf.fonttype:       3  # Output Type 3 (Type3) or Type 42 (TrueType)
#pdf.use14corefonts: False
#pdf.inheritcolor:   False

デフォルトが Type3 フォントで、42 にすると TrueType フォントが使われるということらしい。なぜか TrueType にするとフォントが埋め込まれる。ラッキー。

対処方法2

上記の方法はサブセット埋め込み(sub)になっていないのでファイルサイズが大きい。

そんなときは以下のように pgf バックエンドで描画するとよい(ただし実行には LaTeX 環境の構築が必要)。

plt.savefig("demo.pdf", backend="pgf")

フォント名が変わったけど必要な文字だけサブセット埋め込みになっているのでいい感じ。

$ pdffonts demo.pdf 
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
EJSLOC+DejaVuSans                    CID TrueType      Identity-H       yes yes yes      7  0

何をやっているか?

PDF の描画を pgf というバックエンドにより行っている。LaTeX の処理系を使っている。詳細は以下にある。

matplotlib.org

おしまい。

■ 参考

【読書メモ】Googleのソフトウェアエンジニアリング11, 12章

ちょっとずつ GoogleSWE 本を読んでいるので要約しながら所感をメモる。何においても本を読んだだけで実践できる人って最強だと思うのよね。本を読んだだけでいい気になっている人とは天と地の差がある。天側の人を目指そう。

今回読むのは以下の書籍である。

www.oreilly.co.jp

ちなみに英語版だと以下からオンラインで無料で読める。

abseil.io

今回の範囲は以下の2つ。やっぱ Google を支えるテストの話って気になる。

11章 テスト概観

11.1 何故テストを書くのか

内容: テストコードを書いてテストを自動化しよう。テストは定期的に頻繁に実行しよう。テストの誤りは迅速に修正しよう。テスト文化の醸成は生産性を向上させる。

所感: 自分も普段からテストを書くが、テストの利点として挙げられている「ドキュメンテーションの改善」は特に自分も感じるところである。テストは仕様の一部という意識を持つとよさそう。

11.2 テストスイートを設計する

内容: テストには規模(size)と範囲(scope)という2軸がある。

内容: テストスイートの重要な性質は速度と決定性(determinism)である。テスト規模(size)はこの2つの観点による分類であり、小・中・大の3つのテストからなる。

  • 小テストは、単一のプロセス内で完結し I/O 操作等を実行しないようなもの。テストの実行が早く非決定的な結果を生まないのが特徴。
  • 中テストは、複数のプロセスを使うもの。ただし localhost 以外のネットワーク呼び出しを許さない。非決定的な結果を生む可能性があるがリモートマシンへのアクセスがないので実行時間は膨大にはならない。
  • 大テストは、リモートマシンへの呼び出しができるもの。非決定的だし実行に時間もかかる。

たまに失敗するテスト(flake, flaky test)が増加するとテストへの信頼が喪失するので1%以下にすべき。テストは挙動を実行するために必要な情報のみを含むべき。テスト自体はシンプルに明確に保つべき。
所感: ここはテストの規模(size)についての話。テストの速度と決定性に着目するってのは自分では考えたこともなかった。

内容: テスト範囲(scope)は、テストによってどれだけの量のコードが検証されるかという観点である。小範囲のテストはユニットテスト、中範囲はインテグレーションテスト、大範囲はシステムテスト。前から順番に 80%, 15%, 5% の比を目指そう。

f:id:t-keita:20220112023237p:plain:w300

所感: この分類はわりと馴染みがある内容だが、理想的なバランスが数値として示されているのが Google の経験が詰まっている感じがする。

内容: コードカバレッジはあくまで動かしたコードの範囲であり、テストによって検証されたコードの範囲ではないので注意すべき。また、これらのテストメトリクスを高めることがゴールになってしまうのはダメ。 結局は、ユーザが期待する動作を総合的に考慮しテストスイートを評価することが重要。
所感: カバレッジを高めることがゴールになるのはソフトウェア開発あるある。真の品質を高めることが重要ってのはその通りだな。

12章 ユニットテスト

12.1 保守性の重要さ

内容: 保守性のあるテストとは "just work" する、つまり "普通に動く" ようなものである。具体的には、テストが失敗するまではケアする必要はないし、一方でテストが失敗したときは本当にバグを示すようなものである。脆いテスト(brittle tests)は、本当はバグを生んでいない修正に対して失敗するようになるテストである。テストの失敗が何を意味するのか不明なテストは望ましくない。
所感: テストっぽいものを作るのではく、テストとしてちゃんと役割を果たすものを作ろうという内容。当たり前の話だがチームで共有できていると強い。ちなみに日本語版で "just work" は「とにかく動作する」と訳されていたが、「動けばなんでもいい」という意味に取れるので訳が微妙だと思った。

12.2 脆いテストを防ぐ

内容: 脆いテストにはメンテナンスのコストがかかる。理想的なテストとは、システムの要件が変わらない限り変化しないようなテストである。リファクタリングなのにテストを変更するとか、新機能を追加するだけなのに既存のテストを変更するみたいな場合は怪しい。
所感: 脆いテストを避けるにはどうすればよいのか?そのプラクティスは次に続く。

内容: 脆いテストを避けるため、内部の構造に依存せず public APIs に対してテストを書くべき。テストでは、結果へ至る道筋(how)より、結果そのもの(what)を検証することが重要である。これによってリファクタリング等によってテストが失敗することを避けられる。この原則は Use the front door first principle と呼ばれる。
所感: private なメソッドをどうテストすべきか?というのはしばしば話題になる気がする。Google による見解としては、private なメソッドの粒度ではテストすべきではない、ということなのだろう。リファクタリングされることを前提にテストケースを作ることを意識したい。

12.3 明確なテストを書く

内容: 明確なテストとは存在目的と失敗理由が明確なものである。明確なテストには完全性(completeness)と簡潔性(conciseness)が重要である。

  • 完全性とは、結果に到達する過程を理解するのに十分に情報をテストが提供することである。たとえば、テストの結果の理解に必要な入力値は明記すべき。
  • 簡潔性とは、テストが無関係な情報を含まないことである。たとえば、テストの結果の理解に不要な入力値はヘルパーメソッドで隠蔽すべき。

所感: 意図が分かりやすいテストを書くためのポイントについて。テスト以外のコードを書くときにも有用なプラクティスだと感じた。

内容: テストはメソッドごとではなく挙動(behavior)ごとに書くべき。behavior-driven tests と呼ばれる。挙動は given, when, then の3つから構成できるので、テスト中にこの3つを明記するとよい。テスト名は冗長でもよいのでテストのアクションと結果を明記すべき。
所感: behavior-driven tests は Behavior-driven development とかの概念から派生したものだと思われる。個人的にはメソッドごとにテストしてしまいがちなので、もっと behavior を整理してテストケースを考えてみよう。

12.4 テストとコード共有:DRYではなくDAMP

内容: テストは説明的かつ意味が分かりやすいものであるべき。DAMP(Descriptive And Meaningful Phrases)が目指すべき姿。そのため、コードの繰り返しを減らす目的でコードを共通化すべきではない。ただし、テストしたい挙動と関係のない情報をヘルパーメソッドで隠蔽する形で共通する場合などはアリ。
所感: ヘルパーメソッドの使い方は上手いと思った。複数のテストで同じインスタンスを使い回すのではなく、関心のある部分だけ上書きするような構造にすることで意図が明確になる。

全体的な所感

Google のテスト思想やユニットテストのプラクティスがよく分かった。それほど難しいことはしていないように感じるが、メンバー全員が説明的なテストを作るってのは簡単ではないと思う。あと、カバレッジの話は少しあったが、ユニットテストを設計するときの観点とかは本書に書かれてないだけでノウハウがあるのかな。品質保証においてテスト観点は重要だし、上手く網羅的に設計できる人とそうでない人がいる印象がある。

正規分布の覚え方

正規分布ガウス分布)の確率密度関数の式って覚えづらいですよね。今回は自分的に頭に入りやすい覚え方をまとめる。

覚えたい式を以下に書いておく。これを一度見ただけで覚えられる人は天才。

 f(x) = \frac{1}{\sqrt{2 \pi} \sigma}\mathrm{exp}(- \frac{(x-\mu)^2}{2 \sigma^2})

正規分布の式の覚え方

気合いで覚えること

まずは ガウス積分 の結果を覚える。

\int_{-\infty}^{\infty} \mathrm{exp}(- x^2) dx = \sqrt{\pi}

ここで被積分関数 \mathrm{exp}(- x^2)確率密度関数っぽくしたい。全区間積分が1になるように正規化した関数を考える。ガウス積分の結果を考慮しつつ以下の確率密度関数  f(x) を作る。

 f(x) = \frac{1}{\sqrt{\pi}} \mathrm{exp}(- x^2)

この関数の形はこんな感じである。

f:id:t-keita:20220108013850p:plain:w300

そして、これが 平均 0, 分散  \frac{1}{2}正規分布 になっていることを覚える。

覚えることは以上である。なんならこの平均と分散は計算して導いてもよい。

平均と分散を調整する

ここから平均  \mu、分散  \sigma^2正規分布の式を導きたい。

まずは分散を調整する。具体的には関数  f(x) の分散を  \frac{1}{2} から  \sigma^2 にしたい。

標準偏差でいうと  \frac{1}{\sqrt{2}} から  \sigma にしたいので、関数を  x 軸方向に  \sqrt{2} \sigma 倍に拡大すればよい。これは  x \frac{x}{\sqrt{2} \sigma} で置換すればよい。

 f(x) = \frac{1}{\sqrt{\pi}} \mathrm{exp}(- (\frac{x}{\sqrt{2} \sigma})^2)

おっと、面積も \sqrt{2} \sigma 倍になってしまったので定数も修正しよう。

 f(x) = \frac{1}{\sqrt{2\pi} \sigma} \mathrm{exp}(- (\frac{x}{\sqrt{2} \sigma})^2)

最後に、平均が  0 から  \mu になるように調整する。

この関数は平均が  0 なので、平均を  \mu にするには  x 軸方向に  \mu だけ平行移動すればよい。これは  x x - \mu で置換すればよい。

 f(x) = \frac{1}{\sqrt{2 \pi} \sigma} \mathrm{exp}(- (\frac{x - \mu}{\sqrt{2} \sigma})^2)

これが  \mathcal{N}(\mu, \sigma^2)正規分布だ。

おしまい。