
Figma
イベント

マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
ワンキャリア 企業向けプロダクトの開発チームに所属している西川(X: @takashi54461358 )です。 さて、皆さんは開発にAIを導入した後こう思ったことは無いでしょうか?「コーディング速度は上がったが、もっと開発速度は上がらないのだろうか?」と。
Coding Agentと業務ツールを連携した業務改善は、開発現場では当たり前になりつつあります。しかし、その恩恵は本当に組織全体に広がっているでしょうか。「一度触ればすごさはすぐ伝わる。ただ、その一...
はじめに こんにちは、ZOZOTOWN開発1部iOSブロックの @kitasuke です。 前回の記事「 ZOZOTOWN iOS のアーキテクチャとチームの進化 」では、MVCからMVVM、そしてMVVM + Repositoryへのアーキテクチャ進化を取り上げました。あわせて、レビュー文化をチームに根づかせてきた3年間も振り返っています。 ただ、アーキテクチャを文章で定義しても、書き手によって命名や責務分割はぶれが生じますし、AIに任せると過去の望ましくない実装パターンまで律儀に再現されます。 ドキュメントによる「努力目標」では、アーキテクチャは守りきれません。 そこで発想を逆にしました。アーキテクチャを「守るべきルール」ではなく、 構造化されたスキーマ として定義し、人間とAIの双方がそれに従うしかない形にします。Swiftの型システムがコンパイル時に不正を弾くのと同じ発想を、アーキテクチャのレイヤーにスキーマという形で持ち込みます。それが本記事で紹介する 「スキーマでアーキテクチャを縛る」アプローチ です。副産物として、設計からコードを自動生成するパイプラインも動いています。 目次 はじめに 目次 どんなスキーマを定義したのか architecture-guidelines.md — コンポーネントをスキーマで縛る architecture-templates.md — スキーマから Swift を導出するルール どうやって縛っているのか 画面ごとの設計を YAML で表現する /architectureと/codegen — 実際の運用 /architecture: 仕様書やデザインから YAML を起こす /codegen: YAMLからSwiftのコードを生成する 何が変わったのか AIの書くコードがレビューを通る水準になった レビューで「プロダクト品質」の話ができるようになった まとめ どんなスキーマを定義したのか 全体像はこうなっています。 仕様書 (Confluence) / デザイン (Figma) / 既存コード │ ▼ /architecture ┌─────────────────────┐ │ 設計 YAML │ ←── AI / Codegen 向け │ Human Doc (Markdown) │ ←── 人間向けレビュー資料 └─────────────────────┘ │ ▼(人間がレビュー・編集) │ ▼ /codegen Swift コード一式 ↑ 全工程でガイドラインとテンプレートが参照される 土台となっているのが、チームで整備した 2つのドキュメント です。 architecture-guidelines.md — 各コンポーネントのスキーマ(何が正しいか) architecture-templates.md — スキーマからSwiftを導出するテンプレート(どう書くか) architecture-guidelines.md — コンポーネントをスキーマで縛る 各コンポーネント(ViewModel、Repository、Translatorなど)を、型・依存・命名・必須ルール・禁止パターンなどのフィールドで厳密に定義しています。たとえばViewModelのスキーマは次のとおりです。 ### ViewModel - type : `@MainActor final class` - imports : [ Foundation, Combine ] - imports_forbidden : [ APIModule ] - depends_on : [ RepositoryProtocol, UIModelTranslator, DataModel, UIModel ] - nested_types : [ ViewState, Router ] - naming : { Feature } ViewModel - required : - ViewState enum で画面状態を管理(複数 Bool 禁止) - @Published private(set) で外部からの直接変更を防止 - 1 ユーザーアクション = 1 input メソッド(did{Verb}{Noun}) - forbidden : - キャッシュロジック(Repository の責務) - ログ送信の直接呼び出し(UseCase/別 Repository に分離) 自由に書ける余地を 意図的に潰している のがポイントです。ViewModelがAPIモジュールをimportした時点でアウトです。 @Published を private(set) にしなかった場合もアウトです。自己流のMVVM解釈を許さない設計になっています。 architecture-templates.md — スキーマから Swift を導出するルール スキーマだけではSwiftコードの具体的な書き方までは決まりません。命名規則、ファイルの生成順序、各レイヤーのSwiftコードテンプレートなどを、もう一段別のドキュメントで固めています。 ガイドラインがスキーマで、テンプレートが導出規則です。 この2つが揃うことで、アーキテクチャのスキーマから具体的なSwiftコードが一意で決まる状態になりました。 どうやって縛っているのか 人間・AI・ツールの全員が、同じスキーマで動くようになっています。順に見ていきます。 画面ごとの設計を YAML で表現する コンポーネントのスキーマが決まっても、画面ごとの実装は別物です。そこで、 画面ごとの設計を1枚のYAMLで記述 します。 feature : ProductList domain : Product api : - id : fetchProducts method : GET path : /products response : items : [ Product ] actions : - trigger : didAppear api : fetchProducts - trigger : didTapRetry api : fetchProducts condition : "state == .error" models : data : - name : Product fields : id : String name : String brandName : String price : Int imageURL : URL ui : - name : ProductListUIModel fields : nameText : String brandText : String priceText : String このYAMLは、ガイドラインが定めたスキーマの「値」にあたります。画面のAPI、アクション、データモデルが構造化されて並んでいるだけで、曖昧さの入り込む余地はありません。 /architecture と /codegen — 実際の運用 この縛りを日々の開発で実行しているのが2つのスラッシュコマンドです。 /architecture : 仕様書やデザインから YAML を起こす 重要なのは、このYAMLを人間がゼロから書いているわけではない という点です。Confluenceの仕様書やFigmaのデザインを入力にすると、 /architecture コマンドが設計YAMLと人間向けMarkdownの大部分を自動生成します。 人間の作業は「書く」ではなく「判断する」に寄っています。生成されたYAMLを読み、責務分割やエッジケースの扱いなど 設計判断が必要な箇所だけ に手を入れます。スキーマが縛ってくれているので、AIが起こしたYAMLも標準から外れた形にはなりません。 /codegen : YAMLからSwiftのコードを生成する レビューが終わったYAMLを /codegen に渡すと、Swiftコード一式が出力されます。具体的には、View / ViewModel / Repository / プロトコル / モック / ユニットテストの雛形 / 依存注入のコードです。 たとえば先ほどの ProductList.yaml のうち、以下の部分に注目します。 actions : - trigger : didAppear api : fetchProducts - trigger : didTapRetry api : fetchProducts condition : "state == .error" この部分を /codegen に流すと、ViewModelは次のように生成されます。 @MainActor final class ProductListViewModel : ObservableObject { enum ViewState { case loading case loaded(ProductListUIModel) case error(Error) } @Published private ( set ) var state : ViewState = .loading private let repository : ProductRepositoryProtocol func didAppear () async { await fetchProducts() } func didTapRetry () async { guard case .error = state else { return } await fetchProducts() } private func fetchProducts () async { state = .loading do { let products = try await repository.fetchProducts() state = .loaded(ProductListUIModelTranslator.translate(from : products )) } catch { state = .error(error) } } } ガイドラインで定義した制約が そのまま反映されている のが分かります。たとえば @MainActor final class 、 @Published private(set) 、ViewState enumでの状態管理、 did{Verb}{Noun} 命名規則などです。YAMLの actions はそのままViewModelのメソッドに、 condition はguard文に対応しています。コード生成は仕組みの主役ではなく、スキーマで縛った結果として得られる副産物です。 何が変わったのか AIの書くコードがレビューを通る水準になった スキーマで縛ったことで、実際にAIの出力が目に見えて安定しました。命名・配置・レイヤー構成がプロジェクト標準に揃い、ハルシネーションもほぼ消え、同じYAMLを何度通してもほぼ同じコードが出てきます。 AIの生成するコードは、そのままレビューを通る水準に達しました。 これが縛りの直接的な見返りです。 レビューで「プロダクト品質」の話ができるようになった コード品質(命名、配置、責務分割)はスキーマが自動的に揃えるので、レビューで議論する必要がなくなりました。その分、UXが成立しているか、エッジケースの仕様が妥当か、ビジネスゴールに沿っているか、といった プロダクトとしての品質 に時間を使えるようになっています。コードの良し悪しではなく、 プロダクトの良し悪し を議論できるようになったのは、狙い通りの大きな変化でした。 まとめ アーキテクチャは「努力目標」ではなく「スキーマ」で守ります。Swiftの型システムが不正を弾くのと同じ発想を、設計レイヤーにも持ち込みます。人間とAIを同じスキーマで動かすことで、チームのアーキテクチャを長く保てる状態を目指しています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com








