TypeScript 初心者が unknown と仲良くなった話

こんにちは。株式会社フィックスポイントのよしだです。

今回は、TypeScript 初心者のエンジニアがいかに unknown と向き合って習得していったか、という内容の発表がありましたのでご紹介します!

コミュニケーションツールをかなり有効活用した内容になっていますので、フィックスポイントの文化についても参考になれば幸いです😌

TypeScript 初心者とはいえ、R の経験が7年程度、Python は2年程度、その他の言語(JavaScriptLua 等)も経験がある方の発表ですので、その点も頭の片隅に置きつつ読んでみてくださいね✨

なぜ TypeScript??

実際の習得プロセスについてお話しする前に、TypeScript を始めた前提に触れておきましょう。

大きな理由の一つ目は、社内でよく使われている言語であり、始めるきっかけとしてちょうどいい案件があったためでした。

いつか自身も使う可能性があるだけでなく、JavaScript の経験があるため Go よりは始めやすいという背景もありました。

理由の二つ目は、シェルスクリプトの代わりに向いてそうだと思ったのだそうです。

GNUBSD を気にしなくてよく、型もあって Deno さえ入れれば使えるため依存パッケージにも対応できる等、使えそうな要素が多かったことが理由でした。

想定と実践

TypeScript を始める前の気持ちとしては、「いけそう」だったそうです。笑

JavaScript の Super Set らしいし、Python に型注釈するるくらいの感じでできそう!Denoのおかげで環境構築も楽そう!

といった具合だったのだそうです😊

そして実際、割とスムーズに使用することができたようです。

ただ、ここで壁となったのが今回のテーマである「unknown」でした。

ここから unknown と向き合う人生が始まります。

unknown との戦い

まず、やりたいこととして「APIJSON レスポンスをパースして型安全に扱いたい」という動機があったとのことでした。

const x = JSON.parse("なんか複雑なJSON");
console.log(x.foo);

これ自体は問題ないのですが、 JSON パースは any で返ってきてしまいます。

発表者は、過去に vim-jp にて「any は滅べ、困ったら unknown」と聞いたことがあったそうで😂、そのように対応すべく着手を始めました。

const x: unknown = JSON.parse("なんか複雑なJSON");
x.foo;  // Object is of type 'unknown'

これだと x の型が不明のため foo プロパティが取れないと言われてしまいます。

そこで、 xobject かを確認してみました。

if (typeof(x) === "object") {
  x.foo;  // Property 'foo' does not exist on type 'object'
}

結果、「x がオブジェクトでも foo ってプロパティがあるとは限らないでしょ?」と言われてしまいました😓

そりゃそうだけど…という思いをぐっと抑えつつ、存在が不明な場合に ?. というものが使えると聞いたことがあったので、試しに使用してみたそうです。

if (typeof(x.foo) === "object") {
  x?.foo;  // Property 'foo' does not exist on type 'object'
}

ダメでした😇

そこで、foo プロパティの存在を確認してみました。

if (
  typeof(x) === "object" &&
  Object.keys(x).includes("foo")
  // Overload 1 of 2, '(o: {}): string[]', gave the following error.
  // Argument of type 'object | null' is not assignable to parameter of type '{}'
) {
  x.foo // Property 'foo' does not exist on type 'object'
}

もっとしっかりダメ出しされてしまいました。

Object.keys(null) な可能性を排除する必要があるので、さらにきちんと確認してみました。

if (
  typeof(x) === "object" &&
  x !== null &&
  Object.keys(x).includes("foo")
) {
  x.foo // Property 'foo' does not exist on type 'object'
}

またまたダメでした🤗

どうにもこうにもダメだったため、times にて助けを求めたところ例に漏れず有識者から助け舟がありました。

要点を拾うと、ここまでにやったバリデーションの後で as を使って型アサーションするべしとのことでした。

実際にやってみると…

const xValidated = x as { foo: unknown; }
if (
  typeof(x) === "object" &&
  x !== null &&
  Object.keys(x).includes("foo")
) {
  const xValidated = x as { foo: unknown; }
  xValidated.foo
}

できました🎉

…が、発表者としては下記二点のようなちょっとした不満がまだあったそうです。

  • 巨大な if 文は読みにくい
  • if 文の外に出ると型アサーションは忘れ去られる

これを解決すべく、下記の書き方を試してみることにしました。

function assertIsFoo(x: unkwnown): asserts x is Foo

実はこちら、弊社 Gather にてペアプロを行っていた他社員の話を聞きかじったことから使ってみたのだそう!

Gather にて交流しているとこういったメリットも享受できるのですね。

Gather については初回の記事に記載していますので、興味のある方は見てみてください😊

blog.fixpoint.co.jp

少し話が逸れてしまいましたが、実際に使用した内容が以下の通りです。

type Foo = {
  foo: string;
  [key: string]: unknown; // APIレスポンスで他のキーを省略する場合
}

function assertIsFoo(x: unknown): asserts x is Foo {
  if (
    typeof(x) === "object" &&
    x !== null &&
    Object.keys(x).includes("foo")
  ) {
    const foo = x as Record<keyof Foo, unknown>;
    if (typeof(foo.foo) === "string") {
      return;
    }
  }
  throw "x does not satisfy Foo";
}

const x: unknown = JSON.parse(`{foo: "foo"}`);

assertIsFoo(x);
x.foo;

見事、目論見通りに動かすことができました✨

参加者の反応

今回はエンジニアからの反応が相次ぎました!

勉強会にて発表すると、有識者からより便利な情報を得られたり派生した議論が生まれたりするため、とても勉強になります♪

最後に

今回は、社内外のコミュニケーションツールを駆使して unknown と仲良くなった過程の発表についてご紹介しました。

発表者からも、「特に実践して学ぶ派の人はアンテナを広く張ることが大事」とまとめのコメントがありました。

今後も社員同士の会話から解決に至った例が出てくると思いますので、お楽しみに!





株式会社フィックスポイントでは、一緒に働いてくれるメンバーを募集しています!

詳細は、こちらをご覧ください。