この動画は、ブール値がプログラミングにおいて過度に使用されており、多くの場合で他のデータ型に置き換えるべきだという主張を展開している。特にデータベース設計において、ブール値の代わりにタイムスタンプや列挙型を使用することで、より柔軟で拡張性の高いシステムを構築できることを具体例とともに解説している。また、データの正規化や状態機械の概念、権限管理の適切な実装方法についても言及し、開発者が陥りがちな設計上の落とし穴について警鐘を鳴らしている。

ブール値への疑問と代替案の必要性
あんたらブール値好きか?イエスかノーで答えてみ?わしはどっちかっていうと微妙なとこにおるんやけど、それってつまりブール値をそんなに好きやないってことやな。今日はそのことについてちょっと話したいと思うねん。
わしがアプリケーションでブール値を使う時、特に保存してるデータで使う時、それが必ずしも正しい選択やないことが多いんや。最初にブール値を選んで良かったと思うより、ブール値から離れることの方が多いねん。
これについてはいろいろ思うことがあってな、いろんな言語での列挙型のカオス状態から、データベースのアーキテクチャや設計、大きなチームサイズやスケールでコードベースをどう維持するかまで、全部触れていくつもりや。わしもこの辺は経験してきたし、ブール値みたいな単純なものが、このスケールになると複雑になるのは面白いけど、実際そうなるねん。掘り下げることがたくさんあるから、すごく楽しみやで。
とはいえ、この動画の収益化ブール値はtrueに設定してあるから、まず今日のスポンサーの話をしてから、詳細に入っていくで。
スポンサー紹介:Embrace
もしユーザーがいないなら、この部分は飛ばしてもらってもええ。でもユーザーがいて、そのユーザーのことを十分理解できてないと感じてるなら、よう聞いとき。Embraceがあんたのユーザー理解を変えてくれるからな。
たいていの観測性プラットフォームはエラーに集中しすぎてると思うねん。ネタバレやけど、わしのサービスのエラーのほとんどは、変なChromeエクステンション使ってる人らのせいや。ええやん。めっちゃいいやん。でも、ユーザーがオンボーディングフローを通る可能性はどんくらいやろう?ユーザーが特定のAIモデルを試した時、サービスに戻ってくる可能性は?ユーザーを維持するファンネルの出来はどうや?これこそが観測性にあるべきデータやのに、観測性ツールはパフォーマンスとクラッシュばっかりに集中してて、Embrace以外はほかのことをほとんどやってくれへん。
この人らはモバイル開発の世界から来てるねん。ここ数年、モバイル向けの最高の観測性ツールを作ってきて、エラーのトレースを提供するだけやなくて、アプリを使う体験により重点を置いてるねん。そしてその専門知識をウェブに持ち込んでくれてる。これはほんまに重要や。なぜなら、ウェブは日に日にアプリケーションプラットフォームになってきてるからな。
わしらはウェブ用に作られたアプリケーションで時間を過ごしてる。ブラウザ経由でも、Electron経由でもな。そして今、人らがあんたのものを使う時に実際に何をしてるかを見ることができるプラットフォームがあるねん。
これがわしらが話してることや。完了、エラー、放棄、セッション数を教えてくれるチャートやで。このデータは他の場所からも取得できるけど、実際に役立つチャートでってなると話は別や。頑張って。ああ、時間の経過とともに完了がどう変化するかの観測性があるのは、ほんまにええもんや。
これが多分、embraceをembrace(受け入れる)させるスクリーンショットやろうな。駄洒落も込めて。マジで、複数ステップのプロセスがあって、ユーザーがどのステップで失敗してるか見たいなら、これやで。
ユーザーが実際にあんたのものをどう体験してるかを追跡するのに、めっちゃ便利やねん。開発者らはエラーレポート以上のものに値するし、ユーザーもそうや。今日必要なデータを soyv.link/mbbrace で手に入れよう。これはワクワクするで。
ブール値を避ける理由
わしはもうしばらく、サーバーサイドのコードやデータベースでブール値を使ってへんねん。もちろん、クライアントではいろんなことにまだ便利やけど、この記事についてはすごく興味があるで。ちなみにNicoleがこれを書いてくれたことに大感謝や。きっと気に入ると思うわ。
そのブール値は多分他の何かであるべきやろうな。わしらが最初に学ぶ型の一つがブール値や。現代のコンピュータの多くをブール論理が支えてるから、使うのはかなり自然なことやねん。
それでも、わしらがもっと使うのを控えるべき型の一つでもあるねん。ブール値を使うほぼすべての場面で、それは他の何かであるべきや。大胆な発言やな。どこに向かうか見てみよう。
コツはその「他の何か」が何なのかを見つけることや。これをやる価値はあるで。システムについてたくさんのことが分かるし、設計が向上するからな。
たとえ最終的にブール値を使うことになったとしても、よく出てくる可能性のある型がいくつかある。ブール値を隠してるやつらな。それぞれを見てみよう。あと、ブール値を使うのが理にかなうケースもな。これは網羅的やないで。きっと他にも意味を成す型があるやろう。
日時(DateTime)
日時に早速入るかなと思ってたで。それがわしの最初の例になるはずやった。多くのブール値データは、時間的なイベントが起こったことを表現してるねん。
例えば、ウェブサイトではよくメール確認をしてもらうやろ。これはデータベースでis_confirmedというブール値カラムとして保存されることが多くて、それは理にかなってるんやけど、データを捨ててることになるねん。確認がいつ起こったかっていうデータをな。
これはよく見るやつで、誰かがある時点でこのデータを知ることが重要やと気づくねん。そしたらhas_confirmedがtrueで、confirmed_atが何かのタイムスタンプになってる。コードベースでこれを見た回数は数え切れへんで。
そして確認をtrueにマークする関数がある時、この両方のフィールドを確実に更新せなあかんねん。この確認更新が複数の場所にあるなら、そのうちのいくつかの場所では漏れる可能性が高いで。
結果として、ある場所ではタイムスタンプが設定されて、別の場所では設定されへんくなって、今度はhas_confirmedがtrueやけどconfirmed_atが設定されてないユーザーができてしまうねん。これをわしは「スプリットブレイン」と呼んでるわ。
わしはソフトウェア設計の話でスプリットブレインについてよく話すねん。スプリットブレインって言う時、何らかの形で結合されてるデータが2つの異なる場所に存在してるっていう意味や。この場合、誰かが確認してるなら、確認日時を持ってる。確認してないなら、確認日時を持ってない。
ここでは同じデータを2回エンコードしてるねん。でも、一方のデータはもう一方より高解像度や。確認日時は、確認したことと、いつ確認したかの両方を教えてくれる。だからもうhas_confirmedフィールドはいらんねん。
最初からそれがあることが間違いやったと思うで。一つから推測できるのに、2つの場所にデータを置きたくないからな。
一般的に、データを導出できる時は、データを保存する代わりにそれをすべきや。そしてキャッシュが必要なら、キャッシュはクライアントや消費者がいる場所にできるだけ近いところで発生すべきやねん。避けられるなら、データベースの別のフィールドの隣にフィールドを置いてキャッシュすべきやない。
これは最も分かりやすい例の一つや。ええスタートやで。
後でな、確認プロセスにバグがあったことが分かるかもしれん。タイムスタンプを使って、確認が保存された時間に基づいて、どのユーザーが影響を受けたかをチェックできるねん。これがあったらええなと思うことがどんなに多いか、驚くで。
代替案は、ログや分析をすべて調べて、確認が実際に変更された時のログを見つけることや。ログがあればの話やけどな。わしも経験したけど、楽しくないで。日時として保存するだけでええねん。
インデックス化と高速なクエリレスポンスのためによくやられてるって言ってる人がいるな。いや、ブール値と同じくらい簡単に日時にもインデックスを作れるで。インデックス化した時に日時でソートもできるしな。
常にタイムスタンプであるべきやと言ってるんやない。タイムスタンプかundefinedであるべきやと言ってるねん。nullableであるべきや。そしたらnullなら確認してないって分かるし、nullやなかったら確認してるって分かる。インデックス化もめっちゃ簡単やで。
列挙型(Enums)
次は列挙型や。おお、大変やで。残りのブール値データの多くは、何かのタイプかステータスを示してるねん。ユーザーが管理者かどうか?is_adminカラムをチェックする。ジョブが失敗したか?failedカラムをチェックする。ユーザーがこのアクションを実行することを許可されてるか?そのためのブール値を返す。イエスかノー。
これらは通常、列挙型の方が理にかなってるねん。絶対に同感や。completed boolean、failed boolean、in_progress booleanみたいな完全なめちゃくちゃを見た回数は数え切れへんで。
これは特に面白いねん。なぜなら最終的に起こることは、何かが失敗した時に悪い部分的なケースがあって、failedをtrueに更新するんやけど、in_progressを更新し忘れて、completedは依然としてfalseのままになることや。
そして、in_progressやcompletedをチェックするクライアントコードのどこかに、または両方をチェックするけどfailedを適切にチェックしてない場所があると、実際には失敗してるのにローディングスピナーが表示されることになるねん。ああ、神様、チャット、トラウマを刺激されてるわ。
is_active boolean、is_inactive boolean。わしの意見では、競合する状態は重複できない方法で表現されるべきや。completedとfailedが同時にtrueになれないなら、データアーキテクチャでそれを許可すべきやない。in_progressとcompletedが同時にtrueになれないなら、アーキテクチャでそれを許可すべきやない。
理想的な世界では、これはstatusやろうな。そしてstatusはこれらの多くの異なる値のうちの一つになれる。failed、completed、in_progressな。これも拡張性がずっと高くなる。statusが消費されてるすべての場所をチェックできて、新しいstatusを追加したり、statusを削除したりする時、クライアントコードがそれを処理することを確認できるからな。型安全なシステムで作業してるなら、これはさらに強力になるで。
ここでちょっと面倒になり始める部分は、いつこれが起こったかも知りたい場合や。last_updatedを持てるけど、last_updatedが全体のフィールド用で、statusだけのものやないなら、それはよくないねん。
でも、これをstatus みたいなサブオブジェクトに分割するとしたら、statusのサブstatusを何て呼ぶんや?そういう場合はstateが好きやで。ええ提案や。
これはサブフィールドやから、開発者であるあんたや、チームの他の人らが同時に両方を更新する可能性がずっと高くなるねん。考え方は分かるやろうし、サブフィールドって言う時、これがデータベースのJSONオブジェクトであるべきやって意味やないで。結合する別のテーブルでもええし、他のいろんなものでもええねん。でも理想的には、この両方の値が必要なら、それに応じてエンコードするやろうな。代替案はcompleted、completed_at、failed、failed_at、in_progress、started_atになって、全然よくないからな。
いつものように、あんたのニーズは違うかもしれん。わしの意見では、これに対する比較的有効な構造はこんな感じやろうな。
statusがあるけど、started_atもある。これは多くの理由で欲しいかもしれんし、finished_atもある。これも多くの理由で欲しいやろうな。今度は、statusとstartedとfinished_atの時間から、何が起こったかをある程度推測できる。
completedで、startedとfinished_atの時間があるなら、どれくらい時間がかかったか分かる。failedで、startedとfinished_atがあるなら、失敗するまでどれくらい時間がかかったか分かる。これらの情報から欲しい他の状態をすべて導出できるねん。
でも、よく見る一般的な間違いがあって、これはよく見るねん。ジョブが失敗した時を欲しがるから、データベースをfailed_atで更新するねん。明らかな直感や。UIでジョブがいつ失敗したかを表示したいねん。その情報が欲しいねん。だからfailed_atをデータベースに入れる。
でも、他のことからもそれを推測できるねん。status failedとfinished_atのある時間を見れば、そこからジョブがいつ失敗したかを推測できる。でも、欲しいデータから始めてデータベースに保存すると、本当にひどいアーキテクチャになってしまうねん。
でも、欲しいデータをどう取得するか、将来の柔軟性のためにどういう形のデータを保存するのが最も理にかなうかを考えれば、最終的に欲しいデータを取得できるねん。
わしらが望もうが望むまいが、すべてのデータはある種のグラフや。すべてのものは他のものから来るねん。この構造から多くのことが分かるで。ここから継続時間が分かる。ここからいつ失敗したかが分かる。ここから現在のステータスが分かる。ここから平均的にジョブが失敗するのにどれくらい時間がかかるかが分かる。
これらはすべて、ここから推測できる情報の断片や。そしてそのうちのいくつかは連鎖してるねん。継続時間が分かって、いつ失敗したかが分かる。そして、これらの情報を使って平均的にジョブが失敗するのにどれくらい時間がかかるかを把握できるねん。
これがデータを正しく設計した時に得られるものや。でも、そこに継続時間だけを保存してたら、failedブール値を保存してたら、またはfinished_atだけを保存してstarted_atを保存してなかったら、情報の断片を見逃してるかもしれんねん。
でも、ソフトウェアの設計を通じて発生する重要な部分を保存すれば、反対側で得られるものは本当にええもんになるねん。
データ正規化が、わしがもっと分かりやすい言葉に分解しようとしてる重要な概念や。考え方は、データベースの制約がデータが取りうる期待される状態を強制できるということやねん。
ここでも少し物事を壊してる部分があるねん。statusがin_progressの時でもfinished_atが値になる可能性があるから。間違って設定できるからな。でも、ここで見せてるcompleted、completed_at、failed、failed_atのカオスよりは、その可能性はずっと低いねん。考え方は分かるやろ。
列挙型の大ファンやし、開始時間と終了時間の大ファンや。また、わしは永遠にこれを主張して戦うで。ほぼすべてのデータベース、わしが触ったことのあるすべてのテーブルは、created_atとupdated_atフィールドを持つことで、ある程度の利益を得るねん。これらはほとんどデータやないけど、最終的には絶対に役に立つ。わしはすべてのテーブルにcreated_atとupdated_atを置いて、それがわしを何度も救ってくれたねん。
Convexの言及が出てるな。そうや、ConvexはこれをやってくれるねN。ありがたいことに、ほとんどのORMもこれをやってくれる。ただのええ設計や。
記事に戻ろうか。すごく気に入ってるからな。ユーザーの役割もほんまによくあるやつや。is_guest true、is_user true、is_paid_user true、is_adminみたいなやつな。いや、これらが競合するなら、列挙型であるべきや。
キーが競合する可能性があるなら、一つのキーにしようとすべきや。これは一般的に公正なアドバイスやと思うで。言われてる通り、ジョブのステータスもここではほんまによくあるやつやな。さまざまなステータスにわたる列挙型である単一のフィールドstatusだけがあるねん。
ただし、これらの各イベントにはタイムスタンプフィールドが欲しいやろうけど、statusも明示的に保存するのがベストやねん。
これは笑えるな。わしは事前にこれを読んでなかったんや。同じ結論やで。これは状態機械に似始めるねん。わしの言語で話してるやん。
受け入れたくなくても、データベースはすぐに状態機械になるねん。チャットからのもう一つのほんまにええ指摘や。これはGraphiteで働いてるJoldからやな。列挙型は素晴らしいけど、文字列を使ってランタイムでZodで強制するのが好きや。マイグレーションが少なくて済むし、リスクもかなり低いからな。
絶対に同感や。一般的に、特にTypeScriptでは、列挙型はちょっと変なもんや。TypeScriptでの列挙型の状態は厳しいねん。でも、列挙型のように考えて、厳密な型と入力・出力用のバリデーターがあるなら、問題ないで。
そこでの唯一のリスクは、そこから何かを削除した場合、データベースマイグレーションはマイグレーションですらなくて、今度はバリデーターを失敗させるデータができることや。でも小さな断片、大きなパズルって感じで、考え方は分かるやろ。
これはデータベースに保存するためだけのものやないねん。ユーザーの権限をチェックする時も、多くの場合それに対してブール値を返すからな。
権限をチェックする。ユーザーのブール値false。誰も何もすることを許可されてないみたいやな。これはこの場合素晴らしいねん。trueはユーザーができるという意味で、falseはできないという意味や。通常はそう思うけど、ここで、そして任意のブール値について、本当に疑問を持ち始めることができるねん。なぜなら、値のアプリケーションロジックの意味を型から推測できないからな。
代わりに、選択肢が2つだけでも、これは列挙型として表現できる。allowedとnot_permittedみたいにな。ボーナスとして、列挙型を使うなら、権限チェック失敗の理由を返すみたいな、より豊富な情報を得ることができるねん。そして列挙型の将来の拡張にも安全やで。
役割と同じように、面白いことに、神様、これはめっちゃ大きな脱線になるであろうことに陥らないよう努力するわ。
これがわしが役割レベルのセキュリティが嫌いで、FirebaseやSupabaseみたいなものが嫌いな理由や。権限は単なる状態やないからな。保存できるものやない。データベースの役割はデータを保存することや。権限はデータやない。ロジックや。権限はデータから導出されるけど、データやない。権限やねん。
これがわしがConvexを好きで、FirebaseやSupabaseをそれほど好きやない理由や。FirebaseやSupabaseでは、ユーザーができることをデータ構造の一部として保存させるねん。データベースは、このフィールドにユーザーIDが入ってるから、これらの行はこのユーザーがアクセスできるという概念を持ってるねん。
誰かがサインをドロップしたな。わしが今まで描いた中で一番のお気に入りの図や。覚えとき、わしはアンチGraphQLやないねん。実際のところ、最近の多くの人らと比べると、わしは比較的GraphQLプロや。でも、GraphQLはサーバーとデータベースの間に入るべきやない。サーバーとユーザー、クライアントの間に入るべきやねん。GraphQLはその2つの間に入るべきで、データベースとサーバーの間やないねん。今度はそのロジックを定義するコードを書く能力を諦めることになるからな。
役割レベルのセキュリティはロジックやし、誰かによって書かれたもんや。コードが動いてるけど、動いてるコードはあんたのもんやない。そして何が何の権限を持ってるかを見過ごすのはめちゃくちゃ簡単やし、いつも変な状態になってしまうねん。
最近見てるセキュリティ問題の多くは、FirebaseやSupabaseで権限を適切に設定してない人らから来てるねん。これら2つのバックエンドを使ってるサイトを探すハッカーの集団がいるくらいや。コードで設定してないから、設定を間違えてる可能性がめちゃくちゃ高いからな。
権限はロジックやし、データが適切に構造化されてるなら、ロジックはデータにあるべきやない。ロジックは、データを取って、それに基づいて決定を下すものであるべきやねん。
ここまでのところ、著者に絶対に同感やで。
ブール値を使うべき場合:条件式
条件についてはどうやろう?いつブール値を使うべきか?わしが主に遭遇したケースは一つで、評価用の条件式の結果を一時的に保存する時に理にかなうねん。これはある意味、コンピュータが変数を再利用するための最適化か、プログラマーが大きな条件に名前を付けて中間値を保存することで理解しやすくするための最適化や。
ブール値を中間値として使う試行例がここにあるで。巨大な三項演算子やネストしたブール値がif文にただ落とし込まれてて、それが何なのか、何をするのか全然分からんコードレビューでのコメントを残すことがどんなに多いか、言えへんわ。
それを取り出して名前を付けるだけで、人生がずっと楽になるねん。これは何か長い条件になるはずやけど、持ってへんから変数にするで。let userCanDo = AかつBかつC、またはDでない。
これは素晴らしいねん。わしならこれをさらに細かく分解するで。これらの変数があるねん。ここでもわしなら、もう少し細かく分解するで。const isAAndB = AかつB。const isCOrNotD = CまたはDでない。そしたらほら。
意味のあるコードの増加やないけど、これを意味的にずっと読みやすくするねん。特にA、B、C、Dが具体的なものの場合はな。考え方は分かるやろ。わしはほんまに明示的なコードが好きや。わしは4〜5語の長さの変数名を書くことで知られてるねん。誰が気にするねん?どうせコンパイル時に削除されるやろ。
コードを読みやすくするだけやし、どうせオートコンプリートもあるしな。毎回タイプアウトしてるわけやないねん。
命名がそんなに難しいものやないねん。開発者らが思い込んでるほどはな。わしがマーケターでもあり開発者でもあるから偏見があるかもしれん。でも、ものを説明するだけやと言うねん。Upload Thing、Marker Thing、Web Hook Thing、Pick Thingの作成者として言うけどな。考え方は分かるやろ。
ものをそれが何であるかで名前を付けるだけや。でも、この作為的な例でも、いくつかの列挙型の方が理にかなうやろうな。計算してることに名前を付けるためにブール値は残しとくと思うけど、残りは列挙型でのマッチであるべきやねん。そうや、同感やで。
これはわしがよくやることや。わしはよくコードレビューで、チームにこんなランダムなチェックを関数に分解するよう求めるねん。より効率的やからとか、そういう理由やないで。別のイベントを立ち上げることが必ずしもええことやないからな。
でも、これについてええことは、非常に明示的なcan_user_do_thisが得られることや。今度はそのものに名前があるねん。コメントを残せるし、変更を加えて異なる場所を壊すこともできる。とにかく物事がほんまに明確になるねん。
わしが書く関数の半分は、コードで巨大なifチェックをする代わりに、こんな感じのものやねん。最近はswitch文よりもこれを使うことの方が多いで。早期returnできて、switchからの価値の多くをスキップできるからな。
考え方は分かるやろ。確かに、すべてのブール値が消える必要はないやろうな。ソフトウェア設計で常に正しい単一のルールは多分ないやろう。うーん、常に正しいルールを考えてみよう。上司の妻とデートするなとか?not falseは常にtrueやな。trueも常にtrueや。while trueは確実に常にtrueやし、長すぎるくらいtrueやで。
わしの常にソフトウェアで正しいルールは、開発者は何かを気にしすぎるということや。それが何を気にしすぎるかは分からんけど、何かを気にしすぎてるねん。
ブール値は狡猾やで。データには理にかなってるように感じるけど、ロジックには理にかなってるねん。この区別が大好きや。これも権限のことと同じやな。
権限はロジックであってデータやない。ブール値は非常にロジック駆動やねん。ええ状態やない。ええロジックや。そして、ロジックの一部として、たくさんのブール値が出てくるやろう。それはそれらをデータベースにブール値として詰め込むべきやという意味やないねん。コードがデータを取って、使えるものに変換してるだけという意味や。
でも、ブール値は論理的なチェックのセットの一部やねん。ええデータアーキテクチャに属する何かとは必ずしも言えんねん。著者が言うように、データは通常、その下にある何か別のものやねん。データとしてブール値を保存することで、そのデータをアプリケーションロジックと密結合させてるねん。うお!バンガーやで。また、わしが前に描いた図やな。
これが欲しいものなら、それを追加するのはめっちゃ簡単やねん。ジョブが失敗したかどうかなら、そのif文の結果のtrueかfalseを貼り付けるのはめっちゃ簡単やで。でも、データベースにそれを入れたくないねん。データベースからそれを取得したいねん。ロジックがデータベースから欲しい情報を取得できるようにしたいねん。
データベースには知ってるデータを表現してもらいたいねん。代わりに、批判的であり続けて、ブール値が依存するデータは何かを尋ねるべきや。そして、代わりにそれを保存すべきかもしれんな?練習で楽になるで。実際、すべてのええ設計がそうやねん。前もっての少しの思考が、長期的には多くの時間を節約してくれるねん。
絶対に同感や。これは素晴らしい記事やった。この記事を書いてくれたNicoleに感謝やで。もし経験豊富な開発者からのコーチングサービスを探してるなら、わしは忙しすぎるわ。彼らは忙しくないようやな。興味があるなら、そこにリンクがあるで。
ええもんやで。Nicole、これを書いてくれてありがとう。いつものように、リンクは説明欄にあるで。素晴らしい記事、素晴らしい内容や。
これを0から1のスケールで評価するとしたら、絶対に1を付けるで。また次回まで、平和やで、オタクども。


コメント