「プロダクトと共に進化する」Design Systemをチームで導入した話

こんにちは。Mercari Advent Calendar 2019の18日目はCatalog ListingチームiOSエンジニアの@kokoheiaが担当します。

私が所属するCatalog Listingチームでは、先月から全機能の開発にDesign Systemの導入をしています。Design Systemは、高品質なプロダクトを継続的にお客様へお届けするために、常にプロダクトの進化と共に改良していくものです。本日は、そんなスケーラブルなDesign Systemを構築するためのメルカリiOSチームの工夫や、チームで導入してみて思ったことをご共有しようと思います。

Design Systemとは

近年、海外ではShopifySaleforce、国内ではPairsさんなど、様々なテックカンパニーがDesign Systemを導入している記事を見ますね。メルカリでも、デザインチームとArchitectチームの主導により今年から本格的にDesign Systemの導入が始まっています。

Design Systemの定義は様々ありますが、個人的には下記のような位置付けで理解しています。

  • 大規模なプロダクトにおいて、プロダクト全体で一貫したデザインを確立するためのシステム
  • より良いプロダクトや開発のプロセスの改善を目指して、常に変更・改善していくもの

Design Systemを導入することで、プロダクトを通して一貫した体験をお客様に届けることが可能になることや、コンポーネントの再利用により開発の効率が上がるなどのメリットがあります。

私たちは日々プロダクトに様々な変更を加え、改善していますが、プロダクトのUIコンポーネントやスタイルのソースを内包するDesign Systemも同じように常に改善していく必要があります。 一度作ったきり管理されていなかったり、更新の頻度が極端に遅いようなDesign Systemは逆に開発効率やプロダクトの品質を下げるリスクを伴います。

今クオーターに向けてiOS開発用のDesign Systemモジュール(簡略化のためDesign System for iOSと呼びます)をiOSのArchitectチームの方々が整備してくださいました。先月から私が所属するチームでも全機能の開発にDesign Systemの導入をしています。

プログラマティックなUIの採用

メルカリのDesignSystem for iOSにおいては、AtomsやMoleculesなど、すべてのUIコンポーネントはプログラマティックなUIで構築されています。つまりxibやstoryboardは一切使用していません。Design SystemのUIコンポーネントにプログラマティックなUIを採用するメリットとしては主に以下があると思います。

  1. 一つのファイルで複数のコンポーネントを抽象的に定義できる

  2. UIのコードに対するレビューやコンフリクトの解決がしやすくなる

Design Systemは、「常にプロダクトと共に改良していくもの」ですので、常に拡張に対してOpenである必要があります。つまり、抽象的で表現力のあるコンポーネントを作ることを意識する必要があります。この観点でプログラマティックなUIはDesign Systemと相性が良いと言えます。

xibやstoryboardファイルで「抽象的なView」を定義する場合、そのViewにおける一つの具体的なデザインを選んでデフォルトとするか、あるいはサイズやカラーなどの情報を持たないようなコンポーネントとして定義することになります。つまり、xibやstoryboardを扱うInterface Builderの強みの一つである「UIコンポーネントのデザインが可視化される」という部分はあまり生かされません。一方で、プログラマティックなUIではコードで抽象的にViewを定義することができ、かつコードを見るだけでViewの定義がわかるので、見通しも良くなるというメリットがあります。

プログラマティックなUIを使う中でUIコンポーネントの見た目を把握したい場合は、外部ファイルに定義しておけばそれを参照できます。なお、メルカリではZeplinに定義されたUIコンポーネントやスタイルガイドを参考にしながら開発しています。

また、継続的に改良が行われて、複数リポジトリの様々な部分で使われるDesign SystemのソースコードはそのコントリビュートやReviewのしやすさも重要です。この点においても、プログラマティックなUIのメリットが生かされます。

ちょうどよい抽象度のコンポーネントを作る

スケーラブルなDesign Systemを作るためには、コンポーネントの階層ごとに適度に抽象的なコンポーネントを作ることが大切です。具体的な実装の一部を例を交えながら話してみたいと思います。

例えば、以下のようなCard Componentをアプリの中で使いたかったとします。

f:id:kokoheia:20191218155147p:plain

上記のViewの抽象化をするために、まずは適切にAtomsとMoleculesに分ける必要があります。今回は、このように分けます。

f:id:kokoheia:20191218155235p:plain

Cardのデザインバリエーションを表すために、Enumのcaseを使って抽象化をします。EnumのAssociated valueとして、独自のDesign Systemで定義されたColor型の配列を持つcolors caseと、ColorText型を持つtext caseの二つを用意します。このようにEnum型でContentを定義しておくことで、将来的にImage Viewを中に持つCardコンポーネントを作りたくなった場合などにも一つcaseを増やすことで対応できます。

// Molecules
public final class Card: UIView {
public enum Content {
case colors([Color])
case text(ColorText)
public struct ColorText {
let text: String
let color: Color
}
}
private var content: Content {
didSet {
// Set layout
}
}
public func configure(content: Content) {
self.content = content
}
...
}
// Color
public final class Color: UIColor {
static let basic =  colorLiteral(red: 0.2853391469, green: 0.505721271, blue: 1, alpha: 1)
static let sub =  colorLiteral(red: 0.4346325397, green: 0.9842769504, blue: 0.9277977347, alpha: 1)
static let accent =  colorLiteral(red: 0.7721353173, green: 0.5875638127, blue: 0, alpha: 1)
}

また、カードのサイズにも柔軟性を持たせて再利用性を上げるために、ここにもEnumのcaseを使います。

public final class Card: UIView {
public enum Size {
case basic
case small
}
public var size: Size {
didSet {
setLayout()
}
}
private func setLayout() {
switch size {
case .basic:
self.frame.size = CGSize(width: 375, height: 261)
case .small:
self.frame.size = CGSize(width: 229, height: 171)
}
...
}
...
}

タップしたときのHandlerを定義して、呼び出し元のViewController(ViewModel)でそのhandlingをするようにして、Viewの見た目部分の定義と切り分けるようにします。

public var didTap: () -> Void

モジュールとして切り分けてCarthageで管理する

スケーラブルなDesign Systemを構築するためには、実装の内容だけではなくバージョン管理にも工夫が必要です。

現在、Design Systemのソースコードはモジュールとして別で切り分けた上で、Carthageを通じてアプリ本体のプロジェクトにimportしています。この方法によるメリットは以下のようなものがあります。

  1. Cartfileを使ってDesign Systemのバージョン指定ができる
  2. アプリ本体のリポジトリとは独立してDesign Systemの開発をすることができる。

Design Systemを導入しようと思った時に、プロジェクト内に直接コンポーネントを追加することもできますが、その方法ではバージョン管理が難しそうです。また、モジュールを切り分けて別々のリポジトリとして開発することにより、コンフリクトを避けて安全に開発することができます。現在はMercariのリポジトリ同様に、Merpayのリポジトリも同じDesign Systemを利用しています。このように複数リポジトリから同じDesign Systemを参照できるのも、モジュール切り分けによる恩恵です。

f:id:kokoheia:20191218155634p:plain

Design Systemを導入して何が嬉しかったか

Design Systemを導入したことで、チーム開発の観点では以下のようなメリットがあったと感じています。

1. 非デザイナー職もデザインレビュープロセスに入りやすくなった

Design Systemという画一された基準ができたことにより、新たな機能を開発するときに、エンジニアやPMなどデザイナーではない職種もデザインレビューのプロセスに入りやすくなりました。 私のチームでも、新画面のデザインにおいてDesign Systemに沿っていない部分があったため、エンジニアからデザイナーに確認をする場面がありました。もちろん、デザイナー・エンジニア・PM全ての職種がDesign Systemを理解して新機能開発に取り組むべきではありますが、ヒューマンエラーや情報共有不足によって、ケアが不足する場面は生じます。Design Systemをリファレンスとして非デザイナー職もデザインレビューをすることで、プロダクトの品質をさらに高めることができると思いました。

2. Viewの一覧性を(部分的に)保ちつつ、プログラマティックにUIが作れるようになった

メルカリのDesign System for iOSは全てプログラマティックUIで作られているという話をしました。プログラマティックUIには多くのメリットがありますが、Viewの一覧性がないということがしばしば欠点として挙げられます。上記でも述べたように、私たちはZeplinで定義されたUIコンポーネントやスタイルガイドを見ながら実装するので、プログラマティックなUIでも部分的にViewの一覧性を保ちながら開発することができます。もちろん全てが全てDesign SystemのコンポーネントでViewを構築できるわけではないのでその範囲は限られますが、それでもこの点はDesign System導入の一つのメリットだと感じました。

3. 長期的な観点でUI開発のスピードが上がる

Design Systemを使うことでUIコンポーネントを再利用することができるので、長期的な観点で開発のスピードは上がります。言及しておきたいのは、短期的な観点ではあまりメリットを感じにくいかもしれないということです。私たちはxibによる開発からプログラマティックUIによる開発に切り替えたこともあり、単純なLayoutの開発時間だけで考えると、従来よりも時間がかかるViewもありました。また、開発の中で、自分たちが新たに構築するViewをDesign Systemに入れたほうが良いという場合もありました。そのような場合、チームのエンジニアがDesign SystemのコードベースにPRを出してコンポーネントを加える必要があるため、短期的には工数が増える場合はあります。しかし、Design Systemはプロダクトとともに進化し続けるものなので、導入直後よりも、長期的な観点でそのメリットが見えやすいのかなと思いました。これからDesign Systemを導入される人は、短期的な負荷にのみ目を向けず、長い目で様子を見ることも大事なのではないでしょうか。

4. エンジニア側から機能提案をしやすくなった

Design Systemの導入により、エンジニア側からも機能提案がしやすくなったと感じています。これはまだ事例があるわけではありませんが、将来的にあり得るメリットだと感じています。例えば小さな機能の実装を提案したいときに、細かいデザインをゼロイチでデザイナーにお願いしなくても、7~8割の完成度で提案することができます。もちろん最終的にはデザイナーによるReviewと改修が必要になることが多いと思いますが、このように非デザイナー職でも、ある程度の基準に沿ったデザインを構想できるのはとてもいいことだと思っています。以前からメルカリではエンジニアが機能を提案して実際に採用される場面がありましたが、最近Hack Weekという一週間のハッカソンを定期的に行う取り組みもあり、エンジニアが作りたいものを作れる機会がどんどん増えています。このような流れで、Design Systemが多くのアイディアを形にしてくれるという恩恵もあると感じました。

終わりに

最後まで読んでいただきありがとうございました。メルカリのDesign Systemはまだこれからもどんどん進化していくものなので、さらなるアップデートをご期待ください!

明日 19 日目の執筆担当は@paniniです。引き続きお楽しみください!