パート1)
私が取り組んでいるアプリの最大の部分をリファクタリングしたい-それはかなりスパゲッティです。これは、サーバーへのリクエストを作成し、JSONを解析してデータベースにデータを保存する単一のクラスです(特定のビジネスルールが満たされた場合に変更が必要なUIについても知識があり、これらすべてを確実に知っている必要はありません) )。それは5000 LOC :(。
クラスを細かく分割する1つの方法は、要求をセマンティクス(ユーザー要求、検索要求など)で分離することです。それは本当に便利でしょうか?
また、ネットワーキングコードは、解析コードとDBコードから分離する必要があると思います。問題は、これを最善の方法で実行するためにどのアプローチ(アーキテクチャー/設計パターン)を使用すればよいか本当にわからないことです。 (たとえば、JSON解析ライブラリは古く、メンテナンスされていません。問題が発生し、密結合が原因でスタックしてしまったため、変更したかったのです)。
パート2)
また、次の問題についていくつかの提案を受けたいと思います。DBは主にオフラインモードとキャッシュに使用します。ユーザーはオフラインモードでデータを表示し、データを変更して(要求はシリアル化されます)、オンラインに戻ると、サーバーに対して要求が行われます。また、それをキャッシュとして使用してUIに何かを表示し、バックグラウンドでリクエストを行って新しいデータや変更されたデータがあるかどうかを確認します(ETagなどの同様のメカニズムを使用してサーバーに確認します)。
したがって、これはユーザーが画面に入ったときのフローです。
enter_screen ->
check_cache + background_request ->
show_UI with cache data ->
update_UI when the background request finishes and new/modified data is available.
これはどういうわけか抽象化できますか?私は、キャッシュにヒットしたときにすぐにコールバックを受け取り、要求が終了したときに後でコールバックを受け取る、ある種のデリゲートパターンを考えていました(キャッシュと新しいデータを区別するためのオプションのフラグを使用)。
私は現在、約iOSアプリで働いています。 4歳。私は1年前にチームに参加しました。私の意見では、コードは醜く、ハックでいっぱいで、非常に不安定なアーキテクチャを持っています。私たちはそれに取り組んでいる2人のiOS開発者です。クライアントは技術的な専門知識がなく、私たちが抱えているいくつかの問題のすべての影響を理解していない可能性があります。 「リファクタリング」が必要な理由をクライアントに理解させることは困難な作業です。私はできる限りリファクタリングするのに苦労していますが、まだやるべきことがたくさんあります。
アプリには、アプリの複雑さを増す多くの機能もあります。ディープリンク、プッシュ通知、オフラインモード、非ブロックUIなどすべてが、かなり大きくて毛深いデータモデルと組み合わされています。
クライアントは常に機能をリクエストしているので、技術的な負債が多いように感じます。
モバイルの世界は急速に変化しており、新しいテクノロジー(サイズクラス、自動レイアウト、Swiftなど)が登場しています。これはすべてのテクノロジーに当てはまりますが、モバイルの世界ではレースがもう少し激しいと感じています。現在、私は仕事でSwiftを使用することを夢見ています(ただし、自宅で暇なときに遊んでいますが)。
フランクが言ったように、あなたはあなた自身でこれの多くをすでに理解しているように見えるので、私は主な質問に関して一つだけアドバイスをします。
ビジネス価値を高めるために加えた変更に加えて、徐々に、段階的にリファクタリングを行います。 5000 LOCモンスターを一度に5つの1000 LOC生き物に分解しようとしないでください。リストしたすべての長期的な目標、ネットワーキングコード、JSON解析、データベース処理などをすべて念頭に置いている限り、コードベースの独立した自己完結型のコーナーに着実に進んでいきます。
もちろん、実際のコードを見たことはありませんが、あなたの言ったことから、次のアーキテクチャに近いものを提案します。疑似コードをごめんなさい、私はそれを高速で明確にしたかっただけです;)
UIレイヤー:
dataService = new DataService();
dataService->makeDataRequest(requestName, requestParams[], cachedDataArrivedCallback, freshDataArrivedCallback);
showWaitMessageToUser();
サービス層(DataService架空クラス):
processor = RequestProcessorFactory.getProcessor(requestName);
if(processor.cachedDataAvailable(requestParams)) {
cachedDataCallback(processor.getCachedData());
}
freshData = processor.getFreshDataAsync(requestParams, freshDataArrivedCallback);
RequestProcessor :: getFreshDataAsync:
networkTransport = NetworkRequestFactory.getTransport(requestName);
networkTransport.send(requestParams[]);
rawResult = networkTransport.result();
deserializer = DeserializerFactory.getDeserializer(requestName); //JSON deserializers
result = deserializer.deserialize(rawResult);
storageHandler = StorageHandlerFactory.getStorageHandler(requestName); //Database layer
storageHandler.storeData(requestName, result);
cacheHandler = CacheHandlerFactory.getCacheHandler(requestName);
cacheHandler.store(requestParams, result);
freshDataArrivedCallback(result);
ダイアグラムは好きではありませんが、これはかなり明確なはずです。
UIは、データを提供するリクエストでサービスレイヤーを呼び出します。データが利用可能になるときに実行するコールバックをそこに渡します。そして、ユーザーへのメッセージを表示し、ユーザーの操作を待機します。
サービス層は最初に、この特定の要求(ユーザー要求、検索要求など)の処理を担当するRequestProcessorを取得します。この特定のリクエストパラメータのデータがキャッシュされているかどうかをプロセッサに確認します。そうであれば、それを取得し、UIコールバックの1つをトリガーします。次に、requestProcessorに新しいデータを取得するように要求します。
要求プロセッサはネットワーク層を呼び出して、要求をサーバーに中継します。それは現在古い&醜いJSONライブラリであるいくつかのデシリアライザーを使用して応答をデシリアライズしますが、私が提案するものでかなり簡単な別のものに切り替えることができます。
逆シリアル化された結果はデータベースハンドラーとキャッシュハンドラーに送られます(ただし、アプリ内で同じである可能性がありますが、わかりません)。まあ、きれいな結果はあなたが今欲しいハンドラに行くかもしれません;)
さて、ようやくリクエストプロセッサがfreshDataArrivedCallbackを呼び出して、新しいデータがUIで表示できるようになったことを通知します。
これは、これをどのように分割できるかの一例にすぎません。スキームにはさまざまな改善があるかもしれません。たとえば、freshDataArrivedCallbackを渡さないで、DataServiceに保持し、DataServiceが独自のコールバックを公開してカップリングを減らすことをお勧めします。
この例では、ファクトリーを時々使用していますが、不要な複雑化を避けるために、ハンドラーが1つしかない場合はファクトリーをスキップできます。
私はあなたにアイデアを与えるために私の考えを5分短くすることを試みました;)私はあなたがこれが有用であることを望みます。
これは興味深い質問でした。この回答が役に立てば、私は喜んで深く掘り下げます;)ところで、私はiOS開発をやろうとしたことはありません;))
私はこの質問に対する賢明な答えをすでにいくつか見ることができますが、私は拡大して自分の意見を述べたいと思いました。
ロジックを大きなクラスに結合し、多くの異なる機能間で密結合しているように見えるシステムについて説明しました。これを試してリファクタリングする最初の方法は、最初に「良い」アーキテクチャ/コードベースがどのように見えるかを理解することです。
SOLIDの開発原則に精通していると思いますが、そうでない場合は、ここで実際に適用されます。私は最終的にあなたに適用されるいくつかの原則を選びますが、それらすべてを確実に理解する必要があります。
単一の責任原則(SRP-またはSOLIDのS)は、1つのことだけを扱い、1つのことだけを扱うクラスを作成することをお勧めします。この原則を使用すると、個々のタスクに焦点を当てたコードが表示され、それらの範囲外のものには無知です。たとえば、JSONの解析は完全に別の責任であるため、ネットワークコードはJSONの解析とは何の関係もありません。
依存関係の逆転(DI-SOLIDのDの)は、クラスは「抽象化に依存する必要がある」と述べています。コンクリーションに依存しないでください。これはISPと連動します。つまり、消費クラスはインターフェイスへの依存関係を使用し、IOCコンテナを残して、必要に応じて適切な具象クラスを挿入します。これにより、コードから依存関係が取り除かれ、SRPを適用できるほか、テストがはるかに容易になります。
うまくいけば、これにより、優れたコード品質が何で構成されるかがわかります。 SOLIDがコードベースでフォローされている場合、依存関係が注入された、インターフェイスに対するクラスのコーディングが表示されます。クラスは、他のタスクを実行せずに、そのドメインにのみ重点を置く必要があります。
そのため、アーキテクチャに関しては、ロジックを別個のレイヤーに分離し始め、レイヤー間のロジックはサービス呼び出しを介して処理することができます。これに対する非常に一般的なアプローチは、Model-View-Controllerアーキテクチャで、UIをビューに、データをモデルに、配線ロジックをコントローラーに分離します。これは通常、JSON解析、データアクセスなどの特殊な機能を提供する、より低い「サービス」レベルでサポートされます。サービスレイヤーは上記のものとは別にして、最も一般的にはRESTful APIを介してインターフェイスを公開する必要があります。
上記のように配置した場合、JSONパーサーを置き換える例では、IJsonParserインターフェイス(またはそれが何であれ)を実装し、IOC( Dependency Injection)コンテナ。新しいクラスがインターフェースのすべてのロジックを実装する限り、必要な変更はそれだけです。
2番目の質問は、このスレッドで別の質問をする必要があると思います。これは、この回答で混乱を招く可能性があり、確かに多くの選択肢があるためです。