TypeScript|reaonly 不要論

ども、Nash です。

この記事は「TypeScript の readonly について、状況によっては不要なのではないか?という考えについてまとめた記事」になります。

では、見てみます。

TL;DR

  • readonly の使い勝手が少し微妙なので割り切って使わない選択もいいのでは、という考え方。
  • 具体的には、チーム・プロダクトがまだそこまで成長してなく、堅牢性を上げるよりもやることがある状態のとき。

背景

いままで readonly をあまり使ってこなかったが、「これってどんだけ有用なんだろう」と思って調べてみた。

利用箇所としては、関数の引数で配列 or オブジェクトを受け取るときに破壊的な変更をさせたくないので readonly を設定する。例えば sort は破壊的な変更をする関数のため関数内部で使っていて気付いたら配列を破壊してることもあるので。

const accending = (
-   list: number[]
+   list : readonly number[]
): number => {
-    return list.sort((a, b) => a-b);
+    return [...list].sort((a, b) => a-b);
}

TS Playground

readonly の問題点

引数に readonly を設定すると関数内部で破壊的変更が起きないのでより安全なコードになる。

これだけ見ると導入するほうがいいのでは?とも思うが個人的に考える TS の readonly の問題点を整理してみた。

(1) mutable first な設計

TS はデフォルトで mutable になっている点。逆であってほしい。いまからの変更は現実的ではないのはわかるがせめて compiler に config を導入してほしい

(2) readonly 宣言が冗長

Rust だと可変な場合は mut で TS だと普遍な場合は readonly となる。ただでさえほぼすべての引数に必要となる上に文字数的にも readonly って書くのはちょっと長い。

また必要に応じて書き方としてReadonly<T>である必要もあるのだが、どっちにしても長い。

この対策としてはエイリアスを設定してもいいのかなとも思う。

type R<T> = Readonly<T>;

(3) readonly がネストに対応してない

readonly の設定がネストしたオブジェクトに対応していない。そのため、自前でDeepReadonly<T>を作っておかないといけない。

しかも、DeepReadonly<T> であるべきところでReadonly<T>を使ってしまいそう。これの回避としてデータ構造の内部詳細を意識して型をつけないといけない。漏れをなくすならすべて常にDeepReadonly<T>にすべきで、必要な場所のみをreadonlyを抜くのがいいがコード冗長さに拍車がかかる。

const fn = (obj: Readonly<{ child: { a: number } }>) => {
  obj.child.a = 99;
  return true;
};

const obj1 = { child: { a: 1 } };
fn(obj1);
console.log(obj1.child.a); // => 99

(4) readonly が完璧に動作しないことがある

Object.assign 使うと readonly なのに破壊できる。readonly とは何だったのか。

const log = console.log;

const fn = (obj1: Readonly<{ a: number }>) => {
  return Object.assign(obj1, { b: 1 });
};

const input: Readonly<{ a: number }> = { a: 1 } as const;

log("input is", input); // => { a: 1 }
log("result is", fn(input)); // => { a: 1, b: 1 }
log("input is", input); // => { a: 1, b: 1 }

readonly の pros/cons

readonly 導入における pros/cons をまとめるとこうなる。

メリット

  • 関数のシグネチャから破壊的変更がないことがわかる(100%ではないが)
  • 誤って破壊的変更を入れてしまっていたら TS compiler が検知してくれる

デメリット

  • コードが冗長になる
  • readonly を設定しても破壊できるコードがありえるので 100%の担保にならない

結論

readonly を入れることでより堅牢になる点は間違いないが、個人的に使い勝手が悪く感じてじている。 そのため、メリデメを考えると常に脳死で使うべきでもないようにも感じた。

得られるメリットはもちろんあるがコードの冗長性などのデメリットを考えると、チームやプロダクトの成長具合に応じては「意図的に readonly は積極的に使わない」という運用をするのもいいかと思う。