3 / 3
状態遷移にスプレッドを使わない
型が防いでくれるのは「あってはならないものを書くこと」であって、「残っているものを引きずってくること」ではない。
ユニオンに変えた後
チームは安心した。ありえない状態はもうない。
状態を変えるコードは相変わらず手癖のまま書いた。既存の値は残し、変えるところだけ上書きする。短くて読みやすく、型チェックも通った。
setState({ ...state, status: "error", error: e });数週間後
エラーレポートツールを入れることになった。「エラー状態になったらstateを丸ごと送ろう。デバッグしやすいように。」
同じ週に別の人がヘッダーを直した。データがあればユーザー名を表示する。
if (state.status === "error") {
report(state);
}if ("data" in state) {
showUserName(state.data.name);
}そしてある日
ログを見ていた人がおかしなことに気づいた。エラーログのたびに、直前のユーザーの個人情報が丸ごと付いていた。
同じ日、アカウント切り替え中にエラーになったユーザーのヘッダーには、前のアカウントの名前が出ていた。
{
status: "error",
error: e,
data: 前の User,
}なぜこうなったか
スプレッドは前の状態のフィールドを全部コピーしてくる。そしてTypeScriptは、スプレッドで入った余分なフィールドをチェックしない。
ユニオンが防いだのは「error状態にdataを書くこと」だった。すでにあったdataがついてくることまでは防げなかった。
壊れる前に
状態ごとに遷移関数を用意し、それでだけ変える。関数は必要なフィールドだけで新しいオブジェクトを作るので、引きずられるものがない。
「既存のは残してここだけ」はAIが一番よく使う手癖でもある。毎回レビューで止めるより、書ける道をなくす方が安い。
const toLoading = (): State => ({ status: "loading" });
const toSuccess = (data: User): State => ({ status: "success", data });
const toError = (error: Error): State => ({ status: "error", error });
setState(toError(e));