エンジニア立ち居振る舞い:生産性を計測する

お題「エンジニア立ち居振舞い」

僕は開発以外の立ち居振る舞いとして、いくつかのツールと習慣によって開発の生産性を計測している。

自分のチームではスクラムを採用していて、各タスクにはストーリーポイントが割り振られている。相対的な作業量のようなものだ。また、個人的にポモドーロテクニックを採用している。25分開発したら5分休憩する周期を1ポモドーロと呼んで、そのリズムを繰り返すヤツです。

毎日、完了したタスクのストーリーポイントと開発に費やしたポモドーロを計測している。Google Spreadsheetに書いている。やっていることはそれだけ。

そうすることで見えてくることがいくつかある。まずは、曜日ごとの開発できる時間だ。会議が多い曜日はせいぜい4ポモドーロだなーとか、リモートワークできる曜日はこれくらいだなーとか。曜日ごとのポモドーロの平均をSpreadsheetで計算して見ている。そして、これは見積もりのときに利用できる。来スプリントに開発できるポモドーロを平均値から見積もれる。

あとは、完了したタスクのストーリーポイントとポモドーロから、1ポモドーロあたりのストーリーポイントが分かる。自分のなかでは、この値を生産性の指標として考えてる。なんとなくグラフにしたりして、生産性を上げるモチベーションにしている。

一番良いことは、上で話した「来スプリントのポモドーロの見積もり」と「1ポモドーロあたりのストーリーポイント」から「来スプリントで完了できるストーリーポイントの見積もり」が計算できることだ。この計算に基づいて、来スプリントでこなすタスク量を決めている。

実績に基づいた見積もりができるようになったおかげで、マネージャー側からは安定したスケジュールが組めるようになるし、開発側からは無理のない仕事ができるようになる。さらに、無理のない仕事ができるようになると、無理のない生活ができるようになる。このことは最近結婚した僕にとって一番重要なことなのだ。

Timepiece 1.0.0をリリースした

最初のバージョンはSwiftが出た2014年だった。それ以降、細かな機能追加やバグ修正を繰り返し、0.5.0でSwift 2.3、0.6.0でSwift 3に対応した。どこかのタイミングで多くのStar、issueがつくようになり、多くのユーザーが使ってくれるようになった。

そして昨日、ようやく安定版となる1.0.0をリリースした。1.0.0では破壊的な変更を行った。

メソッド名の変更

// 0.6.0
Date.date(year: 2016, month: 10, day: 31)
"2016-10-31T12:00:00+0900".dateFromFormat("yyyy-MM-dd'T'HH:mm:ssZ")

// 1.0.0
Date(year: 2016, month: 10, day: 31)
"2016-10-31T12:00:00+0900".date(inFormat: "yyyy-MM-dd'T'HH:mm:ssZ")

よりSwiftらしく、またSwift 3の命名規則に沿ったものに変更した。

タイムゾーンのサポートをやめた

個別の日付オブジェクトについてタイムゾーンをサポートするようにしていたが、バグになりかねない部分であり、後述するメンテナンスコストを下げていく方針に合わないため、この機能を削除した。

Durationの内部表現を変更した

これまでのバージョンでは、期間を表す概念としてDurationというstructを用意していたが、これをやめた。本来、期間を表す概念としてDateComponentsというものがあるため(これまでDateComponentsが期間を表すということをちゃんと認識してなかった)、これを最大限利用した。この変更によって、1.0.0では新たに以下のような計算や処理が可能になった。

// 1.0.0
Date() + (3.hours - 30.minutes)
(3.hours - 30.minutes).string(in: .abbreviated) //=> "2h 30m"

固定フォーマットでの出力をやめた

// 0.6.0
1.weeks.later.stringFromFormat("yyyy/MM/dd")

上のようなメソッドをやめた。DateFormatterで簡単に実装できるが、この機能は暦やロケールを考慮していない実装なのであまりオススメできない。その代わりにDateFormatter.Styleを指定して出力できるようにした。

// 1.0.0
1.weeks.later.dateString(in: .short)

望ましい機能は実装しやすくする。望ましくない機能は実装しにくくする。というのが理想的なAPIインターフェイスであるように思う。

変更の背景

OSSにかけられる時間が減った。メンテナンスコストを下げたかった。バグが出やすい機能はできるだけ削った。機能が豊富な日付操作ライブラリは他にもある。そんな中、最小限の機能でバグがなく正確で少し読めば何をしているか分かるようなライブラリが理想的だとおもった。

CocoaPodsにコントリビュートした

開発中のiOSアプリでCocoaPodsでインストールしたライブラリのライセンス表示を実装する際に、とある理由でライセンスに表示したくない状況があった。いろいろ調べたところ、CocoaPodsが出力するPods-{ProjectName}-Acknowledgements.plistMITといったライセンスタイプが含まれていないことがわかった(ライセンスのテキストはあるけど、そこから抽出するのは大変)。podspecにはライセンスタイプを記載する必要があるため、内部表現としてライセンスタイプをもっているはずだと思った。そこで、それをplistファイルに出力するようにするPull requestを送って、そしてmergeされた。

Pull requestしてみた感想としては、RSpecのようなよく知らないテスティングフレームワークを使っており、自力ではどこをテストすればいいのか分からず困惑した。コミッターの方が修正してくれたようなのでよかった。

1.1.0.beta.1に含まれているので、今後はMITなどライセンスタイプを基に表示するライブラリをフィルタリングできたりできると思う。よかったら利用してください。

通信周りの処理をミドルウェアで整理する

課題感

APIリクエストの送信前、APIレスポンスの取得後にさまざまな処理をはさみたいことがある。例えば、こんな処理だ。

  • ネットワークインジケータの表示・非表示
  • リクエストとレスポンスのロギング
  • 二重送信の防止
  • ログイントークンが有効期限切れだったときに、リフレッシュトークンを使ってログイントークンを更新した後、再送
  • HTTPリクエストのスタブ

ただ、こういった処理をAPIクライアントにそのまま実装していくとAPIクライアントが肥大化するし、かと言ってViewControllerに実装するといろんな箇所で似たようなコードを書くことになる。

解決策

APIクライアントをラップして機能を拡張するミドルウェアをつくる。ミドルウェアAPIクライアントを呼び出して通信処理を実行しつつ、リクエストの送信前とレスポンスの取得後に処理をはさむ。

例えば、APIClientというオブジェクトで本来の通信処理を実行するとする。ロギングを行うミドルウェアはこんな感じになる。

extension Middleware {
    struct Logger: RequestSendable {
        let client: RequestSendable

        func send<T: RequestType>(request: T) -> Task<Void, T.Response, ErrorType> {
            print(request)
            return client.send(request)
                .success { response -> Task<Void, T.Response, ErrorType> in
                    print(response)
                    return Task(value: response)
                }
                .failure { error, _ in
                    print(error)
                    return Task(error: error ?? ApplicationError.Unknown)
                }
        }
    }
}

そして、こんな感じで初期化する。

let client: RequestSendable = Middleware.Logger(client: APIClient())

だけど、ミドルウェアが増えると、以下のように初期化が大変になってくる。

let client: RequestSendable = A(client: B(client: C(client: D(client: APIClient()))))

そこで、ミドルウェア群を簡単に組み合わせるための仕組みをつくる。

extension Middleware {
    struct Stack {
        let middlewareTypes: [RequestSendable.Type]

        init(_ middlewareTypes: [RequestSendable.Type]) {
            self.middlewareTypes = middlewareTypes
        }

        func buildClient() -> RequestSendable {
            let client = APIClient()
            return middlewareTypes.reverse().reduce(client) { (result: RequestSendable, middlewareType: RequestSendable.Type) in
                return middlewareType.init(client: result)
            }
        }
    }
}

これによって、こんな感じで直感的にAPIクライアントを初期化できる。

let client = Middleware.Stack([A.self, B.self, C.self, D.self]).buildClient()

たいていの場合、利用するミドルウェアは同じなのでデフォルトで利用するミドルウェアスタックを簡単に初期化できるようにする。

extension Middleware {
    struct Stack {
        // ...

        static func defaultStack() -> Stack {
            var middlewares: [RequestSendable.Type] = []

            middlewares.append(A.self)

            if someCondition {
                middlewares.append(B.self)
            }

            middlewares.append(C.self)

            reeturn Stack(middlewares)
        }
    }
}

そして、APIクライアントの初期化はこうなる。

let client = Middleware.Stack.defaultStack().buildClient()

まとめ

通信周りのさまざまな処理をミドルウェアという形で実装することで、疎結合なモジュールに分離することができた。将来的に新たな処理を追加する場合でもミドルウェアを新たに実装してスタックに追加するだけでよく、既存のAPIクライアントやミドルウェアに手を加える必要はない。テスト時のみ不要なミドルウェアを除くといった柔軟な設定も可能になるだろう。

Xcodeのカラーパレットを作るコマンドをSwiftで書いた

f:id:naoty_k:20160317020138g:plain

clrというコマンドラインツールSwiftで書いた。上のスクリーンキャストにあるようにXcodeで使うカラーパレットをターミナルから作成できる。

近年ではStoryboard(かつてはXib)がどんどん進化しているので、見た目に関する設定はコードじゃなくてStoryboardに任せたいなという気持ちがある。特に色については、コードでやろうと思うとUIColorのextensionをひとつひとつ書いていく感じになると思うけど、カラーパレットを自作する手もあるなということに最近気づいた。カラーパレットを使うと2、3回クリックするだけで色を指定できるから簡単だと思う。

技術的な解説をすると、自作したカラーパレットは実は$HOME/Library/Colors/以下に*.clrという拡張子で保存されていて、このファイルを共有すれば他の開発者とカラーパレットを共有できる。ただ、これをXcodeでポチポチ自作するのはけっこう大変なので、これをコマンドラインから作れるようにした。カラーパレットはNSColorListというOSXAppKitフレームワークにあるクラスで表されていて、-writeToFile(_:)というメソッドでファイルに出力できる。なので、NSColorListを操作するコマンドラインツールSwiftで実装した。

参考

ブログタイトルを変えた

以前はnaoty.to_sというRuby-ishなタイトルだったけど、Swiftをやっていきたいという気持ちにマッチしなくなってきたのでSwiftyなタイトルに変えた。

AnyTypeはもちろんSwiftのそれのようにいろんなTypeの記事を書いていきたいという気持ちを表しているし、一方でタイピングのTypeにもかけている。一時期はかなり更新頻度が落ちていたけど、どんどんTypeしてブログを更新していきたい。

今後ともこのブログをどうぞよろしくおねがいします。

型消去を用いたSwiftによるリポジトリパターンの実装

リポジトリパターンとは

リポジトリはオブジェクトの参照を取得するのに必要なロジックをすべてカプセル化するためのパターンです。

Domain Driven Design Quickly 日本語訳

iOSアプリ開発の文脈では、オブジェクトをWeb APIから取得するのかRealmから取得するのかといった関心ごとがある。リポジトリを実装することで次のようなメリットがあると思う。

  • どこからどのように取得するのかなどの関心ごとからドメインモデルを切り離せるため、ドメインモデルをクリアに保つことができる。(DDDの観点)
  • テスト時にWeb APIやRealmにアクセスするリポジトリをメモリにアクセスするリポジトリに差し替えること(Dependency Injection)が可能になるため、テストデータを簡単に用意できたりテストのパフォーマンスを向上できるなど、テストしやすくなる。(テスタビリティの観点)

型消去とは

先日行われたtry! Swiftで紹介されたテクニックで、トークの内容については以下の書き起こし記事が詳しいと思う。

型消去とは何か、端的に説明するのはかなり難しい。ただ、リポジトリパターンをSwiftで実装するにあたって非常に強力なテクニックであることが分かったので、型消去を用いない場合と用いた場合とを比べて型消去について説明してみたいと思う。

型消去を用いない場合

例のごとくPokemonオブジェクトを取得するリポジトリを考える。PokemonはWeb APIから取得するかもしれないし、Realmから取得するかもしれない。とりあえず以下のようなprotocolを定義して、Pokemonを取得するインターフェイスを用意する。

protocol PokemonRepository {
    func find(ID: UInt) -> Pokemon?
    func findAll() -> [Pokemon]
}

そして、実際にRealmからPokemonを取得するリポジトリはこのprotocolを実装して以下のように書けると思う。

struct RealmPokemonRepository: PokemonRepository {
    func find(ID: UInt) -> Pokemon? {
        let realm = try! Realm()
        return realm.objects(Pokemon).filter("ID == %d", ID).first
    }

    func findAll() -> [Pokemon] {
        let realm = try! Realm()
        return realm.objects(Pokemon)
    }
}

同様にメモリ内の[Pokemon]からPokemonを取得するリポジトリは以下のように書けると思う。

struct MemoryPokemonRepository: PokemonRepository {
    let pokemons = [
        Pokemon(ID: 1, name: "フシギダネ"),
        Pokemon(ID: 2, name: "フシギソウ"),
        Pokemon(ID: 3, name: "フシギバナ")
    ]

    func find(ID: UInt) -> Pokemon? {
        return pokemons.filter { $0.ID == ID }.first
    }

    func findAll() -> [Pokemon] {
        return pokemons
    }
}

ViewController等でこのリポジトリを使う場合は以下のように書けると思う。

class PokedexViewController: UITableViewController {
    var pokedex: [Pokemon] = []
    lazy var repository: PokemonRepository = RealmPokemonRepository()

    override func viewDidLoad() {
        super.viewDidLoad()

        pokedex = repository.findAll()
    }
}

テストを書く際は以下のようにリポジトリを差し替えることでRealmへのアクセスを回避できる。

class PokedexViewControllerTests: XCTestCase {
    var viewController: PokedexViewController!

    override func setUp() {
        viewController = PokedexViewController()
        viewController.repository = MemoryPokemonRepository()
    }
}

こうしてPokemonを取得するリポジトリを実装することで、どのようにオブジェクトを取得するのかという関心ごとをカプセル化し、テスタビリティのある設計が可能になった。しかし、この実装には大きな問題がある。

型消去を用いない実装の問題点は、ドメインモデルごとに似たようなprotocolを用意しなくてはならないことだ。例えば、今度はHumanを取得したいという場合に同様にHumanRepositoryを定義しなくてはならないし、その次にTownを取得したいという場合にはTownRepositoryを定義しなくてはならない。これらのprotocolはほとんど中身が同じボイラープレートになってしまうだろう。

それでは、より汎用的なRepositoryというprotocolを以下のように定義してみてはどうだろうかと考えてみる。

protocol Repository {
    typealias Domain

    func find(ID: UInt) -> Domain?
    func findAll() -> [Domain]
}

typealiasを使ってGenericsなprotocolを定義することでより汎用的になった。そして、これを実装するリポジトリは例えばこんな感じになる。

struct RealmPokemonRepository: Repository {
    typealias Domain = Pokemon

    func find(ID: UInt) -> Pokemon? {
        let realm = try! Realm()
        return realm.objects(Pokemon).filter("ID == %d", ID).first
    }

    func findAll() -> [Pokemon] {
        let realm = try! Realm()
        return realm.objects(Pokemon)
    }
}

しかし、これはすぐにうまくいかないことがわかる。

lazy var repository: Repository = RealmPokemonRepository

のようなコードはコンパイルエラーになってしまうのだ。Repositoryのようなtypealiasをもつprotocolはtypealiasに具体的な型をもっていないため抽象型と呼ばれ、そのまま変数の型として宣言することができない。

このままおとなしくドメインモデルごとにボイラープレートのようなprotocolを書かなくてはいけないんだろうか(←try! Swift参加前の筆者)。

型消去を用いた場合

あらゆるリポジトリを汎用的に扱えるようにするため、以下のようなAnyRepositoryを定義する。

struct AnyRepository<DomainType>: Repository {
    typealias Domain = DomainType

    private let _find: UInt -> DomainType?
    private let _findAll: () -> [DomainType]

    init<T: Repository where T.Domain == DomainType>(_ repository: T) {
        _find = repository.find
        _findAll = repository.findAll
    }

    func find(ID: UInt) -> DomainType? {
        return _find(ID)
    }

    func findAll() -> [DomainType] {
        return _findAll()
    }
}

AnyRepository<Pokemon>として使う場合は、typealias Domain = PokemonとなっているRepositoryを実装した型のみAnyRepository()に渡すことができる。例えば、ViewControllerではこんな感じで使うことになる。

class PokedexViewController: UITableViewController {
    var pokedex: [Pokemon] = []
    lazy var repository: AnyRepository<Pokemon> = AnyRepository(RealmPokemonRepository())

    override func viewDidLoad() {
        super.viewDidLoad()

        pokedex = repository.findAll()
    }
}

同様にテストではこんな感じになると思う。

class PokedexViewControllerTests: XCTestCase {
    var viewController: PokedexViewController!

    override func setUp() {
        viewController = PokedexViewController()
        viewController.repository = AnyRepository(MemoryPokemonRepository())
    }
}

AnyRepositoryがあることで、PokemonRepositoryHumanRepositoryのようなドメインモデルごとのprotocolは不要になり、それぞれAnyRepository<Pokemon>AnyRepository<Human>のような型を使うことで対処できる。これでボイラープレートのようなコードを書く必要はなくなった。

型消去とは、この例で言うとPokemonRepository型であったrepositoryAnyRepository<Pokemon>という型にしてしまうことを指しているようだ。型消去というのは、より柔軟な設計のための結果として考えることができそう。


以降は型消去とは無関係だけど、リポジトリパターンを実装するにあたって必要となった技術要素を紹介していきたいと思う。

クエリのインターフェイス

上で紹介したRepositoryは意図的に不十分なインターフェイスだった。というのは、findAll()というメソッドはその名の通りすべてのオブジェクトを取得してしまうので、現実的には検索条件やソートなどのパラメータを指定できる必要があると思う。

ここで指定される検索条件は内部的にGETリクエストのパラメータやRealmに渡されるNSPredicateに変換されることになる。また、検索条件といっても単純に一致するためのものだけでなく、不一致や含んでいるかといった検索方法もある。Web APIに問い合わせるのかRealmに問い合わせるのかといったバックエンドに関わらず、これらを統一的に表すクエリの表現が必要となると思った。

そこでAnyQueryという小さなライブラリを開発した。

これを使ってRepositoryはこんな感じに定義できる。

protocol Repository {
    typealias Domain

    func find(ID: UInt) -> Domain?
    func findAll(query query: AnyQuery?, sort: AnySort?) -> [Domain]
}

extension Repository {
    func findAll() -> Domain? {
        return findAll(query: nil, sort: nil)
    }
}

RealmPokemonRepositoryで実際に使う場合はこんな感じになる。

struct RealmPokemonRepository: Repository {
    typealias Domain = Pokemon

    func find(ID: UInt) -> Pokemon? {
        let realm = try! Realm()
        return realm.objects(Pokemon).filter("ID == %d", ID).first
    }

    func findAll(query query: AnyQuery?, sort: AnySort?) -> [Pokemon] {
        let realm = try! Realm()
        var result realm.objects(Pokemon)

        if let predicate = query?.predicate {
            result = result.filter(predicate)
        }

        if let sortDescriptors = sort?.sortDescriptors {
            for sortDescriptor in sortDescriptors {
                guard let key = sortDescriptor.key else {
                    continue
                }
                result = result.sorted(key, ascending: sortDescriptor.ascending)
            }
        }

        return result
    }
}

そして、ViewControllerからはこんな感じでクエリを組み立てることができる。

class PokedexViewController: UITableViewController {
    var pokedex: [Pokemon] = []
    lazy var repository: PokemonRepository = RealmPokemonRepository()

    override func viewDidLoad() {
        super.viewDidLoad()

        let query = AnyQuery.In(key: "ID", values: [1, 2, 3, 4, 5])
        let sort = AnySort.Ascending(key: "name")
        pokedex = repository.findAll(query: query, sort: sort)
    }
}

詳細はREADMEに書いてあるが、例えば、こんな風に複雑な条件も表現できる。

let query = AnyQuery.Between(key: "ID", lhs: 1, rhs: 100) && AnyQuery.NotEqual(key: "type", PokemonType.Fire.rawValue)
let sort = AnySort.Descending(key: "weight") > AnySort.Descending(key: "height")

非同期処理の取り扱い

たいていの取得処理は非同期に行われるため、リポジトリインターフェイスも非同期処理を前提にしなくてはならないと思う。しかし、取得完了時の処理をクロージャとして渡すインターフェイスはコールバック・ヘルにつながるため、Promiseライクなライブラリを使ってオブジェクトの代わりにPromiseオブジェクトを返すような形がいいと思った。

例としてSwiftTaskを使って以下のようにRepositoryを定義してみた。

protocol Repository {
    typealias Domain
    
    func find(ID: UInt) -> Task<Float, Domain, ErrorType>
    func findAll(query query: AnyQuery?, sort: AnySort?) -> Task<Float, [Domain], ErrorType>
}

そして、実装は以下のようになる。

class RealmPokemonRepository: Repository {
    typealias Domain = Pokemon

    func find(ID: UInt) -> Task<Float, Pokemon, ErrorType> {
        return Task<Float, Pokemon, ErrorType> { fulfill, reject in
            let realm = try! Realm()
            if let pokemon = realm.objects(Pokemon).filter("ID == %d", ID).first {
                fulfill(pokemon.pokemon)
            } else {
                reject(RepositoryError.NotFound)
            }
        }
    }

    func findAll(query query: AnyQuery?, sort: AnySort?) -> Task<Float, [Pokemon], ErrorType> {
        return Task<Float, [Pokemon], ErrorType> { fulfill, reject in
            let realm = try! Realm()
            var result = realm.objects(RealmPokemon)
            
            if let predicate = query?.predicate {
                result = result.filter(predicate)
            }
            
            if let sortDescriptors = sort?.sortDescriptors {
                for sortDescriptor in sortDescriptors {
                    guard let key = sortDescriptor.key else {
                        continue
                    }
                    result = result.sorted(key, ascending: sortDescriptor.ascending)
                }
            }
            
            if result.isEmpty {
                reject(RepositoryError.NotFound)
            } else {
                let pokemons = result.map { $0.pokemon }
                fulfill(pokemons)
            }
        }
    }
}

最後に

Swiftリポジトリパターンを実装するにあたってのポイントは3つあった。

以上で説明したことはすべてこちらのサンプルプロジェクトで詳細を見ることができるので、参考にしてほしい。