お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
投稿日:
開発::設計言語::TypeScript

今回は実際の開発現場で遭遇しそうなコードパターンを2つ挙げ、それらを主に可読性向上と保守コスト削減の視点から、どのように改善できるかを具体的に書いてゆく。

サンプルコードはTypeScript + GraphQLを使ったAPサーバーを想定して書いている。

命名とデータ構造の最適化

この項目ではエラーメッセージを返すサーバー側の処理について記述する。

元コード

resolvers/customer-info/helpers/invalid-reason.ts

type ReferencePageLinkMapKey = 'userFormIssue' | 'duplicateUserName' | 'someErrorMessage';

// exportされているがgetCustomerInfo(key, boolean)でreferencePageLinkMap.get(key)が返ってくることを確認するテストに使われているだけ
export const referencePageLinkMap: Map<ReferencePageLinkMapKey, string> = new Map([
  ['userFormIssue', 'https://help.example.com/23843ddvwa'],
  ['duplicateUserName', 'https://help.example.com/xdxdk2t5'],
  ['someErrorMessage', 'https://help.example.com/pp33s'],
  ...
]);

const invalidReason = {
  inputIssue: {
    message: '入力項目にエラーがあります。...',
    links: [
      {
        label: '【登録時のヘルプページをご確認ください】',
        url: referencePageLinkMap.get('userFormIssue') ?? '',
      },
    ],
  },
  ...
};

export const getInvalidInfo = ({
  errorReasonType,
  isModeA,
}: {
  errorReasonType: ErrorReasonType;
  isModeA: boolean;
}): Omit<SomeFormInvalidInfo, 'invalidReason'> => {
  switch (errorReasonType) {
    case 'INVALID_INPUT':
    case 'SOME_INPUT_ERROR':
      return invalidReason.inputIssue;
...
   };
}

このコードの問題点

  • 定義する必要性が希薄な型情報があり、改修コストが高い
  • 変数や関数名、キー名などから相関する機能の関連性を読み取りづらい
  • 定義されたスコープを超えて利用されているもので命名から機能の読み取りが難しいものがある
  • リテラルがべた書きされており将来のリファクタリングに支障がある
  • 不必要にMapが使用されており、実装が冗長になっている
  • 静的な値の取得にNull合体演算子が使用されており、潜在不具合となりやすい
  • 型情報がべた書きされすぎており、見通しが悪い
  • 複数のフラグ値の正規化を利用側で行っており、複雑になっている

改善後コード

リソースファイルと実装が一緒くたであると読みづらいためファイルを分割する。パスも書いているがパスについては深く考慮できていない。

resources/messages/CustomerError.ts

export const ERROR_MESSAGES = {
  formValidationError: {
    message: '入力項目にエラーがあります。...',
    helpLinks: [
      {
        label: '【登録時のヘルプページをご確認ください】',
        url: 'https://help.example.com/23843ddvwa',
      }
    ],
  },
  ...
} as const;

まずtype ReferencePageLinkMapKeyは無駄に型の管理コストが増えるだけなので削除する。意味がないとは言わないが、型の追加削除によって発生するデグレードは普通は型検査でわかるためなくてよいだろう。

次にreferencePageLinkMapでは意味がわからないのでERROR_MESSAGESにリネームする。中身のキーもinputIssueでは分かりづらいのでformValidationErrorとし、フォームのバリデーションエラーであることが分かるようにする。linksについても何のリンクなのかわからないためヘルプのリンクであることを明示できるようにリネームする。

helpLinks.urlについては、共通のURLを参照する概念は普通ないか、あったとしても別に切り出して管理するのはコストなので直に書いてよいと考える。元のコードでもテストコード以外には使われていない。テストコードには実値を書かないと回帰テストとして機能しないため、この方式のほうがより良いと考える。

例えば以下のようなテストコードは回帰テストの観点では意味が薄い。

it('INVALID_INPUTの時にurlが正しい結果になること', () => {
  const invalidInfo = getInvalidInfo('INVALID_INPUT', false);

  expect(invalidInfo.links[0].url).toBe(invalidReason.get('inputIssue'));
});

これは以下のように書くことでページのURLが変更されたときにテストが失敗するため、より価値のあるものになる。但しこれはexpect(invalidInfo).toStrictEquals({ ... })の形で一括判定したほうが、テストコードの可読性の観点からより良いだろう。

it('INVALID_INPUTの時にurlが正しい結果になること', () => {
  const invalidInfo = getInvalidInfo('INVALID_INPUT', false);

  expect(invalidInfo.links[0].url).toBe('https://help.example.com/23843ddvwa');
});

ERROR_MESSAGESをリソース変数としてみなせば、これはべた書きというより定数定義とみなせるため、基本的には問題ないと考える。勿論これは同じURLを持つものが大量にあるなど、ケースによっては考慮の余地はあるだろう。

またMapをやめ、敢えて直値を書くことで万一存在しなかった場合に、元にあった以下の空文字が返ってきて機能しなくなる問題も解決している。そもそも通る余地がないロジックなので存在しなくてよい。

url: referencePageLinkMap.get('userFormIssue') ?? ''

またERROR_MESSAGESas constを付与することで、意図しない破壊が起きる可能性を減らすことができる。

handlers/error/GetCustomerInfoError.ts

export const getCustomerInfoError = (errorType: CustomerErrorType) => {
  switch (errorType) {
    case CustomerErrorType.INVALID_INPUT:
    case CustomerErrorType.SOME_INPUT_ERROR:
      return ERROR_MESSAGES.formValidationError;
...
   };
}

まずgetInvalidInfo()getCustomerInfoError()にリネームすることで意味を分かりやすくしている。次に複雑な型情報を除去してシンプル(= (errorType: CustomerErrorType) =>)にする。errorTypeerrorReasonTypeisModeAを合成した結果を渡すことを想定している。これによって、この関数内の複雑性を減らすことができる。またCustomerErrorTypeはENUMなので、case 'CODE1':のようにリテラルべた書きを回避できるうえ、仕様変更などで値が変わった時にも対応がしやすくなる。

また戻り値の型も削除する。これは静的解析により自明であるほか、Omit<SomeFormInvalidInfo, 'invalidReason'>とあるが、実際に返しているのはinvalidReasonであり、関連性がないためだ(おそらく、偶々よく似た型を転用しているのだろう)。またこの事例では更にinvalidReasonという変数名があり、Omitの内容と視覚的に競合し、コードの読解が難しいため、この修正により可読性が増す。

また改修前はエラーメッセージオブジェクトのキーと、ヘルプページのキー名、そしてswitchのキーの全てが食い違っており、見ていて混乱する内容だったが、ヘルプページのURLをべた書きするように変えたため、混乱する要素を減らせている。

まとめ

コードの記述量を減らし、長大なコードを別々のファイルに分けることや、過度に分割しすぎないことによって可読性が向上し、コード量が減ったため保守コストも削減できたと考えている。

半面、静的解析のコストは上がっているが、静的解析のコスト増よりも可読性がよく、保守コストが低いコードのほうが効率的な開発に寄与するだろう。

また静的な値を取得するためにMapを使い、TypeScriptを利用しているため、理論上undefinedが返ってこないのに、それを期待するロジックを削除することで、コードを読んだ人が混乱する可能性も削減している。

型安全で見通しの良いエラーハンドリング

この項目ではエラーメッセージを返すサーバー側のエラーハンドリングについて記述する。

元コード

try {
  // API呼び出しなど
} catch (error) {
  if (error instanceof ApolloError) {
    if (error.graphQLErrors[0]?.extensions?.ERROR_KBN === 'ERR_XYZ') {
      // エラー処理
    } else if ...
    ...
  }

  if (error instanceof SomeError) {
    ...
  }
  ...
}

このコードの問題点

  • エラーハンドリングが長々とべた書きされていて見通しが悪い
  • 配列の添え字が直に指定されているが意図が読み取れない
  • 例外処理なのにOptional chainingが多く確実な例外処理に支障がある(例外処理で例外が発生するリスクは最小限にする必要がある)
  • extensions配下の型情報が暗黙的でわからない
    graphQLErrors: ReadonlyArray<{
     extensions: {
         [attributeName: string]: unknown;
     }
    }>
    

改善後コード

const handleApolloError = (err: ApolloError) => {
  if (err instanceof ApolloError) {
    // graphQLErrorsが空であれば例外をスローし、そうでなければgraphQLErrorsを返す
    const gqlErrors = parseGQLErrors(err.graphQLErrors);
    // graphQLErrorsの中身をパースし、意味のある型を付けて返す
    const extensions = parseThisFunctionErrors(gqlErrors);
    if (extensions.ERROR_KBN === 'ERR_XYZ') {
      // エラー処理
    } else if ...
  }
}

try {
  // API呼び出しなど
} catch (error) {
  if (error instanceof ApolloError) {
    handleApolloError(error);
  } else if (error instanceof SomeError) {
    ...
  }
  ...
}

べた書きされているとコードの見通しが悪いため、catch句の中では例外種別ごとにハンドリングする処理に飛ばし、そっちで処理できるようにする。

error.graphQLErrorsは要素が0の場合があるため、parseGQLErrors()のような共通関数を作り、要素があれば中身を返す、なければ例外をスローして、より上位の処理に飛ばすなどの処理を共通的に行うようにする。この仕組みを共通化することで、この要素に対する処理の一貫性を持たせることができる。

またgraphQLErrorsの詳細については処理によって内容が異なるであろうことから、ドメインごとにparseThisFunctionErrors()のような関数を作り、その中で適宜データを整形するのが望ましいだろう。

そうして結果的に意味のある型情報を持ったextensions、あるいは適当な結果情報をハンドリングすることで、型安全かつ、責務が別れ、疎結合な実装に寄与する。

この形式であればhandleApolloError()は必要に応じて別ファイルに切り出し単体テストを書くこともできるし、このままでもあっても規模が小さければ十分テスト可能だろう。

なお、parseGQLErrors()parseThisFunctionErrors()の具体的な内容については今回は省略する。

投稿日:
ジャンル::雑記

ネットの一部で稀に話題になるネタだが、数年前からヒロポンがまだ現役で存在することが気になっていたのでちょっと真面目に調べてみた。

日経メディカルには処方薬としてのヒロポンが掲載されており、これは毎年更新されており、添付文書の更新があることから存在は明らかである。更に製造元も住友ファーマに変わっている。

Wikipediaにも情報があるが、結局どれも二次情報なので一次情報に当たることにした。

とりあえず住友ファーマの製品情報を見るのが鉄板だろうというので見に行くと見事ビンゴ。但しここの情報は医療関係者向けなので基本的に一般向けの情報でないことには留意されたい。

まず製品基本情報のページをあたると「麻薬製品一覧」という項目がある。

paste-image-2025-19-22_0-18-31-287.png

ここに進むと会員限定ページが出てくるが、あからさまにJSで制御されているのでDOMをいじってみるとヒロポンの存在を確認することができた。

paste-image-2025-20-22_0-19-39-658.png

詳細を見るとヒロポンの情報を見ることができたが、日経メディカルにある以上の情報はほぼない。

paste-image-2025-22-22_0-21-18-875.png

また他にも厚生労働省発表の令和3年3月31日 大麻等の薬物対策のあり方検討会にもヒロポンが掲載されていることから、今日においてもヒロポンが存在することが推測できる。

paste-image-2025-24-22_0-23-43-660.png

但し1ページ目以降に一切出てこないことや消費統計にも存在が明確に見られないことから、実際に使用されているのかは不明だ。ヒロポンの効果・効能がナルコレプシー、各種の昏睡、嗜眠、もうろう状態、インスリンショック、うつ病・うつ状態、統合失調症の遅鈍症の改善や、手術中・手術後の虚脱状態からの回復促進及び麻酔からの覚醒促進、麻酔剤の急性中毒、睡眠剤の急性中毒の改善であることからして、全て代替薬が存在することから、恐らく実用性はないと思われる。

また院内処方限定で持ち出しできないことや、この資料に覚醒剤の使途が一切書かれていないこと、「国際条約と国内法の関係(向精神薬)」のページでメタンフェタミン等が赤字になっていないことから、製造体制が存在するだけで実際には流通していないのではないかと思われる。

しかしこの資料を見るとビバンセカプセルが覚醒剤原料として書かれてあり、非常に興味深かった。ビバンセはADHD向けに流通している薬でコンサータと同等の流通管理がかかっている薬なので、性質上は向精神薬に近そうなものだが、分類上はそうなっているようだ。

確かに令和5年7月改訂 東京都保健医療局 向精神薬取扱いの手引を見てもビバンセカプセルの名前はない。

また余談だが、カフェインは精神刺激薬の一種で一般的に向精神薬や麻薬、覚醒剤の仲間である。ヒロポンも元は無理やり動かすための薬とされていたので、そういう意味では似たような存在といえるだろう。覚醒剤とは乱暴に言ってしまえば眠気覚ましのことだ。

投稿日:
ジャンル::買い物

数年前からメガネの老朽化が気にかかり、特に鼻パッドを交換したかったのだが買った眼鏡や、眼鏡修理の専門店を名乗る店に行っても「在庫なし」とか、「その鼻パッドは昔流行ったもので今はないです」とか言われて手にいられずにいた。

そこでもうここはフルオーダー眼鏡としてゼロから作るしかないのか…と思い、フルオーダー眼鏡店に行くものの、樹脂フレームしか作っておらず、メタルフレームは無理と言われてしまう。だがしかしメーカー品の鼻パッドは規格品なので存在しているはずで、それは取り寄せられるという話を聞き、無事入手できた。ついでにレンズも買い替え、実に11年ぶりに鼻パッドとレンズを交換した。

交換前の写真を撮り忘れていたのだが、フレーム以外がピカピカなので新品といっても通るかもしれない。

20250410_010845178.JPG

交換前のパーツ。だいぶ黄ばんだり汚れている。

20250409_145853254.JPG

今回鼻パッドの取り寄せをしていただいたのは、ハンター坂にあるめがね舎ストライクさんだ。

レンズは購入店である京町筋のALOOKで購入した。ALOOKはほとんどが眼鏡市場になってしまったため、今や貴重な店舗だ。余談だがレンズ交換を注文したところ「他社のフレームでも一応やりますが…」と軽く渋られたのが面白かった。2014年に買ったものなので無理もない。

交換した結果、レンズの剥離や傷がなくなり、黄ばみも消えたため、以前より視界がクリアになったので良かった。

投稿日:
ジャンル::サイト運営

管理人についての内容がどうも薄いので、もう少し厚みを持たせようと書いていたら記事一本分になってしまったので供養のために残しておく。

途中で我に返り、書くことをやめたので後半が雑だが供養なので気にしない。

副産物としてちゃんと新しい自己紹介も出たので、以前のものとの新旧比較も残しておく。

没記事

1995年くらいからインターネットに生息してる職業プログラマ。神戸の三宮の辺りに生息している。

名前はLycolia Rizzimと書いて「リコリア・リジム」と読むが、長いので普段は「りこ🍥」と名乗っている。当たり前だがハンドルネームだ。

いわゆるインターネット老人会の会員で1995年くらいからインターネットに生息している。ネット黎明期の荒波に揉まれながら育ってきたタイプの人間だ。小学生の頃はダイアルQ2ソフトの駆除に精を出していた時代もあった。

小学校高学年くらいでチョロQをテーマにした個人サイトで交流をはじめたり、中学生の頃になると鋼の錬金術師の腐創作サイトに出入りするようになり、そういった中で、今日まで続く個人サイトを開いた。この時に作ったサイトはGaiaxコミュニティというHTMLを書いていじれるmixiみたいなプラットフォームだった。もちろん足跡もあった。

中学も半ばを過ぎるとラグナロクオンライン(RO)のβサービスを知るが、PCスペック的にプレイできなかったのでゴゴ市というゲームをしていた。

高校に上がるとROをプレイできるスペックのマシンを手に入れ、授業中は寝て、学校が終わったら全速で帰り、軽く廃人のようにプレイしていた。同時にこの頃になると自分のサイトはPHPを利用した動的サイトに移行し、すぐにCMSを導入して、ブログ化した。つまり私が初めて書いたプログラミング言語はPHPということになる。

この頃になると2chや二次裏、はてな、ぁゃιぃわーるどなどのその当時隆盛を極めたコミュニティによく出入りしていた。基本的アングラサイトや、そこからくる文化が好きだった。ニコニコ動画も黎明期ちょっと過ぎた頃はよく使っていた。

高校を卒業し、専門学校に入るとそろそろネトゲは辞めないといけない…と思い、ROのアカウントを消すのだが、物の一年で作り直してしまい、高校時代など比較にならない廃プレイによりたちまちレベルカンストするといった異常プレイをしていた。MVPボス狩りなどのエンドコンテンツにも精を出し、転生システムが出たらすぐに手を付けたり、FCASやSF皿、AGI-BSなど凝った職業にも手出ししていた。

しかしRの到来、そしてRRの到来で回りから大半が離脱したこと、人口も激減したこと、メインだったLUK極カタール型チンクロの活躍の場が限られるようになり、RRRでいよいよすることがなくなってしまったため、ROを引退し、艦これにシフトしたものの、MMOが恋しくなり、PSO2に行きなじまず、WoTにシフトしたものの1000時間もいかない間にやめてしまった。

そこでROをやっていたころにβに参加していたエミル・クロニクル・オンライン(ECO)に目を付けた。予想通りこれにはハマり、かなりやりこんだ。無課金で課金イリスURのR4を複数手にするほど狂ったほどやりこみ、わずか一年で総合点では勝てないものの、いくつかの分野では古参廃人と肩を並べられる程度の実力を手にした。そしてサービス終了するころには全デュアルカンスト、カンストアナザー5個以上、サブキャラも軒並みデュアルカンスト、アナザーLv3以上というだいぶ狂った領域までもっていけたと思う。

称号システムもほぼほぼ埋めきり、特にボス討伐は無限回廊のトリガーで発生するフィールドボス以外は全部倒した。DDボスは全部倒したし、ゴールドサイン進行も達成した。しかし残念ながらECOはサービス終了してしまった。

最近の趣味は主にブログ執筆やサイト運営、アニメ映画鑑賞や、登山、ログ取り辺り。

アニメ映画では比較的ニッチな作品を見ていることが多い。いわゆる6000人映画みたいなやつ。また映画を見に行くためだけに神戸から松山や山口、淡路島に行くなど精力的に移動することもある。

登山は元々運動不足解消と山にある神秘的な景色を求めて始めたのが始まりで、だいたい六甲山辺りの低山によく行っている。過去最高法は氷ノ山で標高1,510mだ。

ログ取りとしてはLast.fmで音楽鑑賞、YAMAPで登った山、はてなブックマークに参考になった・なりそうなURL、Excelで見たアニメや映画館に行ったログや家計簿など、いろいろなログを取って後から振り返ることを楽しんでいる。

座右の銘は自由気儘。性格はマイペース。16PersonalitiesはINFP-T。好きなものは歴史と伝統。好きな言葉は月に叢雲、花に風。

ROやECO、サモナイや世界樹の迷宮のようなキャラビルドが出来るゲームでは後衛職やヒーラーを前衛や物理攻撃職に回すのを好む。

どうでもいいことへの拘りが深く、他の人が気づかないような細かいことに敏感。

没記事を基に作り直した自己紹介

よくなったのかは果たして微妙なところだが、取り敢えず以前より深みは出たように思う。以前はあっさり過ぎた。長すぎて誰が読むねんという感じだが、こういうのは読みたい人だけ読んでくれればいいのだ。

以前の自己紹介 新しい自己紹介
1.png
2.png
投稿日:
ジャンル::振り返り

大したネタはないがテキトーに振り返ってみる

生活系

漆器の利用

漆器を買った

漆器は今でも日常的に使っている。

まず箸、プラスチック製のように熱した鍋に引っ掛けても曲がったり溶けたりしないし、漆が多少剥げてもウレタン塗装のようにバリバリ剥げてこないのがいい。何より拭き漆は見た目がきれいだ。

汁椀は底や飲み口の漆が剥げてきたが、やはり器が厚くなりすぎないのが便利だ。

漆器は木の温かみみたいなのもあり、使っていて気分がいい。

魚の煮付けをすると調味料が減る

魚の煮付けをすると料理油と醤油、みりん、砂糖がめちゃくちゃ減ることに気が付いた。調味料って減るんだ…という衝撃の事実!

しかし塩は減らない。食卓塩を6年以上前に買ったはずなのだが驚くほど減っていない。これでもここ3年で急激に減っているのだが、いつなくなるのだろうか?別になくなる必要もないのでいいが…。

20250407_232121904.JPG

木のまな板が汚れない話

プラスチックのまな板を使っていた時はもう変色していた気がするが、木のまな板は目立って変色していないように見える。

20250407_195503538.JPG

また、木のまな板を買ってしばらくはそのまま使っていたのだが、去年の秋ごろからちょいちょい水に濡らして使うようになり、今年に入ってからほぼ確実に濡らしてから使うようにしている。これは水に濡らすことで木目が埋まり、汚れづらくなるという情報を得たからだ。

まな板が綺麗だと心も洗われるようでよい。

作品系記事の執筆

がんばっていきまっしょいメイクアガールなど、作品の聖地巡礼や、感想にまつわる記事を多く書いた。普段人と会う中で何かを見た感想をなかなか話せないことが多かったので、こういった形で残していくのは個人的に意義があると感じている。

恐らく大抵の人は普段から仲間内で話しているので話せているのだと思うが、私の場合はそのような相手がいないためブログに出しているところがあると思う。逆説的に言うとそのためにブログをやっているともいえる。

つまり私は自分が思っていることをブログに書くことで、自分が何者であるかを対外的にアピールしようとしているのかもしれない。

去年の振り返り

2024年振り返りとして、去年の振り返りをした。この記事も2025年を振り返るときに役立てるためのネタとして作っている節がある。

振り返りに意味があるのか、飽きないか、そもそも継続するのかなども思わなくはないが、とりあえず今は続く限りやっていくという感じだ。

やっていく中で見えていくものがあるかもしれない。

料理記事

料理カテゴリを拡充し、様々なレシピを分野別に書いていくようになった。元々これはジャンルカテゴリ配下にあったが、単体のルートカテゴリとして分割した。

元々私はクックパッドにレシピを書いていたのだが、そもそもブログがあるのだからブログに書けばいいというのでブログに書き始めた経緯がある。余り外部に依存したくないのだ。

案の定、クックパッドは最近の更新で自分のレシピの確認がしづらくなったので、料理メモとして利用する魅力が減っている。

脳内の書き出し

頭の中にあるものをとにかくアウトプットしようと思った。出さなければそれはないのと同じだし、誰にも伝えられないからだ。

なので、鍵束をいい感じに運用するとかWebカメラを壁面に固定する装置を作ったのようなライフハック的な記事から、紙の本が減ってきているのような日常の気付きといった些細なことから、AviUtlで動画の一部にモザイクをかけ、動かす方法のようなやってみた系や、各サービスのセッションクッキーの保持期間がどの程度か調べてみたとか、スマホサイトでのキーボード挙動まとめのような、一見するとしょうもなそうな調査まで、細かいこともいろいろ書いた。

また3月は非常によく記事を書き、35記事と月間執筆数としては過去最高を記録した。書きすぎだと思うが、取り敢えず書いていく中で方向性とか、何かしらを見つける意味でも多く書いていた。

特に案だけ浮かんでいて放置されていた自作ルーター整備プロジェクトを動かし始められたことを再稼働させたり、脳内で発酵しかけていた個人的に考えてるNext.jsのアーキテクチャを満点の内容ではないにせよ、脳の外に出せたことは、第三者に自分の思想や理念、人物像を伝えやすくなる意味でもよかったと思う。

アカウント整理

いろんなサービスの方針や使い方について悩んだのでXとメルカリのアカウントを削除した。実は去年UberEatsのアカウントも消している。

SNSは今うかどんを拠点にしているが、もし自作ルーターができ、自宅サーバーの運用を軌道に乗せることができたら自鯖運用も検討している。以前は長らく兵庫丼を利用していたのだが、長らく管理人の不在が続いていてサーバーが不安定であるため、こちらは現状縮小中だ。

怪我の治癒

去年安い冷えピタを貼って炎症を起こして以来、ずっと額にかさぶたが出来ていて、もしかして一生このままで瘢痕として残るのではと危惧していたのが、2月くらいから徐々に治ってきたので、これはよかったと思っている。