【JS/TS】Javascript/Typescriptで`null`や`undefined`撲滅!?
はじめに
どもどもてるし〜です。
最近Rustを勉強し始めました。
今までや意識してなかったことや新しい知識を取得できとても有意義な時間を過ごしています。
Rustのドキュメントを日本語訳したものを現在読んでいます。
enum
の単元を読んでいるときに以下の言葉が出てきました。
"Null References: The Billion Dollar Mistake"
私はこの言葉にすごく興味を持ちました。
今回はこの言葉はいったいどういう意味をなのか?
そして、JSやTSにどう活かしていく?
といった記事になります。
"Null References: The Billion Dollar Mistake"とは
null
を開発したことでバグが多く出てしまい大きな損害を与えてしまったことで上記の言葉を開発者が発しました。
より詳細に関しては日本語での記事がなかったのでChatGPTに聞いてしまいました。
背景
null
は1995年Tony Hoareが ALGOL W を設計するのに導入しました。
ですが、null
によってバグやシステム障害が発生し大きなコストを払ってきました。
null
による問題
問題は主に以下の通りです。
- プログラムがクラッシュする
- 見落としやすいバグが発生する
- 取り扱いミスがセキュリティの脆弱性に繋がる
これらはシステムを運用する上で致命的なものになります。
詳細はここでは省略します。
null
問題を防ぐためには??
- TypeScriptの
strictNullChecks
-
Option
型やResult型を作成する(最近の言語ではこれでnullを禁止している) -
null
を使わずデフォルト値を設定する
です。
実際にJS/TSのコードで実践してみる
JSやTSでもnull
やundefined
は出てきます。
1つのソースコードを例にnull
やundefined
を使わない方向性で安全なソースコード🧐🧐にしてみようと思います。
対象のソースコード
以下のソースコードを対象とします。
"use client"
import { createContext, ReactNode, useContext, useReducer } from "react"
import { popupContextType } from "./popup.types"
import { popupInitialState } from "./popup-initial-state"
import { popupReducer } from "./popup-reducer"
import PopupLayout from "@/layout/popup-layout"
export const PopupContext = createContext<popupContextType | undefined>(
undefined
)
interface Props {
children: ReactNode
}
export function PopupProvider({ children }: Props) {
const [state, dispatch] = useReducer(popupReducer, popupInitialState)
return (
<PopupContext value={{ state, dispatch }}>
<PopupLayout />
{children}
</PopupContext>
)
}
export function usePopupContext() {
const context = useContext(PopupContext)
if (context === undefined) {
throw new Error("Providerを下で定義してください")
}
const state = context.state
const dispatch = context.dispatch
return { state, dispatch }
}
上記ソースコードにはundefined
があります。
export const PopupContext = createContext<popupContextType | undefined>(
undefined
)
一応エラーハンドリングはしていますが上記コードを改造していこうと思います。
大規模なアプリになればなるほどエラーハンドリング抜けが出てしまし、思いがけないバグが起こります。今後そのようなことが起こらないようにまずは簡単のプログラムで試してみます。
Result型の作成
今回はエラーハンドリングがあるためResult型を作成していきます。
Result型は以下の通りです。
export const RESULT_OK = "ok" as const
export const RESULT_ERROR = "error" as const
export type Result<T> =
| {
kind: typeof RESULT_OK
value: T
}
| {
kind: typeof RESULT_ERROR
err: Error
}
Result型のデータを作成するオブジェクトを作成
Result型のデータを作成するオブジェクトを作成しておきます。
export const createResult = {
ok: <T>(value: T): Result<T> => {
return {
kind: "ok",
value: value
}
},
err: (message: string): Result<never> => {
return {
kind: "error",
err: new Error(message)
}
}
}
これを作成するのは任意ですが、作成しておくとミスは減るかなと思います。
undefined
をResult
に変換
作成したResult
を実際に対象のソースコードに埋め込んでみましょう。
"use client"
import { createContext, ReactNode, useContext, useReducer } from "react"
import { popupContextType } from "./popup.types"
import { popupInitialState } from "./popup-initial-state"
import { popupReducer } from "./popup-reducer"
import PopupLayout from "@/layout/popup-layout"
+ export const PopupContext = createContext<Result<popupContextType>>(
+ createResult.err("Providerを下で定義してください")
+ )
- export const PopupContext = createContext<popupContextType | - undefined>(
- undefined
- )
interface Props {
children: ReactNode
}
export function PopupProvider({ children }: Props) {
const [state, dispatch] = useReducer(popupReducer, popupInitialState)
+ const result = createResult.ok({ state, dispatch })
return (
+ <PopupContext value={result}>
- <PopupContext value={{ state, dispatch }}>
<PopupLayout />
{children}
</PopupContext>
)
}
export function usePopupContext() {
const context = useContext(PopupContext)
+ if (context.kind === RESULT_ERROR) {
+ throw context.kind
+ }
+ const state = context.value.state
+ const dispatch = context.value.dispatch
- if (context === undefined) {
- throw new Error("Providerを下で定義してください")
- }
- const state = context.state
- const dispatch = context.dispatch
return { state, dispatch }
}
差分は上記のようになり、出来上がったソースは以下の通りになります。
"use client"
import { createContext, ReactNode, useContext, useReducer } from "react"
import { popupContextType } from "./popup.types"
import { popupInitialState } from "./popup-initial-state"
import { popupReducer } from "./popup-reducer"
import PopupLayout from "@/layout/popup-layout"
import { createResult, Result, RESULT_ERROR } from "@/utils/global.typs"
export const PopupContext = createContext<Result<popupContextType>>(
createResult.err("Providerを下で定義してください")
)
interface Props {
children: ReactNode
}
export function PopupProvider({ children }: Props) {
const [state, dispatch] = useReducer(popupReducer, popupInitialState)
const result = createResult.ok({ state, dispatch })
return (
<PopupContext value={result}>
<PopupLayout />
{children}
</PopupContext>
)
}
export function usePopupContext() {
const context = useContext(PopupContext)
if (context.kind === RESULT_ERROR) {
throw context.kind
}
const state = context.value.state
const dispatch = context.value.dispatch
return { state, dispatch }
}
こうすることで思わぬバグやエラーを防ぐことができます!!
まとめ
今回はnullやundefinedが危険と言われている件について、そしてnullやundefinedを削除したコードを作ってみました。
簡単なソースコードだとなかなか恩恵を受けることは少ないかなと思いますが、大規模になるとバグを探す時間が増しコストを大幅に無駄遣いしてしまうといったことになってしまうので、今のうちから意識しておくべき内容ではないかと思い今回記事にしました。
ではでは。
Discussion