くちたと計算記

プログラミングのことを書きます

JJUG CCC 2025 Fall 参加ログ

JJUG CCC 2025 Fallに参加したので感想などを書いていきます。 しっかり書こうと思うと投稿できない気がするので、3行ずつぐらいで許してください。

セッションの感想

1. イベントストーミングの始め方(nrsさん)

speakerdeck.com

本セッションでは、イベントストーミング自体の説明もしつつ、チームにどうやって導入していけば良いかを説明していた。

  • ビッグピクチャやビジネスプロセスモデリングぐらいの抽象度で意思疎通のために作ってみるのは良さそう。
  • 実際にイベントストーミングをやっている様子をみると、自分でもチャレンジできそうな気がしてくる。
  • ビッグワードを小さなドメインイベントに分割していく過程で、ドメイン知識を新たに獲得できる。

2. レビュー負債を解消する ― CodeRabbitが支えるAI駆動開発(Atsushiさん)

speakerdeck.com

本セッションでは、コーディングエージェントの登場によりコードレビューの負荷が高まる中で、人間がどんなマインドでどんな関わり方をすれば良いかを説明していた。

  • イラっとしたら、コーヒー豆を挽く。
  • AIや自動ツールに任せられるものは任せ、WhyやWhatに焦点を当ててレビューをする。
  • レビューガイドラインなどでレビュー自体の品質を均質化していくとよさそう。

3. 乱雑なコードの整理から学ぶ設計の初歩(masuda220さん)

speakerdeck.com

本セッションでは、コード整理術の概要とともに、コード整理術をどのように身体知にしていくかについて説明していた(と思う)。

  • 変更対象のコードを楽に安全に変更するのが目的であることを忘れない。
  • 小さな整理だけでなく、設計方式の変更を伴う整理や、事業戦略上の変更を織り込んだ整理も同時に進めていく必要がある。
  • 設計方式を変更する調整コストを惜しまないようにしたい。

4. レガシーで硬直したテーブル設計から変更容易で柔軟なテーブル設計にする(Kohei Sasakiさん)

本セッションでは、登壇者の方が編み出してきた柔軟なテーブル設計を保つためのノウハウについて説明していた。

  • レガシーなテーブル設計の闇が深すぎる。
  • 項目の追加をテーブル追加で実現することで、過去のデータを保全する。
  • ↑ コアな機能と、あとから追加されたオプショナルな機能とで、データもコードも分けておいたほうが良いということを再認識した。

5. ニューレガシー徹底抗戦ガイド(川島 義隆さん)

wolfchief.notion.site

本セッションでは、レガシーなコードのパターンと生まれる背景について説明したうえで、仕様モデルを使った開発手法を解決策として紹介した。

  • 盲目的にイケてる実装パターンに従うのではなく、仕様を捉え、モデルとして表現することが大事なこと。
  • 仕様モデル相当のドキュメントが無くて困っているので、プロダクトチームのドキュメンテーションとして取り入れたい。
  • 仕様モデルでコーディングエージェントのアウトプット品質を向上させられるなら、やるしかない。実装したくない!

さいごに

開催に関わってくださった方々、ありがとうございました。 もうJavaを使った開発に関わっていない私でさえ、興味関心のあるセッションが多すぎて一つを選ぶのが大変でした。 また参加するのが楽しみです。

UI上の振る舞いを意識したReactコンポーネント設計

概要

Reactコンポーネントの設計における基本的な内容について考えます。 元々は、子コンポーネントがsetXXXという名前のプロパティを持つのは変だと思うという話をしていました。 ここでは、サンプルコードを提示したうえで、サンプルコードにある問題点と改善案を考えていきます。

Reactを前提とした話が多いですが、Vueなどにも応用できる考え方だとは思っています。

題材

以下のような、数値を増減させるCounterコンポーネントを題材にします。

  • プラスボタンをクリックしたら、数値を1増やす。
  • マイナスボタンをクリックしたら、数値を1減らす。

修正前のコードは以下の通りです。

export function Counter({
  value,
  setValue,
}: {
  value: number;
  setValue: (value: number) => void;
}) {
  const increase = () => {
    setValue(value + 1);
  };

  const decrease = () => {
    setValue(value - 1);
  };

  return (
    <div>
      <button onClick={decrease}>-</button>
      <div>{value}</div>
      <button onClick={increase}>+</button>
    </div>
  );
}

問題点と改善案

1. PropssetValueの名前が分かりづらい

Reactでは属性とイベントでコンポーネントをカスタマイズしますが、setValueというプロパティはUIの何を表しているのか分かりづらいです。 他の開発者がCounterコンポーネントを使おうとしたら、setValueがいつどんなときに使われるプロパティなのか頭を悩ませることになるでしょう。

Propsとして関数を受け取る場合、それをイベントハンドラとみなせるならイベントとして定義したほうが、どんなときに関数が呼ばれるのかが明確になります。 今回の題材では、数値が変化するイベントonChange、数値が増減するイベントonDecrementonIncrementなどを受け取れるようにすると良いです。

2. 状態管理が呼び出し元に依存している

今の実装では、Counterコンポーネントを呼び出しただけでは、表示されている数値が変更されません。 呼び出し元でuseStateを使って状態を管理し、valuesetValueをCounterコンポーネントに渡すことで、表示されている数値が変更されるようになります。 Counterコンポーネントを正しく動かすためのルールが多いこと、呼び出し元で状態管理のコードが重複すること、これらの点からCounterコンポーネントは再利用しづらくなっています。

コンポーネントを設計する際は、コンポーネント自身の責務と呼び出し元の責務を適切に分離しましょう。 今回の題材では、表示されている数値の状態管理をCounterコンポーネント内で行い、単独で動作するように設計します。 それ以外は、呼び出し元から属性やイベントハンドラを通じてカスタマイズできるよう柔軟性を持たせます。 このように設計すると、呼び出し元でのコードの冗長さを抑え、コンポーネントの再利用性を高められます。

修正後のコード

export function Counter({
  value = 0,
  onChange = () => {},
}: {
  value?: number;
  onChange?: (value: number) => void;
}) {
  const [count, setCount] = useState(value);

  const increase = () => {
    const newValue = count + 1;
    setCount(newValue);
    onChange(newValue);
  };

  const decrease = () => {
    const newValue = count - 1;
    setCount(newValue);
    onChange(newValue);
  };

  return (
    <div>
      <button onClick={decrease}>-</button>
      <div>{count}</div>
      <button onClick={increase}>+</button>
    </div>
  );
}

設計上のトレードオフ

今回はCounterコンポーネントが単体で動作することを重視したので、Counterコンポーネントで状態を管理しました。 Counterコンポーネントの呼び出し元で状態管理するケースが多くなると、状態管理が冗長であるように思えるかもしれません。 実際に状態管理に関するガイドでも、stateのリフトアップをするときには子コンポーネントで状態管理をしていません。 今回のような汎用的なコンポーネントで状態管理を同様のことをするのであれば、カスタムフックでベーシックな状態管理を共通化したほうが良さそうです。

type Props = {
  value: number;
  onChange: (value: number) => void;
}

export function useCounter({ initialValue = 0, onChange = () => { } }: {
  initialValue: number,
  onChange?: (value: number) => void
}): Props {
  const [value, setValue] = useState(initialValue ? initialValue : 0);

  return {
    value,
    onChange: (value: number) => {
      setValue(value);
      onChange(value);
    }
  }
}

export function Counter({
  value, onChange,
}: Props) {
  const increase = () => {
    onChange(value + 1);
  };

  const decrease = () => {
    onChange(value - 1);
  };

  return (
    <div>
      <button onClick={decrease}>-</button>
      <div>{value}</div>
      <button onClick={increase}>+</button>
    </div>
  );
}

まとめ

ここまで、私が出会った違和感のあるコードを題材に、コンポーネントをどのように設計するのが良かったのかを書いてきました。 今回は、コンポーネントが表現すべきUI上の振る舞いを定めたうえで、それと連動するようにPropsの命名や状態管理を実装するという内容を紹介しました。

Reactの流儀にはゼロから画面を実装する手順が紹介されており、コンポーネントを分割する観点や状態管理の勘所などを学べます。読んだことがない方は、一度読んでみると良いかもしれません。

参考サイト

インドア派の初心者が美術館に行って気付いた美術館の楽しみ方

概要

先日、上野にある国立西洋美術館国立科学博物館に行ってきました。 展示品はどれも興味深く、美術に詳しくない私でも楽しむことができる展示でした。 その一方で、たくさんの展示品に私の体力がついていけず、楽しみ切れない部分もありました。 そんな経験をもとに、もっとこうすれば楽しめたかもなということを書いていきます。

行ったところ

一応、どんな展示を見たのかを紹介します。

全体で約5時間ぐらい観覧しました。 美術館はもちろんのこと、博物館も意外と知らないことがあって面白かったです。たとえば、深層学習で宇宙線の種類を自動で判別するという展示は、わたしが子どものときにはありませんでした。

計画と予習

一度にたくさん観覧すると疲れてしまうので、観覧するものを絞ったほうが最後まで楽しめそうだなと思いました。 たとえば、美術館と博物館のどちらか1か所だけに絞る、常設展と企画展のどちらかに限定するといった工夫が有効です。 美術館によって展示品の傾向にばらつきがあることも多いです。初心者であれば、観覧料の面で気軽に挑戦できる常設展を最初に選ぶと、それほどコストを払わずにその美術館の雰囲気を味わえます。

それとは別に、Webで展示について予習をしてみるといいと思います。どんな展示をしているかというのはもちろんのこと、題材についても調べてみると良いかなと。たとえば、西洋画であればキリスト教や神話についてざっくり学んでみると、作品をより楽しめそうです。

体力

美術館では2~3時間立ちっぱなしになるので、脚の筋肉に持久力を求められます。 私は仕事でも室内に閉じこもってしまうことが多いので、仕事をし続けるためにも意識的に体力をつけていく必要がありそうです。

博物館では解説プレートや音声ガイド、美術館では作品の背景情報など、多くの情報を処理する必要があるため、知的体力が求められます。身体的な体力が知的体力にもつながる可能性があるので、私の場合は定期的な運動で知的体力の変化を試してみようと思います。

まとめ

美術館とか博物館はとても興味深い場所です。 ただし、自分の体力に見合わないような計画を立てて遊びに行くと、最後まで楽しみ切れなくなってしまうかもしれません。 皆さんも体力をつけて自分のキャパシティを増やしつつ、無理のない計画で美術館巡りにチャレンジしてみてください。

バックエンドでリクエストを跨いでリソースをロックするときの設計(案)

やりたいこと

  • リソースを同時に一人だけが編集できるようにしたい。
  • 編集中の人がいるなら、編集画面を開けないようにしたい。

案1. 現在の編集者を編集セッションとして管理する

有効なセッション内でセッションIDが重複しないことは保証されるが、無効になったセッションとセッションIDが重複する可能性がありそう。

  • 編集対象のリソースIDを候補キーにして、現在の編集者を編集セッションのレコードとして管理する。
  • 編集が終わったら編集セッションを破棄する。
  • 編集関連のアクションで、編集セッションを使って認可処理を実施する。
  • 必要があれば、削除された編集セッションを記録として別テーブルに残す。
  • 必要があれば、編集中の内容を保持する。

案1-改

今度は、破棄されたセッションか破棄されていないセッションかの二択であることが分かりづらくなった気がする。

  • 生成したセッションIDを一つのテーブルで管理する。

案2. 編集開始に対して編集破棄イベントを関連させる

破棄されたセッションも「開始されたリソース編集セッション」に残り続けるので、 同時に一人だけが編集可能という要件をテーブルの制約だけで満たせない。

  • 編集開始したら、開始されたリソース編集セッションにレコードを追加する。
  • 編集終了したら、破棄されたリソース編集セッションにレコードを追加する。
  • 現在の編集者をクエリで取得して、認可処理を実施する。

      select
        *
      from
        "開始されたリソース編集セッション"
        left outer join 破棄されたリソース編集セッション"
          on "破棄されたリソース編集セッション"."セッションID" = "開始されたリソース編集セッション"."セッションID"
      where
        "編集者ID" = ?
        and "破棄されたリソース編集セッション"."セッションID" is null
    

最後に

一番のやりたいことは、リソースを同時に一人だけが編集できるようにすること。案2はそれをデータで表現できない点で不採用かなと思った。 破棄したセッションIDと重複するセッションIDが発行されるのは困るので、案-1改のように有効無効問わずセッションIDを一つのテーブルで管理するのは悪くないと思う。

参考

Spring Web MVCの自動テストでWeb APIのパスパターンがケバブケースであることをテストする

概要

Web API: The Good Partsという本を読んで、Web APIの仕様内で統一を図ったほうが良い点があることが分かった。

たとえば、パスで使用する文字列の単語の分かれ目をどのように表現するのか。

この記事では、Spring Web MVCで実装したWebAPIのパスがケバブケースになっているかどうかをJUnitでテストする。

使用する言語やライブラリ

  • Kotlin 1.9.25
  • Spring Boot Starter Web 3.3.3
  • JUnit Jupiter 5.10.3

方針

Spring Framework公式リファレンスによると、パスとリクエストハンドラのマッピングHandlerMappingという型のBeanが保持している。 特に今回実装するWeb APIアノテーション付きControllerで実装するので、RequestMappingHandlerMapping型のBeanを使ってテストする。

実装例

以下のメソッドの戻り値を活用してテストコードを書いた。

このことについては、メソッドを呼び出した結果をコンソールに出力してもらえれば、分かると思う。

以下のコードが、実際に実装したコード。

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import kotlin.test.assertTrue

@WebMvcTest
class WebApiUriTests {
    @Test
    fun `パスがケバブケースで定義されている`(@Autowired handlerMapping: RequestMappingHandlerMapping) {
        val mapping = handlerMapping.handlerMethods
            .flatMap { (mappingInfo, method) -> mappingInfo.patternValues.map { (it to method) } }

        assertAll(mapping.map { (pathPattern, method) ->
            {
                assertTrue(
                    isKebabCasePath(pathPattern),
                    """メソッド${method}は不正なパス${pathPattern}にマッピングされています"""
                )
            }
        })
    }

    /**
     * パスの各部分がケバブケースだけで構成されているかどうかを確認する。
     * ただし、パスパラメータ(`{...}`)はケバブケースかどうかをチェックしない。
     * 全ての部分がケバブケースであれば、trueを返す。
     */
    private fun isKebabCasePath(value: String): Boolean {
        return value.split("/")
            .filterNot { it.startsWith("{").and(it.endsWith("}")) }
            .all { isKebabCaseWord(it) }
    }

    /**
     * 単語がケバブケースかどうかを判断する。
     * ケバブケースであれば、trueを返す。
     * 空文字列もtrueを返す。
     */
    private fun isKebabCaseWord(value: String): Boolean {
        return value.matches(Regex("^[a-z]+(-[a-z]+)*(/[a-z]+(-[a-z]+)*)*$|^$"))
    }
}

以下のControllerを実装して、試しにテストを実行してみる。 かなり雑だけど許してほしい。

@RestController
@RequestMapping
class TestController {

    @GetMapping("aaa")
    fun `1単語`() {}

    @GetMapping("aaa-bbb")
    fun `2単語(ケバブケース)`() {}

    @GetMapping("aaa/bbb")
    fun `1単語が1段ネスト`() {}

    @GetMapping("aaa-bbb/ccc-ddd")
    fun `ケバブケースが1段ネスト`() {}

    @GetMapping("aaa/bbb-ccc")
    fun `1単語とケバブケースが混在1`() {}

    @GetMapping("aaa-bbb/ccc")
    fun `1単語とケバブケースが混在2`() {}

    @GetMapping("{sampleId}/aaa")
    fun `パスパラメータと1単語1`() {}

    @GetMapping("aaa/{sampleId}")
    fun `パスパラメータと1単語2`() {}

    @GetMapping("aAa")
    fun `大文字を含む不正パターン`() {}

    @GetMapping("aaa_bbb")
    fun `アンダースコアを含む不正パターン`() {}
}

テストを実行すると、テストに失敗する。 下二つに定義した不正パターンで失敗していることが分かる。

おまけ

この調子でリクエストパラメータの形式チェックもしたいと思ったが、リクエストパラメータがどの名前なのかを簡単に取得できないので諦めた。 パスのパターンはクラスレベルの@RequestMappingとメソッドレベルの@RequestMappingをマージした結果を簡単に取得できる。 ところが、リクエストパラメータの名前を簡単に取得する方法が分からなかった。

どうしてもやりたい場合、メソッドの引数の情報は取得できるので、引数の型やアノテーションをもとにリクエストパラメータの名前を一覧で取得すればよい。 リクエストパラメータとして扱われる引数の仕様はこれを読めば分かる。 ここまでテストで凝ったことをすると、逆にバグが増えそうなので止めたほうが良さそう。

シンプルに実現できる方法があったら、ぜひとも知りたい。

参考資料

ライフサイクルの異なる変数があったら、コンポーネントを分けることを検討したほうがよさげ

概要

Vuetify3のv-dialogを使って、ダイアログをどのように実装するのが良いか考える中で、ライフサイクルの異なる変数は外部のVueコンポーネントに切り出したほうがいいと気が付いた。

ライフサイクルの異なる変数とは、以下のようなものを想定している。

  • 変数の型に、nullやundefinedのような不定であることを表現する値を含む。
  • コンポーネントを初期化した後にも、何らかのイベントによって値が定まった状態と定まっていない状態とを行き来する。

たとえばダイアログについて考えると、ダイアログが開いているときだけダイアログ内の内容が定まるが、ダイアログが閉じているときには内容が定まらない。 このようなときには、ダイアログの内容を専用のコンポーネントで表現したほうが良さそう。

お題

以下の仕様のVueテンプレートについて考える。

  • 開くボタンをクリックしたら、/api/users/:idにリクエストを送信し、指定したIDに対応するユーザー情報を取得する。
  • ダイアログを開き、取得したユーザーのusernameをダイアログに表示する。
<script setup lang="ts">

const testUsers: { id: string }[] = [ { id: "1" }, /* 省略 */];

const dialog = ref<boolean>(false);
const dialogMessage = ref<string | null>(null);

const open = (id: string) =>
    $fetch(`/api/users/${id}`)
        .then(({username}) => {
            dialogMessage.value = username;
            dialog.value = false;
        });

const close = () => {
    dialogMessage.value = null;
}
</script>
<template>
    <div>
        <v-table>
            <thead>
                <tr>
                    <th>ユーザーID</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="user of testUsers" :key="user.id">
                    <td>
                        {{user.id}}
                    </td>
                    <td>
                        <v-btn @click="open(user.id)">開く</v-btn>
                    </td>
                </tr>
            </tbody>
        </v-table>
        <v-dialog v-model="dialog" @after-leave="close">
            <v-card title="ユーザー" :text="dialogMessage"></v-card>
        </v-dialog>
    </div>
</template>

現状の問題点

変数dialogと変数dialogMessageの型は、以下のような型になるはず。

type DialogProps = { dialog: true, dialogMessage: string } | { dialog: false }

現状では、v-dialogのv-modelにRef型の変数を指定する必要がある関係で、dialogの値とdialogMessageの有無に関連が無くなっている。

v-ifと型ガードでも解決できそう。ただ、表示の制御をv-dialogとv-ifの両方で実施するのは、望ましくないように感じる。

ダイアログの内容を別のコンポーネントで読み込むように修正する

以下の点に注意して、コンポーネントを実装した。

  • v-dialogでダイアログを開くごとに中身を読み込みように修正する
  • データを読み込むためのキーは常に定まっているものとする

ソースコードは以下のようになった。

<script setup lang="ts">

const props = defineProps<{userId: string}>();

const { data: user } = await useFetch(`/api/users/${props.userId}`)

if (!user.value) {
    throw Error("ユーザーの取得に失敗しました。")
}
</script>
<template>
    <v-card title="ユーザー" :text="user?.username"></v-card>
</template>
<script setup lang="ts">
const testUsers: { id: string }[] = [ { id: "1" }, /* 省略 */];
</script>
<template>
    <div>
        <v-table>
            <thead>
                <tr>
                    <th>ユーザーID</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="user of testUsers" :key="user.id">
                    <td>
                        {{user.id}}
                    </td>
                    <td>
                        <v-dialog>
                            <user-dialog-content :user-id="user.id"></user-dialog-content>
                            <template #activator="{props: dialogProps}">
                                <v-btn v-bind="dialogProps">開く</v-btn>
                            </template>
                        </v-dialog>
                    </td>
                </tr>
            </tbody>
        </v-table>
    </div>
</template>

最後に

コンポーネント設計の仕方がいまだに分からないけれど、扱う変数やコンポーネントのライフサイクルについての観点は持っておいた方が良いと考えた。 とはいえ、他に何も分からないので、何か書籍など参考にできる情報があれば積極的にインプットしたい。

v-bindでイベントリスナー(v-on:~, @~)を設定する

概要

コンポーザブルでコンポーネントv-bindで設定する値をオブジェクトで返すときに、イベントリスナーも一緒に返したいと思った。 そんなとき、実装する方法が全然分からなかったので、自分なりに調べて実装してみた。

結論

プロパティのキーを on${大文字始まりのイベント名}にすれば、イベントリスナーとして認識される。

v-onを使った実装

<button @click="console.log('click')"></button>

v-bindを使った実装

<button v-bind="{ onClick: () => console.log('click') }"></button>

レンダー関数とJSX

実は、この書き方はレンダー関数内でVNodeを作る時の書き方。

// イベントリスナーは onXxx として渡す必要があります
h('div', { onClick: () => {} })

レンダー関数とJSX | Vue.js 公式

引用した部分では、h()関数のpropsを設定する箇所にイベントリスナーを設定している。 この書き方を真似すれば、v-bindで渡すオブジェクトでもイベントリスナーを設定できるのではないかと考えた。

※v-bindでも同じ書き方ができると明記されている公式の記事を見つけたわけではない。もし、その記述がある箇所をご存じだったら、教えていただきたい……

活用例

実用的かどうかわからないけど、コンポーザブルで返した関数を使ってダイアログの状態を操作できるようなプログラムを実装してみた。

ダイアログの開閉状態をv-modelで管理し、ダイアログに表示する内容にかかわる情報などをpropsで渡すことを想定している。

export const useDialog = <DataType extends object = {}>(): {
  dialog: Ref<boolean>,
  props: ComputedRef<PropsType<DataType>>,
  open: (dialogData: DataType) => void
} => {
  const dialog = ref<boolean>(false);
  const _data = ref<DataType>();

  const props = computed<PropsType<DataType>>((): PropsType<DataType>=> {
    return {
      data: toValue(_data),
      "modelValue": dialog.value,
      "onUpdate:modelValue": (value: boolean) => {
        console.log(value)
        dialog.value = value;
        if (!value) {
          _data.value = undefined
        }
      }
    }
  })

  const open = (dialogData: DataType): void => {
    _data.value = dialogData;
    dialog.value = true;
  }

  return { dialog, props, open }
}
<script lang="ts" setup>
import {useDialog} from "@/composables/dialog";
import AppDialog from "@/components/app-dialog.vue";

const { props, open } = useDialog<{ title: string; body: string; }>();
</script>
<template>
  <div>
    <app-dialog v-bind="props"></app-dialog> <!-- ダイアログ -->
    <button @click="open({ title: 'タイトル', body: '本文' })">開く</button>
  </div>
</template>

最後に

レンダー関数やJSXでの書き方というのが、全然関係ないところで役に立つとは思わなかった。 レンダー関数やJSXでVNodeを作るやり方は、単純なイベントリスナー以外にも色々とある。 読んでいない人がいたら、意外なことを発見できるかもしれない。