こんにちは、「CA Tech JOB」生としてインターンシップに参加しました、湯本航基と申します。約1ヶ月間、AmebaにてWebフロントエンドエンジニアの業務に携わりました。
主な業務は、Amebaのデザインシステム「Spindle」における新たなコンポーネントの開発です。設計から実装までを一貫して担当させていただきました。
このブログでは、インターンシップ生がどのように業務に取り組むのか、現場で実際に経験したことについてお話ししていきます。
Spindleとは
Spindle(スピンドル)は、「Amebaらしさ」を一貫してユーザーに提供するためのデザインシステムです。Amebaは「つくる、つむぐ、つづく、Ameba LIFE」をビジョンにかかげ、Spindleを基盤としてアクセシビリティやパフォーマンスに配慮したサイトを構築しています。
Spindleのソースコードは、OSS(オープンソースソフトウェア)としてGitHubにて公開されています。そのため、誰でも修正や改善に関するIssueを立てることができます。
また、コンポーネントカタログ(Storybook)も公開されており、利用規約の範囲内で手軽にコンポーネントを試すことができます。
実装したコンポーネント
本インターンシップでは、このSpindleに新たなコンポーネントとして「SegmentedControl」を追加しました。
SegmentedControlという名前には馴染みがない方もいらっしゃるかもしれません。SegmentedControlは、ページ内で機能やモード(View)を切り替える際によく利用されるものです。動作はフォームのラジオボタンと似ており、複数の項目の中から1つ選択できます。
似たような機能を持つコンポーネントとしては、タブ(Tab)があります。しかしタブは主に「ページ」の切り替え(ページ遷移を伴う)に使用されるのに対し、SegmentedControlは「View」の切り替え(ページ遷移を伴わない)に用いられます。
完成したものは以下になります。こちら(Storybook)から実際に触ることもできます。
コンポーネント開発の流れ
では、SegmentedControlの開発の流れを順にご紹介します。
開発の全体の流れは以下の通りです。
- Design Docを書く
- 実装する
a. コンポーネントのコーディング
b. テストケースの作成
c. Storybookの整備 - レビューを受ける
- リリース 🎉
1. Design Docを書く
まず、Design Docの作成に取り組みます。Design Docをひとことで表すと、「エンジニアのエンジニアによるエンジニアのための設計書」です。ここでは、コンポーネントの画面仕様やアニメーションの仕様、Design Tokensの定義、アクセシビリティ要件などをまとめていきます。
1.1. デザイナーとのコミュニケーション
あらかじめFigmaで作成されたデザインを確認しながら要件を明確に言語化していきます。デザイナーと密接なコミュニケーションをとり、不明瞭な点があれば適宜質問を投げて認識を擦り合わせていきます。エンジニアとデザイナーの間の認識の齟齬をなくすためにこのプロセスは非常に重要です。適切なコミュニケーションによって手戻りを最小限に抑えていきます。
私がインターンシップ生であることに関係なく、チームの一員として対等な立場でコミュニケーションをとっていただきました。さまざまな視点から意見を出し合い、コンポーネントの動作や仕様について効果的にブラッシュアップしていくことができました。
1.2. コンポーネントプロパティ(Props)の検討
コンポーネントのプロパティ(Props)は、複数の候補を挙げて慎重に検討していきました。それぞれの利点や欠点を洗い出し、社員の方々を巻き込んで議論を進めます。Spindleでは、汎用的で誰もが使いやすいコンポーネントを目指しているため、さまざまなユースケースを考慮し、抜け目のない設計を追求する必要があります。このため一定のプレッシャーを感じながらも、コンポーネントの責務を明確にするために慎重に検討しました。
このプロセスを通じて、設計段階でゴールまでの全体像を把握する難しさを実感しました。トレーナーの方が「Design Docを書き終えた時点で8割は開発を終えている」とおっしゃっていた意味が理解できました。
1.3. アクセシビリティの検討
Amebaでは、アクセシビリティの向上に力を注いでいます。WCAG 2.1をベースにした「Ameba Accessibility Guidelines」を公開しており、Amebaブランドのサービスで準拠すべき項目が詳細に整備されています。
Spindleにおけるアクセシビリティの考え方もAmeba Accessibility Guidelinesを踏襲しています。Design Docの作成段階でAmeba Accessibility Guidelinesに基づいたチェックリストを使用して、アクセシビリティに関する検討を行います。
私はこれまでアクセシビリティに対する意識が低く、設計や開発の過程で十分な配慮をしてきませんでした。そのためアクセシビリティへの理解が不十分で、どのように進めるか悩んでいました。しかし幸いにも、トレーナーの方からアクセシビリティオンボーディングに参加する機会をいただきました。その結果、アクセシビリティの重要性を深く理解し、再度学び直すことができました。
またトレーナーのご厚意により、オンボーディングの講師とのランチ交流会も開催していただきました。そこでアクセシビリティに関する考え方や具体的な事例についてさらに学ぶことができました。CA Tech JOBでは、このようなランチ交流会を通じて多くの先輩社員と交流する機会があります。この交流を通じて多くの学びを得られるため、私にとってこのランチ交流会はCA Tech JOBの大きな魅力の1つであると感じています。
さて、アクセシビリティに対する理解が深まったところで改めて検討を進めていきます。この段階で、これまでの設計に不備や考慮漏れが見つかることもあります。
例えば、キーボードインタラクションの検討では、キーボードでの選択に不都合があることが発覚しました。キーボードインタラクションでは、視覚障がいをもっていてスクリーンリーダーを使っているユーザーもスムーズに操作できるように、Tabキーによる移動やEnterキーによる選択などの操作体系について考慮する必要があります。
そのために、いくつかの案を以下のように並べて、それぞれpros/consを書き出します。
// 案1 <div role="radiogroup"> <input type="radio" name="size">小</input> <input type="radio" name="size">中</input> <input type="radio" name="size">大</input> </div> // 案2 <div role="radiogroup"> <button role="radio" aria-checked="true">小</button> <button role="radio" aria-checked="false">中</button> <button role="radio" aria-checked="false">大</button> </div> // 案3 <div role="group"> <button role="button" aria-pressed="true">小</button> <button role="button" aria-pressed="false">中</button> <button role="button" aria-pressed="false">大</button> </div>
案1は、 <input type=”radio”>
をそのまま使用し、デザインをオーバーライドする方式です。アクセシビリティの原則として、「WAI-ARIAは必要な場合にのみ使用する」というものがあります。この方式では、WAI-ARIA属性である role=”radio”
と aria-checked
を使用しないためこの原則に従っています。しかし、 name
属性が必要であり、また操作体系の観点から案1は不採用としました。
案2は、親要素の <div>
に role="radiogroup"
を追加し、子要素の <button>
に role="radio"
と aria-checked
を追加する方式です。この方式のメリットは、 ネイティブの <input type="radio">
とアクセシビリティツリー上で全く同じように扱われ、操作体系の懸念もないことです。デメリットとしては、必要とするキーボードインタラクションを追加で実装する必要があることが挙げられます。しかしこれは、案1のラジオボタンを使った上で操作体系をオーバーライドし変更するよりも簡潔なため、特段の問題はないと判断しました。
案3は、親要素の <div>
に role="group"
を追加し、子要素の <button>
に role="button"
と aria-pressed
を追加する方式です。案2との違いに注目します。案2では、フォームのラジオボタンと同様の挙動が意図されています。すなわち単一選択のみを許容します。対して案3では、ボタンそれぞれが pressed
されたかどうかの状態を独立して保持しています。すなわち複数選択も許容することができます。
「単一選択のみを許可するのか、複数選択も許可するのか」という点は、これまでの検討の中で明らかにされていませんでした。そのためこのタイミングでチームメンバーと議論し、単一選択のみを許可する仕様に決定しました。この結果、「案2」を採用することになりました。
2. 実装する
Design Docが完成したら、いよいよ実装に移ります。
なお、実際に書いたソースコードはGitHubから閲覧できます。
2.1. コンポーネントのコーディング
まずはReactを用いてコンポーネントのコーディングをしていきます。Design Docを丁寧に作成したおかげで、要件の技術的な実現方法(How)だけに集中することができました。
コーディングを進める上で注意すべき点は、他の既存のコンポーネントとの一貫性を保つことです。既存のコンポーネントは、パフォーマンスやアクセシビリティに最大限の配慮がなされており、すでに完成されています。したがって実装に少しでも迷うことがあれば、まずはエディタでgrep検索を行って他のコンポーネントでの先行事例がないかを探すことが重要です。これはSpindleの開発に限らず、一般的な開発においても共通するポイントです。
アニメーションに関しては工夫が必要でした。デバッグの過程で意図しない動作がいくつか発見され、それぞれに対して詳細な調査と対策を行う必要がありました。
例えば、白いレイヤーが左右にスライドするアニメーションでは、ページを初めて読み込んだ直後(初期レンダリング時)に、左端からスライドアニメーションが発生する問題がありました。これは機能要件に含まれていない予期しない動作であり、ユーザ体験に悪影響を与える可能性がありました。
これを解決するために、もともとCSSファイルに記載していたスタイルをスクリプト側に移行しました。これにより、初期レンダリング時に白いレイヤーの tansform
の値を適切に設定し、アニメーションの発生を抑制することができました。このような細かな修正のプロセスを反復することで、よりよいユーザ体験の提供を目指します。
<div
style={{
transform: `translateX(calc(${100 * selectedIndex}% + ${
(4 + 4) * selectedIndex + 4
}px))`,
}}
/>
また、文字の太さ( font-weight
)に関するアニメーションがうまく機能しないという問題も発生しました。多くのフォントでは、 font-weight
の値を100刻みでサポートしていないため、 font-weight
自体にアニメーションを指定しても期待通りに動作しません。
解決策として、擬似要素や color
を用いたCSSハックな手法を検討しましたが、アクセシビリティやSEO、メンテナビリティなどの観点から懸念が浮上しました。これらの懸念を完全に解消するのは難しく、問題の本質的な解決策を見つけることもできませんでした。そのため、意思決定には困難を伴いました。
そこで、リードエンジニアやデザイナーなどの多くの関係者を巻き込んで議論を進めることとなり、最終的に新たなガイドラインを策定するに至りました。ガイドラインのおかげで、「文字の太さに関するアニメーションは実装しない」という意思決定ができました(ガイドラインの2.2に該当すると判断)。
この難度の高い課題を乗り越えるには、チームメンバーの存在が不可欠でした。1人では乗り越えられない壁も、チームメンバーからの温かい協力によって乗り越えることができました。
(開発ガイドライン – アニメーションより引用)
Spindleでは、Amebaらしさを表現するためにできる限りアニメーションを付与したいと考えています。ただしプラットフォームでの実装難易度による開発コスト(短期的には開発時間、中長期的には変更のしにくさ、テストのしにくさ、予期せぬ問題の発生)を考慮し、アニメーション対応のガイドラインを作成しました。アニメーションを付与する際には以下のフローにそって判断してください。わからない場合には都度開発メンバーにヒアリングしてください。
- 機能的にアニメーションが必須な場合 (1)
- まず標準的な方法で実装します。それが難しい場合にはハック的な方法を使って実装します (1.1)
- 機能的にアニメーションが必須ではない場合 (2)
- 標準的な方法で実装できる場合は実装します (2.1)
- ハック的な方法が必要な場合は、アニメーション実装をしません (2.2)
2.2. テストケースの作成
コンポーネントの作成が完了したら、テストコードを記述していきます。Design Docの要件を満たすように、テストケースを網羅的に作成します。また、アクセシビリティに関連するテストケースも用意します。これによって、コンポーネントが正しく動作するかどうかを確認します。
以下に実際のテストコードの一部を示します。このテストケースでは、各項目がクリックされたときに、利用者が定義した onClick
関数が実行されることを確認しています。
test('onClick should be called when the button is clicked', async () => {
const onClick = jest.fn();
const user = userEvent.setup();
render(
<SegmentedControl
selectedId={options[0].id}
options={options}
onClick={onClick}
/>,
);
await user.click(screen.getByText(options[1].label));
expect(onClick).toBeCalled();
});
2.3. Storybookの整備
続いてStorybookの作成に取り組みます。SpindleはAmebaのすべての開発者が使うことを想定しているため、コンポーネントの仕様や用途をわかりやすく伝える必要があります。特にプロパティに関する仕様については、実際の使用例を示すことで理解が促進されるように心掛けます。
さらに、コンポーネントのユースケースも網羅的にカバーします。たとえば、 size
プロパティによってサイズを変更できるため、各パターンを具体的な使用例とともに掲載します。さらに、項目数や文字数に関する制約事項も明示し、使い方に迷わないように配慮します。
Storybookの整備によって、コンポーネントの利用者に対して直感的で使いやすいドキュメントを提供し、より良いユーザーエクスペリエンスを実現します。
3. レビューを受ける
実装が完了した後は、Spindleのスーパーエンジニアの方々からレビューを受けます。レビューはGitHubのプルリクエスト(PR)上で行われます。
レビューではまず、Design Docとの整合性を確認することから始まります。そして、技術的な実現方法の適切性、アクセシビリティ、Storybookの表現方法、CSSのパフォーマンスなど、あらゆる側面から包括的にレビューされます。非常に手厚く丁寧にレビューしてくださるので、このプロセスを通じて多くの学びを得ることができました。ただ正解を示すだけでなく、改善のためのヒントや提案もいただけるので、効率的に学習のサイクルを回すことができました。
クリティカルで厳しいレビューもありましたが、それが私の技術的な成長につながったと実感しています。レビュワーの方々のレベルは非常に高く、どのような観点でレビューしているのか、どのようにコメントをしているのかなど、見ているだけでもたくさんのことを学ぶことができます。
レビューを受ける際に重要だと感じたことは、修正後に全体を見直すことです。特にSegmentedControlのような複雑なコンポーネントでは、一部のコードの修正が他の箇所に影響を及ぼすことがよくあります。このようなデグレーションが発生すると、レビュワーの負担が増えるだけでなく、レビューの進行も遅くなってしまいます。
私自身もこの経験から、修正後は頭をリセットして全体を冷静に見直すことの重要性を再確認しました。先入観にとらわれずに改めてコードや仕様を確認することで、見落としていた不具合や改善の余地を見つけることができます。レビューの効果を最大限に引き出すためにも、この見直し作業は欠かせません。
4. リリース 🎉
レビューが完了したらついにリリースです。リリース作業を行い、最後にTwitterで告知を行ったらすべての作業は完了です。おつかれさまでした。
まとめ
1ヶ月のインターンシップを通じて、自身の成長を実感できた貴重な経験となりました。設計から実装までのプロセスを最後までやり遂げることで、自信と達成感を得ることができました。
このインターンシップでは、技術的な学びだけでなく、多くの価値ある出会いと経験を得ることができました。ランチ交流会を通じて他事業部の社員の方々と交流し、さまざまな人生観やキャリアの多様性に触れることができました。それぞれの事業部の特徴やチームの取り組みを知ることで、自身のキャリアに対するビジョンがより明確になりました。
また、社員の方々からは温かいサポートや的確なフィードバックをいただきました。トレーナーとの1on1ミーティングでは日々の成果や課題について話し合い、自身のスキルアップに向けた具体的なアドバイスをいただきました。週次の人事面談ではキャリアに関する相談に親身に応じていただき、自身の成長につながる方向性を導いていただきました。
この1ヶ月間の経験を通じて、自身の技術力だけでなく、コミュニケーション能力や問題解決力など幅広いスキルを磨くことができました。また優秀な先輩社員の方々との関わりを通じて、CA Tech JOBに参加してよかったと心から感じています。
最後に、この素晴らしい機会を与えていただき、本当にありがとうございました。