UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【技術本】Grokking Simplicity 本紹介

DMMF本とは別の優良な関数型思考プログラミングの実践本『Grokking Simplicity』を読んだ

対象読者

本の構成

  • 第 1 章〜第 2 章:関数型プログラミング(以下、FP)の紹介と、何が可能になるのかについて概要説明
  • 第 3 章〜第 9 章: コードをアクション、計算、データで区別することの紹介と実践
  • 第 10 章〜第 19 章: 第一級抽象化の考え方の紹介と実践

最初から順番に読むことを推奨しており、各章に演習問題もついているので進めながら理解度を増していく

コードはJavaScriptで書かれているが、推奨ではなく FP に慣れていない人に教えるのに最適な言語のため利用しているとのこと

本に出てくる要点

以下の要素を使って、シンプルに実装していくための実践本

関数型プログラミング

副作用のない純粋な関数のみの使用を理想とするプログラミングスタイル

副作用というのは戻り値以外の関数の動作のことで、例えば

  • メールを送信する
  • ファイルの読み取り

など

ただ、ソフトウェア開発で副作用がないようにすることは不可能に近い

関数型プログラミングは長い歴史があり、これらの副作用との向きあってきたので対処法がある

そのため、理想だけの非現実的なプログラミング手法では決してないことが説明される

コードをActions、Calculations、Dataで区別する

大前提として関数型プログラミングではコードを以下の3つに区別する

  1. Actions: 実行タイミング、実行回数に依存する
  2. Calculations: 実行タイミング、実行回数に依存しない
  3. Data: 何かを実行することはできないが、実行しなくても意味がわかる

このデータ区別が関数型プログラミング の基本になる

Stratified design

データの区別をする前に、まずはコードを「更新頻度」別にまずは整理する

そうすると幾つかの階層が見えてくる

本にあった自動ピザ作成システムを例に挙げると、以下のように「ビジネスルール」「ドメインルール」「技術スタック」の3つの抽象された階層に分けられる

更新頻度 動作 階層
ピザのメニューを毎週かえる ビジネスルール
ピザを作る ドメインルール
◯◯のプログラミング言語で実装する 技術スタック

関数型プログラミングではこのように抽象レイヤーで階層化して設計することをStratified design(ストラティファイドデザイン)と呼ぶ

階層は3層に分けられるとは限らず、プロジェクトによっては何層にもなることがある

第一級抽象化

抽象化して説明された動作は実施にはもっと処理が複雑である場合が多い

例えば、「ピザを作る」という動作は実際には

「生地を伸ばす」→「ソースを塗る」→「具材をのせる」→「焼く」

のような処理が隠れている

このように本当は大きな処理を隠して「ピザを作る」と表現し、そのレイヤー中で「ピザを作る」として扱えるようにすることを第一級抽象化と呼ぶ

これは再帰的で「生地を伸ばす」もまた大きな処理の抽象表現で、その中の動作もまた処理の抽象表現である 

最終的にはプログラミング言語が提供する機能になる

まとめ

この本は以下の要素を使って、シンプルに実装していくための実践本で、

多くのページをさいて丁寧に説明されているので読みやすい

この要素を使った関数型思考が身につけらればビジネスの変更に耐えらえる堅牢なソフトウェアが作れるかもしれない

【技術本まとめ】実践Next.jsを読んでのメモ

takepepeさんの新しい本『実践Next.js』を読んだのでメモ

AppRouterは使ったことがなかったので、この本で情報のキャッチアップをしたい

本の内容は4章まではAppRouterの基礎情報で、それ以降はかなり実践的なサンプルアプリの実装の話

自分としては以前からNext.jsのPagesRouterは使っていたので、あの機能はAppRouterではどうなるのかという差分を中心に読んでいった

全体的にかなり分かりやすくまとまっていたので、とても読みやすかった

以下はメモ

  • ハイドレーション:任意のDOMにイベントハンドラーをアタッチすること
  • Client Componentからimportされるコンポーネントや関連ファイルもブラウザ向けにバンドルされる
  • 全部に"use client"をつける必要はない→親がClient Componentなら不要
    • usePathnameを使うならClient Componentにする
    • 汎用性の高いコンポーネントは親がServerかClientのどちらにもなりうるので、"use client"は最小限に留める
  • fetch関数のデフォルトのキャッシュ設定
    • 更新頻度が低い:静的データ(何も設定しなければこれとして扱われる)
    • 更新頻度が高い:動的データ(fetch(url, { cache: 'no-store' }) を指定すると毎回取得する)
  • devモードでサーバー立ち上げるとキャッシュの挙動が把握できないので、ビルドして起動する
    →その前にキャッシュの破棄(.next/cache/fetch-cacheを消す)をすること
  • Routeのレンダリング
  • 動的レンダリングRouteになる要因は3つ
    • ① 動的データ取得が行われると動的レンダリングになる
      • { cache: 'no-store' } 有れば動的
      • ビルド時にレンダリングを試みるので、動的レンダリングの場合はビルド時にAPIと接続できないとビルドエラーになる
    • ② 動的関数の使用
      • Cookie参照、リクエストヘッダー参照、URL検索パラメータ参照など
    • ③ Dynamic Segmentの使用
      • パスの一部が [categoryName] になっている場合はcategoryNameがparamsとして取り出せるので、この機能を使った場合
  • import { notFound } form "next/navigation" 知らなかった
  • route.tsを使うとWeb APIになる
  • global-error.tsはハンドリングされなかった例外が最後に行き着く場所("use client"が必要)
  • Route Groups:グルーピングのためにフォルダを作りたいが、パスの一部としたくないフォルダはフォルダ名を () で囲む

    • この下にlayout.tsxをとかおくと、それぞれのLayoutが適用される
    • 全くリーティング対象にしたくない場合は _ をフォルダ名の接頭辞につける(例:_components)
  • 3.3 Parallel RoutesとIntercepting Routesは新しい考え方

    • @を接頭辞につけたフォルダはURLのパスとして無視される
    • 「slot」と呼ばれるノードがchildrenのようにpropsで渡される
    • @配下のSubtreeと表示しているRoute Segmentが一致したらそのノードがレンダリングされる
    • default.tsxは一致するまでの間のslotとして表示される(返すものがなければnullを返す)
    • Intercepting RoutesはRouteを「横取り」するRoute定義でParallelと合わせると、@配下のSubtreeと表示しているRoute Segmentが一致したらそのRoutesを横取りする
    • (.)とか (..) とか相対パスのように定義する
  • 3.4 静的なメタデータはmetadataオブジェクトをexportすれば良い、動的の場合は generateMetadata 関数をexportする

    • メタデータと画面表示時の2回リクエスト送ってしまうので「Requestのメモ化」を内部で行なっている(6.3節で解説)
    • メタデータは継承されるため、個々の設定は不要
      →ただし、パラメータが異なるとメモ化されない
       →パラメータの差異が出ないように目的別に関数を作り共通化すると良い
    • generateMetadata関数の第二引数のResolvingMetadataで継承された親のメタデータを参照できる。
  • 4.1 page.tsxとroute.tsは同じフォルダに入れるとコンフリクトを起こす

  • 4.2 画面と同様にビルド時に以下の要因が検出されると動的Route Handleとみなされる

    1. Dynamic Segment値の参照 (※第二引数にDynamicSegmentのparamsがある)
    2. Requestオブジェクトの参照(※第一引数にRequestオブジェクトがある)
    3. 動的関数の使用(cookies関数とか)
    4. GETとHEAD以外のHTTP関数のexport(POSTとか)
    5. Segment Config Optionsの指定( export const dynamic = "force-dynamic";)
  • 4.3 Route Handlerの良いところはNext.js自身が認証認可機能を備えたWebサーバーなので、画面側からのリクエストにユーザー情報を含めなくてもCookieを頼りにログインユーザーを参照できる
  • 6.4 Next.jsは「Incremental Cache」というデータ取得結果をキャッシュし、必要に応じて更新する組み込みキャッシュシステムが存在する。
    →デフォルトでオン。一年間有効。
  • fetch("url", { next: { revalidate: 60 * 60 }}) で有効期間変更
    このように有効期間を指定するRevalidateを Time-based Revalidation という
  • 6.6 cache関数を使うとfetch関数以外での「Requestのメモ化」ができる
    • Reactコンポーネント内では使わないこと
    • データ取得関数の引数はプリミティブ値でなければならない
import { cache } from "react";
...
export const getProfileFromScreenName = cache(() => {});
  • 6.7 fetch関数以外のデータキャッシュをしたい場合は、 unstable_cache を使う
    • fetchData: キャッシュされるデータを取得するための非同期関数
    • keyPars: キャッシュを一意に識別するためのキーとして使われる文字列の配列
    • options: キャッシュの動作を制御するオブジェクト
    • Reactコンポー内で利用可能
import { unstable_cache }. from "next/cache";
...
const data = unstable_cache(fetchData, keyParts, options)();

ここまでが8章で、9章以降は別の機会に書く

【DMMF】Railway Oriented Programmingのエラーハンドリングの話

Sansanさんの記事でTypeScript開発にRailway Oriented Programmingを持ち込んでエラーハンドリングする話を読んだ

buildersbox.corp-sansan.com

DMMF本の内容のおさらいができて良い記事だった

話の内容は、よくある手続的な実装処理の中で、

  • 精査した結果を単純なbooleanで返却して分岐中で例外をスローしていたり
  • DBへの更新時に例外スローされたり
  • 非同期処理の結果をResult型でsuccessかどうかで分岐して例外をスローしていたり

と、多様な形で表現されたエラーが混在すると、以下の課題がある

  • 『関数のパイプライン』のイメージが崩れてしまう
  • エラー表現が多様なので読みにくい
  • try-catchのcatchでエラーを集約させる書き方だとエラーの型情報が失われてしまう(catch節の中で網羅的に考慮できているかどうかは不明)

そこでRailway Oriented Programmingの考え方を取り入れたという話

Railway Oriented Programming

1つのインプットで2つのアウトプットをするような関数(スイッチ関数)を以下の図のようにくっ付けて2レーンにする手法

手順

  1. 関数の出力をResult型で表現する
  2. 関数にResult型を入力できるようにする
  3. 関数を連結する
  4. エラーハンドリングする

まず、こういうResult型を用意する

type Failure = { errorCode: number };
type Result<Ok, Ng extends Failure> = {
    success: true;
    data: Ok;
} | {
    success: false;
    error: Ng;
}

そして、Result型をインプットにして、またResult型を返す関数を用意する

この時「Result型の成功データをインプットにして、またResult型を返す関数」をインジェクションできるようにしておく

function bypass<
  PreviousOk,
  PreviousNg extends Failure,
  NextOk,
  NextNg extends Failure
>(
  func: (i: PreviousOk) => Result<NextOk, NextNg>,
): (input: Result<PreviousOk, PreviousNg>) => Result<NextOk, PreviousNg | NextNg> {
  return (input) => input.success ? func(input.data) : input;
}

それをpipeで繋いで、最後はResult型のデータをts-patternのmatch関数でハンドリングする

  ...
  (result) => match(output)
      .with({ error: { errorCode: 400 } }, () => { ... })
      .with({ error: { errorCode: 500 } }, () => { ... })
      .with({ success: true }, () => {})
      .exhaustive()

これで確かに網羅的にエラーがハンドリングできて良さそう

【システム開発】技術的な負債の対処の仕方

技術的な負債についてわかりやすいスライドを見てのメモ

speakerdeck.com

  • 借金のように短期間負債を抱えて大きなリターンを得ることはある
  • 問題となりそうな技術的負債は「影響度を知らない」こと
  • 「影響度を知らない」とビジネスに悪影響を及ぼすリスクになる可能性を保持する
  • リスクに対する許容度を把握しておく
    • 設計する時間がなく間に合わせの設計:影響度・発生可能性に応じて対応方針を決める、仮説であることを知りつつビジネスチケットと並べて潰していく
    • 負債かどうかもリスクかどうかもわからない場合は手の打ちようがない(環境の変化によってなっている可能性がある)
  • 技術的負債として事前に認知する仕組みを作る
    • 技術的負債が増えると、開発生産性が下がるので、開発生産性を測定する
      • Four Key Metrics
        • デプロイ頻度
        • 変更のリードタイム
        • サービス復元時間
        • 変更障害率
      • SPACE
        • Satisfaction and well being:開発者がどれだけ満足しているか
        • Performance:開発者のコードは正しく実行されたか
        • Activity:開発者の行動やアウトプットの数
        • Communication and collaboration:メンバーやチームがどのようにコミュニケーションをとり協力するか
        • Efficiency and flow:中断や遅延を最小限に抑えて進めて完了する能力
    • ただし、以下の法則があるので注意
      • グッドハートの法則:測定を目標にすると、適切な目標ではなくなる
      • キャンベルの法則:社会的な意思決定において重要な指標ほど操作されやすい

【PostgreSQL】データのバックアップとリストア

Cloud SQLのDBからデータのバックアップをスキーマごとローカルに持ってきたい

手順が以下にあったのでメモ
cloud.google.com

まず、Cloud SQLからデータをバックアップする

$ pg_dump --dbname="my_context" --schema='"my_context"' --file="/uggds/my-project/{data_source}-{timestamp}-dump.dmp" --format=c --no-owner --no-acl

リストアする

$ pg_restore --dbname=local_my_context "/uggds/my-project/{data_source}_production-2024_03_23_21_02_41-dump.dmp" --format=c

これでローカルにデータを持ってくることができた

【Fastly】サブスクリプションをキャンセルしたい

Fastlyのサブスクリプションのキャンセルをしたい

FastlyのBilling Informationでは以下のようになっていて月$50払っている

この並びの一番下を見ると以下のキャンセルについての記述があったので、サポートチケットを発行して依頼したが返事がなかった

調べたところ、以下からアカウント削除する必要があるようだった

docs.fastly.com

アカウントがキャンセルされると、キャンセル日までに発生した未払いの料金が請求される

【Git】git switchとgit restore

まだgit checkoutを使っていたので知識を更新する

zenn.dev

使いそうなやつだけメモ

  • git switch
    • git switch <branch>:ブランチ切り替え
    • git switch -c <branch>:ブランチ作成
    • git switch -:一個前のブランチに戻る
  • git restore
    • git restore <file>:ファイルを元に戻す
    • git restore --staged <file>:Staging状態のファイルを元に戻す