ドワンゴでニコニコ生放送のWebフロントエンジニアをやっています、 @misuken です。
昨年は時間がとれずアドベントカレンダーに書けなかったのですが、ようやく時間ができたので、書きたくてうずうずしていた最新のコンポーネント分類手法とその周辺のノウハウのお話しを書こうと思います。
2023/12/05 追記
ニコニコ生放送のBCD Design導入事例 という記事を公開しました。
実際に BCD Design を大規模プロジェクトで使用した際の効果や、理想的なディレクトリ構成にするための視点やテクニックも紹介されているので参考にしてみてください。
BCD Design のご紹介
AtomicDesign での悩み事を解消する新しいコンポーネント分類手法 BCD Design を紹介します。
AtomicDesign は設計手法、BCD Design は分類手法です。
BCD Design とは
概念軸による分類
BCD Design はコンポーネント名に使用される単語の意味や性質を相対的に利用することで、 コンポーネントを Base Case Domain の3つの概念へ法則的に配置し、体系的に管理できるようにする分類手法です。
Base - 基礎 (名詞)
基礎的な機能(名詞)そのもの、事実上の “型” を表す単語のことを指します。
Case - 状況 (動詞/形容詞/名詞)
状況(動詞/形容詞)や状態(名詞)を表す単語のことを指します。
Domain - 関心 (名詞)
人(ロール)や物(名詞)など "関心の対象" を表す単語のことを指します。
Common を追加した BCCD 適用(2021-06-10 更新)
Domain 内の単語の抽象度に大きなばらつきが出たり、抽象的、共通の関心が増えてるので、よりキレイに分類する場合は Common を加えた4つに分類します。
Common - 共通の関心(名詞)
関心の中で色々な Domain と組み合わせて使えるもの、どの関心でも幅広く共通で利用できるもののことを指します。
詳しくは BCD Design の Common を徹底解説 をご覧ください。
BCD Design 導入で変わること
AtomicDesign の悩み事を解決
- molecules と organisms の分類が人それぞれ
- atoms や molecules の分類もまちまち
- atoms molecules organisms のディレクトリ内がごちゃごちゃしてくる
指針が何も無いよりはよいものの、期待しているよりも曖昧でうまくスケールしていかない。
こういった悩み事を解決してくれます。
BCD Design 導入のメリット
- コンポーネント名の精度が上がる
- コンポーネント名に統一感が出る
- コンポーネント名から分類を判断できる
- 粒度分類の質が向上する
- organisms が不要になる
- コンポーネント名から粒度を判断できる
- 粒度分け不要な場面も増える
- 大量なコンポーネントにもスケールする
- 複雑なコンポーネントのみへ部分的に導入していける
コンポーネント名
大切な4つのパターン
コンポーネント名を突き詰めていくと、主に4つのパターンに収束することがわかっています。
次の画像では Icon を例にしていますが、これは Icon に限らず全てのコンポーネントに当てはまります。 1
コンポーネント名はとても重要
開発を繰り返す中で、コンポーネント名は思っている以上に重要な存在であることがわかってきました。
コンポーネントが内部に持つ依存や性質から相対的に名前を表すことがポイントです。
決して命名するのではなく、その物の 関心の対象が何 で、 どういう状況に依存していて 、 どういう性質を持っているのか を見抜くこと、これがものすごく重要です。
名前は決めるものではなく決まるもの
名前を決めるのは主観で、名前が(依存や性質によって勝手に)決まるのが写像です。
主観は命名者に起因するブラックボックスであり、時間とともに変化する不定なもので関数的ではありません。
それは他人が同じ前提を持っていないと推測ができず、万人に非中立で理解しにくいことを示しています。
写像はコンポーネントの内部に存在する依存や性質に起因して投影されたものであり、関数的です。
それは他人が推測でき、万人に中立で理解しやすいことを示しています。
"命名" するのではなく "明名" すると考えるのが良いでしょう。
コンポーネント名の見直し
コンポーネント名に統一感がない場合、ほとんどは以下のどれかに該当するはずです。
そのようなコンポーネントが見つかったら何がおかしいのかをよく考えてみましょう。
- 関心に依存しているのにコンポーネント名に表れていない
- 特定の状況に依存しているのにコンポーネント名に表れていない
- **型(基礎)**がコンポーネント名に表れていない
- コンポーネント名の単語が domain case base の順に並んでない
整理された構造を実現するためには、コンポーネント名が適切であることが不可欠です。
関心 = 型 は NG
User Article Comment (関心)をそのままコンポーネント名にすると、色々なところで辻褄が合わなくなるので注意が必要です。
辻褄が合わなくなる理由は、関心の対象をどのような型で表現するのか、その型の部分が未定義になるためです。
よく考えてみれば、 UserCard ArticleSection CommentCard のように暗黙的になっていた型が存在していることに気付けます。 2
こういった僅かな綻びが歪みとなり全体を崩す原因になってしまうのです。
コンポーネント粒度
organisms は不要
AtomicDesign における organisms は厄介な存在です。
- molecules と organisms のどちらにするべきか悩む
- molecules と organisms で循環参照しがち
BCD Design では organisms は使用しない ので悩みが減ります。
粒度は atoms と molecules で十分
とてもシンプルな法則は次のようなものです。
- 単一的なものが atoms
- 複合的なものが molecules
これなら誰でも理解が容易で循環参照の問題も起こりません。
型と粒度の関係性
Base のコンポーネントは常に特定の粒度に連動させるようにします。
あのボタンは atoms このボタンは molecules では混乱するので、アイコンと連携しようがコンポーネント名の末尾が Button で終わるなら常に atoms とします。
Image が Anchor でラップされても主体としては画像に変わりがないため atoms です。 3
あくまで単語の性質として単一的なものか、複合的なものかを判断することが重要です。
コンポーネント名 | 粒度 |
---|---|
Anchor | atoms |
Button | atoms |
Image | atoms |
Dialog | molecules |
Form 4 | molecules |
Menu | molecules |
atoms を atoms であり続けさせるための工夫は以前記事に書いたので参考にしてみてください。
AtomicDesign の atom より小さな世界の扉を開く
軸の転換
粒度軸重視から概念軸重視へ
以下は簡単なブログサービスで作成するであろうコンポーネントを5つの方法で分類した例です。
- 粒度軸で分類しても、概念軸の分類をしないとキレイな構造にはならない
- 概念軸で分類すると、粒度軸で分類しなくてもかなりキレイな構造になる
- 概念軸と粒度軸で分類すると、非常にバランスの取れた構造になる
- 概念軸と関心で分類すると、スケールに強くなる 5
- 概念軸と関心と粒度軸で分類すると、スケールに強く、関心をまとめつつ粒度の恩恵も得られる 5
なぜ粒度軸より概念軸なのか
- 粒度軸の中で概念軸の分類を行う
- 概念軸の中で粒度軸の分類を行う
この2つの一番大きな違いは、関心のまとまりです。
DDD の基本でもあるように、関心が散らばることは悪手、関心の凝集がとても重要です。
先に粒度軸で分類を行うと1つの関心が複数のディレクトリに分散してしまいますが、概念軸で先に分類すると関心を1箇所にまとめることができるため、関心の理解が容易で、アプリケーション全体の複雑さを最小限に留めることに繋がります。
同じ型でまとめるのはアンチパターン
少し話が逸れますが、必ず誰しもがハマるアンチパターンが存在するので書いておきます。
形が似ているからと言って、型でまとめるのは危険です。
forms のようなディレクトリを作ってアプリケーション内の全てのフォームをまとめることを想像してみてください。
そのディレクトリ内のコンポーネント名の先頭に色々な関心の単語が並ぶはずです。
forms が正解なら sections lists dialogs buttons どんどんできるはずです。
それらができる度に関心が散らばっていき、扱いにくいアプリケーションになってしまいます。
このことは知っていてもついやってしまうほどに、ハマりやすいアンチパターンなので気を付けましょう。
再帰的な適用
BCD Design は複雑なコンポーネントに対して再帰的に適用することが可能です。
例えば、あるアプリケーションの中に XxxTool
という、スコープ内で大量のコンポーネントを持ち、そのスコープ内でのみ扱う共通部品があるような場合、構造と依存関係は次のようになります。
再帰的に BCD Design を適用することにより、どのスコープでも常に同じ構造、一定のボリュームで安定してスケールすることができます。
依存関係の解説
再帰的な BCD Design の画像の依存関係を簡単に説明すると次のようになります。
- case や domain は base を参照することができます
- domain は case を参照することができます
- domain 内では主従関係のある兄弟を参照することができます 6 7
- domain 内からは、外界の兄弟を参照することができます
4 は明示的に base case domain から矢印を伸ばしていますが、意味合いとしては以下になります。
-
./domain/*
からは./base/*
と./case/*
を参照できます -
./domain/molecules/*
からは./domain/atoms/*
を参照できます
ただし、参照可能な依存関係であっても、 atoms から molecules を参照することはできません。
依存関係は常に単方向に保つ必要があります。
依存関係は階層を合わせて判断する
先程の図の 4 では ./domain/molecules/xxx-tool/base
内から、 ./case/*
を参照可能としており、一見問題があるように思うかもしれません。
しかし、これは ./domain/molecules/xxx-tool
のスコープの base で、整理すると ./domain
から ./case
を参照している関係になるため、問題が発生しません。
これと同様に、 3 で ./domain/molecules/xxx-tool
内の base
からでも ./domain/molecules/xxx-section
を参照可能な形になっているところも ./domain/molecules/xxx-tool
から主従関係のある ./domain/molecules/xxx-section
を参照していると整理できるので問題が発生しません。 8
高度な依存関係解決法の解説
ここでは依存関係をうまく単方向で解決するノウハウを書きますが、細かい話になるので、依存関係で悩むときに読むことをオススメします。
BCD Design を使うかどうかに関わらず、本質的な部分は同じなので、解決のヒントになるかもしれません。
高度な依存関係の解説を開く
依存関係図の注意点
依存関係図で XxxTool が出来上がるまでの流れと注意が必要なポイントについて解説します。
まずはじめに、 ./domain/molecules/xxx-tool/base/tab-panel
は XxxTool のスコープにおける基礎的な TabPanel です。
この TabPanel はその場で実装するか、 ./base
でライブラリとして提供されている素の TabPanel を import し、 XxxTool におけるスタイルを適用する形になるでしょう。
次に ./domain/molecules/xxx-tool/domain/*
以下の FooPanel BarPanel BazPanel は ./domain/molecules/xxx-tool/base/tab-panel
の Panel を import して使います。(依存関係図の1)
その FooPanel BarPanel BazPanel を domain の主従関係にある XxxTabPanel が import して組み込みます。(依存関係図の3)
最後に XxxTabPanel を XxxTool が import して、組み込むと、単方向の依存関係を維持した XxxTool が完成します。
多くの人は ./domain/molecules/xxx-tool/base/tab-panel
から FooPanel BarPanel BazPanel を組み込みたくなる局面に遭遇すると躓いてしまいます。
これは BCD Design を使っていなかったとしても、コンポーネントを多く扱うプロジェクトでは起きがちなことです。
./domain/molecules/xxx-tool/base/*
からは、 ./domain/molecules/xxx-tool/domain/*
を参照できないので、使う側と使われる側を強く意識して、集約する責務のコンポーネントを用意する発想力が必要です。
domain 以下の base 内にあるコンポーネント名
もう一つ気にする人が多そうなポイントを解説します。
./domain/molecules/xxx-tool/base/tab-panel/panel
が、外界の兄弟にあたる ./case
や ./domain/molecules/xxx-section
から何かを import したとしましょう。
その際、コンポーネント名は Panel のままで良いのでしょうか?
答えは「そのままで良い」です。
./domain/molecules/xxx-tool/base/tab-panel/panel
は XxxTool における基礎的な TabPanel の Panel です。
XxxTool のスコープ(文脈的な意味を含む)においてはそれが標準の Panel であるということから、名前を変える必要はありません。
このあたりはディレクトリ構造を法則的に維持する上でとても重要なポイントになってくるので、スコープがどのように作用するのかもよくよく理解しておく必要があります。
BCD Design の背景にある思想
プロジェクトの要件を一元管理された state と見立てるならば、コンポーネント名やディレクトリ構造は state の写像です。
要件が BCD Design や AtomicDesign という関数(法則)を経てコンポーネント名やディレクトリ構造に描画されていると捉えることができます。
この 要件 = 構造
という同期のとれた状態こそが、万人にとって最もシンプルで理解しやすい状態であると言えます。
なぜなら主観がなく、関数的に推測が可能だからです。
要件がコンポーネントの依存や性質に写像され、そこからコンポーネント名に写像され、そこからディレクトリ構造に写像されるのは自然な流れです。
この一貫性が、全体の統一感、透明性、理解のしやすさそのものに直結します。
その上で、コンポーネントの分類に関連するディレクトリ構造は、減点法であるとも言えます。
加点することはできません。
だからこそコンポーネント名が重要なのです。
最初に満点の持ち点があり、主観が入る度に関数的に推測できない部分ができて減点されていきます。
残った点数がそのディレクトリ構造の分かりやすさの指標になるようなイメージです。
命名すればするほど減点されていく。
それがコンポーネント管理の本質なのかもしれません。
まとめ
コンポーネント名に使用される単語が持つ、意味や性質に着目したアプローチで BCD Design というコンポーネント分類手法と、その周辺のノウハウや思想を説明しました。
AtomicDesign でやってるけどディレクトリがごちゃごちゃしているという方は、BCD Design で整理したらどんな構造になるかサンプルのツリーを作って試してみてはいかがでしょうか。
複雑度が高く、コンポーネント数が多いプロジェクトに対しては特に有効に機能するでしょう。
補足
- 粒度の管理には AtomicDesign から派生したものを使っても問題ありません
- BCD Design はお好みの粒度管理方法と組み合わせて使うことができます
2020-03-28 追記
実際に色々な場面に適用する中で、 BCD よりも Base Case Common Domain の BCCD のほうがよりキレイに分類できる可能性が出てきています。
Common には CategoryList や ErrorList (Case に入れていた名詞)のように、 物の名詞 + 型 が該当し、共通で使える関心(Category や Error)を収めます。
CategoryList は UserCategoryList にも BlogCategoryList にも使用できるように、 Category は User や Blog といった何かの関心と組み合わせて使われる程度に、各具体的な関心と連携可能な共通の関心であると言えます。
2020-06-05 追記
実際にプロジェクトで何度か共通の関心に該当するものと遭遇し、 Common に該当するものを common ディレクトリに入れたところ、とてもキレイに収まっています。
CategoryList や ErrorList といった共通の関心に遭遇した場合は迷わず BCCD における common ディレクトリに収めることをおすすめします。
-
必ずしも1単語ずつの D + C + B ではなく DD + CC + BB ということもあり得ます ↩
-
今回はシンプルにするために端折りましたが本当は Article や Comment は BlogXxx としたほうが良いです。コメントできる対象がブログだけとは限らなくなるのが世の常だからです。 BlogArticleSection BlogCommentCard などは安定感のある名前になります。何かおかしいなと思ったら、何か不足した関係性がないかよく考えてみてください。 ↩
-
主体がコンポーネント名の接尾辞、 "型" になるので、この例なら AnchorImage になります ↩
-
利用される際は必ず molecules になるため、Form は単体でも molecules として扱います ↩
-
base や case それぞれの中でも、主従関係に従っていれば参照できます ↩
-
アルファベット順によっては下に向かって参照する場合もあります ↩
-
この場合は XxxTool が使う側 XxxSection が使われる側という想定です ↩