Mermaid クラス図エディタ
クラス図は型と型の関係を示します。何が何を含み、何が何を継承し、何が何に依存するのか。コードの形そのものが論点であるとき——ドメインモデル、プラグインの境界、継承の階層——に向いています。型の組み合わせ方ではなく実行時に何が起きるかを示したいなら、シーケンス図を使ってください。
決済まわりのドメインモデル
1 枚に 3 種類の関連が入っています。全体より長く生きられない部分を表すコンポジション、支払方法の階層を表す継承、そして多重度ラベルの付いた素の関連です。ここで意味を運んでいるのはほとんど矢印のほうで、クラスの箱はおまけに近いことに注目してください。
classDiagram
class 注文 {
+String 注文番号
+注文状態 状態
+金額 合計()
+void 明細追加(商品 p, int 数量)
}
class 注文明細 {
+商品 商品
+int 数量
+金額 小計()
}
class 支払方法 {
<<abstract>>
+与信(金額 額) bool
}
class クレジットカード {
+String 下4桁
+与信(金額 額) bool
}
class コンビニ払い {
+String 払込票番号
+与信(金額 額) bool
}
class 代金引換 {
+金額 手数料
+与信(金額 額) bool
}
注文 "1" *-- "1..*" 注文明細 : 明細を持つ
注文 --> 支払方法 : 支払う
支払方法 <|-- クレジットカード
支払方法 <|-- コンビニ払い
支払方法 <|-- 代金引換実例で理解する
1. クラス 1 つ
`+` は public、`-` は private、`#` は protected です。半角の丸括弧が付いているメンバーはメソッドとして、付いていなければフィールドとして描かれます。
classDiagram
class 会員 {
+String メールアドレス
-String パスワードハッシュ
+bool 照合(String 入力)
}2. 継承とインタフェース
`<|--` が継承で、「右側が左側を継承する」と読みます。`<<interface>>` は振る舞いではなく単なる注記ですが、図の読みやすさはこれで決まります。
classDiagram
class 会員リポジトリ {
<<interface>>
+検索(String id) 会員
+保存(会員 e) void
}
class Postgres会員リポジトリ {
-Connection 接続
+検索(String id) 会員
+保存(会員 e) void
}
class メモリ会員リポジトリ {
-Map 格納先
+検索(String id) 会員
+保存(会員 e) void
}
会員リポジトリ <|.. Postgres会員リポジトリ
会員リポジトリ <|.. メモリ会員リポジトリ3. コンポジションと集約
違いは寿命です。塗りつぶしのひし形 `*--` は、全体が消えれば部分も消えることを表します。請求書を削除すれば明細も消えます。白抜きのひし形 `o--` は、部分が独立して存在し続けることを表します。
classDiagram
class 請求書 {
+String 請求番号
}
class 請求明細 {
+String 品目
}
class 取引先 {
+String 会社名
}
請求書 "1" *-- "1..*" 請求明細 : 構成される
取引先 "1" o-- "0..*" 請求書 : 発行した4. ジェネリクス
チルダで型引数を書きます。`リポジトリ~会員~` のように日本語の型名もそのまま使えます。入れ子もできますが、できるというだけで、良い考えであることはめったにありません。
classDiagram
class リポジトリ~T~ {
+検索(String id) T
+全件() List~T~
}
class キャッシュ~K, V~ {
+取得(K 鍵) V
+格納(K 鍵, V 値) void
}
class 会員リポジトリ {
+メールで検索(String メール) 会員
}
リポジトリ~会員~ <|-- 会員リポジトリ5. 注記と配置方向
`direction LR` は左から右に配置します。継承の階層は既定の上下より横並びのほうが収まることが多いです。クラスの箱に入りきらない制約は、注記に書くのが正解です。
classDiagram
direction LR
class イベントストア {
+追記(イベント e) void
+再生(String ストリームid) List~イベント~
}
class スナップショット {
+int 版番号
+byte[] 中身
}
イベントストア --> スナップショット : 100 件ごとに書く
note for イベントストア "追記専用。イベントは書き換えも削除もしない。"クラス図 構文早見表
覚える価値があるのは関連の矢印です。これがクラス図を、ただの箱と線の絵から区別しているものであり、しかも右から左に読むせいでよく取り違えられます。
| 構文 | 意味 |
|---|---|
| classDiagram | 図を開始する。大文字小文字を区別する。 |
| class 名前 { ... } | メンバーを持つクラス。閉じ波括弧は単独行に置く。 |
| +メンバー | public。 |
| -メンバー | private。 |
| #メンバー | protected。 |
| +メソッド(型 引数) 戻り値型 | メソッド。半角の丸括弧が付いているものがメソッドになる。 |
| <<interface>> / <<abstract>> | ステレオタイプ。クラスの中の最初の行に書く。 |
| A <|-- B | 継承: B が A を継承する。 |
| A <|.. B | 実現: B がインタフェース A を実装する。 |
| A *-- B | コンポジション: B は A より長く生きられない。 |
| A o-- B | 集約: B は A なしでも存在できる。 |
| A --> B | 向きのある関連。 |
| A ..> B | 依存——A は B を使うが保持はしない。 |
| A "1" --> "0..*" B : ラベル | 両端の多重度と関連ラベル。 |
| class リポジトリ~T~ | ジェネリックの型引数。 |
| note for A "文字" | クラスに注記を付ける。 |
| direction LR | 配置方向を変える。 |
クラス図を壊す 6 つのエラー
Mermaid 11.12.2 で再現したものです。クラス図はここで扱う 6 種類のなかでは寛容なほうなので、以下のうち半分は何事もなく描画されたうえで、意図とは違う絵を返してきます。
表示されるもの
Parse error、末尾が: got 'EOF_IN_STRUCT'
原因
`{` で開いたクラス本体が閉じられていません。このトークン名は珍しく親切で、クラスの中にいるままファイルが終わったという意味です。
対処
閉じ波括弧を単独の行に置きます。
classDiagram
class 注文 {
+String 注文番号classDiagram
class 注文 {
+String 注文番号
}表示されるもの
描画はされるが、メソッドがフィールドとして描かれる
原因
丸括弧が全角になっています。メソッドかフィールドかを決めているのは半角の `()` だけで、全角の `()` は名前の一部として扱われます。日本語入力のまま括弧を打つと自然にこうなり、画面上の見分けも付きにくい厄介な間違いです。実測すると違いははっきり出ます。半角なら `+合計() : 金額` とメソッド欄に並び替えられて戻り値の書式も付きますが、全角だと `+合計() 金額` のままフィールド欄に残ります。
対処
括弧は半角で打ちます。日本語入力中でも括弧だけは半角に切り替える癖をつけておくと安全です。
classDiagram
class 注文 {
+合計() 金額
+String 注文番号
}classDiagram
class 注文 {
+合計() 金額
+String 注文番号
}表示されるもの
Parse error、末尾が: got 'ANNOTATION_END'
原因
シーケンス図の矢印をクラス図で使っています。`->>` はここでは何の意味も持たず、解析器が途中まで読み進めてしまうため、分かりにくいトークン名が出ます。
対処
クラス図の関連を使います。関連なら `-->`、継承なら `<|--`、コンポジションなら `*--` です。
classDiagram
注文 ->> 顧客classDiagram
注文 --> 顧客 : 属する表示されるもの
No diagram type detected matching given configuration
原因
キーワードの大文字小文字が違います。`classdiagram` は `classDiagram` ではありません。
対処
D を大文字にします。
classdiagram
class 注文classDiagram
class 注文表示されるもの
矢印が意図と逆を向く
原因
クラス図の関連は矢じりから逆向きに読みます。`A <|-- B` は「B が A を継承する」であって、その逆ではありません。逆に書いても描画はされます。ただし、親クラスが自分の子クラスを継承していると主張する図になります。
対処
「離れているほうが、矢じりのほうを継承する」と読みます。`<|--` の左に親を置いてください。
classDiagram
クレジットカード <|-- 支払方法classDiagram
支払方法 <|-- クレジットカード表示されるもの
コンポジションと集約が一見同じに見えて、意味は正反対
原因
`*--` と `o--` は 1 文字違いですが、部分が全体より長く生きられるかという実際の意味の差を持ちます。取り違えると、形式的には正しく、ドメインについては誤っている図ができあがります。
対処
親を消したら子も消えるなら塗りつぶし `*--`、消えないなら白抜き `o--` です。
classDiagram
注文 o-- 注文明細 : 明細を持つclassDiagram
注文 *-- 注文明細 : 明細を持つ描画についての覚え書き
いずれもこのサイトが使う Mermaid 11.12.2 で実測したものです。
ここで扱う図のなかで最も速く縦に伸びる
メンバーを 2 つずつ持つクラスを継承でつないで実測すると、3 クラスで viewBox がおよそ 157×548、40 クラスで 161×7726 でした。1 クラスあたり約 194 ピクセルで、6 種類のなかで最も急な増え方です。40 クラスの図は 7000 ピクセルを超え、1 枚の画像としては使えません。`direction LR` は助けになりますが、15 クラスを超えたあたりからは、業務の境界で図を分けるのが正直な対処です。
メンバーの数は幅にほとんど影響しない
幅を決めているのは、最も長いメンバー 1 行の長さであって、メンバーが何個あるかではありません。短いフィールドを 20 個持つクラスは、3 個のクラスと同じ幅です。つまりメンバーには気前よく、クラスの数には渋くしてよい、ということです。たいていの人の直感とは逆になります。
クラス名の全角空白は黙って詰められる
`class 注文 明細` と書くと、描画結果は `注文明細` になります。エラーも警告も出ず、空白だけが消えます。状態遷移図では同じ全角空白が状態を 2 つに割ってしまうので、図の種類によって挙動が違うことは知っておく価値があります。クラス図ではおおむね無害ですが、そのぶん気付かないまま残ります。
ラベルは HTML なので PNG 書き出しは描き直しになる
クラスのラベルは SVG の `<foreignObject>` の中に描かれ、ブラウザはこれを canvas にラスタライズすることを拒みます。このサイトの PNG 書き出しは以前これで静かに失敗し、SVG ファイルを返していました。現在は素の SVG テキストのラベルで描き直してから出力します。PNG は正しく原寸で出ますが、文字組みは画面とごくわずかに異なります。
テーマは色を変えるだけで、レイアウトは変えない
同じソースを明るいテーマと暗いテーマで描画すると viewBox は完全に一致します。テーマを切り替えたせいでクラスの箱の大きさが変わったり、メンバーが枠から溢れたりすることはありません。
別の図が向いている場合
型の体系ではなくデータベースを説明しているなら、ER 図を使ってください。この区別には実質があります。クラス図は振る舞いと継承を表現できますが、テーブルにはそのどちらもありません。逆に ER 図はキーと多重度をきちんと表現できますが、クラス図はそこを誤魔化します。
図がほとんど箱と `-->` だけで、メンバーが書かれていないなら、それはクラス図ではなくアーキテクチャ図です。サブグラフを使ったフローチャートのほうが見た目もよく、余計なことを主張しません。
そしてクラスの一覧をコードから生成しているなら、図も生成すべきかどうかを考えてください。毎週変わるコードベースのクラス図を手で保守すると、1 か月で嘘になります。間違った図は、図がない状態より高くつきます。
他の図の種類
執筆 Dominik Malsch · 最終更新: