d.sunnyone.org
sunnyone.org

ページ

2025-04-05

PostgreSQLの制約トリガー+ビューを使ってテーブル跨ぎで整合性を保つ

RDBMSを使うと、UNIQUE制約やCHECK制約、排他制約などを利用して、指定した条件に沿う形で、データの整合性を保つことができる。しかし、「同じ電話番号を複数の顧客が使ってよいのは、同じ家族に所属している場合のみ」というような、制約の前提に他テーブルを使う(テーブルを跨ぐ)ケースは、よくでてくるわりに実現しにくい。この記事では、そのような制約をアプリケーションではなくPostgreSQLによって実現する方法を紹介する。

 TL;DR

 テーブル跨ぎの整合性を保つための手段として、制約トリガー(CONSTRAINT TRIGGER)がある。手順の例は以下の通り。

  1. 不整合を検出するビューを用意する
  2. ビューに基づいて制約違反を判定する関数を定義する
  3. 制約トリガーを定義し、更新・挿入・削除時に整合性を検証する

前提(ユースケースの概要) 

今回は以下のような顧客と家族の情報を例にする。

  • 顧客は電話番号を持っている(こともある)
  • 顧客は家族に所属している(こともある)
  • 電話番号は原則として別の顧客と重複してはならない
    • 同じ家族に所属する場合のみ重複することができる
  • 電話番号も家族もあとから変更される 
ER図は以下の通り。 

手順

今回使うサンプルはすべて以下に置いてあるので、適宜動かせる。

 1. 顧客・家族テーブルとインデックスの定義(準備)

CREATE TABLE customer (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE customer_phone (
    customer_id UUID PRIMARY KEY REFERENCES customer(id),
    phone_number TEXT
);

CREATE INDEX customer_phone_phone_number_index ON customer_phone (phone_number);

CREATE TABLE family (
    id UUID DEFAULT gen_random_uuid() PRIMARY KEY
);

CREATE TABLE family_member (
    family_id UUID NOT NULL REFERENCES family(id),
    customer_id UUID NOT NULL REFERENCES customer(id),
    PRIMARY KEY (family_id, customer_id)
);

CREATE INDEX family_member_family_id_index ON family_member (family_id);
CREATE INDEX family_member_customer_id_index ON family_member (customer_id);

 2. 不整合検出用ビューの作成

不整合となる行を抽出できるビューを準備する。ここでは、同じ電話番号を持ちながら、異なる family_id に所属している顧客ペアを抽出する。

CREATE OR REPLACE VIEW customer_phone_number_conflicts AS
WITH base AS (
  SELECT
    c.id AS customer_id,
    cp.phone_number,
    fm.family_id
  FROM customer c
  LEFT JOIN customer_phone cp ON c.id = cp.customer_id
  LEFT JOIN family_member fm ON c.id = fm.customer_id
),
pairs AS (
  SELECT
    t1.customer_id AS t1_customer_id,
    t2.customer_id AS t2_customer_id,
    t1.phone_number AS t1_phone_number,
    t2.phone_number AS t2_phone_number,
    t1.family_id AS t1_family_id,
    t2.family_id AS t2_family_id
  FROM base t1
  JOIN base t2 ON t1.customer_id <> t2.customer_id
)
SELECT *
FROM pairs
WHERE t1_phone_number IS NOT NULL
  AND t1_phone_number = t2_phone_number
  AND (
    t1_family_id IS NULL
    OR t2_family_id IS NULL
    OR t1_family_id <> t2_family_id
  );

CTEは使わなくても良いが、重複検知のような場合には、以下の3ステップにするとわかりやすい。

  1. 不整合検知に必要な情報を集める
  2. 1.の行の組み合わせを生成する
  3. 2.から不整合になっている行を探す

ビューの作成自体も必須ではないが、ビューを作成すると以下のようなメリットがある。

  • 整合・不整合の状態を宣言的に記述できる
  • トリガーは複数になるが、1つのビューを作ることで整合状態の知識を分散させずに済む(条件を変えやすい)
    • 実際、この記事を書いている間にcustomerとcustomer_phoneを分割したのだけど、かなり変えやすかった
  • 不整合なデータの確認が容易
  • 検証に使われるクエリのチューニング(実行計画の確認など)がしやすい

 3. ビューを使って整合性をチェックする関数の定義

作成したビューを参照し、行が現れたらRAISE EXCEPTIONする関数を作る。ビューのSELECT以外はほぼしないことがポイント。

顧客の更新時(電話番号変更時)

CREATE OR REPLACE FUNCTION check_customer_phone_number_conflict_on_update()
RETURNS TRIGGER AS $$
BEGIN
  IF EXISTS (
    SELECT 1 FROM customer_phone_number_conflicts
    WHERE t1_customer_id = NEW.customer_id
  ) THEN
    RAISE EXCEPTION 'Phone number conflict: cannot update customer phone';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

家族構成の変更時

CREATE OR REPLACE FUNCTION check_customer_phone_number_conflict_on_family_change()
RETURNS TRIGGER AS $$
DECLARE
  target_id UUID;
BEGIN
  IF TG_OP = 'DELETE' THEN
    target_id := OLD.customer_id;
  ELSE
    target_id := NEW.customer_id;
  END IF;

  IF EXISTS (
    SELECT 1 FROM customer_phone_number_conflicts
    WHERE t1_customer_id = target_id
  ) THEN
    RAISE EXCEPTION 'Phone number conflict: cannot change family';
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

 4. 関数を制約トリガーとして登録

関数を制約トリガー(CONSTRAINT TRIGGER)として登録する。 このとき、DEFERRABLE INITIALLY DEFERREDにすると、トランザクション終了時に検証できる。

なお、顧客側、家族側どちらの変更も監視しなければいけないことに注意

-- 顧客の電話番号変更時
CREATE CONSTRAINT TRIGGER check_customer_phone_number_conflict_trigger
AFTER INSERT OR UPDATE OF phone_number ON customer_phone
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION check_customer_phone_number_conflict_on_update();

-- 家族構成の変更時
CREATE CONSTRAINT TRIGGER check_customer_phone_number_conflict_on_family_trigger
AFTER INSERT OR UPDATE OR DELETE ON family_member
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION check_customer_phone_number_conflict_on_family_change();

 

検出例

パターン1:同じ家族内で同じ番号(OK)

INSERT INTO family (id) VALUES ('00000000-0000-0000-0000-000000000001');

INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000011', 'Alice'),
  ('00000000-0000-0000-0000-000000000012', 'Bob');

INSERT INTO family_member (family_id, customer_id) VALUES
  ('00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000011'),
  ('00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000012');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000011', '000-0000-0000'),
  ('00000000-0000-0000-0000-000000000012', '000-0000-0000');

 

パターン2:異なる家族で同じ番号(NG)

INSERT INTO family (id) VALUES ('00000000-0000-0000-0000-000000000002');

INSERT INTO customer (id, name) VALUES ('00000000-0000-0000-0000-000000000013', 'Charlie');

INSERT INTO family_member (family_id, customer_id) VALUES
  ('00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000013');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000013', '000-0000-0000');
INSERT 0 1
INSERT 0 1
INSERT 0 1
ERROR:  Phone number conflict: cannot update customer phone
CONTEXT:  PL/pgSQL function check_customer_phone_number_conflict_on_update() line 7 at RAISE

パターン3:未所属同士の重複(NG)

INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000014', 'Dana'),
  ('00000000-0000-0000-0000-000000000015', 'Eve');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000014', '000-1111-1111'),
  ('00000000-0000-0000-0000-000000000015', '000-1111-1111');
INSERT 0 2
ERROR:  Phone number conflict: cannot update customer phone
CONTEXT:  PL/pgSQL function check_customer_phone_number_conflict_on_update() line 7 at RAISE

パターン4:電話番号が NULL の場合(OK)

INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000016', 'Frank'),
  ('00000000-0000-0000-0000-000000000017', 'Grace');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000016', NULL),
  ('00000000-0000-0000-0000-000000000017', NULL);

パターン5:family_member を DELETE → 残った側が未所属になって重複(NG)

INSERT INTO family (id) VALUES ('00000000-0000-0000-0000-000000000005');

INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000051', 'Alice'),
  ('00000000-0000-0000-0000-000000000052', 'Bob');

INSERT INTO family_member (family_id, customer_id) VALUES
  ('00000000-0000-0000-0000-000000000005', '00000000-0000-0000-0000-000000000051'),
  ('00000000-0000-0000-0000-000000000005', '00000000-0000-0000-0000-000000000052');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000051', '000-0000-0000'),
  ('00000000-0000-0000-0000-000000000052', '000-0000-0000');

-- Bob を家族から外す  NG(未所属で重複状態になる)
DELETE FROM family_member
WHERE customer_id = '00000000-0000-0000-0000-000000000052';
INSERT 0 1
INSERT 0 2
INSERT 0 2
INSERT 0 2
ERROR:  Phone number conflict: cannot change family
CONTEXT:  PL/pgSQL function check_customer_phone_number_conflict_on_family_change() line 15 at RAISE

パターン6:phone_number の UPDATE による重複(NG)

INSERT INTO family (id) VALUES ('00000000-0000-0000-0000-000000000003');
INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000018', 'Hank');
INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000018', '000-9999-9999');
INSERT INTO family_member (family_id, customer_id) VALUES
  ('00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000018');

UPDATE customer_phone
SET phone_number = '000-0000-0000'
WHERE customer_id = '00000000-0000-0000-0000-000000000018';
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
ERROR:  Phone number conflict: cannot update customer phone
CONTEXT:  PL/pgSQL function check_customer_phone_number_conflict_on_update() line 7 at RAISE

パターン7:トランザクション内で家族と番号を同時変更(OK)

BEGIN;
INSERT INTO customer (id, name) VALUES
  ('00000000-0000-0000-0000-000000000019', 'Ian'),
  ('00000000-0000-0000-0000-000000000020', 'Jack');

INSERT INTO customer_phone (customer_id, phone_number) VALUES
  ('00000000-0000-0000-0000-000000000019', '000-3333-3333'),
  ('00000000-0000-0000-0000-000000000020', '000-3333-3333');

INSERT INTO family (id) VALUES ('00000000-0000-0000-0000-000000000004');
INSERT INTO family_member (family_id, customer_id) VALUES
  ('00000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000019'),
  ('00000000-0000-0000-0000-000000000004', '00000000-0000-0000-0000-000000000020');

COMMIT; 

 

MySQLのときは?

 ビューは問題なく、pl/pgsqlのところは書き方を変えれば良さそうだが、CONSTRAINT TRIGGERがないので工夫が必要そう。
 

他手法との比較

  • UNIQUE制約、排他制約やCHECK制約
    • 原則として他テーブルを参照できない
  • 外部キー制約
    • 互換性を気にしなくてよく、すべて宣言的に収まるので、困りごとが起きないのであれば、こちらが望ましい
      • 制約のために関連がいびつになったり、条件が柔軟だったり、変わるものだったりすると厳しい
    • この方法は、SQLパズル の「パズル16 主任とアシスタント―参照整合性制約の正しい設定」を参照。

まとめ

本記事では、テーブル跨ぎで整合性を保つ例として、電話番号を共有して良いのは所属している家族同士のみというケースを用いて、制約トリガーの使い方を説明した。
 
同様の条件付き制約が必要な場面において、この手順が参考になれば幸いである。

2025-01-19

linariaからPanda CSSに移行した

うちのサイトは今のところNext.jsで書いてあるのだけど、スタイリングにはlinariaを使っていた。次の作業のためにPanda CSSに置き換えたので、その記録を残しておく。

 Panda CSSの特徴

移行時のポイントの前に、気にしておくべき造りの特徴としていくつかあげておく。

object の形でのスタイル指定 

linariaではstyled.div`` やcss``の指示でCSS文字列を記述できるが、Panda CSSではjs/tsのオブジェクトの形で指定する。最近ではよくある形。

const appItemsDivStyle = css({
    display: 'grid',
    gridTemplateColumns: '1fr',
    gap: '15px',

    md: {
        gridTemplateColumns: '1fr 1fr',
    }
});

基本的にはCSSの構造そのものを書くが、Chakraのような記法を受け付けているところもある。ブレイクポイントやデザイントークンのあたり。このへんにどう乗るかは移行時に気にかけておくと良いと思う。

クラスが細かく分割される(Atomic CSSスタイル)

linariaでは基本的にはスタイル指定の部分はまとまったままで、クラス名が生成されて適用される。このようなイメージ。

.sxatkf1 {
  padding-bottom:5px;
  border-bottom:1px solid #012356;
  margin-bottom:20px;
  line-height:1.8;
  font-size:20px;
  font-weight:700
}

classへの指定はこんなかんじ。

<h2 class="mocked-styled-0 sxatkf1">

一方で、Panda CSSではもっと細かく分解される。適当に切り出すとこんな感じ。

    .md\:fs_16px {
      font-size: 16px;
}

    .md\:fs_24px {
      font-size: 24px;
}

    .md\:mt_30px {
      margin-top: 30px;
}

分解されたスタイルは、クラス名のほうで合体される。

<h2 class="pb_5px bd-b_1px_solid_{colors.accent} mb_20px lh_1.8 fs_20px fw_bold md:fs_24px">

今回は問題にならなかったが、classがひとつであることに依存したような造りにもしなっているのであれば注意が必要。

カスケードレイヤー (@layer) を使っている

Cascade Layers  を参照。Panda CSSのみのときはたいして気にならないが、組み合わせるときに注意が必要。

バンドラの外でTypeScript ASTからスタイルを抽出する

zero-runtime CSS-in-JSのひとつのやりかたとして、linariaのようにwebpackなんかのバンドラの処理に手を入れる形でスタイル出力の変換をする形があるが、Panda CSSはts-morphを使ってバンドラの外でTypeScript ASTをparseする形でスタイルを抽出・出力する。

これはviteだのturbopackだのの話に巻き込まれにくいというメリットがあるが、linariaとは異なるタイプの制限があるので、知っておく必要がある。詳しくはマニュアルのDynamic stylingを参照。

eslint pluginによる静的検証がある

公式にeslint pluginがある(@pandacss/eslint-plugin)ので、TypeScriptの型チェックが及ばない部分、例えば動的な変数や変なトークン名なんかはeslint pluginによって検証できる。

zero-runtime らしい制約をlintでチェックしてくれるのは助かる。

移行時のポイント

上記を踏まえつつ、移行時に役に立ったり、ひっかかったりしたポイントを書く。

記述はLLMでだいたい移行できた

linariaからpandacssへの移行をお願いするだけで、だいたい書き直してくれた。後述のkeyframeを移したり、tokenに書き換えたりするところはできなかったので、手動で対応した。

ChatGPTならgpt4o-miniでだいたい行けたし、ollama + phi-4でも書き換えられた(めちゃ遅いけど)。

今回はstyled指定よりcss指定のほうがよく、diffの都合その場のほうがよかったりした(classNameのほうに移そうとしてくれたりしなかったりする)ので、そのあたりは指示した。

変数参照は基本できない→tokenを使う

はまったところとして、importした変数を参照できないので、tokenに置き換える必要がある。Design Tokensを参照。

これは、eslint pluginのno-dynamic-styling (recommendedに入っている) でわかるので、潰していけば良い。

lintでなんとかなったが、panda debugも知っておくと良いかも。

resetを持っている 

pandaは自身でresetを持っている。

もともとが雑だったので、今回はpandaのresetに乗るようにしたが、 preflight オプションでオフにできそうなので、このあたりも見ると良さそう。

素のstyleはlayerに気をつける

もともとaのようにグローバルにスタイルをつけていたのだけど、移行したらそっちが勝ちすぎたので、その部分に@layerの指示を入れた。今回はCascading Layersの記述の通り、@layer resetを指定。

keyframesの定義はconfigに

keyframesはその場に定義できないので、configに移した。configに書く設定はいくつかあるので気をつける。

eslint設定はカスタマイズが必要そう

基本的には@pandacss/recommendedで良いと思うのだけど、今回は以下を足した。

    "@pandacss/no-hardcoded-color": "off",
    "@pandacss/no-unsafe-token-fn-usage": "off"

no-hardcoded-colorは移植の都合。no-unsafe-token-fn-usageは'{sizes.foo}'ってわざわざ書かなくても'foo'でいいところはそうしましょう、みたいなオプションなのだけど、{sizes.foo} みたいに書くとno-invalid-token-pathsが機能しやすいので無効にして{}やtoken()を積極的に使っていくことにした。

記述の妥当性のほかにも、好みの指定もできて、prefer-longhand-propertiesなんかは役に立ちそう。no-margin-propertiesをCSS-in-JSが持っているのはちょっと面白い。

おわりに

思ったより考えてあるなって思った。ひっかかるポイントはあるけど、もうちょっと使ってみようと思う。

2024-12-18

Web入門時に読んだデザインの本まとめ(復刻版)

今回は、下書きがお蔵入りになっていた、デザインを勉強するときに読んだ本のまとめがでてきたので、微調整して公開する。2014年頃にGUIアプリからWebにスイッチした際に書いたもの。基礎的な内容をメインに勉強したので、今も概ね有用と考えている。

以下からは、基本的に本文は2014年のもの、注や指定があるのは2024年のもの。勉強し始めの感想と、10年後の感想をあわせて読むと、モチベーション的に学びやすいかなと思いこのような形にする。

Webサイトなんかを作っていて、どうもデザインする部分に苦手意識があるというか、作業を進める上でネックになっている感じがしたのだけど、 「別に勉強すればいいんじゃね?」ということに気づき、デザインの本を読んで勉強してみた。そのときに読んだ本たちを一挙紹介。

自分の目標は先の通り、Webサイト/アプリを作ることだけども「Webに関する本はWebの実現方法が主体で、 見た目のデザインそのもののセオリーとかは紙のデザインの本が強くない?」と本屋さんで本を探してて思ったので、 紙ベースの書籍やフライヤーなどを作るためのデザインの本を主に読んだ。そのため、 「デザイン」と広く言っているが、プロダクトデザインやインテリアデザインみたいな話は申し訳ないが入っていない。

この記事の読み方

本の情報のほかに、私の主観で以下の内容を書いている。

  • おすすめ度
  • ジャンル: レイアウト、色彩、タイポグラフィ
  • アプリケーション: なし

ジャンルは、「この手のデザインはレイアウト、色彩、文字に分類される気がする」と思ったので、勝手に分類している。 エディトリアルデザイン的には、装丁の部分、印刷や紙についての取り扱いも知識の一ジャンルとしてある気がするが、私がフォーカスしようと思っていない部分なので、ジャンルには入れていない。

アプリケーションは、そのアプリケーションの使い方を取り扱っている書籍について、記憶に残ったものを書くことにした。べったりツールを使っていく書籍もあるが、若干触れるだけというのもあるので、 その本の雰囲気を表す一つの方向性として見てもらえるといいと思う。

日常の資料作りをサポートする、あらゆる人向けのデザイン書籍

エディトリアルデザインの入門書には、本や1枚ものを作ることを目指す本があるのは当然として、 日常的に作るものを題材にした、そもそも本だとかを作ることを目指さない本もある。 とっつきやすいものとして、こういった内容だったものをまずは紹介。

伝わるデザインの基本 よい資料を作るためのレイアウトのルール

2024リンク: 伝わるデザインの基本 増補改訂3版 よい資料を作るためのレイアウトのルール / 技術評論社
  • おすすめ度: ★★★
  • ジャンル: レイアウト / 色彩 / タイポグラフィ
  • アプリケーション: Microsoft Office

資料を作るあらゆる方面の人におすすめの書籍

デザインそのもので仕事をしていくような人たちではなく、むしろ文書、プレゼン資料やポスターのような日常的な資料を書く人たちが、伝わりやすいデザインを実現するための本。ほぼ後述のノンデザイナーズ・デザインブックのような内容なのだが、書体や題材が現在の日本人にとって馴染みのある内容で、 非常に頭に入ってきやすい。デザイナー向けの書籍と異なり、 ツールやフォントもそこにあるものを使って実現しましょう、という形になっているので、すぐ試せる。 「センスないから読みやすい資料作るの苦手だわー」と思っている人にぜひ読んで欲しい。

基本的には伝わるデザイン|研究発表のユニバーサルデザインを書籍化したような内容であるが、紙の話であり、紙のほうが読み進めやすいので本がおすすめ。

ただし、デザイナー向け書籍と比べるとあっさりと記述されているので、興味を持ったらこの後紹介しているような 本を読んでみるといいと思う。

(2024注) 流行がありそうな領域なのに、2014年発売から今でも支持されてるのすごい。

ノンデザイナーズ・デザインブック

2024リンク: ノンデザイナーズ・デザインブック / マイナビ出版
  • おすすめ度: ★★☆ (2024注: 基本★★★だけど日常資料レベルなら★★)
  • ジャンル: タイポグラフィ / レイアウト
  • アプリケーション: なし

非デザイナー向けデザイン書籍の定番

非デザイナーがデザインの話を勉強すると言ったら必ず出てくるであろうこれ。気にしない人がやってしまいがちな例を直しながら、 どういったものが読みやすく伝わりやすいのか、日常的な書類などの身近な例で解説していく。

確かに「コントラスト」「反復」「整列」「近接」という話が、非デザイナーにも伝わるようよく整理されていて読みやすい。 しかし、欧文主体でしかも作例も古い感じなので、ちょっと入ってきにくい。昔読んで、最近勉強したタイミングで また読んだのだけど、欧文の書体選びとか、後から読んだからこそ参考になった(頭に入った)感がある。 なので、読んでおく価値はあると思うけども、時間の限られたサラリーマンでも必読、という感じではないかな、という感じ。

(2024注) 改訂されているようなので、読みやすくなっているかもしれない。一度見てみると良いかも。いろいろ作ってみたあとの2024年のほうが、この本の価値を感じている。

デザイナーの勉強の書籍(基礎系)

実例がまずあってうんぬんというよりは、色は色相/明度/彩度があって、みたいな各構成要素ごとに説明していくようなスタイルで 説明されている書籍をこちらに並べている。

難しさは、先のものと同じくらいのものもあるが、安くない書体を使うことが前提だったり、印刷会社での印刷が前提だったりといった部分が 多く占めるものに関してはこちらに分類している。

タイポグラフィの基本ルール プロに学ぶ、一生枯れない永久不滅テクニック

2024リンク: タイポグラフィの基本ルール-プロに学ぶ、一生枯れない永久不滅テクニック / SB Creative
  • おすすめ度: ★★★
  • ジャンル: タイポグラフィ
  • アプリケーション: なし

文字組について易しく、しかしきっちりと解説している一冊

基本的には文字の取り扱い(書体選び、アキの話などなど)について、他のタイポグラフィの書籍同様に解説している本。 タイポグラフィの知識が全くなくても読めるレベルの読みやすさでありつつも、詳しい例を載せながらきっちりと解説している。 例えば、アキは適切に、てな感じでさらっと流す書籍も見受けられるが、本書では複数の例で、色付けしたりしてわかり 易くなるように説明されている。専門用語にはきちんとふりがなが振ってあるのもうれしい(「平体」とか)。 タイポグラフィを名乗っているが、紙面レイアウトについても触れられている。そのロジックならそれは単にやり方の一つだよね、というのを思う部分もあるわけではないが、 実例も多く読みやすいので、おすすめの一冊である。

配色&カラーデザイン プロに学ぶ一生枯れない永久不滅テクニック

2024リンク: 配色&カラーデザイン ~プロに学ぶ、一生枯れない永久不滅テクニック~ / SB Creative
  • おすすめ度: ★★★
  • ジャンル: 色彩
  • アプリケーション: なし

色の基礎知識と配色の実例について読みやすく説明。PCCSは知っておくべき

色彩調和や配色手法などの基礎知識と、Webサイトを並べて配色について説明した事例集。似たようなタイトルついてるしタイポグラフィのやつとシリーズなのかな? 色彩検定ででてくる話が一通り書いてあるのだけど、読むためのものなので問題集より読みやすいし、まぁ実例がWebサイトなので色彩検定ででてくるファッションとかインテリア/エクステリアの話より馴染みやすい。

PCCS(Practical Color Co-ordinate System:色相とトーンという二軸での色の表現)の使い方を、この本で知ったおかげで色を考えやすくなった。もっと「ちゃんと」表現できるCMYKやHSL(HSB)の状態でも、ある程度色をシステマチックに決めるための方法論を自分の中で持っていれば要らないのだろうけど、そうでなければPCCSとその使い方を知ると便利。ざっくり色を考える方式としては、自分の中で不滅だと思う。

(2024注) 2024年の今でも色を選ぶときのやりかたはこの本の発想がベースになっていて、それを便利にするために作ったpcolorsは今でも使っている。不滅の看板も伊達ではなく、とても助かっている。

デザイン解体新書

2024リンク: デザイン解体新書 / ワークスコーポレーション
  • おすすめ度: ★★★
  • ジャンル: レイアウト / 色彩 / タイポグラフィ
  • アプリケーション: InDesign / Quark Xpress / Illustrator

前提知識が必要だが、実例が非常に多く、実践的で読み応えがある一冊

実際に著者の方がデザインされた本などを題材にデザインについて解説していく一冊。 ある程度の知識があることは前提としており、いきなり読むとつまづく。 例えば「二分アキってなんだっけ?」といったように用語の方面だとか、「こっちのツメはいいけど、こっちはダメといわれても、どう違うのこれ?」という感じで、見慣れてないと違いがわからない可能性すらある(最初そうだった)。 しかし、「ああ、これが紙の世界のスタイルシートなんだ」と思うレベルに要素要素に詳しく書かれた指示と、それに対して、ただきれいだからこうしたという話ではなく、 ちゃんと理由づけて解説されているのが素晴らしい。

2006年なので、若干古さを感じなくもないが、それは置いてでも価値がある。エディトリアルデザインに興味があれば読むべき一冊。 ここに書かれている内容も、やり方の一つではあるのだと思うが、ここまで徹底解説しようとすると、そうならざるを得ない。 すでにWebデザインだとか、デザインの仕事をしていたとしても、書籍の編集に携わったことがないのであれば読んで損しないと思う。

(2024注) いまでもめっちゃ推し。違う頭があることがわかる貴重な本。

キチンと身につけたい人のためのデザインの総復習

2024リンク: キチンと身につけたい人のための デザインの総復習。 / MdN BOOKS
  • おすすめ度: ★★☆
  • ジャンル: レイアウト / 色彩 / タイポグラフィ
  • アプリケーション: InDesign / Illustrator

タイトル通り復習という感じの書籍

レイアウト、色、文字、断裁等について全般的に通して説明した本。同じように全般的に説明している「伝わるデザインの基本 よい資料を作るためのレイアウトのルール」が日常的に作成する資料にフォーカスしたのに対して、こちらは出版物を対象にすることでもう少し踏み込んだ感じになっている。フォントを自由に選びにくいといった文字に対する制約がなくなったので、特に文字についてノンデザイナー向け書籍より詳しい(Wordで1文字ずつ詰め具合を調整しろって言われても辛いものがあるものね)。紙面が前提だし、デザイナーの立場でこういうことになったらこうしてしのぐ的な話もあって、ワークフローを意識した感じ。エディトリアルデザインの分野に入っていくときに最初のころに読むとか、部分部分でやってた人が弱点を見つけるみたいなことを目指したであろうという感じの一冊。

標準DTPデザイン講座

2024リンク: 標準DTPデザイン講座 基礎編 / 翔泳社
  • おすすめ度: ★☆☆
  • ジャンル: TODO
  • アプリケーション: なし

ワークフローの勉強にはよさそう

DTPとはなんぞ、という本。こうしたらよりよいデザインになります、というよりは、こうやって進めていきます、こういうのを使います、という感じの本。 デザインとしてはあまりよくない例があるように思うので、読むのであれば、細かいことは気にせず大枠こんなか、という読み方をすればいいと思う。「CTPってなんだっけ?」とか「校正のフローはこんな感じ」か、みたいな知識的な側面で読むと参考になるとは思う。 なお、2004年と古いので、しょっちゅう変わるものの話についてはあまり役に立たない。PCのスペックとか。

TYPOGRAPHY 2014/05

  • おすすめ度: ★★☆
  • ジャンル: タイポグラフィ
  • アプリケーション: なし

文字が大好きな人たちが書いてることがわかる雑誌

読むほど文字が好きな人たちが書いてるんだろうなってことが伝わってくる。文字について学ぼうと思っていたときに、特集「楽しく学ぶ文字入門」という表示を見かけ読んでみた。この特集はよく書かれており、本当はこれを万人におすすめに入れようかと思ったが実作業的な側面では 「タイポグラフィの基本ルール」のほうが読みやすく、例も多いのでやめた。とはいえ「フォントの見分け方」など、作業とは別の観点ではあるが、 よくでてきそうな疑問について書かれている。何より文字に対する愛が伝わってくるのがよい。買って損はない号だと思う。

(2024注) 雑誌のものを今取り上げなくていいとも思ったのだけど、文字が好きな人がいるっていうのは自分のなかで新鮮な学びだったので、そう思ったときのテキストを残した。ピンポイントでこの本じゃなくても、そういう想いのある本を読んでみると、新しい世界が見れるかも。

デザインのルール、レイアウトのセオリー。

デザインのルール、レイアウトのセオリー。 / MdN Books
  • おすすめ度: ★★☆
  • ジャンル: レイアウト / 色彩 / タイポグラフィ
  • アプリケーション: なし

レイアウトの観点のまとめ

「対称性」「統一感」「メタファー」など、挙げる観点に対して、実際の例や作った例を出して、その価値について解説していく本。 読み進めて難しくはないので、レイアウトが生み出す価値を知っておくという意味で、先に読んでおく側に分類してもよかったのだけど、 実例ベースでの解説であるし、どのような構成要素があるか知っていないと、価値が薄れてしまうかなという意味で、応用編に分類した。 作っているときに、この観点で考えたかな、という意味でチェックポイントとして良いのではないかと思った。

Design Rule Index[第2版]― デザイン、新・25+100の法則

(2024注) 結構いい本だったと思うのだけど、タイトルしか下書きになかったのでタイトルだけ紹介。

その他デザイン関連書籍ピックアップ

上述のものより、ジャンルを絞ってピンポイントで書いているような本を取り上げる。3つのジャンル分けは広すぎるものもあるので、特にしない。

たのしいロゴづくり -文字の形からの着想と展開

2024リンク: たのしいロゴづくり / BNN
  • おすすめ度: ★★☆

タイトルの通り。文字からどうやってロゴにしていくか、そのアイデア集のような感じ。

Photoshop+Illustrator Design Technique

2024リンク: Photoshop + Illustrator Design Technique / 翔泳社
  • おすすめ度: ★☆☆

作例がいくつも載っていて、どうやってそれを作っていくか、というのを説明した本。ツールの使い方のチュートリアルというよりは、 どういったテクニックを使っていくか、というお話。

たのしいインフォグラフィック入門

2024リンク: たのしいインフォグラフィック入門 / BNN
  • おすすめ度: ★★☆

図解するとはどういうことか、どうやって進めていけばいいか、という本。

プロになるためのWebデザイン入門講座 実践で役立つPhotoshop&Illustrator徹底ガイド

2024リンク: プロになるためのWebデザイン入門講座 実践で役立つPhotoshop&Illustrator徹底ガイド / 技術評論社
  • おすすめ度: ★☆☆

Webデザインの用途でPhotoshopの使い方を説明した本。悪くはないと思うけども、Webの「PhotoshopでWebデザインをはじめよう! 」のほうが読みやすいかなーといったところ。

おわりに

当然の話だけど、こうして読んできても、すぐに美しいデザインができるようになったりはしない。 やっぱり経験だな、というのが感想。でも逆に言うと、天才の粋に行かなければ、経験なのかなと思う。環境という話も大きいと思うけども。

読んできて全く役に立たなかったかというと、そんなことはなく、いい悪いとはどういうことか、というのが読む前と比べるとかなり見えるようになったと思う。 そういった意味では、方向性が少しは見えるようになったという意味で、読んできて間違いなく正解だった。 実用性という意味でも、デザイナーの意図が汲み取りやすくなったので、作りやすくなったと感じている。

なお、これを見て「それ読むならこれ読んで!」てな本があれば教えてもらえれば読もうと思うので、おすすめがあればぜひ教えてください。


2024年に読んで

「やっぱり経験だな」というのは、実践で学ぶことが多いという意味で、それはそうと思うのだけど、この本たちを読んでいたおかげで助けられたという場面はかなりあった。この10年の間に、作り運用してきたプロダクトたちの中には自分が表層まで手掛けてきたものもいくつかあるのだけど、使いやすいという評判も頂いている認識で、それはこのときの学習の影響が多分にあると思う。10年前に勉強しておけばよかったなと思うことはよくあるけど、これは10年前に勉強してよかったと思える類だなと、当時の10年後から振り返って思った。

(ブログのデザインがボロボロなことにも気づいてしまったのでなんとかしたい)

2024-11-25

exiftoolとpolarsでよく撮っている焦点距離を調べる

スマホを買い替えて以来、カメラで撮るよりスマホのほうが撮りやすいところあるなあと思っていたのだけど、いまさら焦点距離の微妙な違いが影響しているのではないかと気づいたので、カメラ購入以来控えてきたレンズ購入を検討することにした。キットレンズで行けるところまで行こうと思っていたがついにこのときが来てしまった。

カメラのレンズはSONY FE 28-60mm F4-5.6なので、28mmスタート。スマホはPixel 8 Proで1xが24mmらしい。先の困り具合から24mmは必要ということがわかる。しかし、24mmで十分かはわからない。単焦点で良いのか?ズームのほうが良いのか?そこで実際に撮っている写真からどのあたりをよく使うのか調べてみることにした。

ステップ1: exiftoolで写真ファイル群から焦点距離を収集する

写真のファイルに付与されたEXIF情報にレンズや焦点距離は記録されている。このEXIF情報を取得するツールは様々あるが、exiftoolを使うとコマンドラインで取得することができる。

コマンド例は以下のとおり。

$ exiftool -LensId -FocalLength -csv -ext jpg 20230103-浅草
SourceFile,LensID,FocalLength
20230103-浅草/DSC04803.JPG,Sony FE 28-60mm F4-5.6,35.0 mm
20230103-浅草/DSC04799.JPG,Sony FE 28-60mm F4-5.6,28.0 mm
20230103-浅草/DSC04818.JPG,Sony FE 28-60mm F4-5.6,28.0 mm
20230103-浅草/DSC04814.JPG,Sony FE 28-60mm F4-5.6,35.0 mm

exiftoolはファイルだけでなくディレクトリも引数に撮ることができ、-ext jpgとすると拡張子の絞り込みまでできる(ARWとどっちかで良いので)。さらに、-csvでCSV出力にできるし、-LensIdと-FocalLengthのようにフィールドを限定することもできる。

1ファイルを指定するとすべての情報を出すくらいの機能しかないと思っていて、シェルスクリプトでも書いて回すかと思っていたのだが、コマンド一発で済んでしまった。

ステップ2: polarsで集計する

sqliteに入れてもよかったんだけど、Polarsというものが使われているらしいのでせっかくなので試してみた。PandasみたいなノリだけどSQLも受け付けるらしい。

インストールはpip install --user polarsで入れた。

$ ipython3
Python 3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.31.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import polars as pl

In [2]: pl.Config(tbl_rows=10000)
Out[2]: <polars.config.Config at 0x7e5fd1f90400>

In [3]: df = pl.read_csv('/tmp/summary.csv')

In [4]: df.sql("""
   ...: WITH s AS (SELECT LensID,FocalLength,count(*) AS count FROM self GROUP BY LensID,FocalLength ORDER BY FocalLength),
   ...: a AS (SELECT count(*) as total FROM self)
   ...: SELECT s.LensID, s.FocalLength, s.count::float * 100 / a.total AS percent FROM s CROSS JOIN a
   ...: """)
Out[4]: 
shape: (33, 3)
┌────────────────────────┬─────────────┬───────────┐
│ LensID                 ┆ FocalLength ┆ percent   │
│ ---                    ┆ ---         ┆ ---       │
│ str                    ┆ str         ┆ f64       │
╞════════════════════════╪═════════════╪═══════════╡
│ Sony FE 28-60mm F4-5.6 ┆ 28.0 mm     ┆ 59.369925 │
│ Sony FE 28-60mm F4-5.6 ┆ 29.0 mm     ┆ 3.150373  │
│ Sony FE 28-60mm F4-5.6 ┆ 30.0 mm     ┆ 2.143553  │
│ Sony FE 28-60mm F4-5.6 ┆ 31.0 mm     ┆ 2.273465  │
│ Sony FE 28-60mm F4-5.6 ┆ 32.0 mm     ┆ 2.208509  │
│ Sony FE 28-60mm F4-5.6 ┆ 33.0 mm     ┆ 1.52647   │
│ Sony FE 28-60mm F4-5.6 ┆ 34.0 mm     ┆ 1.721338  │
│ Sony FE 28-60mm F4-5.6 ┆ 35.0 mm     ┆ 2.53329   │
│ Sony FE 28-60mm F4-5.6 ┆ 36.0 mm     ┆ 2.143553  │
│ Sony FE 28-60mm F4-5.6 ┆ 37.0 mm     ┆ 1.85125   │
│ Sony FE 28-60mm F4-5.6 ┆ 38.0 mm     ┆ 2.208509  │
│ Sony FE 28-60mm F4-5.6 ┆ 39.0 mm     ┆ 1.753816  │
│ Sony FE 28-60mm F4-5.6 ┆ 40.0 mm     ┆ 1.623904  │
│ Sony FE 28-60mm F4-5.6 ┆ 41.0 mm     ┆ 0.779474  │
│ Sony FE 28-60mm F4-5.6 ┆ 42.0 mm     ┆ 0.974342  │
│ Sony FE 28-60mm F4-5.6 ┆ 43.0 mm     ┆ 0.779474  │
│ Sony FE 28-60mm F4-5.6 ┆ 44.0 mm     ┆ 0.876908  │
│ Sony FE 28-60mm F4-5.6 ┆ 45.0 mm     ┆ 0.84443   │
│ Sony FE 28-60mm F4-5.6 ┆ 46.0 mm     ┆ 0.552127  │
│ Sony FE 28-60mm F4-5.6 ┆ 47.0 mm     ┆ 0.389737  │
│ Sony FE 28-60mm F4-5.6 ┆ 48.0 mm     ┆ 0.129912  │
│ Sony FE 28-60mm F4-5.6 ┆ 49.0 mm     ┆ 0.584605  │
│ Sony FE 28-60mm F4-5.6 ┆ 50.0 mm     ┆ 0.422215  │
│ Sony FE 28-60mm F4-5.6 ┆ 51.0 mm     ┆ 0.032478  │
│ Sony FE 28-60mm F4-5.6 ┆ 52.0 mm     ┆ 0.324781  │
│ Sony FE 28-60mm F4-5.6 ┆ 53.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 54.0 mm     ┆ 0.519649  │
│ Sony FE 28-60mm F4-5.6 ┆ 55.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 56.0 mm     ┆ 0.032478  │
│ Sony FE 28-60mm F4-5.6 ┆ 57.0 mm     ┆ 0.16239   │
│ Sony FE 28-60mm F4-5.6 ┆ 58.0 mm     ┆ 0.129912  │
│ Sony FE 28-60mm F4-5.6 ┆ 59.0 mm     ┆ 0.584605  │
│ Sony FE 28-60mm F4-5.6 ┆ 60.0 mm     ┆ 7.047743  │
└────────────────────────┴─────────────┴───────────┘

サブクエリで怒られたのとFROMにカンマ区切りにしたら怒られたから書き換えたけど、それ以外は自然に書けた。CTEも使えるしすごい。

端以外はサマってみたいので、polarsのやりかたも使ってみる。

In [80]: s2 = s.filter(pl.col("FocalLength").is_in(["28.0 mm", "60.0 mm"]).not_())

In [81]: s3 = s2.select(pl.col("percent"), pl.col("FocalLength").str.extract(r"^(\d)", 1).alias("T"))

In [82]: s3.group_by("T").agg(pl.col("percent").sum())
Out[82]: 
shape: (4, 2)
┌─────┬───────────┐
│ T   ┆ percent   │
│ --- ┆ ---       │
│ str ┆ f64       │
╞═════╪═══════════╡
│ 3   ┆ 20.363754 │
│ 5   ┆ 2.53329   │
│ 2   ┆ 3.150373  │
│ 4   ┆ 7.534914  │
└─────┴───────────┘

filter()とかselect()にはpl.col("name")を使って何かする。これはpolars.Exprpolars.Expr.strを見ればいいっぽい。 

結果を見て

集計した結果以下がわかった。

  • 28mmが約60%。めちゃ使う。
  • 29mm〜40mmはそれぞれ1%以上ある。まあまあ使うらしい。
  • 41mm〜59mmは1%未満。あまり使わないみたい。
    • 30mm台をまとめて20%, 40mm台は7.5%
  • 60mmは約7%。
24mm単焦点でも困らないかもしれないが、40mmまであると便利そうというところか。この結果を見つつ検討したい。

2023-12-19

例でわかるTypeScript Utility Types

これはTypeScript Advent Calendar 202319日目の記事です。例とともにUtility Typesの紹介をします。

Utility Typesとは

Utility Typesは、型から型を生成するためのTypeScript組み込みの型です。ライブラリを書いているような方ならば普段から使っていると思いますが、Utility Typesが何かわからない方もPartial<Foo>やRecord<string, string>なんかは見たことがあるのではないでしょうか。

どんなものがあるか?というのはドキュメントを見てもらえれば良いので、 詳細はそちらに譲りますが、今日はどういうときに便利なのか?という視点でUtility Typesの主要なものを分類しつついくつか紹介します。

TypeScriptが提供する型: 作るもの

Record<Keys, Type>

Recordは、Keysをキーとするプロパティを持ち、そのプロパティの値の型はTypeであるという型を生成する型です。これは主にデータを格納するためのオブジェクトの型を定義するときに使います。

type FooRecord = Record<"prop1" | "prop2", string>;
// → { prop1: string, prop2: string }

上記のようなケースだとベタに定義したほうが良いのでありがたみはないですが、keyの集合がすでに定義されている場合は便利です。

interface BarData {
    prop1: string;
    prop2: number;
    prop3: Date;
}

type BarDataErrors = Record<keyof BarData, string>
//  → { prop1: string, prop2: string, prop3: string}
stringのように広い型を指定することもできます。 
type StringRecord = Record<string, string>;

もともとRecordは

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

と単純なのでこれを書いてもいいのですが、意図を明示できる上に簡潔に記述することができます。

TypeScriptが提供する型: 主にプロパティを加工するためのもの

Pick<Type, Keys> / Omit<Type, Keys>

PickはTypeのプロパティをKeysに絞った型を作ります。OmitはTypeのプロパティからKeysを外した型を作ります。ホワイトリストとブラックリストですね。

これらの使い方は幅広いのですが、既存の関数のラッパーを作るときに便利です。

例えば、ButtonというReactコンポーネントがあり、propとしてsizeを取るとき、このsizeを“large”に固定したLargeButtonというコンポーネントを作りたいようなケースを考えます。このときにLargeButtonのpropsの型をButtonからsizeプロパティを外したものとして作ると、Buttonと同じだがsizeは渡せないということを明確にすることができます。

type LargeButtonProps = Omit<ButtonProps, "size">;

function LargeButton(props: LargeButtonProps) {
    return <Button {...props} size="large" />
}

Extract<Type, Union> / Exclude<UnionType, ExcludedMembers>

ExtractはTypeからUnionに代入可能なものを取り出した型を作り、ExcludeはUnionTypeからExcludedMembersに代入可能なものを外した型を作ります。

これは、discriminated unionとあわせて使います。例えば、type: “success”のときは正常、type: “error”のときはエラーであるResultオブジェクトを考えます。

interface ResultSuccess {
    type: "success";
    data: Record<string, string>;
}

interface ResultError {
    type: "error";
    errorMessage: string;
}

// 既存ライブラリが合わさっている型だけexportしていたり
export type Result = ResultSuccess | ResultError;

このような場合に、ExtractやExcludeを使うと限定した型を得ることができます。

type SuccessType = Extract<Result, {type: "success"}>
// ResultSuccess
type NonSuccessType = Exclude<Result, {type: "success"}>
// ResultError

Partial<Type> / Required<Type>

Partialはプロパティを任意にし、Requiredはプロパティを必須にします。

Partialは例えばプロパティを部分的に受け取ってオブジェクトを更新するような関数を作る場合に使えます。

function updateFoo(foo: Foo, fields: Partial<Foo>) {
    return {...foo, ...fields };
}

もともとすべてのプロパティがrequiredとは限らないので、Partialと比べるとRequiredを素で使うケースは少ないと思いますが、他との組み合わせ、例えばPickで明確にして使う、ということもできます。

interface Foo {
    name?: string;
    age?: number;
    description?: string;
}

type Bar = Required<Pick<Foo, "name" | "age">>;

NonNullable

NonNullableはTypeからnullとundefinedを外します。

type NullableType = string | null;
type NonNullType = NonNullable<NullableType>;
// string

まあこれはわかりやすいですよね。

TypeScriptが提供する型: 関数に関するもの

関数の型に対して何かをする型は、汎用的に関数を取ってそれをどうにかする関数には不可欠なものですが、具体的な関数がわかっている場合にも以下のような使い方をすることができます。

ReturnType

ReturnTypeは関数の型から戻り値の型を得ます。

ReturnTypeは、関数はわかっているが型にアクセスできない、または定義されていないようなとき、例えばfactory的な関数でその型の明示がないようなケースのときに、その値を中継するなどのことをするために型が欲しいときに便利です。

function createFoo() {
    return {
        a: "abc",
        b: 123
    };
}

type CreateFooReturn = ReturnType<typeof createFoo>;
// { a: string, b: number }

Parameters

Parametersは、関数の引数の型を得られます。ラッパー関数を作るような場合に、既存の関数の型をそのままに引数を足したいなどのときに便利です。

function foo(a: string|number) {
    return a;
}

type FooParameters = Parameters<typeof foo>;
function wrappedFoo(a: FooParameters[0], b: string) {
    other(b);
    return foo(a);
}

Awaited

asyncな関数だとreturn typeがPromiseになりますが、そのままでは扱いにくいです。 そんなときにAwaitedを使うと剥がすことができます。

interface Foo {
    a: string;
    b?: number;
}

export async function createFoo(): Promise<Foo> {
    return {
        a: "bar",
        b: 123
    };
}

///

type RequiredFoo = Required<Awaited<ReturnType<typeof createFoo>>>;
// {a: string, b: number}

自分で作る

Utility TypesもTypeScriptで定義された型でしかないので、このような型は自分でも作ることができます。

例えば、部分的に任意にするPartialPartiallyというものを作ると以下のようになります。

// すでに定義されているHoge
interface Hoge {
    name: string;
    description: string;
    startAt: Date;
    finishedAt: Date;
}

type PartialPartially<T, K extends keyof T> = Partial<Pick<T, K>> & Pick<T, keyof Omit<T, K>>;
// 終わってない(finishしていない)かもしれない型を作る
type HogeMayUnfinished = PartialPartial<Hoge, "finishedAt">;

このように、型の操作の仕方と、型の操作そのものを分離しておくと、あとから見たときの悩み度合いが減って良いです。

おわりに

使わないに越したこともないものもありますが、どうしようもないときは知ってるか知らないかで型の扱いの難易度が変わってきます。

Utility Typeを使って、型操作を楽にしていきましょう。

2023-03-27

PHPerKaigi 2023に参加してきました

YAPC::Kyoto 2023を見て、PHPの世界をまた見てみるのいいかもと思ってPHPerKaigi 2023に行ってきました。結果、今のPHPを知れたという意味でもよかったし、ああいう雰囲気の場所が戻ってきたんだというのをまた体感できたという意味でもよかったです。スタッフの皆様、参加者の皆様お疲れ様でした。

以下、セッションのメモです。

---

勉強になったセッション

勉強になったのはこの「PHPの配列の内部実装について学びたくなった。

PHP4のときにガラケー向けアプリケーションを書いていて、arrayの性能に悩まされたことがあり、あのarrayはなあと思っていたのだけど、今更ではあるもののPHP7で真の配列が導入されたことをここで知れたのでよかった。

印象に残ったセッション

印象に残ったのは「名付けできない画面を作ってはならない - 名前を付けるとは何か

設計の話をするセッションだと思っていたのだけど、もうちょっと広く見ていてなるほどなと思った。気持ちの面も結構大事な話だと思う。

2023-03-21

YAPC::Kyoto 2023に参加してきました

京都観光も兼ねて、YAPC::Kyoto 2023に参加してきました。面白かった。遠征でのカンファレンスの参加は人生初なので、そういう意味でも新鮮な体験だった。

印象に残ったセッション

みんな面白かったのだけど、印象に残ったのはmacopyさんのデプロイ今昔物語だった。


よくわからないホスティング環境を借りてPHP(確か4だったと思う)のプログラムを「ホームページ」に置いてみたりとか、読めないPerlを書いて自分で困ったりしていた時代を思い出して、原点に帰った気分になれてとてもよかった。

遠征での学び

日曜日にYAPCで、京都にせっかく行くのだから土曜日は観光しようと決めていて、土曜日に観光するなら朝から行けたほうがいいよねということで金曜日に行って泊まり、日曜日遅くなるかもしれないから月曜日に帰ろうということで、結果3泊4日になった。歩数計を見ると40km近く歩いていることになっているので、結構満喫したと思う。

旅程に合わせてホテルを変えたので、服は洗濯しちゃえば運ぶの楽じゃないか?と思ってチャレンジしてみたものの、旅から帰ってきたあとコインランドリーに行き来して洗濯するのは結構しんどいというのは学びだった。洗濯と乾燥が一緒ならいいのにな~

荷物まわりはuzullaさんがテクニックをまとめてくれているので、今度参考にして楽をしたい。

俺的!遠方カンファレンスの参加体験向上テク 2023最新版 - uzullaがブログ https://uzulla.hateblo.jp/entry/2023/03/21/132421