Slide 1

Slide 1 text

最初が肝心! 技術的負債解消に向けて最初にやるべきこと 2023.04.12 モバイルアプリの技術的負債 みんなで学ぶ Lunch LT NewsPicks iOSエンジニア / 金子 雄大

Slide 2

Slide 2 text

00 自己紹介 ©NewsPicks Inc. All Rights Reserved. 
 金子 雄大 NewsPicks iOSエンジニア @takehilo_kaneko takehilo takehilo

Slide 3

Slide 3 text

00 NewsPicksについて ©NewsPicks Inc. All Rights Reserved. 


Slide 4

Slide 4 text

00 前回登壇のイベント ©NewsPicks Inc. All Rights Reserved. 
 ● 今日と同じく技術的負債の解消がテーマ ● アーカイブ動画が公開されてるので、今日と合わ せて是非ご参考に https://uzabase-tech.connpass.com/event/253143/

Slide 5

Slide 5 text

©NewsPicks Inc. All Rights Reserved.
 01 リアーキテクチャ実施の背景

Slide 6

Slide 6 text

01 ビジネス優先で走り続けてきた ©NewsPicks Inc. All Rights Reserved. 
 iOSアプリのコードは限界に来ていた... ● NewsPicksはリリースからまもなく10年を迎える ● その間、何度もUIがリニューアルされてきた ● 一方で、コードの保守性は置き去りにされたまま ここまで来てしまった

Slide 7

Slide 7 text

01 技術的負債解消のアプローチ ©NewsPicks Inc. All Rights Reserved.
 ● 既存のアーキテクチャのまま負債を返済していくことはもはや不可能 ● ただし、ゼロから作り直すための時間とリソースは無い 新しいアーキテクチャを作成し、既存の画面・機能を少しずつ新しい アーキテクチャで作り直していくことにした

Slide 8

Slide 8 text

01 今日伝えたいこと ©NewsPicks Inc. All Rights Reserved.
 作り直しに取りかかる前にやっておいたほうが良いことがある ● 技術的負債が生まれにくくすること ● たとえ技術的負債が生まれても、返済可能な状態を維持できるようにすること 具体的に何をすべきかをこの後話していく

Slide 9

Slide 9 text

©NewsPicks Inc. All Rights Reserved.
 02 作り直しをする前に 最初にやるべきこと

Slide 10

Slide 10 text

02 最初にやるべきこと ©NewsPicks Inc. All Rights Reserved.
 ● コードをレイヤー分けしてマルチモジュール化し、依存の方向を強制する ● ユニットテストの実装を強制する環境を作る 強制するというのがポイント!

Slide 11

Slide 11 text

©NewsPicks Inc. All Rights Reserved.
 02 マルチモジュール化して 依存の方向を強制する

Slide 12

Slide 12 text

02 レイヤー分けと依存の方向の例 ©NewsPicks Inc. All Rights Reserved.
 ● 新しいアーキテクチャのモジュールから古い コード(Legacy)を参照できないようにする ● PresentationとInfrastructureはDomainにの み依存し、直接参照し合わないようにする

Slide 13

Slide 13 text

02 なぜ依存の方向を強制するのか? ©NewsPicks Inc. All Rights Reserved.
 ● フォルダ分けをしただけでシングルモジュール構成のままだと、新しいコードから古 いコードを利用できてしまう ○ 負債を返済しているつもりが、新たな負債を生み出すことに... ● モジュールを分けて依存の方向を強制してしまえば、これができなくなる 「古いコードは参照しない」というルールを決めても、それを継続し続 けるのは実は難しい ルールを強制してあげることで、新しいコードをキレイな状態に保ちな がら、負債を返済していくことができる

Slide 14

Slide 14 text

02 依存の方向を強制することによるその他のメリット ©NewsPicks Inc. All Rights Reserved.
 ● 何でも屋クラスが作られづらくなり、各クラスの責務が明確になる。さらに、その状 態を維持しやすい ● ライブラリへの依存がモジュールに閉じるので、ライブラリを使ったコードが散らば らないし、ライブラリの更新、差し替えもしやすくなる 負債が生まれにくく、かつ生まれても返済可能な状態を維持できる!

Slide 15

Slide 15 text

02 どうしても古いコードを使いたいときは ©NewsPicks Inc. All Rights Reserved.
 ● Adapterパターンを使って古いコードを参照する(後述) ○ 戻り値の型を新しいコードのものに変換するなどして、新しいコードの都合に 合わせるようにする ● 利用は最低限にすること

Slide 16

Slide 16 text

02 Adapterパターンを使った古いコードの利用方法 ©NewsPicks Inc. All Rights Reserved.
 ● 利用したいクラス(例: SelfLogic)がLegacyモジュールにあるとする ● Domainモジュールに利用したい機能のインタフェースを定義したプロトコル(例: SeflLogicAdapter)を作成する ● SelfLogicをSelfLogicAdapterに準拠させる ● DIコンテナがPresentationモジュールのクラスにSelfLogicをインジェクトする

Slide 17

Slide 17 text

02 Adapterパターンを使った古いコードの利用方法 ©NewsPicks Inc. All Rights Reserved.
 // MARK: - Domainモジュール protocol SelfLogicAdapter { func someLogic() } // MARK: - Legacyモジュール class SelfLogic { func someLogic() {...} } // SelfLogicAdapterに準拠させる extension SelfLogic: SelfLogicAdapter {} // MARK: - Presentationモジュール class NewsFeedresenter { private let selfLogic: SelfLogicAdapter init(selfLogic: SelfLogicAdapter) { // 実体はSelfLogicインスタンス self.selfLogic = selfLogic } } ● インジェクションをどうやるかはプロ ジェクトによって異なる

Slide 18

Slide 18 text

02 The Composable ArchitectureでのDIの例 ©NewsPicks Inc. All Rights Reserved.
 // MARK: - Domainモジュール public enum SelfLogicAdapterKey: TestDependencyKey { public static let testValue: SelfLogicAdapter = MockSelfLogic() public static let previewValue: SelfLogicAdapter = MockSelfLogic() } public extension DependencyValues { var selfLogic: SelfLogicAdapter { get { self[SelfLogicAdapterKey.self] } set { self[SelfLogicAdapterKey.self] = newValue } } } // MARK: - Legacyモジュール extension SelfLogicAdapterKey: DependencyKey { public static let liveValue: SelfLogicAdapter = SelfLogic.shared } // MARK: - Presentationモジュール struct NewsFeedReducer: ReducerProtocol { @Dependency(\.selfLogic) var selfLogic func reduce(into state: inout State, action: Action) -> EffectTask { ... selfLogic.someLogic() } } ● DependencyValuesを使ってReducerに SelfLogicをインジェクトしている

Slide 19

Slide 19 text

©NewsPicks Inc. All Rights Reserved.
 02 ユニットテストの 実装を強制する

Slide 20

Slide 20 text

02 ユニットテストの実装を強制している例 ©NewsPicks Inc. All Rights Reserved.
 ● 例えば、変更したファイルのカバレッジが ○%以下ならPRをマージできないようにして しまう

Slide 21

Slide 21 text

02 なぜユニットテストの実装を強制するのか? ©NewsPicks Inc. All Rights Reserved.
 ● ユニットテストを実装するためには、テスタブルな設計にしなければならない。その結 果、ロジックがシンプルで、可読性が高くなりやすい ○ 何でも屋クラスや、複雑過ぎるメソッドが作られづらくなる(負債化しにくくな る) ● ユニットテストの実装がしづらいと感じたら、プロダクションコードを見直すことにな り、より良い設計にしようという意識が働く ○ どうすれば良い設計になるかをチームで議論する機会が明らかに増えた ● ユニットテストがあると、リファクタしやすくなる ○ 負債を継続的に返済していくための環境ができる 負債が生まれにくく、かつ生まれても返済可能な状態を維持できる!

Slide 22

Slide 22 text

02 ユニットテストはバランスが大事 ©NewsPicks Inc. All Rights Reserved.
 ● マルチモジュール化して依存の方向を強制したり、テスタブルにした結果、テストする までもないシンプルなコードが多くなった ○ ロジックがほとんどないシンプルなコード、変わることがほとんどないコードにつ いては、テストを書いてもコスパが悪い ● 比較的変更の多いプレゼンテーションロジックを中心にテストを書くのが良い

Slide 23

Slide 23 text

©NewsPicks Inc. All Rights Reserved.
 03 さいごに

Slide 24

Slide 24 text

03 新しく作ったコードもすぐに負債になり得る ©NewsPicks Inc. All Rights Reserved.
 ● 新しく作ったコードが完璧なものとは限らない ● 設計に不備があったり、納期を優先しなければならない状況は常に発生する 技術的負債を返済可能な状態を維持していることが重要! 技術的負債解消プロジェクトを何度もやることにならないように 最初にしっかり準備をしよう!

Slide 25

Slide 25 text

03 偉人の金言 ©NewsPicks Inc. All Rights Reserved.
 自信過剰による再設計は、元のプロジェクトと同じように崩壊する 速く進む唯一の方法は、うまく進むことである Clean Architecture 達人に学ぶソフトウェアの構造と設計 より

Slide 26

Slide 26 text

©NewsPicks Inc. All Rights Reserved.
 Thank you!!