前にどうしてString型ではなくいろいろな型を使わないといけないのかというのをちゃんと答えることができなかったので、いろいろ考えてみた話です。
結論としては、String型は文字列におけるAny型みたいなものなので、型安全性が消失するかです。
String型ってなんだろう
String型は文字列を扱う型です。
プログラミング言語にもよりますが、ここでは特定の文字エンコーディングで扱われる0文字以上の長さを持った文字の連続している型として話を進めます。
フォーマットを持ったString型
String型として扱う文字列の中には一定のフォーマットを持った文字列が存在します。
107-6010
例えば郵便番号は3桁の0から9の文字+4桁の0から9の文字をハイフンで繋いだ文字列になります。(ハイフンを含めない、例外ケースなどについてはここでは触れない)
{
"data": {
"id": "1445880548472328192",
"text": "Are you excited for the weekend?"
}
}
もう少し複雑なケースではRFC 8259で定義されているJSON文字列もあります。例のケースではXのPostレスポンスのフォーマットをJSON形式にした文字列と言い換えてもよいかもしれません。
こういった特定フォーマットをもった文字列をString型として扱うということは、型情報からフォーマットされたという情報が欠落することになり、型安全性が消失したと言えます。
フォーマットを持たないString型
では、特定のフォーマットを持たない自由記述の入力を扱うようなString型はどうでしょうか。
わかりやすいところで言えばXでポストするメッセージなどが考えられます。
ただ、本当にこのメッセージはString型なのでしょうか?
実際にはXのメッセージにはいくつか制約があります。(例外ケースについては触れない)
1文字以上の文字列
140文字以下の文字列
メッセージの先頭と末尾にスペース、改行などは含まれない文字列
このようにフォーマットがないように見える文字列にも決まりごとがあり、String型とは違う型になります。そのため、フォーマットを持たない文字列もString型として扱ってしまうと情報が欠落することになり、型安全性が消失したと言えます。
型で表すというのはどういうことなのか
また、別の視点から話をすると型というのは制約です。
はじめに定義したString型とは特定の文字エンコーディングで扱われる0文字以上の長さを持った文字の連続している型という制約を持っています。
つまりこの制約の上であればどのような文字列を扱っても構いません。
tel: String = "abc"
例えば電話番号を表す変数がString型なら電話番号以外の文字列をいれても型制約の上では許されます。しかし、おそらくこれは仕様上許されません。
tel: Telephone = "abc" // Compile Error
仕様上許されないのであれば、素早く気づくためにコンパイルエラーにしたいというのが人情です。
String型って使わないの?
ここまでString型は使わない理由を書きましたが、いくつかのケースでは普通に使います。
型のない世界から値を受け取るとき
例えばユーザーからのHTTPリクエストの場合ではクエリパラメータ、フォームデータ、リクエストボディなどではString型として扱わないといけません。
そのため型の世界の境界を渡るタイミングで変換が必要になります。
String -> Result<ConstrainedString>
外から内へ境界を渡るときは制約が厳しくなるため失敗する可能性があります。
ConstrainedString -> String
逆に内から外へ境界を渡るときは制約が緩くなるため失敗の考慮は不要になります。
制約を持った型を作るコストが高いとき
そうは言っても制約を持った型を作るコスパが合わないから使わないという判断はよくやります。
このコスパの合う合わないは肌感覚なところはありますが、おそらく一つの基準としては決まったフォーマットがあるかでしょうか。
個人的な経験からフォーマットをもったString型が正しいフォーマットになっていなかったがために苦労をしてきたので、ここは必ず専用の型を作ります。
フォーマットを持たないString型は1文字以上を表すNonEmptyStringのような定番の制約を持った型は作ることはありますが、それ以上は細かくなりすぎるのであまりやらないことも多いです。(やった方がよいのはたしかですが)
制約を特に扱わないとき
例えばCLIツールなどで、APIを叩いてその情報をそのままユーザーに見せるようなケースでは制約が変わってもそのまま追従するためにString型としてそのまま扱うことも多いです。
他にもBFFなどでリクエストを受けて他のサービスにリクエストをするというようなケースでは制約を扱わないため、String型のままにすることも多いです。ただし、仮にリクエストのルーティングで使うような情報があれば専用の型を用意するかもしれませんが、ケースバイケースというのが実情です。
結論
結論として、Any型を使わないのと同じで、String型を使うと型安全性が消失するのでできる限り制約を持った専用の型を使うことが望ましいです。
もし、String型を使うか、制約を持った専用の型を使うか迷った場合には、String型をつかうべき理由があるかという観点で考えると良いかもしれません。たぶん。
というので自分の中の思考を整理できたので満足。