単一責任の原則とオープン・クローズドの原則の関係性について

ここ最近、SOLID原則の「単一責任の原則」と「オープン・クローズドの原則」に関する検討を行っていて、オープン・クローズドの原則と単一責任の原則の関係性に考えていました。

具体的には、オープン・クローズドの原則に従った設計を行うと、自然と単一責任の原則に従うのではないかということです。必ずしも正しいとはいえませんが、多くの場合に成り立つのではないかと思います。

単一責任の原則とは

単一責任の原則(Single Responsibility Principle)は、オブジェクト指向プログラミングにおける設計原則の一つであり、「モジュール、クラス、関数などは、単一の機能についてのみ責任を持ち、その機能をカプセル化するべきである」というものです。

単一責任の原則のメリット

単一責任の原則を適用することで得られるメリットには以下のようなものがあります。

  • 理解しやすさの向上: 責務が明確に分かれているため、コードの理解が容易になり、保守性も向上します。
  • 変更の容易化: 特定の機能を変更する際、影響を受ける範囲が限定されるため、変更に伴うリスクが低減されます。
  • 再利用性の向上: 単一の機能に特化したモジュールは、他の箇所でも容易に再利用することができます。
  • テストの容易化: 単一の機能に特化したモジュールは、個別にテストしやすくなります。

単一責任の原則を満たすための手法

単一責任の原則を満たすための手法として以下のような手法が挙げられます。

  • クラスごとに単一の責務を割り当てる: あるクラスは、特定の機能のみを担当するように設計します。
  • メソッドを小さくする: メソッドは、単一のタスクを実行するように設計します。
  • インターフェースを使用する: 共通の機能をインターフェースで定義し、それを複数のクラスで実装します。

オープン・クローズドの原則とは

オープン・クローズドの原則(Open/Closed Principle)は、オブジェクト指向プログラミングにおける設計原則の一つであり、「ソフトウェアは、既存のコードを変更することなく、機能拡張できるようにすべきである」というものです。

オープン・クローズドの原則のメリット

オープン・クローズドの原則を適用することで得られるメリットには以下のようなものがあります。

  • 柔軟性の向上: 新機能の追加や変更を、既存のコードに手を加えることなく行うことができます。
  • 保守性の向上: 既存のコードを触らずに変更できるため、コードの保守性が向上します。
  • 再利用性の向上: 既存のコードを拡張することで、新しい機能を追加することができます。
  • テストの容易化: 既存のコードを変更しないため、テストが容易になります。

オープン・クローズドの原則を満たすための手法

オープン・クローズドの原則を満たすための手法として以下のような手法が挙げられます。

  • 抽象化を利用する: 共通の機能を抽象化し、それを継承したクラスで具体的な処理を実装します。
  • 依存関係を明確にする: クラス間の依存関係を明確にし、できるだけ疎結合な設計を目指します。
  • ポリモーフィズムを利用する: 異なるクラスで同じインターフェースを実装することで、柔軟な振る舞いを可能にします。

オープン・クローズドの原則に従って設計の例を確認する

ここからが本題です。

オープン・クローズドの原則に従うと、自然と単一責任の原則に従うことになるのではないかと考えました。もちろん、「オープン・クローズドの原則に従っている=単一責任の原則」が必ずしも成り立つわけではありませんが、多くのケースにおいては成り立つのではないかと思います。

拡張に対して開いていて、修正に対して閉じている

オープン・クローズドの原則には、「拡張に対して開いていて、修正に対して閉じている」という考え方があります。「拡張に対して開いている」とは、既存のコードを変更することなく、新しい機能を追加したり、既存の機能を拡張したりできることを意味します。一方、「修正に対して閉じている」とは、既存のコードを変更することなく、新しい機能を追加したり、既存の機能を拡張したりできることを意味します。

新しい機能を既存コードの変更をせずに追加する方法として、継承があります。抽象クラスやインターフェースを使用し、個別の実装をサブクラスで実現することで、新しい機能の追加を新しいクラスの作成で実現することができます。

また、修正に対して閉じているためには、各機能が別々のクラスに分かれているだけでなく、ひとつのメソッドに複数の処理が含まれていないようにすることが重要です。他の処理が含まれているとコードを修正した際に、修正対象ではない処理にも影響を与えている可能性があります。

この点について、具体的なコードで見ていきましょう。

オープン・クローズドの原則に従っていない設計例

例えば、図形クラスを考えてみます。

このクラスは様々な図形の面積を計算することができます。

class Shape:
def __init__(self, type: str):
self.type = type

def get_area(self) -> float:
if self.type == "circle":
return 3.1415 * self.radius * self.radius
elif self.type == "rectangle":
return self.width * self.height
elif self.type == "triangle":
return 0.5 * self.base * self.height
else:
raise ValueError(f"Unknown shape type: {self.type}")

この図形クラスでは、新しい図形を扱えるようにするためにクラス自身に手を加えなくてはなりません。上記のコードにおいては、新しい図形を扱えるようにするにはif文に条件を追加する必要があります。そのため、新しい図形を追加する度にコードの複雑度が増し、同じメソッドに修正を加えるため、デグレのリスクがつきまといます。

オープン・クローズドの原則に従っている設計例

オープン・クローズドの原則の原則に従うようにコードを修正してみましょう。

from abc import ABC, abstractmethod


class Shape(ABC):
@abstractmethod
def get_area(self) -> float:
pass


class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius

def get_area(self) -> float:
return 3.1415 * self.radius * self.radius


class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height

def get_area(self) -> float:
return self.width * self.height


class Triangle(Shape):
def __init__(self, base: float, height: float):
self.base = base
self.height = height

def get_area(self) -> float:
return 0.5 * self.base * self.height

抽象クラスShapeでインターフェースを定義し、具体的な図形はこれを継承する形で定義するようにします。どの具象クラスをインスタンス化するかによってget_areaメソッドの動作が変わります。新しい図形に対応する時には既存コードに手を加えずに新しいクラスを作成することで実現します。新たな図形の追加に対して既存の図形に対して影響を及ぼしません。

拡張(新しい図形の追加)に対して開いており(既存コードに修正を加えずに機能を追加できる)、修正(ある図形の計算方法が誤っている問題を修正)に対して閉じている(他の機能に影響を与えずに修正できる)ことがわかります。

各図形クラスは単一の図形に特化しており、get_areaメソッドは面積を返すという単一の機能を実現しているため、単一責任の原則に従っているといえます。

まとめ

成り立たないこともありますが、オープン・クローズドの原則に従うことは、単一責任の原則に従うことにもなります。これは、それぞれの原則は完全に独立しているのではなく、互いに関連していることを意味しています。一方の原則に従うことは他方の原則に従うことを意味することもあれば、トレードオフの関係にある場合もあります。必ずしも原則に従うことが正しいわけではなく、要求や仕様に応じてバランスをとることが重要です。

textlintで文章の校正を行う

文章を書く時、文法的な誤りや用語の揺らぎは避けたいものです。しかし、自分だけでは気づかないところがあったり、煩雑すぎたりする場合があります。そこで、textlintというツールを使えば、文章の校正を行うことができます。本記事では、textlintについて詳しく紹介し、文章の校正に役立てる方法を解説します。

今回試したコードはhttps://github.com/t0k0sh1/emendationにあります。

textlintがチェック対象としているのは、テキストファイルやマークダウンファイルなどのテキストファイルのみです。WordやExcelなどのファイルを処理する場合はテキストを抽出するなどの工夫をする必要があります。

textlintの導入

自然言語向けのLinterであるtextlintを導入します。

ここでは、校正チェックを行うプロジェクトemendationを作成します。textlintはNode.jsで利用可能はパッケージですので、npm initコマンドで初期化しておきます。

$ mkdir emendation
$ cd emendation
$ npm init -y

textlintのインストール

textlintをインストールします。特段前提条件となるパッケージやライブラリはなく、普通にインストール可能です。

$ npm install textlint

ルールプリセットのインストール

次にルールプリセットをインストールします。技術書や技術系の記事、設計書の執筆を行っている場合は、技術書向けのルールを使用するのがよいでしょう。

$ npm install textlint-rule-preset-ja-technical-writing

lintスクリプトの設定

package.jsonlintを実行できるスクリプトを追加します。filesディレクトリ内にあるテキストファイルを対象にするようにしています。

  "scripts": {
    "lint": "textlint ./files/*.txt"
  },

ルールの適用方法

textlintで使用するルールを適用する方法には、以下の2つの方法があります。

  1. textlintコマンドに—presetオプションで使いたいルールを指定する
  2. .textlintrc.jsonに適用するルールを記述する

実用上は細かいカスタマイズを必要とするため、後者の方法でルールを適用するようにします。

まずは、textlint --initコマンドで.textlintrc.jsonファイルを作成します。

$ textlint --init
.textlintrc.json is created.

コマンドを実行すると以下のようなファイルが生成されます。

{
  "plugins": {},
  "filters": {},
  "rules": {}
}

生成時点ではルールは何も設定されていません。先ほどインストールしたルールプリセットを適用する記述を追加します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": true
  }
}

動作を確認するために、filesディレクトリに以下のようなsample.txtを作成します。

Vueで作成した確認ダイアログを表示し、OKボタンをクリックしたときだけ処理を続行する方法をボタンクリックの場合とフォームを使ったサブミットの2パターンの実装方法を見ていきました
この考え方は他のフレームワークでも適用可能です。今回はVue 3.3.4で動作確認していますが、Vue 3で実装された機能を使っていないため、Vue 2でも動作すると思います。

このファイルは1行目の「。」が漏れているので、この点が検出されると思います。

では、実際に実行してみます。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。           ja-technical-writing/ja-no-mixed-period
  2:84  error  弱い表現: "思います" が使われています。  ja-technical-writing/ja-no-weak-phrase

✖ 2 problems (2 errors, 0 warnings)

2つのエラーが検出されました。

1つは想定どおり「。」が漏れていることを検出していますが、もう1つは想定していなかったエラーです。このエラーは「〜だと思う」という弱い言い回しを検出しているようです。

ルールのカスタマイズ①不要なルールの抑止

不要なルールの抑止の説明を行うためにja-no-weak-phraseのルールを無効化してみます。以下のように記述することでルールを無効化できます。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    }
  }
}

では、再度実行してルールが抑止されていることを確認します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:89  error  文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period

✖ 1 problem (1 error, 0 warnings)

先ほどまで検知されていたja-no-weak-phraseのルールが検知されなくなっていることが確認できました。

ルールのカスタマイズ②独自の辞書ルールの追加

辞書ルールを定義するファイルを作成することで、独自の辞書ルールを追加することが可能です。

ファイル名に制約はないですが、ここではprh.yamlファイルを作成します。

まずはルールを使用するように定義を修正します。

{
  "plugins": {},
  "filters": {},
  "rules": {
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase": false
    },
    "prh": {
      "rulePaths": ["./prh.yml"]
    }
  }
}

次に辞書ルールを定義します。

version: 1
rules:
  - expected: View
    patterns:
      - Vue

ここでは動作を確認するために、ViewVueとタイポする想定でルールを作成しています。expectedに正しい書き方、patternsに誤った書き方を記述します。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  Vue => View                     prh
  1:89  error    文末が"。"で終わっていません。  ja-technical-writing/ja-no-mixed-period
  2:28  ✓ error  Vue => View                     prh
  2:49  ✓ error  Vue => View                     prh
  2:72  ✓ error  Vue => View                     prh

✖ 5 problems (5 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

Vueがエラーとして検出できることを確認しました。

辞書ルールを定義することで、固有名詞の揺らぎや言い回しの不統一などを検知することが可能になります。

辞書ルール定義の発展した方法

辞書ルールが増えたときに管理が煩雑になりがちです。これを解消するために辞書ルールを分割することを考えます。

以下のように記述することで、ルールを外出しすることが可能です。

version: 1

imports:
  - ./rules/すでに.yml
  - ./rules/かつ.yml
  - ./rules/また.yml

さらに各ルールを指定パスに定義しておきます。

version: 1
rules:
  - expected: すでに
    patterns:
      - 既に
version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/
version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/
specs:
  - from: 又
  - to: また

この例は、副詞をひらく書き方を正として、ひらいていない書き方を検出するルールとなります。

以下のように例文を用意して動作確認します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

以下が実行結果です。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

辞書ルールで定義した箇所が検出されることが確認できます。

自動訂正を行う

textlintにはルールによって自動訂正を行うことができます。–fixオプションを使うことで、訂正可能なルールについて自動的に訂正を行うことが可能です。

package.jsonlint:fixスクリプトを追加します。

  "scripts": {
    "lint": "textlint ./files/*.txt",
    "lint:fix": "textlint --fix ./files/*.txt"
  },

先ほどチェックしたテキストに対して、lint:fixコマンドを実行することで、自動訂正されることを確認します。

まずは、修正前のテキストを再掲します。

既に会議は始まっており、私たちは遅れている。且つ、プレゼンテーション資料もまだ完成していない。又は、次の会議までに資料を仕上げ、改めて説明することも一つの選択肢だ。

再度、チェックのみを行います。

$ npm run lint

> emendation@1.0.0 lint
> textlint ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:1   ✓ error  既に => すでに                                                                                                  prh
  1:23  ✓ error  且つ => かつ                                                                                                      prh
  1:48  ✓ error  又は => または                                                                                                  prh
  1:75  ✓ error  一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers

✖ 4 problems (4 errors, 0 warnings)
✓ 4 fixable problems.
Try to run: $ textlint --fix [file]

検出したエラーのうち、チェックマークがついているものが自動訂正可能なものです。できないものもありますが、今回検出したエラーはすべて自動訂正可能です。

では、lint:fixコマンドを実行して自動訂正します。

$ npm run lint:fix

> emendation@1.0.0 lint:fix
> textlint --fix ./files/*.txt


/Users/t0k0sh1/Workspace/emendation/files/sample.txt
  1:75  ✔   一つ => 1つ
数量を表現し、数を数えられるものは算用数字を使用します。任意の数に置き換えても通用する語句がこれに該当します。  ja-technical-writing/arabic-kanji-numbers
  1:1   ✔   既に => すでに                                                                                                  prh
  1:23  ✔   且つ => かつ                                                                                                      prh
  1:48  ✔   又は => または                                                                                                  prh

✔ Fixed 4 problems

メッセージからも訂正されたことが確認できますが、修正後のテキストも確認しておきます。

すでに会議は始まっており、私たちは遅れている。かつ、プレゼンテーション資料もまだ完成していない。または、次の会議までに資料を仕上げ、改めて説明することも1つの選択肢だ。

検知された点が修正されていることが確認できました。

ルールを書くときの注意点(正規表現)

「かつ」が正しい書き方だとした場合、誤った書き方は「且つ」または「且」となります。このような場合、正規表現で長い方を先に書くようにすることで、「且つ」を「かつつ」に訂正しないようにできます。

version: 1
rules:
  - expected: かつ
    patterns:
      - /且つ|且/

また、「又」が誤った書き方で「また」に訂正したい場合、「又」が別の単語で使われていてそちらを訂正しないようにするためには、正規表現で接続詞としての「又」のみを検知しつつ、適切に訂正できるようになります。この例では、正規表現(、|は|は、)部分は$1で充当されるため、「又は」は「または」に訂正されるようになります。

version: 1
rules:
  - expected: また$1
    patterns:
      - /又(、|は|は、)/

正規表現の設定ミスなどにより思わぬ訂正が行われる可能性もあります。必ずチェックで正しく検出できているかを確認のうえ、訂正するようにしてください。

まとめ

textlintは、文章の自動校正ツールとして非常に優れています。自己チェックと併用してtextlintを使うことで、より良い文章を効率的に作成することができるようになるでしょう。ぜひ、textlintを使ってみてください。

Storybookを使ってモックアップを作成する

プロジェクト開発において、高品質なモックアップを作成することは非常に重要です。しかし、モックアップ作成プロセスはしばしば面倒で、費用がかかります。ReactなどのUIフレームワークのデザインガイドライン作成ツールとしてよく知られているStorybookを使うことで、手軽にモックアップを作成できます。この記事では、どのようにStorybookがモックアップ作成に役立つかについて紹介します。

Storybookのモックアッププロジェクトを作成する

Storybookを使ったモックアッププロジェクトを作成します。

今回は、UIフレームワークを選定していない状況で、とりあえずBootstrapとjQueryを使ってモックアップを作成する状況を想定していますので、--typeオプションでhtmlを指定します。

$ storybook-mockup
$ cd storybook-mockup
$ npm init -y
$ npx storybook@latest init --type html

本記事では、storybook-mockupというプロジェクト名で進めますが、実際に試す際はプロジェクトに合わせて命名してください。

最後のコマンドを実行すると、Storybookが自動的に起動します。

Storybookを停止したい場合はターミナルでCtrl+Cを入力して停止してください。これ以降、Storybookを実行したい場合は、package.jsonがあるディレクトリで以下のコマンドを実行してください。

$ npm run storybook

storiesディレクトリを整理する

この手順は必ずしも必要ではありませんが、モックアップ作成作業がやりやすくなるようにstoriesディレクトリを整理しておきます。

どのように整理するのかはプロジェクトの方針やメンバーの好みによって変わりますが、物理ディレクトリの構造とStorybookのサイドメニューの構造が一致している方が探しやすくなると思います。

作成直後の状態では、EXAMPLEというサイドメニュー表示されていますが、ボタンやドロップダウンリストなどのパーツを「コンポーネント(components)」、モックアップを「ページ(pages)」に分けてファイルを整理し、サイドメニューも同様に分けるようにします。

また、Storybookでは1コンポーネント1ファイルにはなりません。このまま、複数のコンポーネントやページを同じディレクトリに並べておくと見にくくなるので、コンポーネント名でディレクトリを作成して、ファイルをまとめることにしておきます。

ご使用のIDEやエディタの機能によって、ファイル移動に伴ってimport文が自動的に修正されますが、操作によっては適切に修正されない場合があります。その場合はimport文を直接修正してください。

Storybookのサイドメニューはstoriesディレクトリの構造に合わせて作成しているわけではなく、.Stories.jsファイル内に定義されているtitle属性の値で構造が決まります。

Button.stories.jsHeader.stories.jstitle属性のExampleComponentsに、Page.stories.jstitle属性のExamplePagesに修正しておきます。

すべてのコードを示すとかなりの量になるので、修正した箇所だけ説明します。コード全体を確認したい場合は、https://github.com/t0k0sh1/storybook-mockupを参照してください。

import { createButton } from "./Button";

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
export default {
  title: "Components/Button",
  tags: ["autodocs"],
  render: ({ label, ...args }) => {

title属性を"Example/Button"から"Components/Button"に修正しています。

import "./header.css";
import { createButton } from "../Button/Button";

Header.jsからButton.jsを参照していますが、そのimport文のパスをディレクトリ構造に合わせて修正しています。

import { createHeader } from "./Header";

export default {
  title: "Components/Header",
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
  tags: ["autodocs"],
  render: (args) => createHeader(args),

title属性を"Example/Header"から"Components/Header"に修正しています。

import "./page.css";
import { createHeader } from "../../components/Header/Header";

Page.jsからHeader.jsを参照していますが、そのimport文のパスをディレクトリ構造に合わせて修正しています。

import { expect, userEvent, within } from "@storybook/test";
import { createPage } from "./Page";

export default {
  title: "Pages/Page",
  render: () => createPage(),

title属性を"Example/Page"から"Pages/Page"に修正しています。

StorybookにBootstrapとjQueryを導入する

StorybookにBootstrapとjQueryを導入していきます。導入方法にはいくつかの方法がありますが、ここではCDNを使って手軽に導入する方法を解説します。

Bootstrapを使用できるようにする

StorybookでCDNを使用する場合、.storybook/preview-head.htmlを新規作成し、そこにCDNにアクセスlinkタグを記述します。

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
  integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
  crossorigin="anonymous"
/>

jQueryを使用できるようにする

こちらも同様に.storybook/preview-head.htmlにCDNにアクセスするscriptタグを記述します。

<script
  src="https://code.jquery.com/jquery-3.7.1.min.js"
  integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
  crossorigin="anonymous"
></script>

これでモックアップ作成の準備が整いました。

ButtonコンポーネントをBootstrapベースに書き換える

最後にすでに存在するButtonコンポーネントをBootstrapベースに書き換えてみましょう。

まずは、現在の実装を確認してみます。

import { createButton } from "./Button";

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories
export default {
  title: "Components/Button",
  tags: ["autodocs"],
  render: ({ label, ...args }) => {
    // You can either use a function to create DOM elements or use a plain html string!
    // return `<div>${label}</div>`;
    return createButton({ label, ...args });
  },
  argTypes: {
    backgroundColor: { control: "color" },
    label: { control: "text" },
    onClick: { action: "onClick" },
    primary: { control: "boolean" },
    size: {
      control: { type: "select" },
      options: ["small", "medium", "large"],
    },
  },
};

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary = {
  args: {
    primary: true,
    label: "Button",
  },
};

export const Secondary = {
  args: {
    label: "Button",
  },
};

export const Large = {
  args: {
    size: "large",
    label: "Button",
  },
};

export const Small = {
  args: {
    size: "small",
    label: "Button",
  },
};

今回注目するのは、primary属性とsize属性です。他の属性もありますが、Button.stories.jsは修正せずにいきます。

import './button.css';

export const createButton = ({
  primary = false,
  size = 'medium',
  backgroundColor,
  label,
  onClick,
}) => {
  const btn = document.createElement('button');
  btn.type = 'button';
  btn.innerText = label;
  btn.addEventListener('click', onClick);

  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  btn.className = ['storybook-button', `storybook-button--${size}`, mode].join(' ');

  btn.style.backgroundColor = backgroundColor;

  return btn;
};

属性に応じてclassNameに設定する値を制御します。方針は以下です。

  • primaryが指定されたらbtn-primaryを指定し、指定されなかったらbtn-secondaryを指定する
  • sizesmallならbtn-smを指定し、largeならbtn-lgを指定し、それ以外は何も指定しない

修正後のコードは以下になります。storybook-buttonクラスをbtnに修正してあります。

import "./button.css";

export const createButton = ({
  primary = false,
  size = "medium",
  backgroundColor,
  label,
  onClick,
}) => {
  const btn = document.createElement("button");
  btn.type = "button";
  btn.innerText = label;
  btn.addEventListener("click", onClick);

  const mode = primary ? "btn-primary" : "btn-secondary";
  let btnSize = size === "small" ? "btn-sm" : size === "large" ? "btn-lg" : "";
  btn.className = ["btn", btnSize, mode].join(" ");

  btn.style.backgroundColor = backgroundColor;

  return btn;

表示した結果は以下のようになります。

まとめ

以上のように、Storybookはプロジェクト開発において、手軽かつ効果的なモックアップ作成ツールであることが分かりました。Storybookを使用すると、コンポーネント化やパーツ化が手軽に推進できるので、デザインガイドラインを作成しながらモックアップを作成できます。Storybookを使うことで、モックアップ作成の開発プロセスを改善し、開発者のストレスを軽減することができます。皆さんにも一度、Storybookを使用して、モックアップ作成をスムーズに進めてみてはいかがでしょうか?

テストやドキュメントで安全に使用できるドメイン名を知っておこう

テスト用に設定したメールアドレスに誤ってメールを送信してしまった、という経験はありませんか?

テストやドキュメントに使用できるドメインが予約されており、これらを活用することでメール誤送信などを防ぐことができます。

テストやドキュメントに使用できるドメインは予約されている

RFC 6761で、特別な使用のためのドメイン名として、特定のTLD(Top-Level Domain)やSLD(Second-Level Domain)が予約されています。

TLD(Top-Level Domain)

TLDとして、以下のドメインが予約されています。

  • .test: テスト用として予約されています
  • .example: ドキュメンテーションや例示に使用するために予約されています
  • .invalid: 明示的に無効なドメイン名として識別するために使用します
  • .localhost: 伝統的な意味でのlocalhostの名前解決のために予約されています

SLD(Second-Level Domain)

RFC 6761では、以下のドメインがドキュメントや例示のために予約されています。

  • example.com
  • example.net
  • example.org

ユースケース

ユースケース別にどのドメイン使用すればいいかについて見ていきましょう。

送受信の確認を行うメールアドレス

RFC 6761で予約されているドメインはメールの送受信には使用できないため、実際に送受信可能なメールアドレスを使用してください。

その際は、誤送信が起きないように十分ご確認ください。

ダミーのメールアドレス

システムのドメインがt0k0sh1.comの場合、t0k0sh1.testを使用すると良いでしょう。

書籍やドキュメント中に例示するURLやメールアドレス

URLやメールアドレスを書籍やドキュメント中に記載する場合、

  • https://www.example.com
  • test@example.com

のようにexample.comexample.netなどを使用すると良いでしょう。

example.jpなどは使用できないのか

RFC 6761にはexample.jpなどのccTLD(country code Top-Level Domain)については言及されていません。

各国で対応が異なると思いますが、日本の場合はJPRSがよくある質問で答えています。

例示に使用可能なドメイン名はありませんか?

次の文字列のJPドメイン名は、例示としてご利用いただけます。

“EXAMPLE”を用いたもの
例: EXAMPLE.JP
   EXAMPLE.CO.JP
   EXAMPLE.NE.JP

“EXAMPLE”の後に1桁の数字(””0″”から””9″”)がつく文字列を用いたもの
例: EXAMPLE1.JP
   EXAMPLE2.CO.JP
   EXAMPLE3.NE.JP

次の日本語ドメイン名

ドメイン名例.JP (日本語JPドメイン名)
XN–ECKWD4C7CU47R2WF.JP (「ドメイン名例.JP」のpunycode表記)

JPドメイン名の活用について | よくある質問 | JPRS https://jprs.jp/faq/use/#q2

example.jpexample.co.jpなどを書籍やドキュメントにおける例示に使用することは可能です。

まとめ

テスト用ドメインとしては、所有しているドメイン名を基に.testを使用することが推奨されます。一方、書籍やドキュメントで例示する場合はexample.comなどを使用すると良いでしょう。

本来の目的とは若干外れますが、テスト用のURLやメールアドレスとして、example.comなども安全に使用することもできます。

楽観的排他制御と悲観的排他制御

楽観的排他制御(optimistic exclusive control)と悲観的排他制御(pessimistic exclusive control)は、両方の方法が大規模なスケーラビリティを持つため、システムの排他制御において非常に一般的な方法論です。

近年のWebシステムでは、特に楽観的排他制御が用いられることがほとんどです。本記事では、楽観的排他制御と悲観的排他制御のどちらが適しているのかを考察し、それらがシステム設計に及ぼす影響について説明します。

排他制御とは

排他制御は、同時に複数のスレッド/プロセス/ユーザーがデータを更新しようとする問題を解決するために使用されます。排他制御方法には悲観的排他制御と楽観的排他制御の2つの主要な方法があります。

悲観的排他制御(pessimistic exclusive control)

悲観的排他制御は、データ更新リクエストについての楽観的な予測を行わない方法です。つまり、一度に1人のユーザーしかデータを操作できないように、予約をすることで競合を回避しようとします。悲観的排他制御は、ロック機構やトランザクションを使用することが一般的です。

楽観的排他制御(optimistic exclusive control)

一方、楽観的排他制御は、データを更新する前に、競合を検出して回避するための最適な方法論を提供することにフォーカスしています。これは、別のユーザーがデータを更新したときに、その影響を判断することができるデータバージョン管理を含むことがよくあります。ユーザー操作中に競合が発生した場合、変更をロールバックするか、サーバから新しいデータを取得してマージするか選択できます。

楽観的排他制御では、競合を検出するためにバージョンまたは更新日時を使用することが一般的です。更新日時を使用する場合には日付の精度に注意が必要で、精度が低いと他ユーザーの更新を検出できない場合があります。

排他制御がシステムに与える影響

悲観的排他制御は、大規模なWebシステムのパフォーマンスに深刻な影響を与える可能性があります。つまり、トランザクションロックが長時間存在する場合には、他のトランザクションによってコミットされたデータに対して待たなければならず、それによって生じる遅延リスクがあります。一方、楽観的排他制御には、ユーザーが変更したい場合には非常に簡単であるため、パフォーマンスの限界に到達する前に、他のタイプの制御よりも多数のデータ更新を許容できます。

また、トランザクションが複数のデータをロックする場合、ロック順番に気をつけないとデッドロックが発生するリスクがある点についても注意が必要です。

楽観的排他制御は、特にオンラインWebアプリケーションにおいて、より親和性があると考えられています。ただし、その欠点が存在する場合があります。書き込みの競合性が高い場合、やはり悲観的排他制御で処理することが必要であるかもしれません。そしてこの場合、楽観的排他制御は厳密ではありません。Webアプリケーションには、トランザクション処理スレッド特有の問題も存在するため、慎重に検討する必要があります。

ただし、頻繁にデータ競合が発生する場合は、ユーザーの生産性やパフォーマンスに悪影響を与える場合があります。

アクセス制御による排他制御

楽観的排他制御とも悲観的排他制御とも異なるアプローチで排他制御を行うという選択肢もあります。

例えば、以下のような方法があります。

  • データを更新する前にデータに対するロックを獲得し、ロックしているユーザーのみがデータを更新できるようにする
  • ワークフローによってデータをユーザーに割り当て、割り当てられているユーザーのみが更新できるようにする

いずれの方法においても、ユーザーに与えて、アクセス権限を有しているユーザーだけが更新を行うことができるようにシステムを設計しています。これにより、更新時に排他制御を行わなくても安全にデータを更新することができるようになりますし、楽観的排他制御で安全に排他制御することができます。

結論

楽観的排他制御と悲観的排他制御は、両方とも重要な排他制御手法です。また、これら以外にも排他制御の手法はあります。システム環境、データ同期の頻度、書き込みの競合状況に応じて、適切な排他制御方法を選択し、最適な手法を使用することが重要です。

楽観的排他制御を使用することで、システムのパフォーマンスが向上し、ユーザーエクスペリエンスを向上させることができます。悲観的排他制御を使用することで、強力なトランザクション管理と競合回避制御を実現できます。

したがって、基本的には、両者が制御戦略の好みに応じて異なる選択肢であり、特定のシナリオと目的に基づいて使用されるべきです。

モバイルバージョンを終了