アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

アーキテクチャを導入する前にできるコードのメンテナンス

いこーよの iOS アプリの開発を担当している namikata です。最近 iOSアプリ設計パターン入門 を読んで設計の勉強をしていますが、設計っていつ考えても難しいですね。猫型蓄音機さんがまとめてくれている実況シリーズを何度も読み直させてもらってます。

nekogata.hatenablog.com

MVVM や iOS Clean Architecture など、 MVC 以外の色々なアーキテクチャに興味はあるのですが、何分 1 人で iOS を開発をしていて、いこーよアプリもまだリリースしてから 2 年ぐらい。 Swift や 各種ライブラリのアップデートも頻繁に行える規模の為 、アーキテクチャを導入することで解決したい課題がそんなに大きな比重を占めてなく、一緒に働くメンバーが増えたら MVC 以外のアーキテクチャの導入を検討するのがいいのかなぁと悶々と考えています。なので、今回は、設計に関わらず、開発を続けて行く上で意識しているコードのメンテナンスに関わる事について、つらつらと書いて行きたいと思います。

共通の性質や振る舞いを Protocol にまとめて共通化する

以前記事にした ViewController での共通の振る舞いを Protocol で共通化する事も、コードのメンテナンス性の向上の一貫だと思っています。

tech.actindi.net

複数箇所で採用される為、 Protocol には、説明用のコメントと合わせて、動画 GIF で詳細を語るようにしています。

f:id:t-namikata:20181122111856p:plain

コメントにリンクされている画像はこちらです

f:id:t-namikata:20181122112014g:plain:w200

最初は、コメントを詳細に残すようにしていたのですが、中々イメージが伝わりにくいので、動画 GIF を撮影して、コメント欄にリンクを貼る事にしました。 動画 GIF の撮影は LICEcap で iPhone シュミレーターを使って作成し、動画 GIF のアップロードは github に適当な issue を立てて、そこに放り込んで参照する url を作成しています。利用されているシーンを実際に見てから、コードに目を通す事で、理解しやすいのではないかと思っています。

Presentation Domain Separation はしっかり意識

Model 層(View と Controller 以外)では、 UIKit の import を極力しないように意識します(画像転送時とかに UIImage そのまま渡したりしている箇所は多々ありますが...)。その意識があるだけで Model 層の単体テストがとてもやりやすくなります。

ライブラリに頼る

ライブラリに依存する事には一長一短あるかと思いますが(Swift 言語のバージョンが上がった時に、利用しているライブラリの対応を待つ必要がある。技術ノウハウが蓄積されていかない 等)、いこーよアプリでは、各種ライブラリを積極的に利用させてもらっています。 ライブラリを利用する目的には「導入する事で開発コストが抑えられて、サービス独自の機能開発に注力できる」事と「Utility や Extension の独自実装を減らして、コードのメンテナンス性が上がる」点が挙げられると思います。今回はコードのメンテナンス性をあげる為に利用しているライブラリを紹介したいと思います。

  • SwifterSwift
    • 便利な Extension をまとめてくれているライブラリ。今までは独自で Extension や Utility を沢山追加していましたが、現在は SwifterSwift で定義されていないものを適宜追加するようにしています。SwifterSwift に慣れておくと、他のプロジェクトでも、ライブラリを入れれば、慣れているメソッドで開発を進める事ができます。
  • SwiftDate
    • 日付に関する処理をまとめてくれているライブラリ。 Date から String への変換処理は頻繁に行われる為、ライブラリを利用する事で、書き方を統一したり、ミスのしやすいタイムゾーンの考慮をなるべく減らせるように努めています(日時の変換は、独自の実装で何度もミスをやらかしました...ついこの間も、日付を String に変換する処理をミスしてしまい 9 時間の誤差を...きちんと単体テストを書きました...)
  • SwiftyUserDefaults
    • UserDefaults を扱いやすくしてくれるライブラリ。こちらも UserDefaults を利用する際の書き方を統一してくれる目的で利用しています。

github.com

github.com

github.com

ライブラリを利用する際には、ライブラリの利用をいつでも辞められる覚悟を持てるものだけ採用するようにしています。Swift は頻繁に言語のアップデートが行われるので、利用しているライブラリが対応してくれないと開発が止まってしまうリスクがある為です。

ライブラリを利用する為のラッパークラスを用意できそうだったらする

画像を非同期で取得するライブラリを例にあげると SDWebImage や PINRemoteImage , Nuke など様々あり、多くの記事でベンチマークによる速度比較や、機能の比較を行っており、将来、利用するライブラリを変更する可能性があります。いこーよアプリではローディングを表示するライブラリに SVProgressHUD を利用していますが、こちらに関しても同じ事が言えます。

将来ライブラリを変更する可能性のあるものを利用する時には、ライブラリを包むラッパーを用意して、コードの各所で利用する時は、ラッパー経由で利用するようにしておくと、ライブラリの変更が用意に行えるようになります。

import PINRemoteImage

extension UIImageView {
    func setPhoto(urlString: String?, emptyImage: UIImage? = nil) {
        guard let url = urlString, url != "" else {
            image = emptyImage ?? UIImage(named: "dammy_image")
            return
        }
        let placeholderImage = emptyImage ?? UIImage(named: "loading_image")
        let errorImage = emptyImage ?? UIImage(named: "error_image")
        pin_setImage(from: URL(string: url), placeholderImage: placeholderImage) { [weak self] result in
            if let _ = result.error {
                self?.image = errorImage
            }
        }
    }
}

いこーよアプリでも SDWebImage から PINRemoteImage に変更したのですが、修正したメソッドは 2 箇所のみで、簡単に切り替える事ができました。

iOS の対応バージョンは 2 つまでを意識する

iOS がメジャーアップデートされるタイミングで、アプリの対応する iOS のバージョンの見直しを行います。基本的には、対応する OS のバージョンは 2 つまでで、最新と一つ前のバージョンです。現在では iOS12 と iOS11 になります。 iOS12 がリリースされて 2 ヶ月ほど経ち、iOS10 以前を利用しているユーザーは 5% 未満になった為、次のバージョンアップで、アプリの対応する OS を 11 以降とすることが決まりました。 OS の対応するバージョンを絞る事で、一部アプリをアップデートできなくなってしまうユーザーの方々が出てしまい、利用してくれている皆さんには申し訳ない気持ちになりますが、より良い機能を提供して、OS をバージョンアップしてもらおうという方向性です。iOS 11 以降を対象とする事で、 OS のバージョンによる条件分岐を書く必要がなくなり Vision.framework もスムーズに利用を開始する事ができました。

常に削除する機能を検討する

いこーよアプリチームでは、週に 1 度 MTG を行っているのですが、「この機能いらないのでは?」と思ったら、 MTG のアジェンダに記入し、相談として挙げて、削除するか残すかを毎週検討しています。完成して動作しているので、ユーザーにデメリットが無ければ残していいかもと思ってしまいそうですが(実装に時間がかかった部分なら尚更)いりません。泣きながら削除します。

これより以下は他の人には全くオススメできませんが、自分のスタイルに合っているので、おまけ程度に載せておきます。

ビルドエラー駆動開発

1人で開発しているので、コンフリクトの心配はほとんどなく、改修が入る度に、気になった所はリファクタリングを行っています。単体テストを書いてある事が理想だと思うのですが、現在のいこーよアプリのステージでは、

  • ミスが起きやすく、クリティカルな問題に繋がりやすい -> 積極的にテスト書こう
  • ミスは起きにくいが、起きた場合はクリティカルな問題に繋がりやすい -> モチベーションが上がっている時にテスト書こう
  • ミスは起きやすいが、クリティカルな問題に繋がりにくい -> ミスが起きた時はユーザーの皆さんと社員の皆さんに土下座
  • ミスが起きにくく、クリティカルな問題に繋がりにくい -> 目をつぶる

のように自分では区分けをしていて、テストでカバーできる範囲は結構少ないです。なので、 Xcode の IDE による error と Warning の検証にどっぷり浸かっています。

邪道な方法ですが、リファクタリングをする時は、クラスやメソッドなどがどこで使われているかは一旦気にせず、リファクタリングを進め、適当なタイミングでビルドを行って、Xcode でビルドエラーになっているところをチェックして改修するといった事をやっています。大々的にリファクタリングした時は、リリース前の目視による動作確認で担保します。まだ画面数も少なく、規模の小さいアプリなので出来る技だと思うのですが、これが一番早いです。このやり方には問題点が沢山ありますが、いいところもちょっとあります。それは、ビルドを何度も行うので、常にビルド時間は短く保とうといった意識が芽生える事です。こまめにビルドを行うので、差分ビルドの時間が一番重要になってきます。

未来の自分に期待する

今の自分の実力で綺麗に書くより、来年の成長した自分でリファクタリングした方がいいという発想です(単に現実から目を背けているといった見方も出来る)

最後に

アクトインディでは、エンジニアを募集しています!

actindi.net

五反田ランチはこのお店がオススメです!

カプリス (Caprice)