Closed2

[TypeScript] readonly の不要論

snamiki1212snamiki1212

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 の問題点

TSのreadonlyの問題点としてざっとこれらがある。

  1. mutable first な設計
  2. readonly 宣言が冗長
    • Rustだとlet / let mutと完結だが、TSだとconst readonly / constとなって冗長。readonlyってすべてに書くのはちょっとコード量が長過ぎる。
    • また必要に応じてReadonly<T>である必要もある
  3. readonly 宣言がnestに対応してない
    • デフォルトでnestに対応していないので、自前でDeepReadonly<T>を作って使わないといけない。
    • しかも、DeepReadonly<T> であるべきところでReadonly<T>を使ってしまいそう。これの回避としてデータ構造の内部詳細を意識して型をつけないといけない。漏れをなくすならすべて常にDeepReadonly<T>にすべきで、必要な場所のみをreadonlyを抜くのがいいがコード冗長さに拍車がかかる。
    • playground
  4. readonly が完璧に動作しないことがある
  5. satisfies と併用できない?(TODO)
    • readonly を使う場所は関数の引数であるケースがメインなので、変数宣言をするときに使うsatisfies とは関係ないが、ただふと「この2つをあわせて使えるのか?」がちょっと気になった

代替

TODO: linterとか?

pros/cons

デメリット

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

メリット

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

結論

readonlyを入れることでより堅牢になる点は間違いないが、使い勝手がものすごく悪いデメリットを考えると脳死で必ず使うべきでもないようにも感じた。

得られるメリットもあるがコードの冗長性などのデメリットを考えると、チームの状況に応じて「意図的にreadonlyは積極的に使わない」という運用

readonly自体がまだコミュニティとして力がそこまで入ってないのでは?と感じて、というのも、Object.assign でreadonlyなのに破壊できたり、immutable-by-default flag みたいなどう見ても有益な設定がなかったりしたので。

感想

TS の言語設計の敗北なのか、負債で仕方がないのか、将来に解決する事案なのか判断がつかないがとりあえず「やっぱRustってすげーよな」って改めて思った。

このスクラップは2022/11/13にクローズされました