back to top

Nullが気持ち悪くなるとき

Cover Image for Nullが気持ち悪くなるとき
Me
Me

いやーめちゃくちゃ日が空いてしまった上に、ChatGPTを使った開発も全然できてません。すいません。。。

言い訳すると稼働する現場が変わってキャッチアップしたりなんだりで時間がなかなか取れないな〜というのと、GPT周りの進化が半端ねえスピードで進んでてちょっと落ち着いて見渡したくなったという感じです。新しくやってるプロダクト上での活用がどうやったらできるか考えたいなー、という。

で、ここは一応GPTに限らず技術のことを書くブログにしたいなと思っているので、最近ちょっと思ったこととか書いてみます。

最近は静的な型解析を使って開発している

今入っているプロジェクトが2つあるんですが、主に稼働している方は以下のような構成でやっています。

Schema: OpenAPI
BE: Rails + sorbet
FE: React(TypeScript)
App: Android (Flutter)

このsorbetというやつがキモになっておりまして、なんとRailsの中で静的な型解析を行うことができてしまうんですねえ。
詳しいことは各自ググってもらいたいんですが、要は「このクラスはこういうフィールド持ってて、その型はこれだよ」とか、「このメソッドはこの型を引数にとってあっちの型を返すよ」みたいなことを書いていくと、リアルタイムにランタイムが解析してくれたり、CIでも同様の解析を行なってPR上で色々教えてくれるすごいやつです。

はい。で、以下のような感じでクラスとフィールドを表現できるんです。

class User
  extend T::Sig  # sorbetを使えるようにする

  const :name, String
  const :age, T.nilable(Integer)
end

読んでの通り、ageフィールドはnilを許容しています。
この時、Userのインスタンスを作ってageに関する処理を書く時に、safe operatorちゃんと書かないと怒られたりします。
もしくは、インスタンスを作る時点で User.new(name: args[:name], age: T.must(args[:age])) みたいに書いておくと、safe operatorが必要になくなるし、仮に引数でageがnilが渡ってきた場合はraiseするので安心です。

なんとなく今までこういうのってあまり気にせず (どっちかっていうとスキーマで決めてやってきた) ので、単純にコードの見方が結構変わって面白いなーとか、Enumとかでexhastiveness checkとかをしておくと壊したコードに簡単に気づけるといった利点があって、静的解析ええやん!と何十年遅れの気づきをしています。

(ちなみに別現場では React(TypeScript) でもちょいちょいコードを書いていて、同様な型システムの恩恵を受けつつ両者の違いを楽しんでいる感じです)

「ない」 ものを 「ある」 と認識しなければいけない時が気持ち悪い

これはSQLの話なんですが、Railsのmodelで下記のようなものがあった場合に。

class User < ApplicationRecord
  enum status: {
    pending: 0,
    active: 1,
    inactive: 2,
    disposed: 3
  }
end

ここでpending以外のものを取って来たい場合は、
User.where.not(status: :pending)
で取ってこれます。

ではもし仮に、このstatusがnullを許容するカラムだった場合はどうなるでしょうか?

pending以外のものを取って来たい場合、先の例では不十分で
User.where.not(status: :pending).or(User.where(status: nil))
としなければなりません。

これは正直気持ち悪いですよね。
実際似たような事例が最近あったんですが、状態値を示すコードなのに「選択可能(=未選択)」という状態をnullにしていたがために「特定の状態値以外」で取ってこようとした時にそれが含まれなくなってしまうという事例でした。改修としてはそのような状態を示すEnum値を加えるに落ち着きました。

この場合も、状況が異なって「nullの場合には引っかからない」ことがコードやビジネス要件として自然な振る舞いならそれで良かったのかも。

型があるからこそ愚直なコードを書くべきなのかも

最初の段落で書いたように型の恩恵を受けるようになると、nullがあってもそれに気づけてしまうが故に、とりあえず「nullableなフィールド用意しておいてあとはロジックの中でよしなに」みたいなことが発生するんですが、これはあんま良くないんじゃないかと最近は思っています。
なんでも受け入れてくれる便利なAPIスキーマを作ったら、使う難しさが爆上がりして結局新しいAPI生やすことになった。みたいなことが将来起こりそう。
だったらもう最初からnamespaceでどんどん分割しておいて、どうやったらスキーマの知識を共有しやすくするかを考えた方が良さげな気がする (GraphQLはこの辺が得意)

型解析があるからこそ、それを使う文脈を明確にしてできる限り分割・分割・分割して細かいスコープに切っていく。
で、モデルの持つ複雑性を小さな型に閉じ込めていくことで、テストを簡単にできる (いっそ型があるからなくてもいい) ぐらいのところに持っていくのが戦略・戦術としていいのかなー、なんて。

なんかダメ出しっぽい雰囲気で書いちゃったけど sorbet 自体はすごく良いので推していきたい。
冒頭の方で書いたように実装する人が「これが来て、こう返す」を明確に書くのを強制されるというのは開発体験的にとっても良いです。

最近は sorbet と GraphQL を組み合わせて開発したら個人的には最強なんじゃないかと思っているので、そういう案件お待ちしてます!笑