Hiroto Furugen
TECH
#Go#TypeScript#LanguageDesign

書き方が一つしかない、という設計 - GoとTypeScriptに見るシンプリシティとクリアリティ

シンプリシティとクリアリティは、似ているようで別のもの

シンプリシティ(simplicity)は「選択肢の少なさ」です。同じことをするやり方が1つしかなければ、書くときに迷わないし、他人のコードを読むときも「なぜこの書き方を選んだのか」を考えなくて済みます。

クリアリティ(clarity)は「読んだときに意図が伝わりやすいこと」です。書き方の選択肢が多くても、その場に一番ぴったりな表現を選べれば、コードは十分クリアになります。

この2つは仲が良く、たいていはシンプルな言語ほどクリアになりますが、常に一致するわけではありません。GoとTypeScriptは、この2つの優先順位が逆転している分かりやすい例です。Goは選択肢を削ることでクリアリティを手に入れようとし、TypeScriptは表現力を上げることでクリアリティを手に入れようとします。同じ「読みやすくしたい」というゴールに、逆のアプローチで向かっている、と言ってもいいかもしれません。

以下、具体的な文法で両者の違いを見ていきます。

同じ「型」を書くのに、書き方の数が違う

TypeScriptで「名前と年齢を持つ人」を表す型を書くとき、実は2通りの書き方があります。

// 書き方1: interface
interface Person {
  name: string;
  age: number;
}
 
// 書き方2: type
type Person = {
  name: string;
  age: number;
};

どちらも動きは同じです。どちらを使うかはチームのルール次第で、初めて他人のコードを読むときに「どっちもあるけど何が違うんだっけ」と立ち止まる原因になります。

Goには構造体(struct)という書き方しかありません。

type Person struct {
	Name string
	Age  int
}

迷う余地がないぶん、Goのコードは書いた人が違っても見た目が揃います。

エラーの扱い方

TypeScript(JavaScript)は、エラーが起きるかもしれない処理を try/catch で囲みます。

try {
  const data = JSON.parse(text);
  console.log(data);
} catch (e) {
  console.log("パースに失敗しました", e);
}

try の中のどの行でエラーが起きたのか、パッと見ただけでは分かりません。

Goにはこの仕組みがなく、エラーになりうる処理は戻り値としてエラーを返し、呼び出す側がその都度チェックします。

data, err := parseJSON(text)
if err != nil {
	fmt.Println("パースに失敗しました", err)
	return
}
fmt.Println(data)

1行ごとに「これはエラーになるかもしれない処理」と「その対処」がセットで書かれているので、コードを上から読むだけでエラー処理の流れを追えます。書く行数は増えますが、読むときに探す手間が減ります。

型が「だいたい合ってる」で通ってしまうか

TypeScriptには any という「何の型でもいい」という型があります。

let value: any = "hello";
value = 123; // エラーにならない
value.foo.bar; // これも実行するまでエラーに気づけない

any を使うと型チェックが実質オフになり、間違いに気づけるタイミングが実行時まで遅れてしまいます。

Goには any に相当する interface{}(または any)もありますが、そこから何かを使う前に必ず型を確認させられる仕組みになっていて、TypeScriptほど気軽には素通りできません。

var value any = "hello"
s, ok := value.(string) // 文字列かどうかを確認してから使う
if ok {
	fmt.Println(s)
}

値が「ない」ことの表現

TypeScriptには「値がない」を表す方法が undefinednull の2つあります。

let name: string | undefined;
console.log(name); // undefined

Goには undefined はなく、変数は宣言した時点で必ず何かの値(ゼロ値)を持ちます。文字列なら空文字、数値なら0です。

var name string
fmt.Println(name) // ""(空文字)

「ない」を表す方法が1つしかないので、迷う場面自体が少なくなります。

まとめ

TypeScriptは書き方の選択肢が多く、表現できることも多い言語です。Goは選択肢が少ない代わりに、誰が書いても似たコードになりやすい言語です。どちらが優れているというより、選択肢の多さは書く人にとっての自由度になり、選択肢の少なさは読む人にとっての分かりやすさになる、というトレードオフだと理解しておくと、それぞれの言語のクセを飲み込みやすくなると思います。

← Blog 一覧へ