hirapi's blog

ちゃんとしたふりをする

「プログラマが知るべき97のこと」を読んだ① 1〜30

読みながら思ったこととかぐっときたとことかをメモ。

xn--97-273ae6a4irb6e2hsoiozc2g4b8082p.com

分別のある行動

  • 技術的負債のために払っているコストを常にトラッキングすること
    • ここでいう「技術的負債」は、「正しく作る」と「手早く作る」が対立したときに後者を取ったために発生した「正しくない作り」のこと
    • 💬 たしかに額がわからないと負債の重要性もわからない

関数型プログラミングを学ぶことの重要性

  • 参照透過性:同じ入力に対して常に同じ出力を返す。状態に左右されない
    • 反対にオブジェクト指向プログラミングでは、オブジェクトの持つ変数の値が状態によって変わっていく
      • オブジェクトではなくロール( 💬 インタフェースと同じ意味合い?)をモックするようなテストを書いて開発すれば、状態依存は避けられるかもしれない
  • 💬 関数型プログラミング、ちょっとやってみるとしたらScalaかElixirあたりになるのかな

ユーザが何をするかを観察する(あなたはユーザではない)

  • ユーザはたとえ非効率でも一度確立したやり方を変えない
    • 💬 そうなんだよなーーー
  • 💬 その点、ギーク向けのツールとそうでないツールで見た目とかカスタマイズ性とかがだいぶ違う気がしているので、他サービスを参考にするときはそれのターゲットが自分のサービスと重なっているか注意する必要がありそう
    • (まあ他を参考にするとかの前にユーザを観察しろという話である)

コーディング規約を自動化する

  • コードの整形処理をビルドプロセスに含めてしまう
  • コードの静的解析でNGが見つかったり、テストカバレッジが低すぎたりしたらビルドプロセスを止める
    • 💬 rubocopとCodecovは前職のCIに入ってた

美はシンプルさに宿る

  • 💬 rubocopでmethod lengthとかABC sizeとかでひっかかるということはシンプルでないということなんだよな
    • そういうときは抽象化がうまくいってないのかもしれない、デザインパターンの力が借りられるかも?

リファクタリングの際に注意すべきこと

  • 既存のコードベースやテストの良い点・悪い点を洗い出す
  • 一度にゼロから書き直すのではなく、インクリメンタルに変更する
  • 修正する理由を明確にする
    • 💬 1つ目のエッセイのとおり負債の「額」を明確にしておくとやりやすそう
  • 書き換えると必ずコードが良くなるというわけではないことを肝に銘じる

共有は慎重に

  • 共有:処理の共通化
  • 共通と言っていい(共通化していい)コンテキストなのかを事前に確認する
    • 異なるコンテキストで、その時たまたま見た目上の処理が似ていただけかもしれない

ボーイスカウト・ルール

  • 来たときよりも美しく🚿

他人よりまず自分を疑う

  • 💬 ウッ頭が
  • 💬 原因の候補をまず洗い出して、自分絡みのものからつぶしていくとか?

ツールの選択は慎重に

  • 💬 設定ファイルの保守が煩雑になるのはたしかに大変そう
  • 💬 クラウド時代はクラウドベンダーが推すベストプラクティス通りにやればよさそう

ドメインの言葉を使ったコード

  • ドメインに沿ったクラスを作り、それが何のために何をするかわかるメソッドを作る
  • 💬 そうか、クラスは「ユーザー定義型」なのか。納得

コードは設計である

作る人間の能力を超えるほどの高度な製品、複雑な設計が求められていて、しかも製品を早く市場に出せという圧力が強いような状況

  • 💬 設計に力を割くべしということかな

コードレイアウトの重要性

  • 💬「目立たない部分」はメソッドに分割してあげたほうがいいんじゃないかという気もする

コードレビュー

  • レビューの瓦解を防ぐ
    • レビュアーをランダムで割り当てる
    • 複数のレビュアーを割り当て、見る部分の担当を分ける
      • ドキュメント担当、例外担当、機能担当
      • 💬 えーなるほどすごい、やってみたい

コードの論理的検証

  • メソッドひとつずつについてみんなで話し合って検証する
  • 💬 時間かかりそう、と思ったけど後世に負債を残すより安いんだろうな

コメントについてのコメント

コードに書けないことのみをコメントにする

  • 💬 保守されていない嘘コメント、よく見かけた 😢
    • ノイズのように見える(なぜあるのかわからない)コメント、読んでもよくわからないので書き換えてよいものか迷わしくて保守されにくいイメージ

学び続ける姿勢

  • 自分で学び続ける方法を身につける
  • 💬 「達人プログラマ」読むべきなのか……
  • 💬 「1週間に1回1時間でも、やらないより良い」そうなんだよな
    • 今なら働きながら勉強できる気がする

誰にとっての「利便性」か

  • コードを書く側ではなくAPIを使う側にとっての利便性を考える
    • 似たようなコードが重なる、みたいなのは書く側の事情
    • 「冗長性」の反対は「利便性」ではない
  • 💬 たしかに

    walk(true)というようなコードを書かされるよりは、単にrunと書ける方が間違いなく使いやすい

    • 分岐に使うような引数が多いと少し疑ったほうがいいのかも

すばやくデプロイ、こまめにデプロイ

  • 開発初期からデプロイフローを作っておく
  • 💬 耳が痛い

    「開発者のコンピュータで一応動作する」という状態から、「デモが可能である」という状態にするまでには、相当な作業が必要になるのです。

技術的例外とビジネス例外を明確に区別する

  • 技術的例外 → トップレベルまで例外を渡す
    • 💬 予測不可能
  • ビジネス例外 → クライアントだけで処理する
    • 💬 予測可能
    • あらかじめ対応を組み込んでおく

1万時間の訓練

ドメイン特化言語

変更を恐れない

  • 💬 冷静に思い返したら昔やってた開発けっこう変更怖かった
    • 1つのクラスに大人数で手を入れてたら怪しいと思え、というやつか
    • こういうのを「怪しいという説もある」だけじゃなくて本気で「怪しい」と思わねばならんのだな
  • 💬 やるぞの気持ち……

    「病気の」システムを治すことがプロジェクトのメンバーにとって経験になるということです。その経験によって、システムは本来どうあるべきかを全員が理解するようになり、システムについてのエキスパートになる

見られて恥ずかしいデータは使わないこと

  • テストデータ・サンプルデータとしても駄目
  • 💬 語呂が良いw

    TERRIBlE HORRIBLE NO GOOD VERY BAD HACK

言語だけでなく文化も学ぶ

  • 💬 つかってる言語はAなのに書き方がどことなく別の言語Bっぽい、というのは自分でも経験ある気がする
    • PHPから入ったあとGoをちょっと書いたときになんかPHPっぽいなと思った
    • OSSとかを写経したり読んだりしてその言語の特性を活かした設計を学ぶべし

死ぬはずのプログラムを無理に生かしておいてはいけない

  • 💬 ウッ頭が
  • 💬 クラッシュしたときにユーザーに次に取ってほしい行動を明示できるのが良いエラー画面だと思っているけど、それとこのコラムは矛盾しない

「魔法」に頼りすぎてはいけない

  • つまり魔法は無い
  • 💬 本当に不要かどうか、不可逆的に外す前に1週間くらい落としてみるといいのかも

DRY原則

「こういう問題が起きそうだから原則をあらかじめ破っておこう」ということをしてはいけない

  • 💬 「DRYやりすぎ問題」もよく聞くけど送り仮名みたいなものなのかもしれない
    • 活用する部分を送って、そうでない語幹部分を漢字で読む
    • 漢字部分がDRY
    • 活用されうる部分まで無理やり漢字に含めると変更できなくなる

そのコードに触れてはならない!

  • 💬 すごい細かく担当がわかれててびっくりした。リリースマネージャって誰……?
    • しかしステージングにあがったものに直接手を加えるのは言語道断であることには同意

GoFのデザインパターンをなんとなく納得する

いつか使いこなす日を夢見て、簡単にぐぐってふーんと思ったことをメモした。
ふと思い出したらラッキーという程度。

参考

techracho.bpsinc.jp

ja.wikipedia.org

www.techscore.com

作成系

  • 🔍「オブジェクトを作成する」という行為をクラスとして抽出している
  • 🙈 ありがち
    • クライアントがなんでも作る = クライアントがオブジェクトに詳しい
    • 別々のクライアントで似たような生成処理がコピペされている
  • 🤔 複雑なインスタンス生成がそのへんに転がってたら見直すべきかも

Simple Factoryパターン

Factory Methodパターン

  • Template Methodパターンと一緒に使われるらしい
    • 手続きを抽象クラスで定義して、具象クラスで各メソッドを実装するというやつ
  • インタフェースAの実装クラス(複数あり、どれを使うかをクライアント側は知らない)のインスタンス化を、インタフェースBの実装クラスのFactory Methodが行う
    • インタフェースAの実装クラスのうちどのクラスのインスタンスを作るべきか、インタフェースBの実装クラス達だけが知っている
  • Factory MethodパターンはFactory Methodを持つ抽象クラスと実装クラスの関係
  • Template Methodの中でインタフェースAの実装クラスをインスタンス化する必要があるとき、インタフェースBの実装クラスのFactory Methodに任せる

Abstract Factoryパターン

  • 複数のクラスのインスタンス化をFactoryクラスにまとめる
    • a factory of factories
  • Abstract Factoryパターンはクライアント側とFactoryクラスの(オブジェクト同士の)関係
    • 「複数のクラスをひとつずつインスタンス化する」という行為を、クライアントからFactoryクラスに移譲する

Builderパターン

  • Foo クラスのインスタンス作成をFooBuilder#build に任せる
  • Builderクラスのメソッドでオブジェクトを作り、クライアントに返す

Prototypeパターン

  • インスタンス化のコストが高いとき、元になるオブジェクトをcloneして新しいインスタンスを作る
  • 💬 使うタイミングがイメージできない

Singletonパターン

構造系

  • 🔍 インタフェースの取り付け方のパターン
    • インタフェースをつける・統一する
      • クライアントから使いやすくする(クライアントの知識を減らす)ため
      • メモリを節約するため(クライアントの仕事を減らす)ため
    • そもそも何をインタフェースとして切り出すか
  • 🙈 ありがち
    • is-aでないのに、メソッドを使いたいために継承する
    • 個別のクラスになんでも実装する
    • 別々のクラスに同じメソッドをコピペする
  • 🤔 Fat Modelや謎継承になりそうだったら見直すといいかも

Adapterパターン

  • 既存のクラスをAdapterでラップしインタフェースを変更する
  • 💬 予期していなかった変更への対応手段?

Bridgeパターン

  • クラスの階層に2つの継承関係があるとき

    ja.wikipedia.org

    • DishwareからPlate・Bowl
    • PlateからWoodPlate・GlassPlate、BowlからWoodBowl・GlassBowl
  • 継承関係を分け、1つを別のクラスに移譲し、注入する

Compositeパターン

  • 木構造を伴う再帰的な構造の中で、葉ノード(Leaf)と内部ノード(Composite)を同一のものとして扱う
    • ファイルとフォルダを区別しないようなケース
    • LeafとCompositeに共通のインタフェースがある
  • CompositeはLeafやCompositeの集合を持っており、自分以下の階層に再帰的に処理を行うメソッドを持っている
    • ルートノードで一度実行すると、Leaf・Compositeの区別なく前ノードに処理が進んでいく

Decoratorパターン

  • もとのオブジェクトをDecoratorクラスでラップし、ふるまいを拡張する
    • もとのオブジェクトをDecoratorクラスのコンストラクタで注入する
    • もとのクラスとDecoratorクラスでインタフェースを共有させ、クライアントから同じように扱えるようにする
  • もとのクラスを継承してクラスを作るよりも動的に変更できる
    • 💬 あるインタフェースを持つべきオブジェクトと無くていいオブジェクトが存在しているとき?

Facadeパターン

  • クライアントと複雑なサブシステムの間で窓口になり、クライアントにシンプルなインタフェースを提供する

Flyweightパターン

  • 使い回すオブジェクト群をFlyweight Factoryクラス内で(配列・マップとして)保持しておき、同じオブジェクトが必要になったときに新しくオブジェクトを作るのではなく保持されたものから返す
  • イミュータブルなオブジェクトを使い回すのに使う

Proxyパターン

  • 別のオブジェクトのインタフェースとして機能する
  • Flyweightパターンと組み合わせて、一つのオブジェクトを元に複数のインタフェースを取り付けることもできる(らしい)

振る舞い系

  • 🔍 オブジェクト同士の通信について、よくある関係のための実現パターン
    • 🔍 関係性・通信をクラスに抽出する
  • 🙈 ありがち
    • クライアント(コントローラ的なもの)で全部やる

Chain Responsibilityパターン

  • 自分のうしろに続く同じ処理オブジェクトを1つ持つ
  • 自分が処理できなかった場合、うしろのオブジェクトに処理を渡す
    • 受け取った次のオブジェクトも、自分が処理できなかったらうしろに渡す
  • 処理の責任を次々に渡していく

Commandパターン

  • 処理に必要な細かい動作・パラメータをCommandオブジェクトに閉じ込める
  • クライアントから呼ばれたInvokerがCommandを介してReceiverを操作する
  • 向いている処理
    • undo、トランザクション
    • 実行せずに処理(順)をためておき、一気に実行するとき
    • 他のものにも同じ処理をさせたいとき

Iteratorパターン

Mediatorパターン

  • Mediatorインタフェース
  • Mediator実装クラス
  • Colleagueクラス:Mediatorを介して通信する人々
  • 💬 クラスにバリエーションが無くてもインタフェースを定義するの、制約を明確に表すためという理解でよいんだろうか(このパターンに限らず)
    • Colleagueクラスの中でMediatorクラスのメソッドを呼ぶので、インタフェースで明確にしておく必要がある、ということか

Mementoパターン

  • ある時点へのロールバックを可能にする
  • Originator:何らかの状態を持つ
    • Memento:状態を保存したもの
  • Caretaker:Mementoを保持しておき、保存したりロールバックしたりする

Observerパターン

  • Subject:イベントを通知する
    • SubjectにObserver群を登録する
    • Observerに通知を送る( Observer#notify を呼ぶ)
  • Observer/Listener:通知を受け取る
    • インタフェースで notify() の実装を強制されている

Visitorパターン

  • Visitインタフェース: visit() を持つ
  • Elementインタフェース: accept() でVisitインタフェースを持つオブジェクトを受け取る
    • 複数のElementを要素に持つコンテナに適用するアルゴリズムをVisitとして分離するパターン
    • コンテナ側を変更せずに処理を追加できる
  • Elementの実装クラスを構成要素として持つコンテナがある
  • Elementクラス
    • accept(Visitor) を定義し、その中で visitor.visit(self) を実行する
  • Visitorクラス
    • ElementクラスA, B, ...に対してそれぞれ visit(ElementA) visit(ElementB) のように定義し、要素ごとにアルゴリズムに沿う処理をする
    • コンテナ全体を受け取るメソッド visitContainer(Container)も用意し、その中でコンテナの各要素にアクセスしてそれぞれに Element#accept(self) を呼ぶ
  • Elementクラスから成るコンテナ
    • accept(Visitor) を定義しの中で visitor.visitContainer(self) を呼ぶ
  • 💬 accept(Visitor) はVisitorの持つアルゴリズムの処理が適用可能だよということか
    • コンテナ側・要素側はアルゴリズムの詳細を知らないが、自身に適用できることは知っている
    • アルゴリズム側は要素にどういう処理をするか、実装クラスそれぞれについて決めている

Strategyパターン

  • Strategy:データ構造を受け取り、何らかの処理をする
  • Context:Strategyを保持する
  • クライアントはContextからStrategyを取得し、データを渡して処理をさせる

Stateパターン

  • Stateインタフェースが強制するあるメソッドについて、Stateクラス毎に実装が異なる
  • Contextが状態(= Stateクラスのオブジェクト)を持ち、状態を更新したり、その時のStateのメソッドを呼んだりする
  • 💬 Strategyとの違いは状態の更新の有無?
    • 1つのContextの中で状態が変わっていくのか、戦略によって複数のContextがあるのか
  • 💬 solidusでOrderの状態によって挙動が違うメソッドあった気がする

Template Methodパターン

  • 抽象クラスでTemplateMethod(ステップの実行順を決めているメソッド)を定義し、具象クラスで各ステップを実装する
  • ステップ中でオブジェクトを生成するところがあったら、Factory Methodパターンで使うクラスを分けたりする

『SQLアンチパターン』を読んだ④「IV部 アプリケーション開発のアンチパターン」

毎日1〜2個読んで、本文や調べたことをnotionにまとめていたのだけど、せっかくなのでその一部をこちらにも転記する。
引用の域を超えてしまうので本の内容をまとめた部分は端折って、感想とその周辺の最低限の内容だけを転載。

これまで:

hirapi.hatenablog.jp

hirapi.hatenablog.jp

hirapi.hatenablog.jp

章のタイトルの通り、SQLそのものというよりはSQL・データベースを取り巻くアプリケーションや、さらにその開発を取り巻くチームについての話だった。

各章

19. リーダブルパスワード(読み取り可能パスワード)

  • たしかに

    アプリケーションが正当な目的でパスワードを読み取れるということは、攻撃者が不当にパスワードを読み取れることも意味する

20. SQL インジェクション

  • エスケープしていないと ' が含まれる名前で予期せぬエラーになる
    • 日本語ではあまり出会わないけどたしかに……

21. シュードキー・ニートフリーク(疑似キー潔癖症)

  • IDが連番になってなくて困ったことあまり無い
  • 「解決策:疑似キーの欠番は埋めない」ですよね

23. ディプロマティック・イミュニティ(外交特権)

  • データベースのドキュメント化、ウッ頭が……
  • Railsマイグレーションはやってたけど、ER図とかがないとリレーションがわからないんだよなあ。テーブル定義とモデルをひとつひとつ見ていかないといけない
  • 前職で開発ブランチ毎に1つQA用のDBサーバが自動で立てられてたの便利だったなー

24. マジックビーンズ(魔法の豆)

  • Railsあるある。serviceクラスの議論になるのかしら
    • controllerからはserviceだけを呼びmodel(AR)を呼ばないということを突き通せばよさそう
  • 基底クラスが持ってるCRUDのメソッド、付随してなにか処理が必要なら = のメソッドを上書きすればいいじゃんと思う

25. 砂の城

  • 『絵で見てわかる〜』にも「障害発生時どうなるか」が書いてある
  • 今までインフラは専門チームに任せきりだったけど、自分でもやってみたいな

『SQLアンチパターン』を読んだ③「III部 クエリのアンチパターン」

毎日1〜2個読んで、本文や調べたことをnotionにまとめていたのだけど、せっかくなのでその一部をこちらにも転記する。
引用の域を超えてしまうので本の内容をまとめた部分は端折って、感想とその周辺の最低限の内容だけを転載。

これまで:

hirapi.hatenablog.jp

hirapi.hatenablog.jp

メッセージ

  • インデックスをきかせるクエリを書く
  • 「〜は駄目」と決めつけずにいろんな方法を検討する
    • 「NULLは駄目」「クエリ数が多いのは駄目」
  • 集約や結合が多いクエリは結果を確認する(例題からの学び)

各章

13. フィア・オブ・ジ・アンノウン(恐怖の unknown)

  • NULLは「わからない」
    • わからないとわからないが同じかはわからない NULL = NULL ⇒ NULL
  • 論理式
    • NULL AND TRUENULL
    • NULL AND FALSEFALSE ← いやいやいや 🤔
    • NULL OR FALSENULL ← いやいやいや 🤔
    • NULL OR TRUETRUE
    • NOT(NULL)NUL
  • COALESCE()
    • 一番うしろに必ず非NULLのリテラル値を入れておけば、他の引数がnullableでも安心して使える
    • 使いこなせたらかっこよさそう( RubyObject#tap 的な)

14. アンビギュアスグループ(曖昧なグループ)

15. ランダムセレクション

  • 再現性・決定性
    • 再現性:同じ手続きをすると同じ結果が得られる
    • 決定性:入力によって一意に出力が定まる
  • テーブルスキャン:インデックスを使わない検索
  • ORDER BY RAND() こんなやり方があるのか……(アンチパターン
  • 解決策:ランダムな主キー値を使う
    • 例題見たとき最初に思ったやつだ
    • 連番じゃない主キーもある、欠番もあるので注意。なるほど
  • 解決策:IDのリストをアプリケーションに読み出してランダムに選び、SELECTし直す
    • たしかにこっちのほうが安全なのか
  • ウインドウ関数、MySQL8.0からあるんだ 👀 使ってみたい

16. プアマンズ・サーチエンジン(貧者のサーチエンジン)

17. スパゲッティクエリ

  • デカルト積(直積):全組み合わせ(↓直積集合 - Wikipedia

    二つの集合 A, B に対し、それらの直積とはそれらの任意の元 a ∈ A, b ∈ B の順序対 全てからなる集合

  • UNIONたぶん仕事でつかったことない
    別々に取得したデータを結合して取得する(UNION句)

  • 似たような状況(広告のレポート)ではCASE式をよくつかっていた気がする
  • カラムの値によってレコード毎に異なるクエリを発行したいとき
    • SELECT CONCAT('UPDATE tableA ', 'SET name = ', tableB.name) でUPDATE文をレコード分自動生成できる、とのこと
    • なるほど〜〜〜、いつか使いたい

18. インプリシットカラム(暗黙の列)

  • SELECT時のワイルドカードは駄目ってよくいわれてたけど、INSERT時のカラム指定はちゃんとやってたか自信無い……
  • メタデータも変わりうる

TCP/IPを知る初手として『ネットワークはなぜつながるのか』を読んだ

『ネットワークはなぜつながるのか 第2版』を読んだ。
今まで何も知らずにWebエンジニアを名乗っててごめんなさいの気持ちになった。

www.amazon.co.jp

基本情報のテキストですぐ嫌になった分野だし、なんなら1年半前くらいにこの本を読もうとして挫折したこともあるけど、最近ふつふつと基礎固め欲がわきあがってきて、初手のひとつとして再挑戦することにした次第。
1週間かけて読み終えた(物理層のあたりはだいぶ飛ばし読みになってしまったけど)ので、過去の自分に「こうしたらがんばって読めるかも」とおせっかいをやく気持ちで、今回読み通せたポイントみたいなものを2つ書いてみた。

わからないところがあっても、メモだけしてとりあえず先へ進む

わからないところはあって当然で、その場で完全に飲み込む必要は無いように思った。理由は

  1. 章の後半で次の章の内容がちょっと出がち
  2. ある事柄について、多面的に知って初めて意義がわかる

の2つだ。

1. 章の後半で次の章の内容がちょっと出がち

この本は全体を通して、ひとつひとつの動作や仕様について「なぜそういうことをする必要があるのか」「なぜそういう取り決めになっているのか」という意図の説明に注意が払われている(ように思う)。
そのため、1章から順にブラウザ、プロトコル・スタック、LANアダプタ、ハブ、と追っていく中で、次の機器と連携する箇所についてはどうしても「ここでは〜という作業をする、なぜなら次に続く機器が〜という仕組みになっているからだ」という話が出てくる。
この時点で「次に続く機器」の仕組みを完全に理解しようとするのは難しいしあまり意味が無い。なぜなら次の章で「次に続く機器」についてちゃんと説明されるからだ。
こういうところはとりあえず次に行って、概要がつかめたときに時に戻ってきて読み返せばいい。

2. ある事柄について、多面的に知って初めて意義がわかる

たとえばサブネットとIPアドレスの話が第1章に出てくる。その時点ではそれぞれなぜ存在しているのか、なぜそうなっているのかよくわからないかもしれない。
けれど読み進めて行くと、第2章でサブネット内の全機器にパケットを送る「ブロードキャスト」を活用する場面(ARP)があり、第3章でグローバルIPアドレスとローカルIPアドレスやアドレス変換の話が出てくる。これらを読むと、サブネットがどういう存在なのか、IPアドレスのネットマスクとは何なのか、ということが少しずつつかめてくる。
いろんな面を知るとイメージが結びやすい。初出時によくわからなくても、へこまずに進んでみるのがいいと思った。

メモを取る理由

わからない点をメモしておくことも個人的には大事だった。
この本は情報量がとても多い。ので、わからない点はたくさん出てくる。それを保留している間にも新しい機器が出てきて新しい仕組みが出てくる。
それらを全部脳内メモリにのせておくのはとても難しく、読み進めているうちにどこがどうわからなかったか忘れてしまう。

私は本とnotion(ドキュメントツール)を行ったり来たりして、わからなかった箇所も含めて内容の大まかなメモを取りながら読んでいた。
パケット送信部分の説明でIPヘッダの項目の表をわからないまま写して、後で受信側がIPヘッダを読み取る場面で「なるほど!」と思えたらメモした表に戻って詳細を追記する、といった感じ。
特にわからない部分には「?」を付けておき、わかったら「?」を消して理解した範囲で説明を付けた。
時間はやや余計にかかるけど、ちゃんと理解するためには役に立つ、はず。

誰が何をするためのヘッダ(情報)なのかを整理する

進んでいくにつれて、ユーザーがもともと送りたかったデータにどんどんヘッダ(HTTPヘッダ、TCPヘッダ、IPヘッダ、MACヘッダ)が付加されていく。ヘッダには、受け取り手が次に何をするべきか、そのパケットをどう扱うべきかを端的に示す情報が詰まっている。

そもそもユーザーは誰にデータを渡したかったのかというと、Webサーバ上で動くプログラムだ。
次々に付けられていくヘッダには、最終目的地であるプログラムに至るまでに仲介する各フェーズで必要な情報が入っている。
本文中で何度か繰り返されるが、例えばIPヘッダの情報を見る機器・ソフトウェアにとっては、TCPヘッダ・HTTPヘッダ・データ本体の3つがまとめて「データ」となる。中継作業に使うのはIPヘッダの情報だけで、なんならTCPヘッダがあろうが無かろうが関係無い。
逆に言えば、IPヘッダにはその機器にとって必要な情報が詰まっている。したがって、IPヘッダの内容を見ればその機器の動きがわかるし、その機器の動きがわかればIPヘッダに存在する項目の意味もわかる。

  • どのヘッダがどのフェーズで使われるのか?
  • そのフェーズではどのヘッダでどの項目を見て、何をするのか?

これらを整理すると、登場する機器・ソフトウェアとヘッダの仕様とがリンクして、全体の流れについて合点がいきやすかった。

この本の章立てや図で表現されているように、ネットワークの仕組みは大小の階層構造から成っていて、例えば「TCP/IP」で検索すると4層の図が出てくる。
本文中の「ブラウザ」が「アプリケーション層」、「TCP」が「トランスポート層」、「IP」が「インターネット層」、「イーサネット」が「ネットワーク・インターフェース層」にあたる。
本文中にも「ハブはイーサネットのルールに従う」「ルータはIPのルールに従う」というように、要所要所で機器と階層の対応について言及される。各機器・ソフトウェアの動きを本文に沿って探検しながら、たまにこの階層図を見て「今の説明はこの部分」と確認しながら進むのもいいと思う。
(もちろんOSI参照モデルの図でも)

感想

よくある試験対策のテキストと違って「なぜそうなっているのか」がわかるように組まれているし、あわせてちょっとした歴史的経緯みたいなものも書いてあり、今までさっっっぱりわからなかったネットワークについてやっとイメージが持てた。
強いて困った点を挙げるなら、あまり馴染みのないネットワーク・インターフェース層について「これはイーサネットだけの話なのか、同じ層の他のものにも共通なのか」という点がよくわからなかった。UDPとの対比でTCPを理解するように、自分で他の仕様と比べてみたらわかるかな。

なんか偉そうに色々書いてしまったけど100%完全に理解したとはとうてい言い難いので、サーバの設定をしたり他の本を読んだりしていろんな角度から理解を深めていかねば。
(とはいえ、読めなかった昔の私よりは読み切った今の私のほうが偉いので、偉そうでもいいやと思い、このまま公開する)

『SQLアンチパターン』を読んだ②「II部 データベース物理設計のアンチパターン」

前回:

hirapi.hatenablog.jp

Ⅱ部は4章だけなので軽め

メッセージ

(だと思ったこと)

  • DBMS・データベースエンジンの仕様に詳しくあれ
  • 思考停止で倣うな

ぐうの音も出ない

各章

9. ラウンディングエラー(丸め誤差)

  • たしかに1社目(広告)で小数値いれるときNUMERICだった

10. サーティワンフレーバー(31 のフレーバー)

  • カラムに入る値を特定の値に限定したいケース、よくありそう
    • Railsenumがんばってくれるけど、アプリケーションコードでenumの値を変更(削除)すると過去のレコードの取得がエラーになるという罠がある(と聞いた)
  • 解決策:入れられる値をレコードとして持つテーブルを作るようにする
    • メタデータにデータを混入させるな事例だった
    • そしたらCHECK制約はカラムの関係性だけにしぼるべきなのか?
      • 終了日は開始日より後ですよ、とか

11. ファントムファイル(幻のファイル)

  • 今まで当然のようにS3にファイル置いてパス名をカラムに入れていた
  • バックアップとかロールバックとか考えたこと無かった……
    • 本当にBLOB型に入れるかは別として、本文にもあったように検討することが大事

12. インデックスショットガン(闇雲インデックス)

  • 雰囲気EXPLAINしかしてなかった 😢
  • ドキュメント読むべしとのことだったのでMySQLに挑戦してみたけどさっぱり
    • 英語のほうがわかりやすいかもしれない
    • 使う時が来たら読む……
  • 実は遅かったのはアプリケーション、という事案たまに見た気がする
  • カバリングインデックス、活用したことない
    • 使う場合(= 検索条件には使わないが取得対象となるカラムを複合インデックスに入れる)と使わない場合(= 検索条件に使うカラムだけにインデックスを貼る)のSELECT・INSERT・UPDATE・DELETEの所要時間を測定してみると自信を持って使えるのかな

『SQLアンチパターン』を読んだ①「I部 データベース論理設計のアンチパターン」

前の職場で読書会があったものの何回かしか参加できず通読できなかった『SQLアンチパターン』を読んでいる。
毎日1〜2個読んで、本文や調べたことをnotionにまとめていたのだけど、せっかくなのでその一部をこちらにも転記する。
引用の域を超えてしまうので本の内容をまとめた部分は端折って、感想とその周辺の最低限の内容だけを転載した(ら元のメモの2割くらいになった)。

www.amazon.co.jp

1〜8章で共通していた視点

  • メタデータとデータを分けて考える
    • データにメタデータを混入させない:テーブル名やカラム名を値として格納しない
    • メタデータにデータを混入させない:属性の値を元にテーブルやカラムを作らない
  • 参照整合性・一意性・データ型はデータベース側で保証する
    • 外部キー制約やユニーク制約をちゃんと貼る、貼れるように設計する
    • インデックス等のオーバーヘッドより、余計なSELECTやアプリケーション上の操作のほうが無駄
    • 常にアプリケーションコードを介してデータ操作が行われるとは限らない
  • 将来的に増えるもの・変わりうるものを想定して決める
    • テーブルやカラムの数には上限がある
    • 個別のテーブル名やカラム名を列挙するようなクエリは保守がしにくい

各章

1. ジェイウォーク(信号無視)

  • 交差テーブル、solidusで慣れっこだぜ
  • 「ベンダー中立」データベースによって挙動や構文が異なるとそのDBベンダーにロックインされてしまうのでよくない、なるほど

2. ナイーブツリー(素朴な木)

  • 経路列挙モデル WHERE '1/4/6/7/' LIKE c.path || '%' で親を取得
  • 閉包テーブル:1つのパスを表現するテーブルを作る
    • 先祖IDカラムと子孫IDカラムを持つ
      • 自己参照(先祖IDと子孫IDが同一)のレコードも入れる
      • 「ノード1が親、ノード2が子」のパスは 先祖ID = 1 子孫ID = 2
    • けっきょく交差テーブルということか
    • 関係性も1つのオブジェクト、みたいにとらえるとイメージしやすいかも
    • solidusでやたら中間テーブルあって大変な思いをしたけど、その分柔軟だったな

3. ID リクワイアド(とりあえず ID)

  • アンチパターン:元のモデルに、主キーになりうる意味のある値(自然キー)が存在する場合にも、全てのテーブルの主キーに id 列を用いる
    • Railsだとそうするしな〜 🤔
    • ↓言われてみれば確かにそう

      主キーは「あるテーブル上の 1 つの行」を識別するためのも のなので、主キーの列名でもそのテーブルの種類が分かるようにしておくべきです。そうすれば「bug_id = 1」という情報だけで、データベース上の 1 行を特定することができます。

  • 主キーの概念とデータ型の概念を切り離し、適切なカラムを主キーにする
    • 主キーは数値型じゃないと駄目、インクリメンタルじゃないと駄目、ということはない
    • なるほどたしかに〜〜〜
  • 解決策:主キーにはわかりやすい名前をつけること
    そういえばRailsでも変えられる……。ただ bug.bug_id はちょっと気持ち悪い気もする
    class Bug < ActiveRecord::Base
        self.primary_key = 'bug_id'
    end

4. キーレスエントリ(外部キー嫌い)

  • MyISAM、外部キー制約無いんだ……
  • カスケード更新 のオプション(MySQL
    • 外部キーを指定する際に、参照先のテーブル(のレコード)の更新時・削除時の挙動が指定できる
    • MySQLリファレンスより
    • RESTRICT ← 参照オプションが指定されていない場合のデフォルト
      • 親テーブルの更新・削除操作を拒否する
    • CASCADE
      • ON UPDATE CASCADE
        親テーブルの該当レコーとの該当カラムが更新された場合、子テーブルのレコードのカラムも同じ値に更新する
      • ON DELETE CASCADE
        親テーブルの該当レコードが削除された場合、子テーブルのレコードも削除する
    • SET NULL
      • 親テーブルの該当レコードが更新または削除された場合、子テーブルのレコードの当該カラムをNULLにする
      • 子テーブル側のカラムがNULLを許容している必要がある
    • NO ACTION
      • MySQLでは RESTRICT と同じ
      • 遅延チェックをするデータベースシステムでは、遅延チェックを指す
      • MySQLでは即時チェックのみ
    • SET DEFAULT
      • SET NULL のカラムデフォルト値版?
      • MySQLの記法では存在するが、InnoDBでは使えない(テーブル定義時にエラーになる)

5. EAV(エンティティ・アトリビュート・バリュー)

  • アンチパターン:属性テーブル
    • IssuesテーブルとIssuesAttributesテーブルを作る
      • IssuesテーブルはIDだけを持つ
      • IssuesAttributesはIssuesテーブルのIDと、属性名と、値を持つ
      • Issue has many IssuesAttributes の関係
        • IssuesAttributesの1レコードはIssueの1属性を表す
    • やばそう
    • へー

      導入時の初期設定が不要で、裏でデータベースを使っているようなソフトウェアパッケージの多くは、 カスタマイズしやすいように、EAV 設計を採用しています

7. マルチカラムアトリビュート(複数列属性)

  • 解決策の従属テーブル、solidusのaddressesが近かったかも

8. メタデータトリブル(メタデータ大増殖)

  • レコードが大きくなると必ずパフォーマンスが低下すると思いこみ、とにかくレコードが増えないようにメタデータを設計してしまう

    • しかし実際は、毎年データも増えカラムも追加されるテーブルではいずれパフォーマンスが低下する
    • たしかに
  • MySQL水平パーティショニング(シャーディング)

  • 垂直パーティショニング
    • MySQLは垂直パーティショニングやってくれない? 謎

      MySQL 8.0 does not support vertical partitioning, in which different columns of a table are assigned to different physical partitions. There are no plans at this time to introduce vertical partitioning into MySQL.

    • 「LOB型のカラムは多くのデータベース製品が自動でパーティショニングする」そうなのか
      ただし * でSELECTすると、全カラムを取るために↑のパーティショニングが無駄になるとのこと