シンプルなiOSソフトウェア設計の4つのルール

1990年代後半、Extreme Programmingの開発中に、有名なソフトウェア開発者のKent Beckがシンプルなソフトウェア設計のルールのリストを思いつきました。

Kent Beckによると、優れたソフトウェア設計:

  • すべてのテストを実行します
  • 重複は含まれていません
  • プログラマーの意図を表現する
  • クラスとメソッドの数を最小限に抑える

この記事では、実用的なiOSの例を挙げて、これらのルールをどのように活用できるかを説明することにより、これらのルールをiOS開発の世界にどのように適用できるかについて説明します。

すべてのテストを実行します

ソフトウェア設計により、意図したとおりに動作するシステムを作成できます。しかし、システムがその設計によって当初意図したとおりに動作することをどのように検証できますか?答えは、それを検証するテストを作成することです。

残念ながら、iOS開発では、ほとんどの場合、テストは回避されます...しかし、適切に設計されたソフトウェアを作成するには、テスト容易性を念頭に置いてSwiftコードを常に記述する必要があります。

テスト作成とシステム設計を簡単にする2つの原則について説明します。そして、それらは単一責任原則および依存性注入です。

単一責任原則(SRP)

SRPには、クラスには1つの変更すべき理由があり、その理由は1つだけであることが明記されています。 SRPは最も単純な原則の1つであり、最も適切なものの1つです。責任の混合は、私たちが自然に行うことです。

テストが非常に難しいコードの例を提供し、その後SRPを使用してリファクタリングします。次に、コードをテスト可能にする方法について説明します。

現在、現在のView ControllerからPaymentViewControllerを表示する必要があるとします。PaymentViewControllerは、支払い商品の価格に応じてビューを設定する必要があります。私たちの場合、価格はいくつかの外部ユーザーイベントに応じて可変です。

現在、この実装のコードは次のようになっています。

このコードをどのようにテストできますか?最初に何をテストする必要がありますか?価格の割引は正しく計算されていますか?支払いイベントをモックして割引をテストするにはどうすればよいですか?

このクラスのテストを書くのは複雑で、より良い書き方を見つける必要があります。まず、大きな問題に対処しましょう。依存関係を解く必要があります。

製品をロードするためのロジックがあることがわかります。ユーザーに割引の資格を与える支払いイベントがあります。割引、割引計算、リストがあります。

これらを単純にSwiftコードに変換してみましょう。

支払いに関連するロジックを管理するPaymentManagerと、簡単にテストできる別のPriceCalculatorを作成しました。また、製品をロードするためのネットワークまたはデータベースの相互作用を担当するデータローダー。

また、割引の管理を担当するクラスが必要であることも述べました。それをCouponManagerと呼び、ユーザー割引クーポンも管理しましょう。

この場合、Payment View Controllerは次のようになります。

今のようなテストを書くことができます

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

他の多くのもの!今すぐ個別のオブジェクトを作成することで、不必要な重複を避け、テストを簡単に作成できるコードを作成しました。

依存性注入

2番目の原則は、依存性注入です。そして、上記の例から、オブジェクト初期化子で既に依存性注入を使用していることがわかりました。

上記のように依存関係を注入することには、2つの大きな利点があります。型が依存する依存関係が明確になり、テストするときに実際のオブジェクトの代わりにモックオブジェクトを挿入できます。

次のように、オブジェクトのプロトコルを作成し、実際のオブジェクトとモックオブジェクトによる具体的な実装を提供するのが良い方法です。

これで、依存関係として注入するクラスを簡単に決定できます。

密結合により、テストの記述が困難になります。したがって、同様に、テストを記述するほど、DIPなどの原則と、依存性注入、インターフェイス、抽象化などのツールを使用して、結合を最小限に抑えます。

コードをよりテスト可能にすることで、コードを壊す恐れがなくなるだけでなく(バックアップするテストを作成するため)、よりクリーンなコードの作成にも貢献します。

記事のこの部分は、実際の単体テストを書くよりも、テスト可能なコードを書く方法に関心がありました。単体テストの作成について詳しく知りたい場合は、この記事をご覧ください。ここでは、テスト駆動開発を使用して人生のゲームを作成しています。

重複は含まれていません

複製は、適切に設計されたシステムの主な敵です。追加の作業、追加のリスクを表し、不必要な複雑さを追加します。

このセクションでは、iOSの一般的な重複を削除するためにテンプレートデザインパターンを使用する方法について説明します。理解しやすくするために、実際のチャットの実装をリファクタリングします。

現在、アプリに標準のチャットセクションがあるとします。新しい要件が出てきたので、今度は新しいタイプのチャット(ライブチャット)を実装します。最大20文字のメッセージを含むチャット。このチャットは、チャットビューを閉じると消えます。

このチャットのビューは現在のチャットと同じですが、いくつかのルールがあります。

  1. チャットメッセージを送信するためのネットワーク要求は異なります。

2.チャットメッセージは短くする必要があります。メッセージの文字数は20文字以内です。

3.チャットメッセージはローカルデータベースに保存しないでください。

MVPアーキテクチャを使用しており、現在プレゼンターでチャットメッセージを送信するためのロジックを処理しているとします。 live-chatという名前の新しいチャットタイプに新しいルールを追加してみましょう。

単純な実装は次のようになります。

しかし、将来、さらに多くの種類のチャットを使用する場合はどうなりますか?
すべての機能でチャットの状態をチェックする他の要素を追加し続けると、コードの読み取りと保守が面倒になります。また、テストはほとんど不可能で、プレゼンターのスコープ全体で状態チェックが複製されます。

これは、テンプレートパターンが使用される場所です。テンプレートパターンは、アルゴリズムの複数の実装が必要な場合に使用されます。テンプレートが定義され、さらにバリエーションを加えて構築されます。ほとんどのサブクラスで同じ動作を実装する必要がある場合に、このメソッドを使用します。

チャットプレゼンター用のプロトコルを作成し、チャットプレゼンターフェーズの具体的なオブジェクトによって異なる方法で実装されるメソッドを分離できます。

プレゼンターをIChatPresenterに準拠させることができます

プレゼンターは、内部の共通関数を呼び出してメッセージ送信を処理し、異なる方法で実装できる関数を委任します。

これで、プレゼンターフェーズに準拠したCreateオブジェクトを提供し、ニーズに基づいてこれらの機能を構成できます。

View Controllerで依存性注入を使用すると、2つの異なるケースで同じView Controllerを再利用できます。

デザインパターンを使用することで、iOSコードを非常に単純化できます。それについてさらに知りたい場合は、次の記事で詳細を説明します。

表現力豊か

ソフトウェアプロジェクトのコストの大部分は、長期的なメンテナンスです。読みやすく保守しやすいコードを書くことは、ソフトウェア開発者にとって必須です。

適切なネーミング、SRPの使用、ライティングテストを使用して、より表現力豊かなコードを提供できます。

ネーミング

コードの表現力を高める第一のこと-そしてそれはネーミングです。次の名前を書くことが重要です。

  • 意図を明らかにする
  • 偽情報を避ける
  • 簡単に検索可能

クラスと関数の命名に関しては、クラスに名詞または名詞句を使用し、メソッドにユーザー動詞または動詞句名を使用するのが良い方法です。

また、異なるデザインパターンを使用する場合、クラス名にCommandやVisitorなどのパターン名を追加するとよい場合があります。そのため、読者は、それについて調べるためにすべてのコードを読む必要なく、そこで使用されているパターンをすぐに知ることができます。

SRPを使用する

コードを表現力豊かにするもう1つのことは、上記で述べた単一責任原則を使用することです。関数とクラスを小さく、単一の目的に保つことで、自分を表現できます。通常、小さなクラスと関数は、名前を付けやすく、記述しやすく、理解しやすいです。関数は1つの目的にのみ使用する必要があります。

ライティングテスト

テストを書くことは、特にレガシーコードで作業するとき、多くの明快さももたらします。よく書かれた単体テストも表現力があります。テストの主な目標は、例としてドキュメントとして機能することです。私たちのテストを読んでいる人は、クラスが何であるかを素早く理解できるはずです。

クラスとメソッドの数を最小限に抑える

クラスの機能は短くする必要があり、機能は常に1つのことのみを実行する必要があります。関数の行が多すぎる場合、2つ以上の別個の関数に分離できるアクションを実行している可能性があります。

良いアプローチは、物理的な行をカウントし、最大4〜6行の関数を目指してみることです。ほとんどの場合、その行数を超えると、読み取りや保守が難しくなります。

iOSでの良いアイデアは、viewDidLoadまたはviewDidAppear関数で通常行う構成呼び出しを切り捨てることです。

この方法では、1つの混乱したviewDidLoad関数の代わりに、各関数が小さくて保守可能になります。同じことがアプリのデリゲートにも当てはまります。すべての設定ondidFinishLaunchingWithOptionsメソッドと個別の設定関数またはさらに優れた設定クラスをスローすることは避けてください。

関数を使用すると、長さを長くするか短くするかを簡単に測定できます。ほとんどの場合、物理的な行のカウントに頼ることができます。クラスでは、別のメジャーを使用します。責任を数えます。クラスに5つのメソッドしかない場合、クラスが小さいことを意味するのではなく、それらのメソッドのみに責任がありすぎる可能性があります。

iOSの既知の問題は、UIViewControllersのサイズが大きいことです。 Apple View Controllerの設計では、これらのオブジェクトを単一の目的に使用することは困難ですが、最善を尽くす必要があります。

UIViewControllersを小さくする方法はたくさんありますが、私の好みは、VIPERやMVPなどの問題をより適切に分離するアーキテクチャを使用することですが、それはApple MVCでも改善できないという意味ではありません。

できるだけ多くの懸念を分離しようとすることで、どのアーキテクチャでもかなりまともなコードに到達できます。その目的は、View Controllerのヘルパーとして機能し、コードをより読みやすくテストしやすいものにすることができる単一目的のクラスを作成することです。

View Controllerで言い訳をせずに簡単に回避できるいくつかのことは次のとおりです。

  • ネットワークコードを直接書く代わりに、ネットワーク呼び出しを担当するクラスのNetworkManagerが必要です。
  • View Controllerでデータを操作する代わりに、それを担当するクラスであるDataManagerを作成するだけです。
  • UIViewControllerでUserDefaults文字列を使用する代わりに、その上にファサードを作成できます。

結論として

正確に名前が付けられ、シンプルで、小さく、1つのことに責任があり、再利用可能なコンポーネントからソフトウェアを構成する必要があると思います。

この記事では、Kent Beckによるシンプルなデザインの4つのルールについて説明し、それらをiOS開発環境に実装する方法の実際的な例を示しました。

この記事を楽しんだ場合は、拍手してサポートを示してください。 iOS開発者のスキルを次のレベルに引き上げる多くの記事をご覧ください。

質問やコメントがありましたら、ここにメモを残すか、arlindaliu.dev @ gmail.comにメールしてください。