(学習メモ)『サバイバルTypeScript』を学ぶPart2 〜書いて学ぶ編〜

『サバイバルTypeScript』の「作って学ぶTypeScript」をやってみたので、印象に残った点など、学習メモです。

typescriptbook.jp

型定義を使う場面の例

  • ドキュメントとしても便利だなと思ったりした

typescriptbook.jp

vitest

  • とりあえず基本的な書き方だけだが、RSpecやminitestを知っていれば、読めるな、という感想
  • 他の言語のテストの書き方も気になった

typescriptbook.jp

import { expect, test } from "vitest";
import { isZero } from "./is-zero";
 
test("0を渡したらtrueになること", () => {
  const result = isZero(0);
  expect(result).toBe(true);
});
 
test("1を渡したらfalseになること", () => {
  const result = isZero(1);
  expect(result).toBe(false);
});
// 画像情報の型定義
type Image = {
  url: string;
};
 
// APIから画像を取得する関数
export async function fetchImage(): Promise<Image> {
  //                              ^^^^^^^^^^^^^^^^型注釈を追加
  const res = await fetch("https://api.thecatapi.com/v1/images/search");
  const images = await res.json();
  console.log("fetchImage: 画像情報を取得しました", images);
  return images[0]; // 画像情報の配列から最初の要素を返す
}

宣言的UI

  • この辺り、言葉と定義の理解が曖昧だったので、改めて勉強できてよかった

typescriptbook.jp

  • 命令的UI
const list = document.createElement("ul");
const apple = document.createElement("li");
apple.innerText = "リンゴ";
list.append(apple);
const orange = document.createElement("li");
orange.innerText = "オレンジ";
list.append(orange);
const grape = document.createElement("li");
grape.innerText = "ぶどう";
list.append(grape);
  • 宣言的UI
function Fruits() {
  return (
    <ul>
      <li>リンゴ</li>
      <li>オレンジ</li>
      <li>ぶどう</li>
    </ul>
  );
}

ページコンポーネント

  • 設定より規約的な話かな?

typescriptbook.jp

Next.jsでは、appディレクトリ配下の構造がページのルーティングに対応します。たとえば、app/page.tsxは/にアクセスしたときに表示されるページとなります。

スナップショットテスト

typescriptbook.jp

  • あるべき状態を保存しておいて、その状態が変わってしまったらテストを落とせるもの
  • テストを書いていると、コンポーネントの一部分に注目して書くが、合わせて全体もテストできるのは便利、と思った
  • 普段RSpecばかり書いていてこれ欲しいなーと思ったことがあるので、印象に残った

(学習メモ)『サバイバルTypeScript』を学ぶPart1 〜静的型付け言語に触れる編〜

静的型付け言語を触ってみたいと思って、『サバイバルTypeScript』を初めてみました。

typescriptbook.jp

まずは「トップ」の章を見ただけですが、フロント/バックエンドの違いは当然あれど、普段使っているRubyとは全く違って面白かったです。
印象に残ったところをつらつらとメモしました。

構造的部分型

  • rubyで言うとhashのkeyが揃っていれば互換がある、みたいな感じ
type Summary = { name: string };
type Detail = { name: string; age: number };
 
const johnDetail: Detail = { name: "John", age: 28 };
const summary: Summary = johnDetail; // 代入できる。構造的部分型として互換があるため
 
const johnSummary: Summary = { name: "John" };
const detail: Detail = johnSummary; // 代入できない。構造的部分型として互換がない(ageを含まないため)

配列のループ

  • rubyではforは全然使わないけど、TypeScriptではどうなんだろう?
const numbers = [1, 2, 3];
 
for (const num of numbers) {
  console.log(num); // 1, 2, 3と出力される
}

タプル型

  • まさに静的型付け言語、というのを実感
let tuple: [string, number];
tuple = ["hello", 10]; // 代入できる
tuple = [10, "hello"]; // 順序が正しくないため、代入できない
tuple = ["hello", 10, "world"]; // 要素が多すぎるため代入できない

オブジェクトメソッド

  • 関数を持つオブジェクト
  • Rubyのハッシュに置き換えて考えると違和感があってとても面白い
const obj = {
  a: 1,
  b: 2,
  sum(): number {
    return this.a + this.b;
  },
};
console.log(obj.sum());

Shorthand property names

  • 便利〜となったが知らないと読めないだろうなーとも思ったのでメモ
const name = "John";
const age = 20;
const obj = { name, age };
console.log(obj);
//=> { name: 'John', age: 20 }

抽象クラス(abstract)

  • abstractを使った基底クラスではnewできない
  • Rubyではない機能で考えたことなかったが、確かにあってもいいよな、となった
abstract class Animal {
  abstract makeSound(): void;
 
  move(): void {
    console.log("roaming the earth...");
  }
}

ゲッターとセッター

  • get とか set を介してしか値の取得と変更をできないようにする
  • rubyは attr_xxx で設定し =で代入や .xxx で取得できるので、設計が結構違うんだなという気持ち

インターフェース

  • 少しだけ理解するのに時間がかかった
  • 基底クラスの考え方と似ている?と思ったけど
    • interfaceには実装がなく、約束がある
    • 基底クラスには実装があり、共通部分を提示する
    • ので、完全に別物か、となった
interface Printable {
  print(): void;
}
 
class MyClass implements Printable {
  print(): void {
    console.log("Hello, world!");
  }
}

ジェネリクス

  • <T>
  • 型を後から決められるようにする
// Tが型変数
function identity<T>(arg: T): T {
  return arg;
}
 
// 型変数Tにstringを割り当てる
const output1 = identity<string>("myString");
 
// 型変数Tにnumberを割り当てる
const output2 = identity<number>(100);

type importとtype export

  • 型だけimport/exportできるのは面白い

  • このほかにも PickとかOmitとか、Excludeとか、型情報付きのオブジェクトにする便利メソッドが豊富だなという印象

image_processing gem 2.0 アップデートで踏んだ落とし穴:`require: false` で解決した話

はじめに

先日、Railsアプリで使用している image_processing gem を 2.0 にアップデートしました。 CIは無事グリーンになったのですが、その後 GitHub Actions でのデプロイが rails build で失敗するという問題に遭遇しました。

この記事では、その原因と解決策、そして背景にある仕様変更について整理します。


image_processing 2.0 の変更点

CHANGELOG によると、2.0 における最大の破壊的変更はこれです。

mini_magick / ruby-vips がソフト依存(soft dependency)になった

  • 1.x 系では image_processing をインストールすれば mini_magickruby-vips も自動的に引き込まれていました。
  • 2.0 からは、使用するバックエンドに応じて Gemfile に明示的に記載する必要があります
gem "image_processing", "~> 2.0"
gem "mini_magick", "~> 5.0"  # ImageMagick を使う場合
gem "ruby-vips", "~> 2.0"    # libvips を使う場合
補足:ruby-vipslibvips の関係**
  • ruby-vips は C ライブラリである libvips の Ruby バインディングです。
  • ruby-vips gem をインストールしても、OS に libvips の共有ライブラリ(例:libvips42)が存在しないと実行時にエラーになります。
  • 両者はセットで必要であることを意識しておく必要があります。

起きたこと:CIは通るのに、デプロイで失敗する

アップデート後、Gemfile に ruby-vips を追記したところ、ローカルとCIでのテストは問題なく通りました。

しかし、StagingにデプロイするGitHub Actions ワークフローが rails build のステップで失敗しました。

Error: libvips is required but was not found

原因:GitHub Actions ランナーに libvips がインストールされていない

ruby-vips gem は require 時に libvips の共有ライブラリを ruby-ffi 経由でロードしようとします。 デプロイ用のランナー上には libvips が存在しないため、Bundler.require が走る rails build のタイミングでエラーになっていました。

解決策:require: false を指定する

デプロイの過程(ビルドステップ)では ruby-vips を require する必要はありません。 libvips が必要なのは、実際にアプリが画像処理を行う実行時だけです。

そのため、Gemfile を以下のように修正しました。

gem "ruby-vips", require: false

require: false を指定すると、Bundler.require が呼ばれても自動的に require "vips" されなくなります。
アプリケーション内で実際に ImageProcessing::Vips を使う箇所では、libvips が OS にインストールされた環境(本番サーバーなど)で正常に動作します。

補足:require: false
  • Bundler はアプリ起動時にGemfile に記載された gem を一括で require します。
  • require: false を付けると、この自動 require の対象から除外されます。
  • gem 自体はロードパスに追加されるので、必要なタイミングで手動 require "vips" することも可能です。
  • 今回のケースのように「インストールはしたいが、起動時に require させたくない」という場合に有効な手段です。

別の解決策:GitHub Actions ランナーに libvips をインストールする

もし CI・デプロイ両方のワークフローで libvips を require したい場合は、ワークフロー定義に以下のようなステップを追加する方法もあります。

- name: Install libvips
  run: sudo apt-get install -y libvips42

ただし今回は、デプロイのビルドステップで libvips が必要な理由がなかったため、require: false での対処がよりシンプルでした。

まとめ

内容
変更点 image_processing 2.0 で ruby-vips がソフト依存になり、Gemfile への明示的な記載が必要になった
発生した問題 デプロイ用 GitHub Actions ランナーに libvips がなく、rails build が失敗した
解決策 gem "ruby-vips", require: false を指定し、ビルドステップでの自動 require を防いだ

同じ問題で詰まっている方の参考になれば幸いです。

『Sidekiq in Practice』読書メモ・感想

「Sidekiq in Practice」を読みました。

nateberk.gumroad.com

実践的な観点でSidekiqの仕組みや設定、留意点などが解説されており、読んだ後の今は、すぐにでも業務のプロダクトに活かしたい!という気持ちになっています。
既存のSidkiqの設定の変更の見直しのきっかけにもなりつつ、開発チームのレビューの観点にも新しく含められそうなところがたくさんあり、大変勉強になりました。

各章ごとに印象に残ったポイントと感想を簡単にまとめました。

1: How Sidekiq Works, And Why

要約・印象に残った内容

  • Sidekiqのクライアントは単なるRubyオブジェクト
  • 実行されるRedisコマンドの数を意識するべき
    • Redisは直列実行なので、一般的にコマンド数が2倍になると実行時間も2倍になる。
Sidekiqの流れの要約
  1. クライアントは、[キュー、クラス、引数]をJSONに変換し、2つのRedisコマンド(SADDとLPUSH)を使ってプッシュ
  2. プロセッサオブジェクトはBRPOPを使ってジョブを取得し、デシリアライズされた引数を使って、ワーカークラスのperformメソッドを実際に呼び出す

感想

  • SidekiqはRedisを使って実現している、くらいまでしか知らなかったので、内部の仕組みまで知れたのは勉強になった

2. Understanding Queueing Systems

要約・印象に残った内容

  • 待ち時間は、サーバーの利用率によって指数関数的に増える(not直線的)
  • 逆に言えば、サーバーを増加することによる効果が大きいとも言える
  • 稼働率を監視する際には、OSレベルのCPU利用率だけに注目しない
    • CPU利用率が20%でもSidekiqが1スレッドのみで処理をずっとしていたら実質100%
  • 飽和状態の解決は、1. Sidekiqのプロセスを増やす 2. 並列性を上げる の手段がある

感想

  • キューイングシステムが持つ特徴と落とし穴、スケールする方法の概要が説明された章
  • CPUの利用率で安心していたこともあったので、まさに、、という感想。

3. Setting Concurrency

要約・印象に残った内容

  • 並列性を上げる方法(concurrency数を上げる)は、思ったよりも高コストになる
    • メモリの問題(glibcとCRubyの相性)
    • GVL(同時にRubyコードを実行できるスレッドは1本だけ)でキューイング(待ち時間)が発生
    • DBコネクションプールとの関係(DBの接続数 < Sidekiqのスレッドだと、待ち時間が爆増する恐れ)
  • GVLにより、Rubyコードは1本のスレッドでしか動けない。
    • APMなどでI/O待ち時間の割合を計算し、concurrencyを決めると良い

感想

  • GVLについて、なんとなく知っていた、くらいだったが、具体的に並列実行を考えたときにどのような動きになるのか、という点を理解できるようになったのはとても良かった

4. Why Is My Queue So Long?

要約・印象に残った内容

  • 以下の計算でサーバー(1プロセス)利用率を50%程度にするのが適切
    1. 提供トラフィックを計算(リトルの法則)
    2. 1秒あたりにどれだけのjobが到着するか × jobのサービスタイム(実行時間)の平均
    3. 並列度の計算
    4. プロセス数 × Amdahlの法則での換算値(I/O割合とconcurrencyから)
    5. 利用率
    6. 提供トラフィック ÷ 並列度
  • 制御しやすくするため、飽和が起こる場所はRedisのキューに限定する
    • CPUやメモリが閾値を超えないようにする
  • Redisの処理がボトルネックにならないよう、引数はなるべく小さくする

感想

  • これは実際にプロダクトの状況を計測して改善できるところがないかさっそく探すことにする
  • 引数を小さくする話は、開発チームとしてのレビュー観点などに入れるのも良さそう

5: How Many Queues Should I Have?

要約・印象に残った内容

  • キューには、criticalなどより、いっそ具体的に in_5_minutesなどの実行開始までの時間を書いておき、このキューごとにオートスケール戦略を立てると良い
  • DB高負荷のジョブやレート制限のあるAPIを叩くジョブなどは、concurrencyを下げたキューを設定して制御する
    • 1プロセス運用でも、CapsuleというProから使える機能を使うことで制御可能

tech.smarthr.jp

  • キューには重みづけを設定し、優先度の高いキューに大量のジョブが積まれて優先度の低いジョブが実行できない、という状態を避ける
  • find_in_batchedを使うなどし、ジョブ数が爆発しないようにする
  • push_bulkを使い、redis操作を1回で済むように工夫できる
  • 並列実行すると何が起こるかわからないジョブは並列数1で隔離キューに置いておく

感想

  • かなり具体的に色々な対応方法が示されていた章
  • 知らないノウハウもあったので非常に有益な章だった

6: Maximizing Servers Without Running Out Of Resources

要約・印象に残った内容

  • リソースを使い切れるかという基準にCPUの使用率を用いないこと
    • I/O比率が高いジョブをたくさん処理していたらもうリソースはフルで使っていてもCPU上は低かったりする
  • 余計な費用がかかることを避けるため、オートスケーリングの単位は小さく(平均トラフィックの25%以下に)する
  • Redisはシングルスレッド + イベントループ設計なので、1スレッドの処理能力が重要
  • Sidekiq Enterprise Swarmも1つの手段

感想

  • Sidekiq Enterprise Swarmを使うとオートスケーリングの単位は大きくなるので、そこはバランスを取る必要がありそう

7: My Jobs Are Running Twice!

要約・印象に残った内容

  • ジョブの重複実行を避けるよりも冪等性を徹底することが大切
    • ジョブの重複実行防止を実現しようとするプラグインはRedisに大きな負荷をかける
    • 厳密に冪等性を管理するなら悲観的ロックという方法もあるが、当然使い方には注意

感想

  • これは重要な割り切りだと思った
  • 重複実行を避ける方法を考えると沼にハマるので、冪等性にして重複実行前提にする、という割り切りが必要
  • これも開発チームの重要なレビュー観点にできそう

8: How Do I Set Database Pool Sizes?

要約・印象に残った内容

  • 総DB接続 = min(接続が要るスレッド数, AR pool) × プロセス数/サーバー × サーバー数
    • 1プロセスあたりのコネクションプールとSidekiqのconcurrencyにも気を配りつつ、DB全体の接続数の上限を超えないようにする
    • pgbouncerを使ったりするのも手段

感想

  • これを機に、(正直今まであまり考えられていなかった)プロセスごとのコネクション数と、DB全体のコネクション数について考えることができた

9: Is My App Thread-Safe?

要約・印象に残った内容

  • 同じ並列度を達成するなら1スレッドでプロセスを増やすより、マルチスレッドにした方がより少ないメモリで実現できる
  • スレッドバグを避けるには、Sidekiqアプリケーションのどこに共有状態が存在するのかを理解することが重要
  • Sidekiqジョブにおける「共有」状態は、複数の Sidekiq::Processor インスタンスからアクセス可能なあらゆるオブジェクト
    • ただし、Sidekiqはジョブごとにジョブクラスの新しいインスタンスを生成する
  • Minitestで並列実行を有効にすれば並列環境でテスト可能

感想

  • 並列実行のテストしたらいいのに、は本当にそうだね、となった
  • 共有についてはRubyの基本的な使い方をしていれば概ね問題はなさそうだなという印象

10. Making Sidekiq Use Less Memory

要約・印象に残った内容

  • CRubyではメモリが一度膨らむと戻りづらい
    1. find_eachでバッチ化、2. mapmap!でコピーによるメモリ爆増を防ぐ といった回避方法
  • メモリは100%に近いでもスループットにほとんど影響がない一方で、101%になるとOOMで終わる(崖)性質を持つことを理解する

感想

  • メモリが思ったより高いところで張り付いてしまっているぞ?の経験は今までもあったので共感した
  • 根が深い問題のようなので、とにかくメモリが爆増しない実装に注意し続ける必要がある

Notion APIのセットアップして、Rubyを使ってページの本文やテーブルビューの一覧を取得する

Notionには、無料で使えるAPIがあります。

今回は、Notion APIのセットアップから、Rubyのfaraday gemを使ってリクエストを投げ、ページの本文やテーブルビューの一覧を取得するまで、を記事にしてみました。

使用したバージョン

  • Ruby: 4.0.3
  • faraday: 2.14.2

1. インテグレーション作成

この作業はワークスペースのオーナーしか実行できない点に注意してください。

www.notion.com

ここでやるのは、権限設定とAPIキーの発行、というイメージです。

「インテグレーション」という公式ドキュメントの記事タイトルですが、画面には「コネクト」という名前になっていて、少し混乱しましたが、おそらく同じことを指していると思います。

今回は以下で作成してみました。

  • コネクト名: TestConnect
  • 認証方法: アクセストークン
  • 機能: 「コンテンツを読み取る」のみチェック
  • ユーザー機能: 「ユーザー情報なし」

2. ページに接続

この作業はワークスペースのゲストアカウントだと仮にフルアクセス権限を持っていても実行できないようです。
また、ページに対して編集権限ではおそらく実行できません。
(この辺りの権限周りについては有料プランなど、色々設定がありそなのでやや曖昧です。もし設定できない場合は疑ってみると良いかもという意味で記載してみます。)

www.notion.com

作成したコネクト(インテグレーション)で読み取りたいページに移動します。

ページ右上の3点リーダーから、「接続」にマウスオーバーすると、先ほど作ったコネクトが表示されていると思います。

これを接続することで、API接続の準備ができました。

3. 実際に読み取れるかを確認

Notion APIのページには、使用できるエンドポイントが記載されています。

developers.notion.com

先ほど作成したトークンがあれば、例えば「Retrieve a page」のページで「Tri it」から、接続確認ができます。

4. Ruby(faraday)でリクエスト

先ほどの「Try it」にもRubyのリクエストサンプルコードがありますが、Net::HTTPを使う方法でした。 今回は、faradayを使ったサンプルコードを紹介します。

page_id = "36e7e3c5cd238088b01ae2e5464f0402"
url = "https://api.notion.com/v1/pages/#{page_id}"
headers = {
  'Notion-Version' => '2026-03-11',
  'Authorization' => 'Bearer ntn_xxx(トークン)'
}
response = Faraday.new.run_request(:get, url, nil, headers)

p JSON.parse response.body

こんな感じで、Notion APIを使ってRubyでNotionの内容を読み取り、取得することができました🎉

5. ページの本文にはRetrieve block childrenが使える

自分がハマったポイントとして、Retrieve a pageエンドポイントでは、本文は取得できないというものがありました。

エンドポイント名からして、ページの内容を本文含めてごそっと取れるのかなーと思ったのですが、どうやらページのプロパティ情報(更新日など)が取れるエンドポイントで、本文の取得はできませんでした。

以下のようなページをしているとき(ページタイトルが「テストのページ」)、「ここに本文が入ります。」は Retrieve a pageのレスポンスには含まれませんでした。

その代わり、Retrieve block childrenというエンドポイントが使えます。
これは、block_idを渡す、という仕様ですが、このblock_idにページID(そのNotionページのURLの文字列)を渡してあげれば良いです。

block_id = "36e7e3c5cd238088b01ae2e5464f0402"
url = "https://api.notion.com/v1/blocks/#{block_id}/children"
headers = {
  'Notion-Version' => '2026-03-11',
  'Authorization' => 'Bearer ntn_xxx(トークン)'
}
response = Faraday.new.run_request(:get, url, nil, headers)

p JSON.parse response.body

6. テーブルビューの一覧取得には Query a data sourceが使える

Notionでは、以下のような「テーブルビュー」というものが作れます。

このテーブルの中のアイテムの一覧を取得したかったのですが、上記で紹介した Retrieve block childrenでは取得できないようでした。
(「テストのデータベース」という名前のデータベースはあるよ、くらいはわかりますが、各項目(記事Aなど)は取得できませんでした。)

調べた結果、「Query a data source」(postリクエスト)を使えば取得できました。

必要になるのは、「データソースID」というもので、以下の画像の箇所から取得できます。

data_source_id = "36e7e3c5-cd23-80e4-822c-000b96b271f8"
url = "https://api.notion.com/v1/data_sources/#{data_source_id}/query"
headers = {
  'Notion-Version' => '2026-03-11',
  'Authorization' => 'Bearer ntn_xxx(トークン),
  'Content-Type' => 'application/json'
}
response = Faraday.new.run_request(:post, url, nil, headers)

p JSON.parse response.body

その他: レート制限について

Notion APIのリクエスト制限については以下に記載があり、レート制限にも言及があります。

rhgc0kvlw3.apidog.io

インテグレーションごとの受信リクエストのレート制限は、1 秒あたり平均 3 リクエストです。平均レートを超える一部のバーストは許可されます。

とあります。

「平均レートを超える一部のバーストは許可されます。」とあるように、瞬間的に1秒あたり/3リクエストを超える分には問題ないようです。
実際、連続的に20回ほどリクエスト投げてみましたが、全て200が返ってきました。

レート制限はページごと?あるいはコネクト(インテグレーション)全体で判定?など気になることもあります(ドキュメントの書き方だとインテグレーション丸ごと使えなくなりそうですが)が、そこまでの言及はされておらず、単なる興味のためにバーストまでリクエストするのも良くない気がしたので、検証はできていません。

『Googleのソフトウェアエンジニアリング』読書メモ・感想

先日、『Googleのソフトウェアエンジニアリング』を読みました。

www.oreilly.co.jp

各章ごとに、気になったトピックなど踏まえながらつらつら感想を書いていきます。

1章「ソフトウェアエンジニアリングとは何か」

「プログラミング」と「ソフトウェアエンジニアリング」という言葉の違いにきちんと言及している書籍を初めて見た気がする。
今まで自分は「プログラマー」なのか、「ソフトウェアエンジニア」なのかよくわからないなと思っていたが、この書籍の定義でいえば、「ソフトウェアエンジニア」となるし、そうでなければいけないと感じた。
つまり、技術スタックや開発チームの長期的な保守性やスケーラビリティが確保されている状態にする仕事を負っている。
特に今は仕事で新しいサービスの開発を行っているので、この点を意識し続ける必要がある。

また、Hyrumの法則(APIを作成すると、あらゆる挙動に依存するユーザーが現れる)は印象に残った。
Rubyで言えばprivateメソッドにしていても使おうと思えば使えてしまうし、インスタンス変数も直接参照することだってできる。(自分もたまにどうしてもgemをカスタムしたいときにやってしまうこともある)

Beyonceルールにも共感した。
少し書籍に書かれている事例とは違うが、とある修正をした結果、CIが通った上で他の機能に障害やバグが起きてしまったのなら、それはその原因を混入したコード(開発者)に責任があるのではなく、その機能のテストをきちんと書けていなかったことが問題という考え方をした方が良さそう。

直前に読んだ『ソフトウェアアーキテクチャの基礎』と同様に、この書籍からも(「はじめに」も含めて、)、「意思決定に絶対的な決定はなく、常にトレードオフがあって、間違える可能性もあるからなるべく早く間違いに気づいて修正できるようにしよう」というメッセージが受け取れた。

2章「チームでうまく仕事をするには」

とにかく全体的に、エンジニアは隠れていないで(一人で閉じこもらないで)、どんどんチームに相談して、失敗を早期に発見して無駄なことに時間を使わず、開発力を上げていけ、ということを言っている章。

「こいつ馬鹿だって思われるんじゃないか」という気持ちで完成前に誰かに見られるのを不安に思う気持ちとか、身に覚えがありすぎて、グサグサ刺さって、時折笑ってしまった。
ただ、Googleに勤めているような人でもこの気持ちになりながら仕事をしていると思うと、おそらく全員のエンジニアが思っているんだなという気持ちになって少し気が楽になったような感じがした。

ジャグリングの例はなるほどなと思った。(ジャグリングの技術を教えられても、自分自身を否定されたようには思わないだろう。コードへの指摘も同じであるはずだ。)
「相手が自分を攻撃するかもしれない」というのは相手を十分に信頼できていないということでもある。

自分は特にチームでの開発で成果を出していきたいと考えている人間なので、この章の書いてあることには強く共感した。
ぜひまた都度読み返したいと思う章だった。

3章「知識共有」

「ずっとアーカイブされることを恐れて質問をしたくない」は、あまり言われないがあると思う。
例えばいつでもslackのチャンネルで質問しても良いよと言われても、後々自分の質問が残っていくことを考えると、質問しづらいと思ったことが自分もある。
そんなケースを救ってくれるのが1対1のメンターをはじめ、口頭で気軽に質問できる場を用意することだなと思った。(あるいは質問を匿名でできる場を用意するのはどうだろう?と思った)

心理的安全性の確立をするには、「なんでも聞いてください!」というだけでなく、自分もその手本となることが必要だと思った。
「リーダーこそ自分のわかっていないことを表明したり素直に質問をすることが肝心で、その方がむしろ尊敬を集める」という記載はとても共感した。

ドキュメント化は、必要だと頭ではわかっていながら、どうしてもコードを書く方に戻りたくなったりして、書かずに置いてしまう心当たりがあったが、これほどまでに必要性を説明されたら、さすがににもう少し書くか…という気持ちになった。 それに関して、URLを技術名にするのはいいアイデアだと思ったし、できるなら試してみたくなった。(keycloakについてのドキュメントはxxx/keycloakなど)

また、Googleのリーダビリティの制度はさすがGoogleだし、改めてすごい世界だ…という感想。

4章「公正のためのエンジニアリング」

基本的に日本で国内向けにアプリケーションを開発しているとなかなか考えない視点の話で興味深かった。
特に、低代表の人にさらに害を与えてしまう可能性の話については、誰かに利益があるようにする裏には、誰かに不利益となるかもしれない、ということだと感じた。
そこで公正ではないことが行われるのであれば、エンジニアとしてストップをする勇気を持ちたい。

5章「チームリーダー入門」

リーダー(マネージャー)の役職に興味がないわけではない自分にとっては参考になる話がとても多かった。
PRなどで目にみえる形で進捗がある開発メンバーに比べ、マネジメントをするようになると「今日は何もしなかった」と思いやすいというのは想像に容易いし、辛い気持ちになりそう。
実際にはマネジメント業務のあれやこれやをしているわけなので、タスクをチケットに切って見える化しておくのも良さそう。

また、マネージャーは管理職と呼ばれたりするが、「管理」という言葉を言葉通り捉えるとマイクロマネジメントに陥るというのは罠すぎると思った。
その中で、「やる方法を気にするのではなく、やり遂げられるかを気にするべき」という箇所は印象に残った。
自分も以前小さいチームのリード的な役割をしていた時を思い返すと、やる方法を気にしていた場面があったような、、と反省した。

自分はマネージャーという役職に興味がないわけではない一方で、なかなかそちらの世界に踏み込めていない一因として、自分はリーダーとして相応しい知識や経験が全く足りないのではないか、というのがある。
ただ、本章を読んで、(もちろん足りない部分があるのは事実だが、)それのみを以て自分には資格がないと思うのは違うか、と考えられるようになった。
自分が勝手に想像するリーダーというのはどんな質問や障害に対しても常に一定の回答と対応ができるような人間で、自分はまだそうなれない、、と思っていたが、思い返せば、多分それができる人はほぼいないし、自分もそんなリーダーを求めているわけでもない(本章にあるとおり、わからないことをわからないと表明してくれるリーダーは良いリーダーだと感じる)ので、前向きな気持ちになった。

6章「スケールするリーダー」

ここでも『ソフトウェアアーキテクチャの基礎』で何度も出てきた「永久的な正解はなくトレードオフがあるだけなので高速で失敗してイテレーションを回せ」という趣旨の話が出てきた。

本書で出てくる「自動運転チームを構築する」というのは、良い指針だと思った。
リーダーの仕事はもっぱら組織・戦略づくりと方向づけであって、組織に何か課題が発生した時に、しばらく放っておいても自律的にその課題を解消することができるようなイメージだと理解した。(自動運転=リーダーは何もしない、のではなく、自動運転ができる状態を常にメンテナンスするイメージかなと思った)

また、緊急な仕事と重要な仕事の章は印象に残った。
以前公務員として緊急な仕事ばかりしていたときのことを思うと、(中には緊急で重要な仕事もあったけど、)確かに自分が思っているよりは重要な仕事は少なかったかも。
そのため、自分が持っているうちの「自分にしかできない決定的な重要なもの」以外の80%は投げ捨てるべきらしい。(もし投げ捨てたうちに重要なものがあればまた戻ってくる。)
些細な例だが、確かに普段電話があって取れなかった時に「もし大切な連絡ならまたかかってくるでしょ」と思って全部の電話にかけ直したりしないことをしているが、考え方としては近いのかなと思ったりした。

7章「エンジニアリング生産性の計測」

計測する価値があるかどうかの判断基準の話が面白かった。
計測した結果が良い結果だったり悪い結果だったりした時に、それぞれきちんと行動がとられなければ意味ない、というのは言われれば当然だが抜けてしまうこともあるのでは、と思った。
良い結果が出ても現状維持だし、悪い結果が出てもなんだかんだ色々理由をつけて維持されることが予想されるようなものに対して計測を行う意味はない。

また、計測するのは必ずしも定量的なメトリクスに限らないというのも参考になった。
どうも定性的な指標は嫌がられるきらいがある気がする(それが正しい場面も多い)が、例えばエンジニアへのアンケートでしか得られない情報も多いのも確かだなと思った。

8章「スタイルガイドとルール」

コーディングガイドラインのルールを作る時は「まず組織が何に価値を置くかを認識しなければならない」し、ルールを定義する場合は「どんなゴールを前進させようとしているのか」の質問をするべき、というのはなるほどと思った。
確かにこの観点が抜けるとルールの一貫性がなくなるし、好みの問題じゃんという議論が多くなってしまう気がする。

本題とは関係ないが、Googleは「20億行以上のコードが入ったコードベースに向けて、約6万回のコード提出が毎日行われている」らしく、改めてとんでもない規模だなという感想。

「コードは書かれるより読まれることの方が圧倒的に多い」
これは何度も肝に銘じたい。

「ルールを決定するに背景の根拠をドキュメント化しておくと、変更するときにその根拠を前提に判断できる」
変更するときに効いてくるというのは新しい視点だった。

9章「コードレビュー」

「コードとは債務である」 変更が難しいクラスなど、いわゆる技術的負債のみを債務と考えがちだが、コードを書けば必ず保守が発生する。

コードレビューがもたらす影響についての記載も面白かった。 - エンジニアにコードが「自分個人のもの」ではなく、集合的な事業の一部であるという認識を強化させる - 批判にさらされることを受け入れやすくする - コード提出前の最後の一確認をするようになる

など、心理的な好影響については考えたことがなかった。

以下は、自分の組織だと行なっていないが、導入を検討・提案する余地がありそうと思った。 - レビューコメントに対する修正を行いさえすれば、再レビューを経なくて良いというもの(尊敬と信頼) - 大半のコードレビューはレビュアー1名。複数人に見てもらう場合でも別々の側面に専念するべき。

レビュー時には以下に気をつけることを改めて意識する(今でも自分としてはできているとは思うけど) - コード作成やのアプローチが間違っていると決めてかかる前に、なぜそのようなアプローチが取られたのかについて質問する

また、Googleでのコード変更の約35%というのは、想像よりもかなり高い割合で驚いた。
Webアプリケーションで全く同じようにはできないと思うが、このくらい変更が少ない単位で高速にレビューを回すというのは自分としては良い方法だと思う。

10章「ドキュメンテーション」

「ドキュメンテーションをコードのように扱う」は参考にしたい考え方だと思った。
やはり別物と考えてしまうために、保守のことを想定しなかったりして廃れていってしまう気がする。

ドキュメントを書く人と読む人が別になるのは結構罠で、意識しないとズレていく原因になるので意識したい。

Googleの取り組みで、自分の組織にも取り入れたいと思ったのは「鮮度日付」の仕組み。
今働いている会社にはせっかく頑張って作成したドキュメント集があるが、鮮度を保つ仕組みがないので、それこそリマインダーなど、何かしらやってみたい。

11章「テスト概観」

概ね、うんうんそうだよね、という話だった。(単体テストの比率の話など)

一方で、「テストは本番環境向けコード同様に扱うべき」というのはちょっとできていないところがあると思った。
レビューでも割と開発者任せにしてしまっているところがある。
ただ最近は、RSpecのうちsystem specには、重要かつsystem specでしか書けないテストだけ書く、くらいは意識しているつもり。

新人エンジニアにテストを書くのが当たり前という講習を受けさせて文化を変えていった話は、組織文化の変容のさせ方として非常に面白かった。
思想は強制するのではなく、「成功する思想は広まるものである」というのも納得の考え方。

12章「ユニットテスト」

こちらも今まで勉強してきたことや先輩に教えてもらった内容通りだなと思って読んでいた。

13章「テストダブル」

  • Googleではなるべく現実的なテストを書くことを優先してモッキングフレームワークの利用は避けている、とのこと
  • 先日も仕事でモックを使ったテストコードがあって、よく見たらテストしたいことがテストできていない、ということに気づいたことがあったので、モックは本当に必要な場面で最低限で使うのがいいのかなというのが自分の感想でもある
  • 依存関係が増えて機能修正などでテストが落ちる量が増えるかもしれないが、むしろそれは望ましく、バグ混入するリスクを減らすという大きなメリットを取った結果の仕方ないトレードオフなのかなと思っている
  • ただし、一方で、超大規模コードなどでそうも言ってられないこともあるのだろうなとも思ったりもした。
  • 「フェイキング」という言葉に馴染みがなかったが、外部APIとの接続機能を作る上で実際のAPIをローカルから叩けないときにダミーのリクエスト先を作っておくことをやるような話のテスト版だなという理解をした
  • 「フェイキング」「スタビング」「インタラクションテスト」など色々用語が出てきた。普段使い分けをあまり意識できていなかったが、それぞれ「あの時やったのは"インタラクションテスト"だったな」などという気持ちで読めて面白かった

15章「廃止」

  • 改めて、コードは債務であり資産ではない。価値があるのはコードが提供する機能。
  • ここでもHyrumの法則が関係してくる。ユーザーが提供側が思わぬところに依存していることがある。
    • 以前読んだ『Web API: The Good Parts』でもいかに破壊的変更が難しいか、という話があった
  • 以下は今まであまり考えたことがなかったが、なるほどと思った点
    • 最初から廃止することを念頭に設計すること。
      • 実際、先日外部APIの移行をした際、移行前のサービス名が入ったカラムやクラスがあって若干面倒な作業が増えたなと思ったので、実体験としても確かに大切だと感じた。
      • 若干話は違うけど、よく言う「失敗しても後戻りできるならどんどんしよう」みたいな話の具体的な方法論としても使える考え方だなと思った
    • 期限を定めない廃止勧告が意味はあまりない
    • 廃止作業をする際に専門で人員をつけないと、ユーザーからはケチだと思われる(自分達の要望が後回しにされていると感じる)
    • 廃止の警告をする際は、「行動可能性(エンジニアが何をしたら良いかわかること)」と「関連性があること(ユーザーが実際に使おうとした際に警告が出るなど)」が必要。
    • 廃止の計測的なマイルストーンは完全の撤去ではなく、インクリメンタル(段階的)なものにしておくべき

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

  • Googleのソースコードの大部分は単一のリポジトリーで管理されている(!)
  • Piperという社内用バージョンコントロールシステムを作って使っている

19章「GoogleのコードレビューツールCritique」

  • LGTMを与える場合は、指摘内容の対応は依頼するものの、再レビューは必要としないもので、タイムゾーンが異なる場合などの時間節約につながっている
    • 「信頼を公然と示すこともまた、信頼を築きチームを強化する優れた方法だ」の考え方は取り入れていきたいと思った。

20章「静的解析」

「ユーザーにコントリビュートする権限を与えよ」については、流石にGoogleのように独自の静的解析ツールを作成するのは大変すぎるが、Rubocopなら適用したいor適用したくないcopを開発メンバーが意見できる仕組みを作るのがこの考え方に倣うことになるかなと思った。

24章「継続的デリバリー」

  • 組織の長期的成功への鍵は常に速度
    • 「より速い方が安全」は良い標語だと思った
    • 1章の感想にも書いたが、この、失敗までを早くする、というのは最近読む本には本当にどこにも書かれているなという印象
  • 利用されるもののみをリリースすべき
    • Googleのように事前に大規模のA/Bテストはなかなかできなくても、リリース済みの機能を監視して使われていなければ削除していく、というのは「コードは資産ではなく負債で、資産は機能」という考え方に立つと非常に腹落ちする話だなと思った
  • 障害を恐れてリリース頻度が下がると逆にもっと開発者が辛いよね、という話は、基本的なことだがやはり大切

【Rails】デプロイのたびにseed処理でパスワードが更新され強制ログアウトされていた

問題の概要

先日、開発・検証環境でデプロイするたびにユーザーが強制的にログアウトする問題に当たりました。

関連して使っていたgemは以下です。

原因を調査すると、以下のことがわかりました。

  • この現象が起きるユーザーはseedで作成したユーザーであること
  • デプロイ時にdb:seed_doを実行している際に起きている現象であること

これを踏まえてseedファイルを確認すると、書き方に原因があったことがわかりました。

問題のあった書き方

User.seed(
  :email,
  { :email => "jon@example.com",   :password => "pass1234" },
  { :email => "emily@example.com", :password => "pass1234" }
)

seed-doのgemを素直に使うとこんなコードになりますが、素のパスワード情報は当然DB側で持っていないので、元のパスワードが何であれ(pass1234であっても)、毎回パスワードを変更する処理がかかります。
パスワード変更することでセッションが切れて、seed_do実行するごとにログアウトされていました。

パスワードも常にリセットしたい場合はこれでもいいのですが、私の場合はログアウトされないようにしたかったのでログアウトされないような対応をすることにしました。

対応策

1. find_or_create_byを使う

User.find_or_create_by!(email: "jon@example.com") do |user|
  user.password = "pass1234"
end

上記のようなコードであれば、同じemailのユーザーが存在すれば処理をスキップするので、passwordの更新はかかりません。

2. bulk_insertを使う

私が遭遇したプロダクトにはすでに activerecord-import gemがインストールされていたので、このgemを使うことにしました。

※ 前提として、Userモデルのテーブルのemailカラムにユニーク制約が設定されているとします。

users = [
  User.new(email: 'jon@example.com', password: 'pass1234'),
  User.new(email: 'emily@example.com',  password: 'pass1234'),
]

# on_duplicate_key_ignore: 一意制制約に違反する場合は処理をスキップする(パスワードを上書きしない)
User.import!(users, on_duplicate_key_ignore: true)

on_duplicate_key_ignore オプションを使えば、ユニーク制約をかけているemailを見て、重複レコードになりそうであればスキップしてパスワードを上書きしません。

注意点

もしseed実行時に間違いなく設定ファイル通りにデータをリセットしたい場合は、今回のような方法だとかえって混乱する場合がありますので、その辺りは理解した上で修正する必要があります。
(氏名レコードが別途ある場合、emailだけ見てスキップするコードだとパスワードの他にも氏名もリセットされないなど)