現在JavaScriptの設定が無効になっています。このページを利用するにはJavaScriptの設定を有効にしてください。2019の2日目の記事です。(@kazuhiro494949)です。2019アドベントカレンダー2日目は、私がiOSアプリ開発を進めていく過程でどのようにクラス設計を行っているか紹介します。普段の業務でなかなか理想通りに設計できないなと思っている方のお役に立てればと思います。背景iOSアプリ開発は、提供されているフレームワークをいかに使いこなすかという側面がとても強いです。そのため実行結果の動きを触りながら常に試行錯誤して、心地よく動かせる実装を実現していかなければなりません。試行錯誤していく過程で記述したロジックは自然と増えていき、些細と言えないコード量になっていきます。いった部分ではユニットテストが効果的に品質を担保し続けてくれます。ユニットテストを書くためにはしっかりとしたクラス設計を必要とします。ヤフーで提供しているiOSアプリが持つコードの規模だと、残念ながらいくつもの課題が出てきてしまいます。実現したいものをリーズナブルかつ保守性高く作っていくクラス設計方法を考えます。SDKで行う開発を想定としておりそれ以外の技術は対象から外しています。課題の定義この記事では私自身が日々のiOSアプリ開発で抱えている以下の3つの課題に対して解決を目指します。一回のユニットテスト実行が無視できないほど遅いSwiftで書かれたiOSアプリの実行結果を確認するには都度コンパイルが必要です。ユニットテスト側に変更が入った場合にはさらにテストターゲットのビルドが必要になるでしょう。ProcessSwiftは実装がそのままダイレクトにビルド時間へ影響してしまいます。型推論をたくさん活用しているコードだとどうしても全体的にビルドが遅くなることが広く知られています。Xcodeそのため最初は気にならなくとも、さまざまな機能が追加されていくにつれてビルド時間もじわじわと遅くなっていきます。UIを中心に開発している場合にはテストコード中にも非同期処理がどうしても多くなります。XCTestExpectationを使うと以下のように一時的に実行完了するまで待つ必要が出てきます。これが積み重なることで全体のテスト実行そのものも無視できないほどの時間になっていきます。Frameworkによるモジュール分割です。依存関係が必要最小限になるよう強制でき、場合によっては変更時のビルド時間・テスト実行時間を劇的に短縮できます。Appモジュール分割をするということは、何らかの基準でコードを分けなければなりません。クラスをレイヤー構成に分割してその単位でモジュールを作ることが一般的です。Richardsでは、どのようなレイヤー構成が最適なのでしょうか。iOSアプリ開発ではおすすめされるパターンがある程度決まっています。Architectureなどがあるのですが、どれを選択するかは難しい問題です。あらゆるケースを想定すると細かくレイヤーを分ける必要があります。オーバーエンジニアリングになってしまいます。新たにレイヤードの考えを導入しても全体へ浸透させるのはなかなか骨が折れる作業です。その結果としてモジュール分割まで到れるかというと、道半ばで心が折れてしまうことも多いのではないかと思います。SDKを使ってUI表示やアニメーションを実装する場合、最終的な表示だけ見るわけにはいきません。その結果が得られるまでの動きも含めて、違和感ないかチェックする必要があります。画面遷移を伴うアニメーションであれば、その画面だけで完結するわけではありません。既存の動きや変更する値などの兼ね合いでも変わってきてしまいます。前提としているPFが技術仕様通りに動くとは限りません。PF側も十分な注意を払ってくれてはいますが、どうしてもドキュメントには書かれていないデータの流れを踏んでしまうこともあります。OSSであればPFのソースコードを掘り下げていくこともできます。SDKの場合は残念ながらそのようなことはできません。そのために大枠の設計を決める時であっても、常にコードを書き、実行して実際の動きをみながら考えてアップデートしていく必要があります。単純なケースとして、以前勉強会で話したLTを紹介します。初めに頭の中で考えて試しに実装したものと全く違うコードが最終成果物として出来上がりました。Yahoo!デベロッパーネットワークどこまで複雑に作り込むかにもよりますが、こういったケースはiOSアプリ開発でそれほど珍しいことではないと考えています。PFの技術仕様やそれを落とし込んだコードのイメージだけからクラス設計をするのは難しいとわかります。成果物の最終的な仕様は作りながら決まっていき、リリース後も変化してしまう2や背景のところで書いたように、成果物の仕様はPF側の実装が実際にどのように振る舞うかにかなり依存します。繰り返し同じようなものを作っていれば慣れたものかもしれません。この分野では、初めて触る機能・使い慣れていないAPIを扱うことも多いでしょう。iOSは今の所一年に一回大きなアップデートがあり、使っているAPIが拡張・廃止されたり急に振る舞いを変えるといったことも頻繁に起こります。最新OSバージョンではなめらかに動くけど1つ前のバージョンでは同じコードで微妙にカクつくといったことは日常的に起きます。書いたコードをこの先どのように変更していくのかは事業の今後の方向性に依存するでしょう。何年か後に本人以外の人がぜんぜん違う文脈で利用していることも普通に起きます。2設計の難しさこのように考えていくとクラス設計は少しずつ難しい問題となっていきます。恥ずかしながら私の場合、はじめに自分が描いたアーキテクチャ・設計通りにそのアプリがメンテナンスされ続けた経験はほとんどありません。3これら3つの問題に対して、組織的な課題として解決するやり方もあるかと思います。外部仕様から見直すことで解決することもあります。ここではいったんこの状況を受け入れて、クラス設計の段取りによって課題が致命傷とならないよう視点を変えてみます。解決策の提案iOSアプリ開発で新規のプロダクトや新機能を複数人にて開発する場合、私がクラス設計で意識するのは下の4つです。ユニットテストが現実的な時間で実装・実行可能な状態にすることを目指すiOSアプリ開発の現場では、開発を始める時にどういうクラス設計にするかという話から入るケースを目にします。ある程度プロトタイプを作った上で、動くのを確認したらそれを捨てて設計から考え直すケースもよく見ます。それをそのままプロダクション用のコードとしてブラッシュアップさせていくことが多いです。4UIKitをベースとしているiOSアプリであれば、UIViewControllerを基点に作っていきます。controller上へロジックを全てベタ書きして動くものをなんとかして作ります。出来上がった段階でその書かれたコードを元に、テストを書きながらテスト可能な実装へ少しずつ切り出していきます。追求し始めると何日も手が止まってしまうのとあまり本質的ではないため無理に考えなくてもいいと思います。(DI)を駆使してテスト可能なコードを書くということへ集中していけば十分なのではないか、と考えています。プロパティやメソッド内の実装部であればイニシャライザで注入するメソッド内で使われているローカル変数であればメソッド引数で注入する注入する時はProtocolを経由させて実装を切り離すぐらいでやっていて、DIコンテナなどは使用しません。DIに関する概要的な説明は、以前社内LTで話をした時の資料を御覧ください。普段の開発でここまで考えながら作り込むことはありません。扱っている言語やフレームワークのアーキテクチャに合わせるDIによってテストを実現するためにロジックやステートメントのみをクラスとして切り出していくこともあります。controllerもしくは単なるviewとして切り出して集約させます。これは、できるだけUIKitのアーキテクチャの延長で作っていくために効果的な方法です。UIViewControllerやUIViewは、アプリが特定の状況になった時やユーザーが何か入力した時にフックされるメソッドを数多く持っています。それらはUIViewControllerとUIViewで構成されたツリー構造の上を伝わって、適切なタイミングで適切な画面のイベントがフックされる想定となっています。これは全体の画面構成がどうなっているかにも依存しており、そのタイミングを全て理解してクラス設計するのはとても難しいです。画面上に現れた時に遅延して呼ばれます。そのあたりの複雑さは以前私がQiitaに書いた画面回転系の記事を読むとイメージしやすいかもしれません。11時代のレイアウトUIKit全体のライフサイクルは公式ドキュメントに詳細が記載されているため、そちらを御覧ください。UIKitさらに、画面遷移時にフックされるイベントなんかも当然あります。Touchなどの遷移イベントは、後からOSのバージョンアップに合わせてUIViewController上へ実装されていきました。UIViewControllerTransitioningDelegateUIViewControllerPreviewingDelegateそういった複雑さ・不確実性に対処するためには、下手に考えずにできる限りUIKitの構造に乗っかるのが無難だと考えています。controller内部に実装したロジックを大元のツリー構造から大きく切り離さず、それを中心に据えたほうが実行結果が壊れにくくなります。徐々に切り出していくという観点だけで言えば、サンプル的な例でしかないですが以前勉強会で話した時の流れがイメージとしては近いです。Yahoo!デベロッパーネットワークここでは例としてUIKitを出しましたが、他のフレームワークでも同様です。土台となるものがどのように設計されているのかを理解することが重要だと思います。クラス間の依存が単方向でかつできるだけ少なくなるようにするとはいえ複雑になるにつれ、ユニットテストを回しつつ試行錯誤で動くものを作っていく時間がどんどん重たくなるのは見えています。いずれはモジュール分割が必要です。一から独自に考え始めるととても大変なので、ここでいくつかあるレイヤードアーキテクチャの力を借りましょう。それらの特定の型にはめる必要はありません。その中で活用されているアダプターパターンやDIといったデザインパターンを参考にするぐらいでも構いません。SDKやSwiftのスタンダードライブラリで提供されているクラスへの依存は気にしなくてもいいでしょう。サードパーティライブラリへの依存も大きなものに関しては極力やめるというのが実用的だと思います。どのようなモジュールに分割するかは正解を出すのがなかなか難しい問題です。特定の型を参考にしたとしても、実務でコードを書いているとどのクラスをどのモジュールに置くべきか迷う時が多々あります。一度きれいに分割してもいずれは一部のモジュールのコード量が多くなりビルドが遅くなっていきます。DIとアダプターを使ってそろそろ分割したいなというタイミングでモジュールに切り離せるようにしていけるのがいいのではないかと考えています。コーディングスタイルや実装の統一感にはこだわらないここまでの話はコードの構造に重点を置いており、コーディングスタイルには触れていません。スタイルが統一されていて読みやすいことは大切なのですが、開発時にはあえて議論しすぎないようにしています。私の場合、Lintツールなどで多少制約を入れつつ、基本的には言語やフレームワークのドキュメントに書かれたルールと実装者の考えを尊重します。5これに関してさまざまな意見があると思います。常にそれが正しいとは思わないものの、先に書いた課題を回避しながらスピーディーに開発していくにはそのほうがいいと考えています。使用する技術についてもできるだけこだわらないようにしています。iOSアプリ分野では以前からレイアウトを組む時にどのような技術を使うべきかが盛んに議論されてきました。Storyboardを使うべきかコードでレイアウトを組むべきか、AutoLayoutを使うべきか否かといったものです。私はというと、最終的に目指しているUI次第で全部使ってやることが多いです。レイアウトのアウトラインはStoryboardでAutoLayout+UIStackViewによって組んだほうが良いと思っています。一方で目まぐるしくレイアウトを変える必要がある場合など、Frame計算を直接行ったほうがシンプルになるケースもあります。いずれも何らかの課題を解決する道具なので、それぞれが有効に機能する範囲は存在するでしょう。これに関しても他の人がどうするかはその道具を使う合理的な理由があれば統一しません。全体の流れを通して重要なのはPFの作りとその上に直接書いた動くコードを一旦受け入れ、テストがかけるという目的に向かって少しずつ良くしていくことです。要するにリファクタリングで、きれいに作ることを意識しすぎず最初からそれを開発の中に入れていくというやり方です。最後になぜこのようなクラス設計をする必要があるかは、開発のスピードを落とさずに以下を両立していくためでした。実際の実行結果を触りながら、心地よく動くコードを試行錯誤して書くユニットテストを頻繁に実行しながら、実装を固めていく背景にOOPの原則を敷いてはいるものの、この記事の内容は殆どが私自身の経験則へ依存しています。そのため、当てはまらないケースも当然あると思います。弊社のプロダクトでもそれぞれが抱えている課題に対して様々なアプローチが取られています。「こんな課題を抱えていて、こう開発を進めていくことで解決しています」という話が他にもあればぜひ聞いてみたいです。最初に設定した課題を同じように抱えている人たちも、広い世の中を見渡せばいるんじゃないかなと思っています。いった方々にとって多少なりともヒントになればうれしいです。UIViewControllerPreviewingDelegateここは開発チームの構成にも依存するでしょう。シニアの開発者を集められるのであれば、複雑な言語機能・クラス設計を扱うことは選択肢としてとても魅力的です。私の能力不足もあって、設計からもとの意図がしっかり伝わることもなかなかありません。自分がメンテナとして関わっている間ですらそれが起きてしまうことがあります。説明のためにやや単純化していますが、実際には自分はある程度紙の上でクラス設計します。絶対に必要なわけではなくあくまで動くコードを重視します。これはコードレビューをする時も同様で、元のコードを作ってくれた仲間に優しくあることができるやり方だと思っています。背景としてAutoLayoutやStoryboardの独特さ、Swift言語との相性の悪さなどがあり、議論自体はとても生産的で面白いと思っています。JAPANのiOSアプリを開発しながら、iOSアプリ黒帯として社内外へ技術情報を発信しています。JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。JAPANのサービス開発を支える技術やデザイン、文化を発信しています。Reserved.このページの先頭へ。