この記事は ドワンゴ Advent Calendar 2020 の 23 日目の記事です。
ニコニコ生放送でフロントエンドエンジニアをやっている @misuken です。
去年、新しいコンポーネント分類法である BCD Design を発案し、使い始めて1年くらい経った中で Tag というコンポーネント名を使わなくなった話を紹介します。
BCD Design とは
BCD Design とは、 AtomicDesign のような粒度軸ではなく、概念軸で Base Case Common Domain のディレクトリに分けて管理するコンポーネント分類法です。
粒度軸の分類は性質的に関心の分散が避けられないのに対し、概念軸で分類すると関心の分散が起こらないので、大量のコンポーネントを体系的に管理することに向いています。
命名から明名へ
BCD Design はコンポーネント名を命名ではなく、明名という視点で名前を明らかにしていくアプローチを取ります。
命名とは名前を付ける行為であり、人の主観によって名前が定義されます。
それに対して明名は、そのもの自体の性質をもとに名前が表現されるので、主観が入りにくいメリットがあります。
この明名のアプローチを取ったことが Tag というコンポーネント名がおかしいことに気付くきっかけになりました。
誰もが当たり前に作る Tag コンポーネント
アプリケーションでタグをコンポーネントで表現する際、ほぼ 100% の人が Tag というコンポーネント名で作ると思います。
自身も BCD Design を実践する以前、当たり前のように Tag という名前でコンポーネントを作っていました。
誰もが当たり前に使う Tag というコンポーネント名はどこに問題があるのでしょうか?
BCD Design でコンポーネントに使用される単語一つ一つをコンポーネントの性質から起こし、 Base Case Common Domain に当てはめながら単語の精度を上げていく様子とともに、その理由を説明していきます。
Tag を BCD Design の観点で捉える
BCD Design に従って明名されるコンポーネントの場合、最後の単語は必ず Base にあたる型名で終わります。
Anchor Button Form List Menu Card といった具合です。
これらの型名には、その名前である所以とも言える性質が存在します。
型名 | 粒度 | 性質 | 構成 |
---|---|---|---|
Anchor | atom | 選択した際に別のリソースへ移動する機能を提供する | テキスト or 任意の要素 |
Button | atom | 選択した際に紐付けられたアクションを実行する | テキスト or 任意の要素 |
Form | molecule | 入力要素を包括し、ボタン操作によって入力内容を送信する | 任意の要素 |
List | molecule | 0個以上の項目を並べる | ul ol li 構成 |
Menu | molecule | ある場面で取り得る選択肢を提供する | ul li 構成 |
Card | molecule | ある対象を適度な量の情報でまとめた単位、対象に対する操作が提供される場合もある | 情報部と操作部 or 任意の要素 |
このように、 Base のそれぞれの名前で終わるコンポーネントは、その性質を持っていることが保証されるべきです。
逆を言うと、それらの性質を持っている場合にその型名を使用するべきです。
それがアプリケーション全体の理解のしやすさに繋がります。
そして、その型名が持つ性質をもとに、どのような構成であるかも明らかにできるため、 AtomicDesign における粒度も定めることができます。
この Base の型というのは、インタフェースや抽象クラスの概念をさらに少し抽象化したような存在と言えます。
Tag の性質とは
BCD Design の Base に対する考え方を踏まえた上で、 Tag というコンポーネント名を考えてみます。
コンポーネント名が Tag であるならば、 BCD Design では末尾の単語は Base になるので、 Tag 型としての性質が存在することになります。
Tag の性質を言い表してみようとすると意外と難しく「ある対象にタグ付けするもの」と言いたくなってしまいますが、それでは意味がありません。強いて言えば「ある対象に対する印付けするもの」といったところでしょうか。
次に粒度が何であるか、これはほとんどの人が atom と答えるでしょう。
たしかにそうとも言えるのですが、これは必ずしも単純ではありません。
例えば、検索時に使用されるタグなどでは、同じタグ名に紐付いた情報が何個あるかを数値として表現することがあります。
この場合、人が認識するのはそのまとまり自体が Tag であり、タグの名前の部分だけが Tag という認識にはなりません。
文字列だけを表現する Tag の場合は atom っぽいのに、少し他の情報が増えた瞬間に molecule になる。というように、 Tag の表現内容によって粒度が変わってしまうと atoms と molecules のどちらのディレクトリに存在するか推測不能になってしまいます。
さらに、表現内容が変わるたびに atoms と molecules を行き来することにもなってしまい、分類が不定な状態が理解の難しさに直結してしまいます。
- 実装に依存して粒度が決まるのだとしたら、実装を見ないと粒度がわからない
- 実装は粒度ディレクトリの中にあるので、粒度がわからないと実装がわからない
- 目的のものがどこにあるかは、可能性のあるディレクトリを手当り次第開いてみないとわからない
タグに数値を一緒に表示するぐらいなら atom でもいいんじゃないか、という考え方もあるかもしれませんが、ニコニコ生放送の場合は以下のようなリッチな Tag も存在するので、atom とするには無理があります。
BCD Design を使用する場合、 Atomic Design の粒度を使用する機会は減るものの、本質を理解することで全てのコンポーネント名の明確な根拠を説明できるようになるので、 Base の型の粒度に関しても矛盾が生じないか調べることには価値があります。
Tag の正体
ある時期に既存のコンポーネントの名前を全て洗い出して、 BCD Design に沿っているかを調べる機会があり、そこで型名と実態がズレているものや型名が不足しているものが見つかったのですが、Tag という存在にはとても悩まされました。
ここまでいろいろと考えてきて、どうやら Tag は Base ではなさそうだということがわかります。
Tag が Base ではないということは、 BCD Design では Case Common Domain のいずれかということになります。
Tag は動詞でも形容詞でもないので、 Case ではありません。
となると次は Common です。
共通の関心事である Common に分類されるものは、名詞で Article Comment (BlogArticle NewsArticle) のように、より具体的な関心事と組み合わせて使えるもの、または Error のように全ての概念で横断的に使える関心事が該当します。
Tag を関心事として捉えてみると、さほど違和感はありません。
「タグを登録、タグを編集、タグを削除」
「コメントを追加、コメントを編集、コメントを削除」
表現する内容は違えど、タグとコメントは文脈の抽象度的にも一致度が高いと言えます。
Common としての Tag
Tag を Common と捉えて BCD Design の法則に当てはまるか検証してみます。
単純な Tag の型名は Label
Tag が Common ならば BCD Design のコンポーネントの末尾に Base の単語が入って TagXxx になるはずです。
最も単純なタグの場合、短い名称としての文字列を表現するだけなので、それはただの Label であり、 TagLabel と表現するのがしっくりきます。
Label はただ単にテキストを表示するだけの atom であり、主に <span>
で表現されるでしょう。(フォーム内では label で表現する場面もあります)
リッチな Tag の型名は Card
では、先程の数値やボタンを含むようなリッチなタグの場合は何になるのでしょう?
この答えを導き出すにはかなり頭を悩ませたものの、色々考えた末に Card が適切ということがわかりました。
もともと、タグという情報量の少ない物に惑わされていたのですが、捉え方によっては匿名でコメントした際の情報量とさほど変わりません。
例えば CommentCard というコンポーネントにメッセージしか表示されていないとしても不自然な点はありません。
UserCard にユーザーのサムネイル画像と名前が表示されている場合、サムネイル画像を設定していないユーザーがいれば名前だけが表示されることになるでしょう。
このように、情報量が多いか少ないかが焦点ではなく、関心の対象をどの型で表現するかという視点が重要であることがわかりました。
その上で 関心の対象 x 型
の作用が目的に照合するかを検証してみると良いでしょう。
これにより Tag x Card
が "タグを適度な量の情報でまとめた単位、タグに対する操作が提供される場合もある" を意味するコンポーネントとして定義できます。
この結果から得られること
名前に問題があると、その時点で整理できなくなることがあるということ。
BCD Design の観点から分析を進めるまで、Tag は atom と molecule のどちらとも言いにくいコンポーネントでしたが、TagLabel と TagCard という名前のコンポーネントで表現することにより問題は解決されました。
同じ理論は Category にも適用できるので、新たに得た理論を横展開して明るい範囲を広げられます。
もし Tag や Category が Base として存在していたとすると、名称が違うだけでほとんど同じ機能と構成になるはずです。それはつまり関心の対象が違うだけで型としては同じものであることを指します。
このように、主観や思い込みではなく、そのものの性質を BCD Design のテンプレートに当てはめることで、それまで見えなかった世界が見えるようになっていきます。
そして、この分類スキルは単語の文脈が持つ性質自体を利用した普遍的な概念なので、どのアプリケーションを作る上でも有効です。開発する様々なアプリケーションでインタフェースや粒度が一致しやすくなると、設計やレビュー等の様々なコスト削減が可能となり、大きな武器になると考えています。
Base は表現型
今現在、開発チーム内では Base の型のことを、UI型や表現型と呼んでいます。
普通に型と呼んでしまうと TypeScript の型と混乱するためです。
もし誰かが Tag というコンポーネントを作ったとしましょう。
その場合、レビューのタイミングで「このコンポーネントは表現型が足りていないので、タグを何で表現するのか考えてみてください」のようなアプローチを取れます。
ただ単に「名前が揃っていない」という命名の指摘ではなく、性質と名前の繋がりを意識してもらえるようになります。
この表現型のわかりやすい例を上げます。
例えばユーザーの一覧を表示する要件があった場合、ほとんどの人は UserCard というコンポーネントを作ると思います。User というコンポーネントで表示する人もいるかもしれませんが、現代においては UserCard が主流でしょう。
その理由を問われたら、無意識に「カードの形状だから」と答えることが多いと思います。
しかし、本質は「ある対象を適度な量の情報でまとめた単位、対象に対する操作が提供される場合もある」だから Card という単語を使うのです。
このように、コンポーネントによっては世の中の慣習と偶然一致してが暗黙的にうまくいっているパターンもあります。
これを根拠をもとに論理的に実現するのが BCD Design です。
ユーザーの情報を詳細に表示するのであれば UserDetail となりますし、ブログ記事の投稿内容を全部表示するなら BlogArticleDetail となります。
Card と Detail は表現方法の違いを表す単語と説明できます。
TagCard の内部も表現型を意識する
Card 型は、情報部と操作部から構成される傾向があります。
情報部には Information 型を、操作部には Toolbar 型を割り当てます。
これらはどちらも BCD Design における Base に分類される表現型です。
型名 | 粒度 | 性質 | 構成 |
---|---|---|---|
Information1 | molecule | ある関心事の情報のまとまりを提示する | 任意要素 |
Toolbar2 | molecule | ある関心事に対する操作のまとまりを提供する | ボタン or 任意要素 |
<div class="tag-card">
<div class="information">
<a class="name-label" href="#">タグ名</a>
<span class="count-number">99</span>
</div>
<div class="toolbar">
<button class="lock-button"><svg /></button>
<button class="delete-button"><svg /></button>
</div>
</div>
とてもきれいな構造と文脈を表せていることがわかります。
Information や Toolbar は他の構成と交換できるように作っておくと、差し替えが効いて非常に便利です。
このようによく整理された情報構成を実現すると、スタイルも文脈に沿って当てることができるので、ソースレベルで理解しやすいコードが書けます。
なぜ NameLabel なのか
先程の HTML の中で name-label
というクラス名が気になると思うので、そこも解説していきます。
タグ名を表す要素に名前を付ける場合、 TagName か Name にするのが一般的と思います。
しかし、ここも忠実に Base である表現型である Label を付けています。
Name という単語は表現方法ではないので、表現型にあたる Base には該当しません。
そのため Name というコンポーネントは存在し得ないことになります。
Name は表現しようとしている関心の対象なので、共通の関心の Common にあたるものです。
それを Label で表現するという文脈を NameLabel が意味しています。
名前の競合を防ぐメリット
これには意外と重要なメリットもあります。
表現型がコンポーネント名に付いていると、 Props を渡すときに名前の競合を防ぎやすくなるのです。
HTML の要素の中には name 属性を持つものもあるので、 Props で名前が競合するリスクが存在します。実際には name 属性を持つ要素の性質との兼ね合いで、意外と競合しにくいのですが、 title 属性はよく競合します。
何らかのタイトルを表示しつつタイトルチップも表示したい場合などは titletip という名前で迂回したり、特別な処理を施すことになるでしょう。こういった特定のコンポーネント特有の処理が増えれば増えるほど、全体が理解しにくくなっていきます。
TitleLabel のように表現型をコンポーネント名に明示していれば、そういった問題も起きず、常に法則性が保たれて全体を理解しやすい状態に保てます。
なぜ NameAnchor ではないのか
タグ名はアンカーで表現されているので NameAnchor のほうが正しいのではないか?と思う人もいるでしょう。
なぜ NameAnchor ではないのかというと、タグを表示する場所によっては、アンカーの掛かっていない状態のタグを使用する場合があるからです。
例えば、タグを登録するフォームで確定する前の段階などは <a>
ではなく <span>
で表現したい。
つまり、アンカーが掛かろうが掛かるまいが、常に NameLabel は存在し、その NameLabel にアンカーが掛かることがあるという方向性なので、 NameLabel になります。
ログインリンクのように、リンクで置くこと自体が目的で、リンクが掛からないときに存在意義がなくなるコンポーネントは LoginAnchor にします。
ニコニコ生放送で使用している Anchor コンポーネントは、 href
を省略すると <span>
で表示する仕組みがあるので、 NameLabel をリンク化したいときだけ href
を渡すようにして実現しています。
カード表現時のアンカーの扱い
これは横道にそれた話になるのですが、カード表現時のアンカーをカード全体に適用していないことが気になる人向けに書きます。
カードのクリック時はページ遷移することが多いので、ついついカード全体をアンカーで囲いたくなりますが、そうするとカードの内部にボタンがあったり、部分的に別の情報のページへ遷移するリンクがあるなど、アンカーやボタンが二重になる問題が発生します。
そこで、カード全体のクリックはイベントハンドリングで処理し、サムネイル画像やカードのメインの文字列をアンカーで囲う方法を取ります。
こうすることで、カードのクリックに対応しつつ、アンカー部分を Command + Click した際に別タブで開く動作も確保できます。
その上で、 Tab フォーカス時の最適化も施すとより良いでしょう。
Tabキーでフォーカスを移動した際、サムネイル画像とカードのメインの文字列で同じリンク先に二回フォーカスが当たるのは無駄なので、サムネイル画像のアンカーには tabindex="-1"
を指定してフォーカスが当たらないようにします。
<article class="user-card">
<a class="thumbnail-image" href="#" tabindex="-1"><img class="resource" /></a>
<div class="information">
<a class="name-label" href="#">ユーザー名</a>
</div>
<div class="toolbar">
<button class="follow-button"><svg /></button>
</div>
</article>
もし href が渡されない場合はこうなります。
<article class="user-card">
<img class="thumbnail-image resource" />
<div class="information">
<span class="name-label">ユーザー名</span>
</div>
<div class="toolbar">
<button class="follow-button"><svg /></button>
</div>
</article>
表現型で会話が可能になる
表現型を意識し始めると、だんだんユビキタス言語のような存在になっていきます。
ユビキタス言語はドメインによる会話ですが、表現型は "List の Item に Card を並べて、 Card の Toolbar に Button を置く" のように型による会話です。
このメリットはとても大きいと感じています。
- 関心の対象には言及せず、型の組み合わせだけで構造や機能の会話が成立する
- 頭の中で考えるときも表現型の単位で組み上げられるので、コンポーネント設計やHTML設計も楽になる
- 表現型の性質と構成を定義した表をチームで共有すると、コミュニケーションコストを大幅に削減できる
最近はニコニコ生放送のチーム内で表現型が共有されてきたこともあり、細部まで認識を合わせた会話がしやすくなってきました。
複雑なものを作る上ではこういったベースのコスト削減が重要になってくるように思います。
まとめ
BCD Design を実践することにより、Tag は関心の対象であり、コンポーネント名として適切ではないことがわかりました。
Tag というコンポーネントを作るのは、 User というコンポーネントを作るようなものです。
また、表現型を意識することにより、低コストで高品質なものを作るベースが整うことや、複数のアプリケーション間でも統一感を出していく鍵になることを紹介しました。
ニコニコ生放送でも、React を使い始めてから作成してきた既存のコンポーネントがたくさんあるので、徐々に BCD Design に寄せていっている段階です。
開発ツールでHTMLのクラス名を見ると、 BCD Design を感じられる名前がたくさん見られると思います。
表現型で会話ができるようになってきてからはレビューも楽になったので、今後もさらに楽をできる世界を目指して頑張っていきます。
きっとコンポーネントがキレイに整理された世界で仕事をしたいと思っている方も多いと思うので、少しずつ BCD Design の考え方を取り入れてみてはいかがでしょうか。
おまけ
BCD Design を使い始めて1年経ち、すでに atoms molecules organisms のディレクトリを作る場面がほぼなくなりました。
ライブラリとして使用している Base 名だけの基礎的なコンポーネントのリポジトリでは atoms と molecules を使用していますが、それ以外だと AtomicDesign のディレクトリ分類はほとんど不要に感じています。
organisms は完全に不要なので、今後新たに作られることはないでしょう。