Quantcast
Channel: クックパッド開発者ブログ
Viewing all 726 articles
Browse latest View live

カジュアルレビューのススメ

$
0
0

こんにちは、ユーザーファースト推進室 デザイナーの吉井です。 現在は毎日の買い物を便利にする、クックパッド特売情報に関わるデザイン業務全般を担当しています。

さて、デザイナーの方なら誰しも経験があることだと思いますが、デスクに向かって黙々と仕事をしているとき、ある瞬間で行き詰まったり、深く考えこんでしまうことってありますよね。むしろその繰り返しが日常茶飯事だ、という方も多いのではないでしょうか。

何を隠そう私自身もその一人なのですが、そういったときの突破口というものは、自分のデザインから少し距離を置いてみることで意外なほど早く見つかるものです。

他の人の客観的な意見を取り入れてみたり、時には一晩二晩寝かせてみたり... 解決に繋がる糸口はシーンによって様々ですが、今日はそんな悩みをベースにした最近の取り組みについてご紹介したいと思います。

クックパッドにおけるデザインレビューの仕組み

クックパッドのデザイナーは、基本的にユーザーファースト推進室というひとつの部署に集められています。そのためそれぞれの席も近く、普段から気軽に意見を求めやすく、困ったときにいつでも相談し合える環境が作られています。 加えて、そのような普段のコミュニケーションを記録する/体系化する仕組みとして、GitHubのIssue機能を使ったデザインレビュー(下図)が導入されています。

f:id:hrtk441:20151102181845p:plain

このデザインレビューは過去のエントリでも触れらているように、それぞれの担当する施策において、デザイナー同士で(時にはエンジニア、ディレクターも交えて)仮説設計から視覚的なデザイン、プロトタイピングの使用感などについて意見を交わし合うのが目的です。

小さな施策をひとつ考えるにしても、果たしてそれがクックパッドとしてユーザーに届ける価値あるものになっているのか、品質として問題ない水準に達しているのかなど、日々多くの議論が重ねられています。 事実、このレビューを通すことで自分ひとりではなかなか気づけなかった(後々見返してみれば、なんで気づかなかったのか不思議なほど当然のことだったりする)見落としにも気づくことができるので、私自身この仕組みに助けられることもしばしばです。

しかしながら、これはあくまでオンラインでのコミュニケーションです。 当然全員が常にすべてのIssueをウォッチできる状況にあるとは限りませんし、それだけに頼ってしまうあまり予想外のコミュニケーションコストを費やしてしまうことも少なくありません。

特に私は少し前から、特売情報事業を執り行う部署に席を寄せて業務を行っています。 開発効率の向上や事業体制の強化など、事業部隊に入り込むことに多くのメリットもありますが、周囲に自分以外のデザイナーがいない環境になり、デザイナーどうしのコミュニケーションという観点では少し障壁が出来てしまったように感じていました。

カジュアルに始めよう

そこで私は、タイトルにもある「カジュアルレビュー」と称するものを普段の業務サイクルに取り入れてみることにしました。 とは言っても何も特別なことではなく、デザイナーが集まる定例MTGや夕会などの時間の枠を少しだけ使わせてもらい、自分が担当している施策についてその名の通りカジュアルに共有するものです。

f:id:hrtk441:20151102181846p:plain

こういったことをやろうとしている、という初期段階から、まだラフであるがこのようなデザインを考えているなど、どんな段階でも構いません。ただ肩肘張らず、画面を見せながら簡潔に内容説明をするだけです。時間にして本当に数分程度でしょうか。しかしこのわずか数分を介することで、

「あ、その問題ならこの前も自分が担当している施策でもあった」

「こういうことができるとより良さそうだよね」

「◯◯あたりの他社事例が参考になりそうなので研究してみては?」

「向いている方向としては良い(または少し違う)」

「この観点が不足しているように見えるがどの程度考えられているのか」

...などなど、取り入れるべき意見の判断は適宜必要になりますが、オンラインのコミュニケーションより圧倒的な情報量を持つレビューを、初期に、効率的に、且つ即座に集約できます。 更に各施策についてまとめて周知が図れるこのカジュアルレビューを通すことによって、その後のデザイン制作の速度、効率性を飛躍的に上げることが可能になるのです。

より質の高いデザインへ

デザインの精度をより高めていくためには、それだけでは終わりません。上記カジュアルレビューでもらった意見を元に、プロトタイピングを繰り返し、ある程度デザインが固まってきた段階で再度GitHubでのデザインレビューを依頼します。

カジュアルレビューによって各デザイナーもその施策内容に一度は触れているので、

「すみません、文面からは読み取れなかったのですが..」

などのように不要なコミュニケーションコストが発生することはなく、それぞれがある程度の共通理解を持った状態で本格的なレビューに当たることができるのです。

正直なところ私自身、常にユーザーファーストなものづくりを心がけているつもりでも、個人で業務に没頭していると気づかないうちに事業主体の目線になってしまいそうなことがあります。 例えば実際のケースとしても、特売情報にアクセスしてもらうバナーをアプリ内に新しくデザイン・設置する、というような施策があったのですが、ふとした間に「バナーを置く」ということが目的になっていて、「なぜ、誰のために」それをするのかという本質的な視点がつい弱くなってしまっていたこともありました。 しかしチームやメンバーがそれを未然に防いでくれ、一人ではなかなか見えづらくなっていた気づきを多く与えてくれるのが、今回ご紹介したような取り組みであると、私はそう考えています。

おわりに

デザインとは、客観的な評価のもとに成り立つものだと思います。 自分がいくら価値がある、優れたデザインだと信じこんでいても、それを見る人や使う人にその意図が伝わっていなければ、残念ながらそれは何の意味も成しません。 私自身、自分のデザインに対しては「臆病であること」を意識するようにしています。

本当にそれがユーザーにとって分かりやすく、価値があるものになっているのか? そのように良い意味で自信を持つことなく、臆病になって考え続けることが大事だと考えているからです。

本エントリでご紹介した内容はそんな私が独自に始めた試みですが、勿論それ以外にも気軽に意見が言い合える環境はクックパッドに数多く備わっています。 デスクに向かって黙々と仕事をして、どこかで行き詰まったり、深く考えこんでしまっているデザイナーの方は、時に画面から距離を置いてカジュアルに意見をもらうところから始めてみるといいのではないでしょうか。

クックパッドでも引き続き、カジュアルに意見が言い合えるデザイナーを募集中です!

クックパッド株式会社 採用情報


iOSアプリケーションでコードベースのレイアウトを積極利用する

$
0
0

 海外事業向けのiOSアプリケーション開発を担当している西山(@yuseinishiyama)です。クックパッドは現在、海外複数カ国に向けてサービスを展開しています。

 XcodeにはInterface Builderと呼ばれる、リッチなGUIを持ったデザインツールが付属しており、これを用いて画面のレイアウトを構成することが主流となっています。弊社ブログでも、iOS開発でstoryboardとxibをうまく使い分けるプラクティス等の記事で、GUIベースのレイアウトについて触れています。しかし、現在私が担当しているプロジェクトでは、Interface Builderを用いずに、レイアウトの大半をコードで記述しています。

 今回は、コードベースのレイアウトを実装していく中で得た知見を、以下の3つの部分に分けて共有したいと思います。

  • Interface Builderを用いたレイアウトとコードベースのレイアウトの比較
  • コードベースのレイアウトのデメリットへの対処
  • コードベースのレイアウトの実装

 ちなみに、当該プロジェクトがサポートしているiOSバージョンはiOS7以上です。そのため、iOS8以降でしかサポートされない機能については触れません。また、既存のObjective-CアプリケーションをSwiftで書き換えた話でも触れましたが、殆どのコードをSwiftで記述しているため、今回掲載するサンプルコードも全てSwiftとしました。

Interface Builderを用いたレイアウトとコードベースのレイアウトの比較

 本節ではInterface Builderを用いたレイアウトとコードベースのレイアウトとを比較し、それぞれのメリットについて説明します。

Interface Builderを利用したレイアウトのメリット

 Interface Builderを利用してレイアウトを組むことには以下のメリットがあります。

  • 画面遷移をグラフィカルに確認できる
  • レイアウトの結果がひと目で分かる
  • プログラミング経験の無い人であっても容易にパラメータを調整できる

本項では、それぞれについて説明します。

画面遷移をグラフィカルに確認できる

 Storyboard上には複数のViewControllerを配置することが可能です。そのため、複数の画面間の遷移をコードを読まずとも把握することができます。

f:id:yuseinishiyama:20151104150929p:plain

レイアウトの結果がひと目で分かる

 GUIを利用してレイアウトを行うため、実装しながら実行時のレイアウトを確認することができます。そのため、レイアウトを変更する度に、アプリケーションをBuild&Runして結果を確認する必要がありません。

f:id:yuseinishiyama:20151104150931p:plain

プログラミング経験の無い人であっても容易にパラメータを調整できる

 マージンを変更したり、背景色を変更したりする際に、ソースコードを変更する必要がありません。GUI上のパラメータを調整するだけでこれらの値を変更できるので、プログラミング経験の無いデザイナー、ディレクターの人でもデザインを変更することが可能です。

f:id:yuseinishiyama:20151104150933p:plain

コードベースのレイアウトのメリット

 一方で、コードベースのレイアウトには以下のメリットがあります。

  • ViewControllerを適切に初期化できる
  • コードの変更をレビューしやすい
  • 定数を用いて、一貫したデザインポリシーを適用できる
  • 要素の変更に柔軟に対応できる
  • 要素の設定に関するコードが分散しない

本項では、それぞれについて説明します。

ViewControllerを適切に初期化できる

 StoryboardからViewControllerを初期化して、そのViewControllerへと画面遷移するケースを考えてみましょう。

// Storyboardから初期化されるViewControllerclass DetailViewControllerFromStoryboard: UIViewController {
    var recipe: Recipe!

    // (省略)
}

class MasterViewController: UIViewController {
    // (省略)// Storyboardのインスタンス化let storyboard = UIStoryboard(name: "Main", bundle: nil)

    // StoryboardからViewControllerをインスタンス化let vc =
      storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewControllerFromStoryboard

    // 遷移先のプロパティを設定
    vc.recipe = Recipe()

    // 画面遷移
    navigationController?.pushViewController(vc, animated: true)

    // (省略)
}

 このコードには2つの問題点があります。1つは遷移先のViewControllerのプロパティrecipeImplicitlyUnwrappedOptional<Wrapped>として宣言されている点にあります。

 上記のコードにおいて、recipeは必須の(常に値が存在することが期待されている)プロパティです。しかし、StoryboardからViewControllerをインスタンス化する場合、インスタンス化の時点では、ViewControllerの持つプロパティを初期化することができません。そのため、必須のプロパティであっても、インスタンス化した後に代入する必要があります。

let vc: DetailViewControllerFromStoryboard=...
vc.recipe = Recipe()

 つまり、ここでは以下の2つを満たす必要があります。

  • recipeが一時的にnilになることを許容する
  • recipeが必須のプロパティであることを表現する

 そして、この両方を満たすには、プロパティをImplicitlyUnwrappedOptional<Wrapped>として宣言する必要があります。これは、IBOutletとして、Storyboard上の要素をコードと接続する際に自動生成されるコードに関しても同様です。

@IBOutlet weakvar label: UILabel!

 しかし、ImplicitlyUnwrappedOptional<Wrapped>を使う以上、潜在的にはクラッシュする可能性があります。例えば、recipeプロパティに値を代入せずに画面遷移してしまい、その後、このプロパティにアクセスすればクラッシュしてしまいます。

let vc: DetailViewControllerFromStoryboard=...// recipeプロパティを設定し忘れている
navigationController?.pushViewController(vc, animated: true)

 もう1つの問題点は、as!を用いた強制的なキャストが発生している点です。これは、instantiateViewControllerWithIdentifier(_:)の戻り値の型がUIViewControllerなので、具体的な型へとキャストする必要があるためです。もちろん、このコードも潜在的にはクラッシュの可能性があります。ViewControllerのIDの指定を間違った場合などが典型的なクラッシュの例となるでしょう。

// typo: Detail -> Deteillet vc =
  storyboard.instantiateViewControllerWithIdentifier("Deteil") as! DetailViewControllerFromStoryboard

 Swiftは静的型付けやnull許容性のコントロールによって、実行時のエラーを減らし、コードの誤りができるだけコンパイル時に検出されることを意図している言語です。一方で、Storyboardを用いたViewControllerのインスタンス化は「プログラマが」気をつけないとクラッシュする可能性があるという点で、Swiftの安全性を損ねていると言えます。このことは、Interface Builderが元々はObjective-Cという言語の動的な特性を最大限に活かしたツールであるということを考えると、当たり前かもしれません。

 一方で、Interface Builderを用いずに、コードだけでViewControllerを初期化する場合のコードは次のようになります。

class DetailViewControllerWithoutStoryboard: UIViewController {
    let recipe: Recipeinit(recipe: Recipe) {
        self.recipe = recipe
        super.init(nibName: nil, bundle: nil)
    }

    requiredinit?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class MasterViewController: UIViewController {
    // (省略)let recipe = Recipe()
    let vc = DetailViewControllerWithoutStoryboard(recipe: recipe)
    navigationController?.pushViewController(vc, animated: true)

    // (省略)
}

 通常のイニシャライザを利用できるので、プロパティrecipeが必須のプロパティでかつ再代入不可能であるということを強制することができています。

let recipe: Recipeinit(recipe: Recipe) {
    self.recipe = recipe
    super.init(nibName: nil, bundle: nil)
}

 ちなみに、init(nibName:bundle:)UIViewControllerの指定イニシャライザです。

コードの変更をレビューしやすい

 Storyboardは単なるXMLですが、その構造が非常に複雑であるため、レイアウトの変更があった際に、差分を見ただけでその意図を汲むことは容易ではありません。また、意図的に編集していない箇所が勝手に更新されることがある、という点も問題です。次のスクリーンショットは、ある制約の値をStoryboard上で12から10に変更した場合の差分です。

f:id:yuseinishiyama:20151104150925p:plain

 一方で、コードであれば変更箇所のチェックは容易です。次のスクリーンショットは、ある制約の値をコード上で12から10に変更した場合の差分です。「userNameLabelというラベルの末尾のスペースが12から10に変更された」ということが明らかに見てとれます。

f:id:yuseinishiyama:20151104150834p:plain

 ちなみに、コード上での制約の設定を簡潔に行うために、PureLayoutというライブラリを使用しています。このライブラリに関しては後述します。

定数を用いて一貫したデザインポリシーを適用できる

 Interface Builderを用いてレイアウトを構成する場合、レイアウト間で共通の値を設定することができません。しかし、コードベースのレイアウトであれば、定数を定義しておき、それを複数のレイアウト間で共有することが可能です。

struct Constant {
    struct Inset {
        staticlet S: CGFloat=8staticlet M: CGFloat=12staticlet L: CGFloat=18
    }
    struct CornerRadius {
        staticlet S: CGFloat=3staticlet L: CGFloat=5
    }

    // (省略)
}

 このように定数を定義しておけば、ポリシーが変更された場合もその定数の値を変更するだけで変更を全体に適用できます。例えば、「アプリ全体で余白感がもっと欲しい」となれば、この定数の値を大きくすれば良いでしょう。

要素の変更に柔軟に対応できる

 レイアウトを実装している途中で、View要素の型を変更したくなるケースがあるかもしれません。例えば、当初はUIButtonで実装していたものを、より複雑なレイアウトになっため、UIViewとサブビューで置き換える場合などがそれにあたります。Interface Builderでレイアウトを行っている場合、このようなケースでは、一度元の要素を削除してから、新たな要素を追加し、もう一度制約を設定し直すということが求められます。しかし、コードベースのレイアウトであれば、単純に型を変えるだけで対応できます。

要素の設定に関するコードが分散しない

 Interface Builderを用いれば、GUI上でView要素のプロパティ、例えば背景色やフォントなどを設定できることは既に説明しました。しかし、実際のアプリケーションでは、これらの値が動的になることも頻繁にあります。そのようなケースでは、コード上でView要素のプロパティを設定しなければならず、結果として、「あるプロパティはInterface Builderで設定され、あるプロパティはコードで設定されている」というような状況が発生します。こうなってくると、最終的な見た目がどこで決定するのか分からない状況が生まれてしまいます。特に、Interface Builderで設定した不必要な値がコードで上書きされていたりすると、混乱を招きます。

 コードベースでレイアウトしていれば、ビューに対して適用される全ての設定をコードから把握できます。ビューの設定に関するコードでViewControllerが汚染されることを嫌う傾向もありますが、Swiftではプロパティの初期化に式が利用できるので、以下のようにView要素の初期化に関するコードを一箇所にまとめれば、ViewControllerの可読性を下げることもありません。

 次のようにクロージャを利用してプロパティを初期化すれば、ありがちなviewDidLoad()の肥大化を防ぐことができます。

finalclass DetailViewController: UIViewController {
    // クロージャを利用して、プロパティを初期化// ラベルの設定が一箇所にまとまっているprivatevar descriptionLabel: UILabel= {
          let label = UILabel()
          label.translatesAutoresizingMaskIntoConstraints =false
          label.font =...
          label.textColor =...
          label.numberOfLines =...return label
    }()

    // (省略)
}

コードベースのレイアウトのデメリットへの対処

 前節で、Interface Builderを利用したレイアウトには次のようなメリットがあることを説明しました。

  • 画面遷移をグラフィカルに確認できる
  • レイアウトの結果がひと目で分かる
  • プログラミング経験の無い人であっても容易にパラメータを調整できる

 裏を返せば、コードベースのレイアウトには次のようなデメリットがあることが分かります。

  • 画面遷移をグラフィカルに確認できない
  • レイアウトの結果がひと目で分からない
  • プログラミング経験の無い人が容易にパラメータを調整できない

 本説では、これらのデメリットにどのように対処しているか、ということについて説明します。

画面遷移をグラフィカルに確認できないことへの対処

 ある程度の規模のプロジェクトになると、その画面遷移は大変複雑になります。同じ画面に、複数の箇所から遷移したり、画面遷移時にナビゲーションコントローラのスタックを操作したり、といった状況になることも珍しくありません。こうした、複雑な画面遷移になってくると、Storyboardを使ってグラフィカルに表現したとしても、あまり視認性が高くありません。かえって複雑になり、管理が難しくなる可能性さえあります。

 担当しているプロジェクトも、すでに画面遷移が複雑になりつつあります。また、かなり大規模になる可能性があるプロジェクトなので、Storyboardを利用した画面遷移の管理にはあまり期待していません。

レイアウトの結果がひと目でわからないことへの対処

 レイアウトの結果が実行するまで分からないことは、コードベースのレイアウトの最大の欠点と言わざるを得ません。ところで、レイアウトの結果が実行するまで分からない、という問題は以下2つの問題へと分解できます。

  • 特定の画面やアプリケーションの全体像が把握できない
  • レイアウトのデバッグが困難

 当該プロジェクトでは、Zeplinというツールを用いてレイアウトを管理しているので、前者に関しては問題になりません。Zeplinを確認すれば、レイアウトの全体像が確認できるからです。

 以下は、Zeplin上で表示されているレイアウトの例です。マージン、ラベルのフォント等のプロパティが表示されていることが分かります。基本的にレイアウトに関する作業は、これを正解の状態として、それを実現する作業になります(誤解を招かないために補足しておくと、これはデザインがトップダウンで決定されるという意味ではありません。当然エンジニアもデザインの段階から議論に参加しますが、最終的に決定されたレイアウトが、Zeplin上に展開されるという意味です)。

f:id:yuseinishiyama:20151104150936p:plain

 後者に関しては、確かに、コードを見ただけで最終的な結果を想像するのは容易ではないので、デバッグする際は何度もBuild&Runを繰り返すことになり、余計な時間を消費します。しかし、Xcode7+Swift2になってからコンパイル時間が大きく改善されたので、こうしたトライアンドエラーは現実的な時間内に行えるようになりました。

 また、AutoLayoutをラップしたサードパーティーライブラリを使用すれば、幾分か直感的にコードを記述することが可能なので、慣れてくれば、実行して確認しなくとも正しいレイアウトのコードを書くことができるようになります。

プログラミング経験の無い人が容易にパラメータを調整できないことへの対処

 プログラミング経験の無い人によるデザイン調整が可能である、ということは、デザイナーが直接レイアウトを修正するようなプロジェクトでは当然重要でしょう。GUIによるレイアウトを採用する理由で、最も良く耳にするのもこれかもしれません。

 一方で、私が担当しているプロジェクトでは、直接デザイナーがデザインを修正するようなケースはほとんどありません。なぜなら、デザインから実装までのフローが次のようになっているためです(デザインの変更がある場合も、基本的には同様のフローを繰り返します)。

  1. SketchInVision等のツールでプロトタイプを作成(デザイナー)
  2. エンジニアがプロトタイプを確認した上でデザイナーと議論し、デザインをFixする(デザイナー、エンジニア)
  3. Zeplin上でレイアウトを確認できるようにする(デザイナー)
  4. Zeplinを確認し実装する(エンジニア)

(注:カッコ内はその作業を担当する職種)

 また、デザイナーはデザインを行うと同時に、「デザイン設計」も行っています。ここでの「デザイン設計」とは、デザインの意図を元に、統一したデザインポリシーを定義することを意味します。例えば、「投稿に関連するアクションに紐付いたボタンの色」、「List形式のビューの親ビューに対するInset」といったように、コンテキストに対して決められた値が存在します。このことから、局所的なデザインの微調整が入ることはそれほど多くありません。

 こうした要因から、当該プロジェクトでは、プログラミング経験の無い人がデザインの修正を直接行えないことは問題になっていません。

コードベースのレイアウトの実装方法

 本節ではコードベースのレイアウトの実装方法について説明します。

サードパーティライブラリを使用してレイアウトのコードを簡潔にする

 標準の方法を用いて、コードでビューの制約を記述するのは簡単とは言えず、コードも冗長になりがちです。しかし、これを解消するためのサードパーティライブラリが存在しています。例えば、以下のライブラリが有名でしょう。

 私が担当しているプロジェクトでは、PureLayoutを採用しました。PureLayoutを利用すれば、制約に関するコードをかなり簡潔に記述することができます。

// imageViewの横幅に対する制約を設定し、それを変数に代入するvar imageWidthConstraint: NSLayoutConstraint
imageWidthConstraint = imageView.autoSetDimension(
  .Width, toSize: 120)


// labelを親ビューの中心に配置する
label.autoCenterInSuperview()

// 複数のビューをy軸方向に中央揃え、固定間隔で配置するlet views: NSArray= [view1, view2, view3]
views.autoDistributeViewsAlongAxis(
  .Vertical, alignedTo: .Vertical, withFixedSpacing: Constant.Inset.M)

レイアウトに関するプロパティやメソッド

 本項では、コードベースでのレイアウトを実現する上で、重要なプロパティやメソッドを紹介します。

translatesAutoresizingMaskIntoConstraints──AutoLayoutでAutoresizingMaskを表現する

 このプロパティがtrueになっていると、AutoresizingMaskと同様の挙動を実現するための制約が自動的に設定されます。もし、AutoLayoutベースのレイアウトを組むのであれば、このプロパティをfalseにする必要があります。

 Interface Builderを用いてViewを生成すれば、この値は自動的にfalseに設定されるのですが、コードで生成するとデフォルト値のtrueが設定されてしまします。そのため、コードだけでAutoLayoutベースのビューを生成する場合は、その都度、明示的にfalseを代入しなければなりません。

loadView──viewプロパティの初期化

 コードだけでViewControllerを実装する場合、ViewControllerのviewプロパティもコードで設定する必要があります。viewプロパティはloadView()内で設定する必要があります。以下のコードは、UITableViewのインスタンスを生成し、viewプロパティに設定しています。

finalclass ViewController : UIViewController {
    privatelet tableView: UITableView= {
        let tableView = UITableView()
        tableView.separatorStyle =.None
        return tableView
    }()

    overridefunc loadView() {
        view = tableView
    }

    // (省略)
}

 この時、スーパークラスのloadView()を呼んではいけないということに注意してください(参考 : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instm/UIViewController/loadView)

 もし、UIViewControllerの上にUITableViewを配置しているだけのStoryboardがあるような場合は、このように記述してファイル数を減らしたほうがプロジェクト全体の見通しが良くなるかもしれません。

viewDidLoad──viewプロパティの設定

viewDidLoad()ではviewプロパティが初期化されていることが保証されているので、viewに対する設定、例えばaddSubview(_:)などはここで行います。

finalclass ViewController: UIViewController {

    privatelet userAvatarImageView : UIImageView= {
        let imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints =false
        imageView.contentMode =...return imageView
    }()

    privatelet userNameLabel: UILabel= {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints =false
        label.font =...
        label.textColor =...return label
    }()

    privatelet descriptionTextView: UITextView= {
        let textView = UITextView()
        textView.translatesAutoresizingMaskIntoConstraints =false
        textView.placeholder =...
        textView.font =...
        textView.textColor =...return textView
    }()

    overridefunc loadView() {
        view = UIView()
    }

    overridefunc viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(userAvatarImageView)
        view.addSubview(userNameLabel)
        view.addSubview(descriptionTextView)
    }

    // (省略)
}

updateViewConstraints/updateConstraints──制約の設定

 制約の設定は、ViewControllerであればupdateViewConstraints()で、通常のViewであればupdateConstraints()の中で行います。制約が重複して追加されないように注意しましょう。

finalclass ViewController : UIViewController {
    privatevar didSetupConstraints =false// (省略)overridefunc updateViewConstraints() {
        func setupConstraints() {
            imageView.autoPinEdgeToSuperviewEdge(
              .Top, withInset: Constant.Inset.M)
            imageView.autoPinEdgeToSuperviewEdge(
              .Leading, withInset: Constant.Inset.M)
        }

        if !didSetupConstraints {
            setupConstraints()
            didSetupConstraints =true
        }

        // スーパークラスのメソッドの呼び忘れが無いように注意するsuper.updateViewConstraints()
    }

    // (省略)
}

requiresConstraintBasedLayout──AutoLayoutベースのViewであることを明示する

 あるViewがAutoLayoutベースのレイアウトなのかどうかは、そのViewに対して、その制約を変更するような処理が実行された時点で決まります。たとえば、制約が追加された時などです。これは、すべての制約をupdateConstraints()の内部で設定している場合、いつまでたってもupdateConstraints()が呼ばれず制約が設定されない、ということを意味します。このような自体を避けるために、AutoLayoutベースのUIViewのサブクラスでは、このクラスメソッドをオーバーライドしてtrueを返すようにしましょう。

overrideclassfunc requiresConstraintBasedLayout() -> Bool {
    returntrue
}

 上記の問題を解消するために、setNeedsUpdateConstraints()を呼んでいるコードも時々見かけますが、厳密にはrequiresConstraintBasedLayout()を使うことが正しい方法です。

 requiresConstraintBasedLayout()について触れている記事はあまり見たことがないので、無視されがちなメソッドなのかもしれませんが、その挙動を正しく理解しておく必要があります。The Mystery of the +requiresConstraintBasedLayoutは、このメソッドの挙動について検証しています。

viewDidLayoutSubviews/layoutSubviews──フレームに依存する処理の記述

 AutoLayoutベースのレイアウトであっても、最終的なフレームに依存するコードを書くケースもあり得ます。そのようなコードは、ViewControllerではviewDidLayoutSubviews()、通常のViewではlayoutSubviews()内に記述します。

overridefunc layoutSubviews() {
    super.layoutSubviews()

    // この時点でフレームが確定している// imageViewを丸くする
    imageView.layer.cornerRadius = bounds.size.height/2
    imageView.layer.masksToBounds =true// 最終的なラベルの幅にあわせて、preferredMaxLayoutWidthを設定する
    label.preferredMaxLayoutWidth = label.bounds.width
}

 このケースのように、レイアウトのどのフェーズで何が決定され、また決定されていないのか、ということを把握していないと期待する結果を得られないことがあります。コードベースのレイアウトを行う場合は尚更です。レイアウトのパスを正しく理解するにあたって、Advanced Auto Layout Toolboxが大変参考になります。

おわりに

 本記事では、コードベースのレイアウトの利点と、その実装方法について説明しました。

 私が直面しているケースでは、コードベースのレイアウトに軍配が上がったため、また、主張を明確にするためにコードベースのレイアウトを推奨するような内容となりました。しかし、実際はプロジェクトやチーム毎に適切な判断を行うべきでしょう。

 また、GUIベースのレイアウトとコードベースのレイアウトは相反するものではありません。実現したいレイアウトにあわせて、その都度、適切な方針を選択するべきです。その上で、コードベースのレイアウトを選択することになった場合に、今回の記事が少しでも皆様の役に立てば幸いです。

 最後になりましたが、海外展開はまだまだ初期のフェーズであり、やらなければならないことが無数に存在します。また、海外の文化、ユーザーのモバイル利用環境など様々な事柄を考慮しなければならないため、必然的に開発の難易度は高くなります。私たちは、こうした困難に積極的に立ち向かい、海外でクックパッドのサービスを展開することに協力してくれるモバイルエンジニアを積極募集中です!

弊社採用ページ(海外グループ iOS/Android アプリエンジニア)

Wantedly 募集ページ

複数のエンジニアと開発を円滑に進めるためのissueの立て方

$
0
0

こんにちは。クックパッド特売情報ディレクターの田中です。

前回ヘルスケア事業部の濱田くんのエントリーでエンジニア以外のGitHubの利用について紹介されていましたが、今回は私がチーム開発で実践しているissueの立て方についてご紹介したいと思います。

チームが大きくなってきてヒズミが生じてきた

本来、ディレクターが開発を伴わない価値検証を十分に行った上で仕様を考え、デザイナー・エンジニアに引き継ぐのが理想的だと思います。 私自身も当初はその開発の進め方を採用していましたが、チームが大きくなり、ディレクター1人で関わるエンジニアが増えてくると、状況は変わってきました。 マルチタスク的に仕様を考えていたために詰めが甘い部分が多く、手戻りが発生してしまったり、仕様の準備が追いつかず、エンジニアの手が空いてしまうことが増えてしまったのです。 当初は自分自身の頑張りが足りないからだと、徒に気合いと根性で対応していましたが、それでも本質的には問題が解決しませんでした。

施策の完成度80%でissueを書くことで周囲を巻き込む

そこで、自分自身で100%と思われるまで施策を考えるのを諦め、開発案件の骨子のみを簡潔にまとめた80%の段階でissueを早く立てるようにしました。 私の場合は下記の要件だけをまとめて立てることが多いです。 この時、よほど確定的な機能や案件以外では詳細な遷移図や画面イメージを作成することはありません。

# タイトル
施策の呼称
名前が仮の場合はその旨も記載する

## 概要
施策の背景と狙い

### ユーザーストーリー
ユーザーが直面している課題と施策による問題解決の内容
特売情報では下記のフォーマットを利用することが多いです

「『具体的なユーザー』は
『〇〇したい』(疑いの無い欲望)が
『□□で出来ない』(欲望の障壁となっている問題)ので
『△△することに価値がある』(施策による問題解決の具体的内容)」

### シナリオ
ユーザーが何をみて、何を感じ(考え)、どんな行動をとることで問題が解決されそうか

## どうなったら成功か
施策の効果検証方法

### 成功のイメージ
施策が成功した時、ユーザー・クライアント・サービス提供者がどんな状態になるか

### KPI
成功のイメージは何によって定量評価できるか

## その他
### 未決定の問題
仕様が詰まりきっていないと自覚している点、技術観点での懸念などをまとめる

### リリースフロー
リリースまでに解消する必要がある検討事項・承認事項をまとめる

一見、詳細が詰まっていないがためにエンジニアやデザイナーに負担をかけてしまう様に思いますが、早い段階でissueを立ててアウトプットした方が結果的に開発前に仕様をブラッシュアップすることができ、開発がスムーズに運ぶことが分かってきました。 その理由は下記3者のレビューを早い段階でもらうことができるからです。

エンジニア

ディレクターが詳細な仕様を詰めたものの、「これは莫大な時間がかかる!」「現実的じゃない!」「エンジニアは魔法使いじゃない!」と手戻りが発生することは少なくありません。 開発に着手するよりもかなり早くissueを立てることで、

  1. ボトルネックの洗い出し

  2. 現時点で着手している案件とのコンフリクトの回避

を行うことが可能です。特に1.に関しては、工数を膨らませる可能性のある要因を事前に把握できることで、仕様を最適化したり、技術的なアプローチに頼らなくてもディレクターが手を動かせば解決可能な箇所に先んじて手を入れることができるはずです。

デザイナー

弊社では施策のユーザーストーリーとシナリオを定義することが多いですが、これはデザイナーとのコミュニケーションを円滑化するのにも役立ちます。私は、手書きで手早くまとめることができ、かつデザイナーに意図を伝えやすいことから、シナリオをユーザー遷移図で表現することが多いです。

f:id:yojitanaka:20151105165255p:plain

これらの施策の要諦さえブレないように定義しておくことで、

  1. UX設計の妥当性の評価

  2. 表現方法のバリエーションの提示

を事前にアドバイスしてもらうことが可能です。これらを踏まえて仕様を練りなおしたり、画面イメージを構築することで、デザイン・開発にボールが移った時の手戻りを最小限に留められます。

第三者

早くissueを立てることで実際に開発に着手するまでの時間が長くなるため、多くの人のレビューを受けることができるようになります。施策が直接影響する部署からリスクを事前に洗い出してもらうことが出来たり、直接施策に関わりのないと思われた部署のエンジニアから、より良い技術的なアプローチを提案してもらうことが出来たこともありました。

f:id:yojitanaka:20151105165310p:plain

まとめと注意点

今回は、私が複数のエンジニアと開発を進める上で工夫しているissueの立て方をご紹介しました。 一つ注意していただきたいのは、早く要点をまとめたissueを立てることが重要なのであって、早く雑にissueを立てることを推奨しているのではありません。特に自分でも経験がありますが、骨子が明確になっていないと、当初自分が意図していたものから大きく議論が膨らんで収集が付かなくなってしまうことも少なくありません。 自分自身でもより良い方法を模索している最中ですが、同じような境遇にある方のお役に立てれば幸いです。

チーム全員でユーザー価値の向上に取り組むための開発プロセス設計

$
0
0

こんにちは、買物情報事業部の前田 (@TakatoshiMaeda) です。

今回は、クックパッド特売情報のサービス企画、開発を行っているチームがどのようなプロセスで日々ユーザー価値の向上に取り組んでいるのかお話します。

チームでは様々な取り組みを行っていますが、今回は

  • バックログ運用
    • 計画のもととなる、サービスで実現したいストーリーリスト
  • スプリント計画
    • バックログから実際の行動計画に落としこむまでのプロセス
  • ふりかえり
    • スプリント計画の実施結果を振り返る仕組み

の3つについてご紹介します。各取り組みのより詳細な内容についてはスクラムガイドをご覧ください。

バックログ運用

特売情報の開発チームはディレクター/デザイナー/エンジニアで構成されていますが、全てのメンバーがサービスをどのように良くしていくべきか考え、日々活発に議論しています。

日々の何気ない会話や、業務の中で得られた知見から生まれたアイデアをチームで集積する場としてバックログを活用しています。(実際にはPivotalTrackerで運用しています)

f:id:takatoshi-maeda:20151106170128p:plain

3つのステータスがありますが、Icebox -> Backlog -> Currentの順に開発するストーリーや機能が移動していきます。

  • Icebox
    • 次々回以降のスプリントで取り組みたい項目
    • チームメンバー全員編集可能
  • Backlog
    • 次回のスプリントで取り組む候補となる開発項目
    • Currentに入りきるかどうかは気にせず、次のスプリントで取り組みたい項目を全て列挙する
    • チームメンバーと相談しながら優先順に並べる
    • チームメンバー全員編集可能
  • Current
    • 現在のスプリント(後述)で取り組んでいる開発項目
    • スプリント計画MTG(後述)で作成、Fixする

チーム全員が考えている価値仮説を1つの場所に集約し、スプリント計画を立てる際の議論の中心とするのが狙いです。

また、次回のスプリントで取り組む開発項目を未確定事項が多い状態でもチームで共有し、未来に対しての認識をすり合わせる効果も感じています。

スプリント計画

さて、バックログを運用するだけでは前へは進めません。バックログを元に実際に計画をし、遂行するフェーズが必要です。

開発チームでは2週間を1単位としたスプリントを運用しています。

f:id:takatoshi-maeda:20151106170029p:plain

これから、ブログサービスを運営しているチームを仮想の例として、スプリント計画をチームで決定する為の「スプリント計画MTG」をどのように運用しているのかについてご紹介します。(例は実際のストーリー粒度とは大きく異なります。)

スプリント計画MTGは次の二部構成になっています。

  • 2週間で何を実現するのかを議論する第一部
  • 実現可能性を高めるために、実際のタスク計画に落としブラッシュアップしていく第二部

第一部

スプリント計画の第一部では、

  1. 今回のスプリントで実現したいことをディレクターから説明
  2. バックログの見直しと優先順位の再検討
  3. スプリントバックログの構築

の流れで進めていきます。

まず、1では今回のスプリントではどういった課題に取り組み、サービスをどのような状態にしたいのかをディレクター(オーナー)から話をしてもらいこれからの2週間をどのようにするかの目線をすり合わせます。

これから作成するスプリントバックログ*1の優先順位を検討していく為の判断軸をチームで作るためです。

f:id:takatoshi-maeda:20151106170813j:plain※未整理のバックログ

ここでは、「記事との新しい出会いをもっと作りたい」というオーナーのイメージをメンバーに共有しました。

判断軸の共有ができた後は、実際にスプリントバックログを構築していきます。

  • なぜ今その開発項目に取り組むのか
  • 解決したい課題は何か
  • 2週間の中で取り組める分量か

を議論し、取り組む意義を確認しながら開発項目を誰が推進して実現するかの決定(アサインの決定)を行い、2週間の計画を作成していきます。

f:id:takatoshi-maeda:20151106170704j:plain※整理済みのバックログ

2週間の中でチームとして何に取り組むのかが定まりました。

この段階では詳細なタスクについては未確定です。このまま作業に取り掛かると、作業の溢れや依存関係から予期せぬ事態に見舞われるリスクは高いでしょう。

この計画が本当に実現可能なのか、リスクをはらんでいないかをチームで把握をするために、スプリントバックログを用いて第二部に進んでいきます。

第二部

第二部では、第一部で作成したスプリントバックログを元に

  • 取り組む開発項目のタスク分解
  • メンバーの作業の依存関係の整理
  • マイルストーンの設定
  • 今決定できない事項の整理と把握
  • 不確実な事項の整理と把握

を定めます。

まずは、抽象度の高い開発項目を実際に各メンバーでタスクに分解していきます。

f:id:takatoshi-maeda:20151106170626j:plain

この段階ではやることは見えてきていますが、チームで開発している以上は相互の作業の依存関係が発生します。

「あれ、これ終わってないと次に進めない...」といったことがないように、タスクのマイルストーンを設定し作業の依存関係を整理していきます。

f:id:takatoshi-maeda:20151106170532j:plain

また、チーム外との連携作業も発生するため、まだ決定できない事項や、不確実な事項が発生します。

スプリント計画MTG前に出来るだけ整理をしておく努力はもちろんすべきですが、完全に取り除くことは不可能です。

そこで、不確実性が高い事柄は「いつまでに何を決めるか」をマイルストーンに組み込みます。

マイルストーンを作成していくと、明らかなオーバータスクや実現の可能性が低い事柄が明らかになってきます。

その場合にはメンバーで話し合い、スプリントバックログの見直しを行い、改めてタスク分解を行うことで無理のないよう計画を立てていきます。

ここまででスプリント計画MTGは終了です。

タスクマイルストーンが定まれば後は実際に開発を進めていくだけです。ここからは開発を進め、完成したものからリリースをし、検証して行きます。

実際に作業を進めていく中で想定外の事態が発生することは避けられないため、水金の段階でマイルストーンと照らしあわせてメンバーの進捗状況を確認しあい、必要であれば計画の見直しを都度都度行っています。

ふりかえり

2週間が立てばスプリントも終了です。

綺麗に作業が完了する清々しいスプリントもあれば、作業がなかなか進まず仕掛りが残ってしまうスプリントも発生します。*2

そこで、この2週間を振り返るために最終日に振り返りを行い、より良く作業をしより良いサービスを提供していくために次のスプリントにどう取り組むべきかを話し合います。

次回のスプリントに向けたバックログの見直し

ここでは、計画した内容で何が終わったのか、何が終わっていないのかをチーム全体で見直します。

見直しと同時に2週間の中で得られた知見から新たにIceboxに追加できる項目があれば議論をし、Iceboxや次週以降のバックログに新たなアイデアを反映していきながらバックログを整理しておきます。

次週のスプリント計画MTGをスムーズに行うためです。ただ、計画を立てるまでは行いません。

整理が終わったら、KPT*3の時間です。

感情カードを用いたKPT

発生した問題に対してどのように取り組むべきかをKPTで振り返りをしているのですが、一工夫して感情カードを用いたKPTを運用しています。

f:id:takatoshi-maeda:20151106170034p:plain

f:id:takatoshi-maeda:20151106170039p:plain

この2週間を振り返ってくださいと言われてもすぐには言葉は出てこないものです。

そこで、2週間どのような感情が沸き起こってきたのか、どのような思いで仕事をしていたのかを考えてもらい、カードを自由に選択してもらってチームメンバー全員で発表しあいます。

アイスブレイクとしてももちろんなのですが、メンバーが何を考えて日々仕事をしているのか、何が辛くて、何が楽しいのかを改めて深く知ることで相互理解を促進する役割も担っています。

この発言から、Keep/Problemを拾い出していき、より良く業務に取り組めるようチームで取り組むTryを決定していきます。

Tryが決まり、振り返りが終わるとスプリントは終了です!

次週からはまた新しいスプリントが始まります:-)

最後に

いかがでしたでしょうか。もちろん、この運用の精度を上げていくためにまだまだ日々悩んでいる最中ではあるのですが、現在の開発チームの取り組みについてご紹介しました。

今回ご紹介した取り組みは

  • より良いサービスを提供するために、より効果的なストーリーに取り組むにはどうするべきか

    • プロダクトバックログの品質を向上させていくためにはどうするべきか
  • より中長期的なビジョンをどう定義し、スプリント計画に反映していくのか

等、プロセスの外側にある重要な視点についてはカバーできていません。

あくまでチームがより良いユーザー価値のために立ち向かう姿勢を持ちやすくし、自律的に行動するための仕組みです。

限られた資源の中で、提供できる価値を最大化するためにどうするべきかは今まさに試行錯誤の真っ只中です。

またの機会に、取り組みについてご紹介できればと思います。

この事例が皆さんの課題解決の一助となれば幸いです。

f:id:takatoshi-maeda:20151106170044p:plain

買物情報事業部では、共にユーザー価値を最大化してくれる仲間を積極募集中です!

Androidアプリケーションエンジニア

iOSアプリケーションエンジニア

Railsエンジニア

もし会社に興味がない方でも、より良い開発プロセスとは?より良いサービスを生み出していくためには?といった悩みをぜひ議論できればと思っていますので、お話できる方がいらっしゃいましたら@TakatoshiMaedaまでお気軽にご連絡いただけると嬉しいです。

*1:スプリント内で取り組む開発項目リスト

*2:褒められたことではないですが、僕も仕掛りを残してしまうことが多々あります....

*3:ここではKPTそのものについての説明は省略します

Swift2で作るコマンドラインツール

$
0
0

会員事業部の三木(@)です。

この記事では、業務改善のために開発者向けのツールをSwiftで開発してみたため、その知見についてお伝えしたいと思います。

なお、この記事はXcode7.1上でSwift2.1を使った開発を前提としています。

作ったもの

クックパッドiOSアプリでは開発の際に、新しい機能を実装したり、インターフェイスを改善したあとにiOSシミュレーターの動画を撮影しPull Requestに貼り付けています。

動画を撮影する際には、汎用的にスクリーンキャストを撮影する社内ツールを使っていたのですが、使いづらい面も多かったため、 簡単にiOSシミュレーターの操作をアニメーションgifとして記録したいという需要がありました。

そのため、空き時間を使って、簡単なユーティリティを実装しました。

f:id:gigi-net:20151109141251g:plain

なぜSwiftで作るのか

今回は、OS Xの開発用SDKであるCocoaを使い、直接ウィンドウの描画結果を取得したいという需要があったため、 一般的なコマンドラインツールによく使われているスクリプト言語ではなくSwiftを採用しました。

わざわざSwiftでコマンドラインツールを実装することには以下のような利点があります。

Swiftで実装する利点

OS XのネイティブAPIを直接扱える

最大の利点は、Cocoaの資産を簡単に利用できることです。

Cocoaをスクリプトから利用する方法として、他に、AppleScriptやJavaScript(JXA)、MacRubyなどによるbindingが存在しますが、 どれも言語として書きづらかったり、全ての機能を利用できないこともあります。

高速に動作する

スクリプトと異なり、ネイティブコードとして実行されるため高速で動作します。

バイナリを配布しやすい

バイナリ形式で配付することで、環境構築をせずともそのまま動作します

Objective-Cに比べて開発しやすい

SwiftにはObjective-Cと比べ、REPL環境やPlaygroundが利用でき、検証が容易です。

また記述量も減り、記述しやすい点も挙げられます。

Swiftで作らない方が良い場合

その一方で、採用しづらい理由として、以下のような点が挙げられます。

  • Mac以外では動作しない
  • SwiftやAPIの仕様が変更される可能性がある
  • 開発環境を整えづらい

これらの特徴を理解し、最適な場面でのみ採用することが肝要です。

Swiftで簡単なスクリプトを書く

早速、Swiftで簡単なスクリプトを記述してみましょう。

Swiftで書いたコードはswiftコマンドを利用して、スクリプト言語のように即座に実行することができます。

例えば、スクリプトからCocoaを呼び出し、ダイアログを表示させてみます。

#!/usr/bin/env swiftimport AppKit

let alert: NSAlert= NSAlert()
alert.messageText ="Do you like cooking?"
alert.addButtonWithTitle("Yes")
alert.addButtonWithTitle("No")
alert.alertStyle = NSAlertStyle.WarningAlertStyle
let response: Int= alert.runModal()
switch response {
  case NSAlertFirstButtonReturn:
    print("Yes")
  case NSAlertSecondButtonReturn:
    print("No")
  default:
    break
}

このような短い実装で、簡単にOSのUIを利用したスクリプトを記述することができます。

エントリーポイントの定義なども必要ありません。

他のスクリプト言語のようにshebangを利用することもでき、あたかもスクリプト言語のようにSwiftのコードを実行することができます。

$ chmod +x alert
$ ./alert

f:id:gigi-net:20151109140206p:plain

パッケージ管理を行う

簡単なスクリプトであれば上記のようにすぐに実装することができますが、ある程度複雑なスクリプトを実装しようとすると、外部ライブラリを利用したいケースが出てくるでしょう。

SwiftでもRubyでいうBundlerのような、依存関係を解決してくれるパッケージマネージャとしてCarthageが存在するので、今回はそれを利用します。

CarthageはSwiftで実装されたパッケージマネージャで、iOS/Macアプリ用のライブラリを管理することができます。

似たようなツールとしてCocoaPodsがよく知られていますが、CocoaPodsはアプリケーションへの組み込みを想定したツールであるため、今回のようなケースで利用することが難しいです。

その一方で、Carthageは設定方法がCocoaPodsより複雑である反面、Xcodeのプロジェクトファイルを用いてビルドや依存関係を解決してるので、シンプルでありコマンドラインツールの開発でも利用しやすいことが特徴です。

つい先日、Swift2.xに対応した最新バージョンがHomebrewにリリースされたため簡単に導入することができます。

コマンドラインオプションを実装する

SwiftではProcess.argumentsからコマンドライン引数を受け取ることができます。

let arguments = Process.arguments.suffixFrom(1)
print(arguments)

これは以下のように実行できます。

$ ./arguments I love beer
["I", "love", "beer"]

この状態では、単に文字列として受け取れるだけなので、コマンドラインオプションを自前で実装するには手間がかかります。

Swiftでは他の言語のようにオプションパーサーが標準では用意されていないため、OptionKitCommandantなどの外部のライブラリを利用する必要があります。

今回はCarthageを使い、OptionKitを導入してみます。

まずプロジェクトにCartfileを置き、依存関係を定義します。

github "nomothetis/OptionKit" ~> 1.0.0

その後、updateを実行することで、Carthage/Build/Mac以下にビルド済みのフレームワークが生成されます。

$ carthage update

OptionKitを用いると、以下のように簡単にコマンドラインオプションを実装することができます。

#!/usr/bin/env swift -FCarthage/Build/Macimport OptionKit
import CoreFoundation

let arguments = Array((Process.arguments[1..<Process.arguments.count]))

// Define optionslet frameRateOption = Option(trigger: OptionTrigger.Mixed("f", "fps"), numberOfParameters: 1, helpDescription: "Recording frames per second")
let outputPathOption = Option(trigger: OptionTrigger.Mixed("o", "outputPath"), numberOfParameters: 1, helpDescription: "Animation output path")
let helpOption = Option(trigger:.Mixed("h", "help"))

// Create Parserlet parser = OptionParser(definitions: [frameRateOption, outputPathOption]) 

// Parse optionsdo {
    let (options, _) = try parser.parse(arguments)

    if options[helpOption] !=nil {
        print(parser.helpStringForCommandName("option-parser"))
        exit(EXIT_FAILURE)
    }
    
    iflet frameRate: UInt= options[frameRateOption]?.flatMap({ UInt($0) }).first {
        print(frameRate)
    }
    
    iflet outputPath = options[outputPathOption]?.first {
        print(outputPath)
    }
} catch let OptionKitError.InvalidOption(description: description) {
    print(description)
    exit(EXIT_FAILURE)
}

ポイントとして、1行目のshebangを変更して、今フレームワークをインストールしたディレクトリをサーチパスとして追加しています。 これによって、実行時に自動的に依存しているフレームワークを読み込んで実行することができます。

OptionKitではSwift2から実装された例外が利用されており、オプションの取得に失敗すると例外が送出されます。

このスクリプトを実行し、-hコマンドを呼び出すと、以下のようにオプションの一覧が表示されます。

$ ./option-parser -h
$ usage: option-parser [-f|--fps] [-o|--outputPath] [-h|--help]

signalをフックする

最後にシェルからINTERRUPTTERMINATEのシグナルを取得する方法を見てみましょう。

今回はSwiftからCのライブラリであるsignalを直接呼び出しています。

signalには引数として関数ポインタを渡す必要がありますが、通常の関数の場合は、以下のようにCの関数ポインタのように扱うことができます。

import CoreFoundation
func callback(_: Int32) {
    print("process killed")
    exit(EXIT_SUCCESS)
}

signal(SIGINT, callback)
whiletrue {
}

クロージャを渡したい場合は少し複雑です。 ここではSwift2から導入された@conventionシンタックスを利用しています。

SwiftではCの関数ポインタの型は@conventionを用いて表されます。 signalの引数はvoid (*)(int)型であり、これをSwift2上では@convention(c) (Int32) -> Voidという型で表せます。

その後、Swiftのクロージャを上記で定義した型にキャストし、関数ポインタとして渡します。

import CoreFoundation

typealias SignalCallback = @convention(c) (Int32) -> Voidlet callback: @convention(block) (Int32) -> Void = { (Int32) -> Void in
    print("process killed")
    exit(EXIT_SUCCESS)
}

// Convert Objective-C block to C function pointerlet imp = imp_implementationWithBlock(unsafeBitCast(callback, AnyObject.self))
signal(SIGINT, unsafeBitCast(imp, SignalCallback.self))
whiletrue {
}

これにより、シェルからのCtrl + Cをフックして、独自の処理を実行することが可能になりました。

今回は動画の撮影終了を検知するのに利用しています。

バイナリとして配付する

あとはCocoaの知見があればコマンドラインツールを簡単に作成することができます。

このままスクリプトとして配布してしまうと、Xcodeを導入したり、開発環境を整えなければスクリプトを実行することができません。 そのため、今回はビルドしてバイナリとして配付してみましょう。

xcodebuildを利用してプロジェクトファイルからビルドする方法もありますが、今回は構造がシンプルなのでxcrunを使ってビルドしてみます。

$ xcrun -sdk macosx swiftc main.swift \-FCarthage/Build/Mac \-Xlinker-rpath-Xlinker"@executable_path/../Frameworks/"\-o simrec

外部のフレームワークは実行バイナリに含めることができないため、実行バイナリのパスから相対パスでフレームワークを読むような設定にします。

今回は、実装したフレームワークとバイナリの配布にはHomebrewを用いることを想定して、以下のようなディレクトリ構成でインストールされると仮定します。

├── Frameworks
│   └── OptionKit.framework
└── bin
    └── simrec

そのため、実行バイナリであるsimrecから見て../Frameworks/に配置された外部フレームワーク(今回はOptionKit)を読めるようにビルドしています。

まとめ

この記事ではSwiftを使ったコマンドラインツールの実装例をご紹介しました。

なかなか知見が少なく取っつきづらい部分も多いですが、お役に立ちましたら幸いです。

また、今回実装したものを公開していますので、ご興味のある方は参照してみてください。

giginet/SimRecorder

JavaScriptチャートライブラリを選ぶにあたって考えたこと

$
0
0

こんにちは、トレンド調査ラボの井上寛之(@inohiro)です。 クックパッドの検索ログを基にした法人向けデータサービス「たべみる」の開発を担当しています。

本稿では、現在開発を行っているスマートフォン向けウェブアプリケーション(Rails)で採用した、 JavaScriptチャートライブラリを選定するにあたって検討した観点について述べます。 また、実際に採用したライブラリと、その利用例を簡単に紹介します。

ウェブ上に無数にあるJavaScriptチャートライブラリから、最適なものを一つ選択するのは なかなか難しい作業ではないかと考えています。おそらく、これから記述する条件を満たすライブラリは数多く存在し、 今回私が選択したライブラリ以上に良いものがあるのではないかと思います。 「何を以って良いライブラリとするか」という議論もまた難しい話題です。 そのようなライブラリについては、はてブコメントでそっと教えていただけると幸いです。

描くチャートの種類

現在開発中のアプリケーションでは、以下の二種類のチャートを描く画面がありました。

  • ラインチャート
  • スパークラインチャート

ラインチャートは、いわゆる折れ線グラフです。

スパークラインチャート(スパークラインとも)は、ラインチャートの一種です。ただし、X軸やY軸が省略されており、 詳細な値はわかりません。時系列的な傾向を、ぱっと見て簡単に把握できることを目的としたチャートです。

スパークラインチャートは、以下のようにExcelなどの表計算アプリケーションで簡単に作成することができます。

linechart

ラインチャートを描くライブラリ

ラインチャートを描くライブラリを選定する際に検討した条件について述べます。

今回描くラインチャートでは、ビジネス上の要求として「Y軸(および値)を表示しない」という条件がありました。 具体的な数字は示さずに、その項目の時系列的な変化を視覚的に示すためにチャートを利用したいということです。

他の条件としては、チャートライブラリに限った話ではありませんが、ドキュメントやウェブ上の閲覧できる事例は多いほうが良いでしょう。 継続的にメンテナンスされていることも大切です。ソースコードが公開されていれば、おかしな挙動に対してPRを送ることもできそうです。

また、スマートフォン向けウェブアプリケーションであるため、レスポンシブであると嬉しいです。 さらにはRuby Gemとして公開されていると、Railsアプリケーションから使う場合に便利です。 これは、vendor/assets 以下にファイルを直接 コピーするのと比較して、ライブラリの最新バージョンに追従しやすくなりますし、 アプリケーション本体のソースコードもコンパクトに保てる利点があります。

少々話が脱線してしまいましたが、選定における条件をまとめると、以下のようになります。

  • ラインだけ表示したい
    • Y軸を非表示にするオプションがある
    • ツールチップを非表示にするオプションがある
  • 扱うのが簡単
  • オープンソース
  • ドキュメントが十分にある
  • 継続的にメンテナンスされている
  • 商用サイトに利用しても問題のないライセンス
  • レスポンシブ
  • Ruby Gemとして公開されている(導入・管理が簡単)

この条件で、今回選んだライブラリは「Chart.js」です。 ドキュメントはシンプルですが詳細であり、 とりあえずのものを作ってみるのも簡単でした。 レスポンシブオプションを有効にすることで、画面の横幅が変わった時に動的にグラフや背景の方眼が再描画され、 画面幅が異なるスマートフォンから使うウェブアプリケーションに便利です。

また、Ruby Gemsとして公開されている(chart-js-rails)ので、 Gemfileに一行追加するだけで、すぐに利用可能です。

イマイチな点としては、ラベルを等間隔に配置するようなオプションが無いことです。

Chart.jsによるラインチャートの描画例

以下に描画の例を示します。

linechart

  • Gemfile
gem 'chart-js-rails'
  • 画面
<canvas class="chart" />
  • データセットとグラフの作成(coffee script)
    • ラベル(X軸)を等間隔に表示したかったので、偶数番目を空文字列にしています(2行目)
#= require Chart

dataSet ={labels: _(labels).map((label, index)=>if index %2==0then''else label),datasets:[{strokeColor :"#dcdcdc",pointColor :"#dcdcdc",pointStrokeColor :"#ffffff",data: values[1]},{strokeColor :"#ff9933",pointColor :"#ff9933",pointStrokeColor :"#ffffff",data : values[0]}]}newChart($("#chart").get(0).getContext("2d")).Line(dataSet,{# -- global options --animation:falseresponsive:truescaleShowLabels:falseshowTooltips:false# -- line chart options --bezierCurve:falsedatasetFill:false})

他の候補

  • Google Chart Tools
    • Chart.js の方がレスポンシブ対応の面で上回ったので、今回は不採用
    • chartkikという使いやすそうなGemがあります
    • google_visualrを利用すると、render _chartメソッドを使って、JSを書かずにグラフが描けます
  • NVD3
    • d3.js はカスタマイズ性が高く難しいそうですが、NVD3を使うと比較的簡単に描けるようです
    • (この記事を書くことになってから知ったので、選定時は漏れていました)
  • morris.js
    • 簡単に使えそうです。社内ツールでも使われています
    • (こちらも後日になって教えてもらったため、選定時は漏れていました)

スパークラインチャートを描くライブラリ

スパークラインチャートを描くライブラリを選定するにあたって検討した内容は、実はあまりなく、 商用サイトで利用しても問題ないかという点だけでした。

今回の場合は既にjQueryを使っていたので、jQueryのプラグインとして使える「Peity」を利用しました。スパークライン以外にも、かわいいパイチャート、バーチャートを描くことができます。

似たライブラリとして、「jQuery Sparklines」があります。 GitHubリポジトリを見たところ、masterへの最新のコミットが2013年9月であり、またPull Requestがかなり放置されてしまっている状態から、メンテナンスされていないと判断しました。

PeityもRuby Gem(jquery-peity-rails)として公開されています。

イマイチな点としては、HTMLタグの中に直接データを書く必要があることです。data属性に書いておいて、いい感じに指定できるとなお良い気がします。

Peity によるスパークラインチャートの描画例

以下に描画の例を示します。

img

  • Gemfile
gem 'jquery-rails'# 必要に応じて
gem 'jquery-peity-rails'
  • 画面
<span class="sparkline">0.13,0.17,0.22,0.30,0.41,0.85</span>
  • ラインチャートを作る
#= require 'jquery'#= require 'jquery.peity'

option ={fill:"#ffd592"height:16max:nullmin:0stroke:"#ff9900"strokeWidth:2width:75}
$('span.sparkline').peity('line', option)

他の候補

まとめ

本稿ではスマートフォン向けウェブアプリケーションで利用する、 JavaScfiptチャートライブラリの選定において検討した観点について述べました。 細かい条件や要求は個々のプロジェクトによって異なりますが、選定上の観点は使いまわせると思います。

まとめると、今回は以下の観点で検討しました。

  • 適切なライセンス
  • 継続的にメンテナンスされている
  • オープンソースである
  • ドキュメントが十分にある
  • 扱うのが簡単(<=>カスタマイズ性が高い)
  • レスポンシブか
  • 他のライブラリとの兼ね合い
    • jQuery と使うのか、Angular.js と使うのか、等
  • Gem として使うことができる(導入が簡単か)
    • とくにRailsアプリケーションの場合
    • イマドキは webpack 等を使うのが良さそう

今回は検討しませんでしたが、他の観点としては、

  • サポートするチャートの種類
  • サポートするブラウザ、バージョン
  • 描画方式
    • DOM要素に対してイベントをバインドしたい場合は、SVGを選択する等
  • Bower や npm から使えるか

などが考えられます。

また、Wikipediaの英語版には「Comparison of JavaScript charting frameworks 」 というページがあり、チャートの種類ごとにサポート状況が一覧できます。ただし、比較的名が知られたものしかリストされていないかもしれません。

無数にあるJavaScriptチャートライブラリの全てを見て回り、条件のあうものの中から最適な一つを見つけるというのは、 あまり現実的ではありません。 読者の皆さんが次にチャートライブラリを選定する際に、本稿をご活用いただければ幸いです。

ディレクターがコードを書いてみた時の学び

$
0
0

こんにちは。ユーザーファースト推進室 ディレクターの林田です。

ユーザーファースト推進室では「企画やディレクション、実装・デザインなど、一気通貫して役割を担えることで、より良いサービス開発が可能になる」という考えの下、ディレクターでも企画・進行管理だけでなく、コードを書いたり、場合によってはデザインを行うことがあります。

今回は、一気通貫してプロジェクトを進めるスキルの習得を目的として、あるアイコンの部分公開をディレクションから実装まで一通り行いました。 その中で私が感じた「ディレクターがエンジニアと仕事を進める上で気をつけたいポイント(主に技術面)」についてご紹介したいと思います。

※ 尚、ここでは経験から得た学びの共有にフォーカスしたいと考えていますので、ABテストの手法や結果等については敢えて触れません。ご了承ください。

実装始める前後で考えたこと

実装開始前

これまでクックパッド上のコードをほとんど触ったことが無かった私は、

「アイコンの実装だけだし、そこまで大変じゃなさそう」

「ABテストの仕組みをアイコンが実装されている所に入れて、その上で2つのアイコンを出すように設定すれば大丈夫そう」

と感じていました。その上で、以下の4点:

  • アイコンの出し方や計測ポイントなどの仕様を決める
  • デザイン・実装
  • 他部署の施策との調整
  • レビューなどのやり取り

を含めて大体4日程度あれば出来そうだ、という予測を立てました。

実装開始後

クックパッドでアイコンが実装されている箇所を確認し、実装に着手しました。 しかし実装を進める内に、

  • アイコンの実装箇所
  • 部分公開の導入方法
  • 出し分けの仕組み
  • アイコンのデザイン
  • クリック率の計測方法
  • 他部署の施策との調整(技術面)

など、実装前に考えていたこと以外に、いくつもの考えるべきポイントが残っていたことが明らかになり、結果として、アイコンの差し替えを行うために10日もの期間を要することになってしまいました。

実装で感じたギャップと技術の 'ブラックボックス感'

結果として予想よりも多くの時間がかかってしまったことの主な原因として、実装をする前後で考える事に大きなギャップがある事だと考えられます。

エンジニアに実装をお願いする際「技術的なことはわからないから・・・」と、技術をブラックボックスのように感じているディレクターの方も多いのではないでしょうか? この "ブラックボックス感 "が、今回実装する前後で考えた事の "ギャップ"だと感じています。

このような "ギャップ"をできるだけ解消することが、ディレクターとエンジニアがよりスムーズに仕事を進める上で重要なことではないか、と考えています。

f:id:KoichiHayashida:20151111192846p:plain

引用元: ⒸJeriff Cheng (http://free-images.gatag.net/2013/02/03/200000.html)

気持よくサービス開発を進めるために技術を知る

これらを踏まえ、エンジニアとプロジェクトを進める際に、ディレクターとして今後気をつけたいと感じたことを以下に記します。

「技術に関する知識」を身につける

このような経験を踏まえ、ディレクターでも実装工程についてエンジニアと話ができる程度の「技術に関する知識」を身につける必要があると考えています。

開発言語に関する基礎知識を身につけたり、このボタンを押したらどのような条件で処理が進むのか、などプロダクトが動く仕組みを理解することで、エンジニアが進める業務がより具体的に想像できるようになります。

また、これらの知識を身につけることで、決めた仕様に抜け漏れがないか、予想外の挙動になった場合でもそれがバグなのか、それとも仕様なのか、適切に判断することが可能となります。

私の場合は入社後にRails Tutorialなど、いくつかドキュメントを読んで簡単なwebアプリケーションを作ることで、ある程度知識を身につけました。 先日「総合職で入社した新卒がクックパッドでエンジニアになるまで」でも少し触れられていましたが、知識を身につけるのに役立つ書籍やドキュメントは多くありますので、ご覧ください。

実装にかける日数は、必ずエンジニアと相談して決める

それでももちろん、日常的にコードを書いているエンジニアとは知識の広さも深さも叶いません。プロダクトの中にどのようなコードが書かれているのか、最もよく知っているのはエンジニアです。 ある程度の工数を予想したら、エンジニアと無理のない日程が組まれているのか適宜確認することで、実現可能なプロジェクト進行が可能になると考えています。 (リリース日が先に決まってしまう場合も多いかと思いますが、その場合でも「どれくらい無理をしないといけないのか」を互いに認識しておくことは重要なことです)

これらのポイントに気をつけることで、エンジニアとディレクター間での認識のズレを極力小さくして、より良いサービス開発に取り組めると考えています。

まとめ

今回は、ディレクターとエンジニアがプロジェクトを進める際に気をつけたいポイントについてご紹介しました。 もちろん、今回書いたことはあくまで技術面のみに焦点を絞ったものでありますが、ディレクターが技術について理解していることは、エンジニアと近い目線で話すためにも必要なことだと感じています。 ディレクターの技術との付き合い方については、まだまだ模索している最中ではありますが、少しでもお役に立てれば幸いです。

モニタリングのためにLibratoを導入しようとしてどのように失敗したか

$
0
0

こんにちは、インフラストラクチャー部の菅原(@sgwr_dts)です。

インフラストラクチャー部は基本的にクックパッドのインフラに関わる業務を行っていますが、関連会社やグループ会社のインフラまわりについても作業を行ったりお手伝いしたりします。今回、グループ会社である「みんなのウェディング」のAWS化に伴ってそのお手伝いをさせていただいたので、そのときのモニタリングシステムの構築についての失敗談をお話ししたいと思います。

みんなのウェディングのAWS移行

みんなのウェディングは2015年4月にクックパッドグループに加わった結婚式場の口コミサイトです。いままでみんなのウェディングはVPSのホスティングサービスで動いていたのですが、グループ会社化に伴って大規模なリニューアルを進めており、その一環としてAWSへの移行を行いました。

AWSへの移行作業では様々な要素を検討する必要があります。パフォーマンス、セキュリティ、コスト、可用性、データマイグレーション……etc。それらのひとつにモニタリングシステムの構築があります。サービスはダウンしていないか? 一部のサーバに過剰な負荷がかかっていないか? リソースに十分な余裕はあるか? 等々、サービスの健康状態のモニタリングはとても重要です。

今回、AWS上のモニタリングシステムを構築するに当たって、私はLibratoというクラウドサービスを利用してモニタリングシステムを構築しようとしました。

Libratoはメトリクスの可視化・モニタリングを行ってくれるモニタリングクラウドサービスです。システムから送信したメトリクスをグラフにして、設定した閾値に従ってアラートを上げることができます。競合するサービスとしてはDatadogNew Relicなどがあげられます。

このLibratoを使って運用の手間を少なくしてスマートなモニタリングシステムを構築しようとしたのですが……結論から言うと失敗しました。

クラウドサービスでのモニタリングシステム構築

既存のモニタリングシステムはいろいろありますが、MuninやNagiosのようにメトリクスの可視化と監視を別々に行うものもあれば、Zabbixのようにそれらを1つのツールで行うものもあります。また、EC2の上に自前でモニタリングシステムを構築せず、クラウドサービスを利用する方法もあります。 今回は以下の様な観点から選定対象を絞り込みました。

  • インフラエンジニアが少ないのでなるべく運用に手間をかけたくない
  • メトリクスの可視化と監視は同時に行いたい
  • GUIでの操作がわかりやすい、手間がかからない
  • APIでプログラマブルに操作が可能
  • グラフがきれいで見やすい
  • 費用はなるべく抑えたい

最終的に、モニタリングクラウドサービスであるDatadogとLibratoを比較検討しました。

  • 機能面ではDatadogの方がかなり優れていました。Libratoはグラフ化・ダッシュボード・アラート、と基本的な機能のみ。グラフの種類もDatadogほど豊富ではありません。とはいえ、モニタリングシステムとしては(1つの点をのぞいて)必要十分な機能がそろっていたと思います
  • 価格面ではLibratoほうが安くしやすい課金体系でした。Datadogはホスト単位の課金、Libratoはメトリクス単位の課金です。大量のメトリクスを収集する場合、DataDogの方がコストパフォーマンスが良さそうなのですが、モニタリングに必要なメトリクスがかなり絞られていたため、サーバあたり$1〜$2/月とかなりコストを抑えることができそうでした。また将来的にサービス単位のメトリクスを収集・可視化することも考えており、その点でもLibratoの課金体系は都合の良いものでした
  • APIは、どちらもきちんとしたものが用意されています。プログラムを使った省力化はどちらでも行えそうでしたが、Libratoは低機能故にデータ構造がシンプルで後述のコード化を簡単に行えそうでした

主に価格の低さと以下の理由から今回はLibratoを採用してみることにしました。

fluentdを使ったメトリクスの収集

前述の機能比較で「(1つの点をのぞいて)必要十分」と但し書きを付けたのは、Libratoにはメトリクス収集用のエージェントが存在しないためです。普通に考えればその時点ですぐに検討の対象から外れるのですが、メトリクス収集エージェントについてはアイデアがありました。それはfluentd+fluent-plugin-dstatを使ってメトリクスを収集するというものです。

fluent-plugin-dstatはコマンドラインのリソース統計ツールdstatを使ってメトリクスを収集し、それをfluentdに流すプラグインです。これをメトリクス収集エージェントとして使おうと思ったのは次のような理由からです。

  • dstatで監視対象のメトリクスの大部分をまかなえる。また足りないメトリクスもプラグインで追加できる
  • fluentd(td-agent)は基本的にすべてのサーバにインストールされており、流したデータをいろいろなツールで柔軟に活用できそう
  • モニタリングシステムとコマンドラインの統計ツールを一元的に管理できる

3つ目の理由については、モニタリングシステムとは別にdstat・vmstatのようなツールで「その場」のメトリクスを調べることがそれなりにあり、特定のミドルウェア用にXXXstatをインストールしたり自作したりすることがあったので、それらを一元的に管理してdstatのプラグインに集約することで、監視の追加やstatツール作成の手間を軽減することをねらったものです。

Libratoのコード化

Libratoのようなクラウドサービスを使う場合、運用の手間は確かに減らせるのですが、反面、Web GUIからの作業—マウスポチポチ業が増えていきます。確かにちょっとした作業であればGUIは簡単でよいのですが、たくさんの変更を行う必要がある場合にはとても手間がかかります。また、人力の作業なのでオペレーションミスも起こしやすくなります。一般的にクラウドサービスはAPIを用意することでプログラマブルにオペレーションを行えるようにしています。しかし、普通のミドルウェアの「設定ファイルを変更して設定ファイルを再読込させる」といった作業に比べると若干面倒です。

これに対して私は「サービスの設定をDSLで定義して、設定ファイルの変更の様にオペレーションを行えるようにする」という方法をよくとります。以前の書いた記事でも、そのためのツールを紹介させていただきました。

LibratoのきれいなAPIとシンプルなデータ構造では同様のアプローチがとりやすく、そのためのツールであるlbrtを作成しました。このツールではグラフ・アラート・ダッシュボードをすべてRuby DSLで定義することができます。

たとえば、ダッシュボードを定義するRubyコードは以下のようになります。

space "My Space1"do
  chart "chart1"do
    type "stacked"
    stream do
      metric "login-delay"
      type "gauge"
      source "*"
      group_function "average"
      summary_function "average"endend

  chart "chart2"do
    type "line"
    stream do
      metric "login-delay2"
      type "gauge"
      source "*"
      group_function "breakout"
      summary_function "average"endendend

GUIで作成したダッシュボードなどをExportしてコピーアンドペーストで増やしていくこともできますし、DSLはRubyコードなので、AWSのAPIからサーバの情報を収集しそれに併せて自動的にグラフ・アラート・ダッシュボードを増やすことができます。

といった感じで、fluentdを使ったメトリクス収集を行うようにし、lbrtがある程度できた時点ではうまくいきそうだと考えていました。 このときは。

メトリクスが足りない!

dstatは様々なメトリクスを収集できますが、同梱されているプラグインですべてのメトリクスをすべて収集できるわけではありません。ApacheやSolrなどのいくつかのミドルウェアについてはプラグインを探すか作るかする必要があります。しかし、dstatのプラグインはHubサイトのようなものが存在せず、またコミュニティ製のプラグインを探してもほとんど見つかりません。そのため、大半のプラグインは自作する必要があります。しかし、Pythonに明るい人間がそれほどおらず、AWS移行のためのその他の作業もあって、サービスインまでにプラグインを全て自作するのは難しい状況でした。

いろいろ考えた末、この問題の解決策としてzabbix-agentを使ってメトリクスを収集することにしました。実はモニタリングをすべてクラウドサービスに委譲した例が今までなく、保険としてZabbixを使ったモニタリングシステムも一応は準備しておいたのです。dstatのプラグインは少ないですが、zabbix-agentのプラグインは山のようにあります。fluent-plugin-zabbix-agentというzabbix-agentのデータをfluentdに流すためのプラグインも作成し、とりあえず必要なメトリクス収集はできるようになりました。

しかし、このあたりから本末転倒感が漂ってきました。

グラフ・アラートが足りない!

当然のことながら、メトリクスが収集できれば終わりというわけではなく、それをグラフにしてダッシュボードに配置して適切なアラートを設定する必要があります。lbrtにはテンプレートの機能が備わっているので、OSやMySQL・Apacheなどミドルウェアごとにテンプレートを作成してやれば、半自動的にグラフ・ダッシュボード・アラートが設定されます。しかし、自作のツールであるlbrtに既存のテンプレートがあるわけがなく、すべてのテンプレートを1から書くことになります。書くべきテンプレートはOS、MySQL、Apache、Nginx、Solr、RDS、ElastiCache、ELB、Rails……

ここにいたって締め切りにはとても間に合わないと悟り、Libratoを使ったモニタリングシステムの構築はあきらめました……

結局……

最終的には、予備として準備しておいたZabbixをきちんと整えてサービスのモニタリングを行っています。Zabbixは広く使われているモニタリングツールなので、必要なテンプレートはすべて既存のものを利用することができました。AWSとの連携については、スクリプトを書いてcronや自動登録で連携できるようにしました。ダッシュボード(スクリーン)に関しては、ホスト毎のメトリクスを俯瞰的に見るダッシュボードを手動、またはAPIで作り込むことは大変だったため、Muninのようにホストごとのグラフ一覧を自動的に見られるようにするWebアプリケーションを自作しました

当初のもくろみは完全に失敗してしまいましたが、一応、十分に安定したモニタリングシステムを構築することはできました。

今回の件については、OS・ミドルウェア毎のメトリクス収集に必要な作業が完璧に抜け落ちており、作業量の見積もりが完全に間違ってしまったことが、失敗の大きな要因と考えています。それを踏まえた上で、Datadogや他のサービスについて利用事例をもっと詳しく調べられていれば、Libratoに無駄な工数をさくこともなかったかもしれません。Zabbixが保険として機能してくれたのは不幸中の幸いでした。

フォローしておくと、Librato自体はスケーラビリティがありコストに優れた良いクラウドサービスです。今回はLibratoがあまりカバーできていない範囲を無理矢理がんばろうとしたことが問題だったと思います。

同様のモニタリングシステムを検討・構築しているかたがいましたら、この記事が参考になれば幸いです。

インフラストラクチャー部では、意欲的なシステムの構築に挑戦したい仲間を積極募集中です!


部署横断な施策の考え方

$
0
0

検索・編成部の日高(@kaa)です。

クックパッドでは事業部制の組織になっており広告事業部、会員事業部など分かれることで 目標を明確にしそれぞれの数字に集中、スピードを上げる体制になっておりますが、私の 動き方としては各事業部の取り組みにくい部分、アプリ全体としての改善といった部署横断 的な仕事になっております。

そのため、通期での目標もありますが基本的には施策ごとに目標を定めて実行していきます。 特定の目標に対してどう取り組むかという動き方と違い、なにが必要になりそうか考えてそれを どのような結果を目指してやるかを考える必要があります。

何に対して、どう取り組むかも自由に考えると、やるべきに見えることはいくらでもあります。 (結果は出さないといけません) ある機能の使い勝手をよくするだったり、ある数字に貢献するだったり、環境を整えたり 事例を作ることで他部署が動きやすくするだったり。 また会社として大きな数字に対しては各事業部が取り組んでいるので、それぞれの施策について 関係者に相談しながら決定することが多くあります。

施策の基本思想

ベターはやらない、ベストに集中する

経営の話としてもよく耳にする言葉ではないでしょうか。

では、ベストな施策とはなんでしょう。 単純に考えるといくつかの案のなかで一番効果のありそうなもの、となりますが。。

それはあくまでもいくつかの案の中のベストと思われるもの、でしかありません。 しかし比較検討ではそれをやるべきか、違う施策を考えるべきか判断に迷います。

そんな中で、これを満たせないものはベストと呼べるはずがない、という自分なりの指標があります。

ベストなアイデアが満たすべき最低条件

以前広告業界にいた頃、いい企画とはなにかということをよく話していたのですが、 ひとつの絶対条件として納得できるものがありました。それは

「みんなをハッピーにできるかどうか」

広告は見る人を不幸にしてはいけないという基本思想から発展しています。みんなとは関わる人全て。

見る人が不幸なものはそもそも駄目だし、広告主が出稿する価値がなければだめだし、 作る人が不幸でも継続的に続けられません。

自社サービスでも共通する部分があり、ユーザーが不幸だとそもそも価値がないし、 他部署に悪影響がでて不幸でも困ります。継続的に仕事していくためにはみんな幸せな状態が理想です。

みんなの幸せを考える

人がなにを幸せに感じるかは人それぞれです。

広告であれば広告主の考える広告の目的を見誤るとまず次の仕事はありません。 販促なのか、ブランディングなのか、また別の何かなのか。

自社サービスでの施策でいうと、社内のだれかが不幸になる施策はベストではないということになります。 不幸な人がいる施策はどんなにある効果があっても継続性に問題を抱えてしまいます。

まず、その齟齬をなくすためには関係しそうな人の幸せを把握しておく必要があります。

みんなの幸せを把握する

事業部制だとわりとシンプルで、基本的には部署の目標の数字への影響です。 また影響は±0だと思われても時期によっては変動リスクにしかならないのであまり幸せなことではありません。 それなら別の、直接の目標ではないがポジティブな要素のほうがましと言えます。

クックパッドでは人事評価システムで各々の目標がある程度見られたりするので、事前に把握しておくようにしています。 また定期的にどの部署がどんな施策をやろうとしているか情報共有されます。 もちろん資料だけでわからないこともありますので、部署によりますがクックパッドではホワイトボードに付箋などを貼る手法を採用していることが多いので週1,2回、各部署の共有ボードを巡回もしています。

幸い現在のオフィスは1フロアですので、1回30分もあれば十分可能です。

関わる人の幸せを把握して考えるのは、やろうとする施策の可能性に関わりますし、微調整の予想もできます。 ダメな場合に次の提案の用意しやすさも変わりますし、相手の巻き込みやすさも変わります。

施策を詰める

関係部署がある場合はその施策でどのような影響がでるかを調整していく必要があります。 調整というと面倒なことに思えますが、「みんなを幸せにする」というスタンスに立つと調整タスクにはいいことしかありません。

もしそのアイデアが関係者にとって問題がありボツになるとしても、それはその相手を幸せにできないから、といえます。 取り組み方を見直すとしても、一番その施策に影響があり関わっている人の意見を取り込めます。

もし相談の結果効果が小さくなるようなことしかできなさそうであれば、アプローチを変えるかやめて違うことをやればいいのです。 効果が小さそうであれば、周りの時間を使っただけなのでそもそもダメなんですが。

そう考えると他部署との調整タスクはダメな企画がボツになるか、企画が磨かれるかの2択といえます。 つまり、いいことしかありません。

幸せを実感する・共有する

幸せとはなにか?実感するものです。実感できて初めて存在できます。

また幸せには様々な種類があります。 新しい幸せは言われないと気付かなかった幸せかもしれません。 その事業部にとって直接目標にしている数値ではないが、ポジティブな要素の数値だったり。 数値以外にもこれまで行っていないアプローチの施策の事例だったり。

常になんかしら相手にプラスの要素を与えることを意識しておかないと、ただ相手に時間を取らせただけになりますし、もしかしたらただの変動リスクになります。

まとめ

今回は、事業部制の企業での部署横断チームとしての施策の考え方について説明しました。 組織体制にはそれぞれ得手不得手があり、状況によるためなにが最適ということはありません。

事業部制のスピード感、目標への集中力を活かしつつ、こぼれやすい課題や時間がかかりがちな 新しいアプローチの検証・取り組みなどをフォローしていきたいと思います。

採用情報

GitベースのコードリーディングTips

$
0
0

こんにちは、投稿推進部の森川 (@morishin127) です。

エンジニアが既存のプロダクトの開発に携わる際、他人の書いたソースコードを読み解くところから始まります。過去に書かれたコードの意図を理解することは自分が書いたものでもしばしば難しく、他人が書いたものならなおさらです。この記事では過去に書かれたコードを理解するための工夫についてお話したいと思います。

なお、この記事ではプロダクトのソースコードはgitおよびGitHubのPull Requestを利用して開発が進められていることを前提としています。

特定の行から関連するPull Requestページを開く

クックパッドのソースコードには概してコメントがあまり書かれておらず、見ただけでは理解しづらいような特殊な方法をとっている場合のみコメントを書いている印象です。基本的に実装に関する説明はソースコード中ではなく、GitHubのPull Requestページに書かれていることが多いです。

そのためコードの意図を知りたい時にはその箇所の変更に関するPull Requestのページを見に行きますが、そのページを検索するのは少々面倒です。そこでコード中の特定の行からその変更に関わるPull Requestページに直接的に辿り着く方法をご紹介します。

特定のコミットハッシュからそのコミットを含むブランチのマージコミットを見つけるこちら(StackOverflow)の方法が社内でシェアされていました。マージコミットが分かればそのコミットログに含まれるPull Request番号からPull RequestページのURLを生成することができます。これをシェルスクリプトの関数にしたものが下記になります。(※一部Rubyが混入しています)

open-pull-request () {merge_commit=$(ruby -e 'print (File.readlines(ARGV[0]) & File.readlines(ARGV[1])).last'<(git rev-list --ancestry-path $1..master)<(git rev-list --first-parent $1..master))if git show $merge_commit|grep-q'pull request'thenpull_request_number=$(git log -1 --format=%B $merge_commit|sed -e 's/^.*#\([0-9]*\).*$/\1/'| head -1)url="$YOUR_REPO_URL/pull/${pull_request_number}"fi
    open $url}

$YOUR_REPO_URLの部分にはhttps://github.com/<owner>/<repo>を設定してください。

(2015-11-17 17:05 追記)
こちらのツイートで言われているように、hubコマンドをインストールしている方は$YOUR_REPO_URL`hub browse -u`に置き換えるのが良さそうです。

詳細を知りたい行でgit blameしてコミットハッシュを割り出せば、上記の関数を用いて該当のPull Requestページを開くことができます。

open-pull-request <commit hash>

Xcodeプラグイン

私は業務でXcodeを用いることが多く、上記のコマンドを実行するためにエディタからシェルに移りgit blameするのを煩わしく思ったためXcodeのプラグインにしました。Xcode上でショートカットキーを入力するだけで現在カーソルのある行に関連するPull Requestページを開くことができます。プラグインは新規に作成したのではなく近い機能を提供していたlarsxschneider/ShowInGitHubをForkしてその一機能として実装しています。

morishin/ShowInGitHub
https://github.com/morishin/ShowInGitHub

morishin/ShowInGitHubをcloneして手元のXcodeでビルドすると~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/ 以下にプラグインが配置されるのでXcodeを再起動すると有効になります。

最初に実装された時のコミットまで遡る

コードリーディングをする際にgit blameは強力です。blameすることで特定の行が変更された最新のコミットが割り出せ、そこからコミットメッセージやPull Requestの情報を辿ることができます。しかし最初の実装時の情報を知りたいのにその後にバグ修正などが行われ変更されていると、最初に実装された際のコミットが分からず困る場合があります。そこでtigというコマンドラインツールを用いるとblameを繰り返し簡単に過去に遡ることができます。

jonas/tig
https://github.com/jonas/tig

git blameと同じようにコマンドラインからtig blame <ファイル名>を叩くとコミット情報が表示されるので、さらにblameしたい行を選択して,キーを押すとその行が最後に変更されたコミットを起点として再度blameした結果が表示されます。これを繰り返すと簡単に目的のコミットを発見することができます。

古いコミットのコードを実行する時のgit-new-workdir

過去のコードを読むだけでなく、実際に実行して動作を確認したい場合は古いコミットをgit checkoutしてプログラムを実行します。昨今多くのプロダクトではパッケージマネージャを利用しており、checkoutの後にbundle installpod installといったパッケージのインストールを実行する必要が多いかと思います。そして確認を終えて最新のコミットに戻ってきた際にも再びインストールを実行する必要があり、長い時間パッケージのインストールで待たされることになります。

このような場合はgit-new-workdirというコマンドを使って新たな作業ディレクトリを作成するとディレクトリ毎に別々のコミットをcheckoutできるので、最新にcheckoutしたディレクトリはそのまま置いておいて別の作業ディレクトリで古いコミットの動作確認をすることができます。git-new-workdirgit-core/contrib/workdir/git-new-workdirに入っているコマンドです。ちなみにgit-new-workdirの存在はCTO氏から聞きました。使い方についてはこちらの記事が参考になります。

git-new-workdir が便利 - #生存戦略 、それは - subtech
http://subtech.g.hatena.ne.jp/secondlife/20121207/1354854068

(おまけ) あるビューがどのクラスのものかを特定する

こちらはGitには関係が無いのですがコードリーディングに関連するのでおまけとして記載しておきます。

既存のプロダクトをさわる際、自分が手を加えたいビューに関するコードがどのファイルに書かれているものかを探るのは意外に大変です。コードを読もうにもどこに書いてあるかがわからずファイルの探索に時間を取られてしまう場合があります。RailsアプリとiOS/Androidアプリのそれぞれで、実行時に表示されているビューからそれを司るファイルを特定する方法をご紹介します。

Railsアプリ

クックパッドではr7kamura/view_source_mapというgemを利用しています。これを利用するとレンダリングされたHTMLソース内に、どこがどのpartialのものかをコメントとして表示することができます。

r7kamura/view_source_map
https://github.com/r7kamura/view_source_map

iOS/Androidアプリ

Xcodeではアプリを実行中に"Debug View Hierarchy"ボタンをクリックすると次の画像のようにビューの階層構造が可視化されます。この画面で任意のビューをクリックするとそのビューのクラス名が表示されます。

Android Studioにも類似の機能があり、アプリの実行中にAndroid Device Monitorから"Dump View Hierarchy for UI Automator"ボタンをクリックすると実行画面が表示され、ビューをクリックすると右側のリストからそのクラス名が確認できます。

まとめ

この記事では既存のコードを読解するための工夫についてお話しました。皆さまの日々のコードリーディングの一助になれば幸いです。

アラートエスカレーションシステム"Waker"の紹介

$
0
0

インフラストラクチャー部の荒井(@ryot_a_rai)です。今回は社内で利用しているアラート通知システムであるWakerの紹介をします。

Wakerはアラートを受け付けて、指定されたユーザに電話などの手段でアラートを通知するためのアプリケーションです。

PagerDuty

アラートのエスカレーション・通知といえばPagerDutyが有名ですが、弊社では主にコスト面でPagerDutyを利用していません。日本国内への電話発信はGlobal Phone Alertsとしてカウントされ、通知数にもよりますが1ユーザ当たり$29〜49/月かかります。これを高いとするか、安いとするかはそれぞれかと思いますが、ユーザが増えてくると高くなるためユーザの追加がしづらく監視の委譲・分業がすすめづらいという点がありました。

また、弊社ではシンプルなエスカレーションと電話通知のみが必要されていた、という背景があり、Wakerを開発しました。

Waker

Wakerの主な機能は以下です:

  • インシデントの作成
    • メール受信やAPI経由でインシデントを作成できます
  • エスカレーション
    • 指定した時間、担当者から反応がない場合、次の担当者にエスカレーションすることが可能です
  • 通知
    • ユーザごとに通知先、通知するタイミングを設定できます
    • 通知先としてはTwilio(電話の発信)、Mailgun(メールの送信)、HipChatを選択できます
    • (ちなみに、Slackは現状サポートされてません。求むPR)

WakerはZabbixなどの監視システムからアラートを受け取り、事前に設定しておいたエスカレーションルールにしたがって、通知を行います。以下はWakerを使った通知システムの構成例です。通知はTwilioのみ、などの小さな構成でも利用できます。

f:id:ryotarai:20151118164833p:plain

エスカレーション

インシデントが作成されると、紐付いたTopicに設定されているEscalation Series(Escalationの集合)にしたがってエスカレーションされます。Escalationにはインシデント発生から何秒後にどのユーザにエスカレーションするかを指定することができます。エスカレーションされたユーザは事前に設定しておいたNotifierにしたがって通知を受け取ることができます。Notifierにはエスカレーション後何秒後に通知するか、どこに通知するかといった項目を設定できます。

また、エスカレーションとは別にNotifierを設定することもでき、これをつかってHipChatなどのチャットに通知することも可能です。

f:id:ryotarai:20151118164847p:plain

エスカレーションされた人は、電話のプッシュボタンやメールのリンクから"Acknowledge"することでアラートを受け取ったことを表明できエスカレーションを止めることができます。

f:id:ryotarai:20151118164923p:plain

エスカレーションルールの管理

エスカレーションの順番や担当者は日替わりや週替りでローテーションしていることも多いかと多いかと思いますが、Waker自体の設定でローテーションを設定することはできません。その代わり、Google Calendarのカレンダーを参照して、エスカレーション先のユーザを変更する機能が用意されています。

例えば、以下の予定が作成されていると17:00から19:00の間は荒井、星、成田の順にエスカレーションされ、19:00から21:00の間は星、成田、荒井の順にエスカレーションされるようになります。

f:id:ryotarai:20151118164949p:plain

弊社では予定の繰り返し機能を使って週ごとに担当者が変わるようにしています。

インストールと設定

WakerはふつうのRailsアプリで、MySQLとRedisがあれば動作します。インストール方法や設定はドキュメントをご覧ください(近日中にドキュメント内容拡充予定です)。

まとめ

以上、簡単にですがアラート通知システムWakerの紹介をしました。

Wakerは発展途上のアプリケーションです。機能リクエストやバグ報告、Pull Requestなどお待ちしております。

おまけ: Itamae Meetup

Wakerとは全く関係ないですが、OSSのサーバ構成管理ツールであるItamaeのMeetupが12/9(水)に開催されます。ぜひご参加ください!(Itamaeについての過去の記事はこちら

参加登録はこちらから!

レシピ投稿を通して「料理を楽しく!」を実現する

$
0
0

こんにちは。投稿推進部ディレクターの中山です。 私の所属している投稿推進部では、読んで字の如く「投稿を推進する」ことをミッションとしています。つまり、「ユーザーの皆様がより多くのレシピをクックパッドに投稿してくださるにはどうしたら良いか」を常に考え、実践していくのが仕事です。

レシピを投稿するのは何のため?

ユーザーの皆様は、なんのためにレシピを投稿するのでしょう。 逆に言えば、なぜ労力をかけてクックパッドにレシピを投稿してくださるのでしょう。

「つくれぽを貰って励みになる」「自己表現の一部」「自分のための覚書として便利」…等 理由は人それぞれあると思います。ただ、間違いなく言えるのは「料理が楽しくなる」からだと私たちは考えています。

目先の投稿より、課題の解決を

目先の目的として「とにかくレシピを投稿してもらう」ことを考えてしまえば、投稿数に応じてプレゼントを配ったり、ポイントで還元したりという施策が有効なのかもしれません。もちろんそれもユーザーさんにとって楽しい企画ですし、投稿の励みなるでしょう。
ただし、それだけではユーザーさんの「料理を楽しくしたい」という欲求の根本的な解にはなりません。楽しい企画も、その受け皿となるレシピ投稿そのものが便利で使い易いものになっていなければ、有効に機能しないと思うのです。

料理=毎日こなさなくてはならない作業ではなく
料理=楽しみにすること。
それがクックパッドのミッションであり、私たちはレシピの投稿を誰にでも簡単で楽しくできるものすることで、このミッションを実現しようとしています。

投稿のハードルを下げる

今まで一度もレシピを投稿したことのない人にとって、自分でレシピを書いて公開するという行為はちょっとハードルが高い印象があるかもしれません。 ログを取ってみると、実は【レシピを書く】のボタンを押して書き始めようとした人のうち、何も書かずに離脱してしまう方が少なからず居ることがわかり、特にiOSアプリではその傾向が高めに出ていました。
なぜ…?と考えたところ、iOSのアプリではレシピを書き始める際、下の図のような遷移でまず初めにタイトルを入力しなければ先に進めない仕様になっていたのです。このような仕様になった経緯は色々あるのでここでは割愛しますが、ここで「いきなりタイトルからは書けないよ…」と投稿を諦めてしまう方もいたかもしれません。

f:id:akoakon777:20151118165623p:plain

そこでレシピの作成画面を以下のように修正し、タイトルも他の項目と同じ1枚のページで編集できるようにすることで、どこからでも書き始められるようにしました。 さらに、レシピの重要な要素である写真も編集時は小さなサムネイル画像でしか確認できなかったのを、公開された状態と同じサイズで表示することにしました。

f:id:akoakon777:20151118165636p:plain

これにより、初めての方でも抵抗なくレシピを書き始めやすくなり、編集の段階で公開時のレシピの全体像をイメージできるようになったと思います。数字的な効果は現在計測中ですが、使ってくださっている方からは「いつもタイトルはあとから決めるので、便利になった。」「大きなサイズで写真を見ながら編集できるのは嬉しい!」などの感想をいただいています。

投稿を「楽」にする技術の活用

また、クックパッドには多数の優秀なエンジニアが在籍しており、その技術は日々進歩しています。この「技術力」をユーザーさんの「楽しい!」「便利!」に変換できるよう設計するのもディレクターの役割です。

以前こちらのエントリーで紹介されている、分量の単位をサジェストする機能もその一つです。 この機能はiOSのアプリで先に導入され、先日Androidのアプリにも実装されました!ぜひ皆さんご自分で使ってみて、レシピ投稿の楽しさを感じてください。

こうして頭の中にある料理のアイデアを手軽に書き進めて「レシピ」という形が出来上がると「やった〜!」という達成感が生まれます。さらにそのレシピにつくれぽが届いたりすれば「すごい!私のレシピが誰かの役に立っている!」というちょっとした感動を味わっていただけるのではないでしょうか。

最後に

上記はほんの一例です。 投稿推進部ではレシピの投稿をより便利にし、料理の楽しさを実現する機能を今後もどんどん実装していきたいと思います。
自らの料理のアイデアを「レシピ」として表現することで、料理の楽しさを感じられる方が1人でも増えること。そしてその「レシピ」が多くの方に活用され、世界中の食卓がもっともっと豊かになることを願っています。

「毎日の料理を楽しみにすることで、心からの笑顔が増える!」

これは私たちが掲げる永遠のテーマです。

スタッフもクックパッドの1ユーザー

$
0
0

こんにちは、会員事業部でプロのレシピを担当しています ツヤ です。

2015年10月にプロのレシピ(プレミアム会員プラン)をリリースしました。クックパッドプレミアム会員であれば(決済方法に条件あり) プロのレシピを月額100円(税別)で利用できるプランです。

開発中に発生した検証についてのお話です。

f:id:tsucook:20151120120613p:plain:w200

プレミアム会員であれば・・・

サービス内にはユーザーの状態による条件分岐が随所にあります。

ここでのユーザーの状態とは、クックパッドに登録済みかどうか。またどのような方法でログインしているか。プレミアム会員であれば、どの決済方法を選択してるユーザーなのか。ということにします。

プロのレシピ(プレミアム会員プラン)の「プレミアム会員であれば」という条件に対して、ユーザーの状態は クックパッドで提供している 決済方法の種類と同じだけ存在します。

※ クックパッドで提供している決済方法

  • クレジットカード決済
  • キャリア決済
  • コンビニ決済
  • iTunes Store決済
  • Yahoo!ウォレット

など

検証作業

ユーザーの状態により表示・処理が異なる場合、それぞれの状態でページ確認する必要があります。 また、確認するのは自分(エンジニア)やチームのメンバーだけで無く、 デザイナー、カスタマーサポート、法務など様々な部署のスタッフに確認してもらうこともあります。

f:id:tsucook:20151120120511p:plain:w600

上記はプレミアムサービスので利用中のお支払い方法を表示しています。 支払い方法に応じて適切なロゴが使用されているか検証しています。(もちろん feature specでもテストしてます)

デザイナーとしては、適切なロゴは使用されているか。法務としては利用規約へのリンクを確認したり、 サポートではユーザーからのお問い合わせに備え表示されている内容を確認しておくことが大切かと思います。

ユーザーの状態を作成するのはエンジニアであっても・・・

エンジニアであればDBを直接操作して、その状態を作ることは可能だと思いますが検証するのはエンジニアだけではありません。 また、たとえエンジニアでも状態が複雑な場合、仕様を把握する必要が発生し容易なことではなくなることもあります

エンジニアなら・・・

>状態が複雑な場合、仕様を把握する必要だったり容易なことではなくなります。

その状態を作成するスクリプトはあります。なので 困っていません。

エンジニア以外は・・・

困っています。

エンジニアに依頼しないと、その状態のユーザーを作成できません。その都度「(エンジニア)さん。○○○なユーザー作って下さい。」そんな依頼を受けます。

時々であればいいのですが、頻繁に、大量に・・・依頼されると エンジニアも困ります。

そこで

利用シーンですぐに作成できる

ページフッターにユーザー作成メニューを設置

このメニューから状態を選択することで、ユーザーを作成し、さらにそのユーザーでログイン済になり同じページが表示される機能です。

これでエンジニアでなくても、様々な状態のユーザーを作成し複雑な条件分岐の検証が容易にできるようになりました。

f:id:tsucook:20151120120518p:plain:w200

※ この機能は、開発環境でのみ動作します。

サービスを支えるスタッフも1ユーザー

開発・検証ツールは手を抜きがちだったり、開発優先度でも後回しにされがちです。 しかし、そこで削減されたコストであったり、品質検証は最終的にサービス利用者に返ることだと信じています。

クックパッドのサービスを利用するすべてのユーザー。サービスを支えるスタッフも1ユーザーとして大切にしていきたいものです。(自戒)

数値改善サイクルを取り入れて、学びをサービスに活かす仕組みづくり

$
0
0

こんにちは!ユーザーファースト推進室のデザイナー木村と申します。

今回はクックパッドニュース編集部で取り組み始めた、数値改善のお話をご紹介したいと思います。

すでに数値改善をバリバリやってるサービス開発者の方には退屈な内容かもしれませんが、普段あまり数値に触れる機会のないデザイナーをはじめとするサービス開発に携わっている方を想定読者として、おおまかな流れについてお話したいと思います。

クックパッドニュースとは

クックパッドニュースは「読むとお腹がすくニュース」をコンセプトに、クックパッド内外で流行っている食にまつわるトレンドをはじめとした食の情報発信しており、「冷凍卵」「ジャーサラダ」「おにぎらず」など多くのトレンドを生み出しているメディアサイトです。

「体験」や「ストーリー」を数値に落としこむ

わたしがはじめて編集部とMTGした際に持ち上がったのは「クックパッドニュースのスマートフォンサイトの体験が悪く、UIの改善もずっと行っていなかったので、相談させて欲しい」というものでした。

「体験」といっても、そこに含まれている情報量は膨大です。チームメンバーが考える「よい体験」も、「手触りのよさ」「わかりやすさ」だったり「きれいな写真」「読みやすいテキスト」だったりと人によってまちまちでした。

そこで、まずはチーム全体で「よい体験」「わるい体験」の指標となる数値を設定するところからはじめました。

KPIの再設定

クックパッドニュースでは、ここ1年ほど、サービス改善よりも「よい記事を書いてたくさんのユーザーに届ける」という記事執筆に注力していたため、KPI(Key Performance Indicator:重要業績指標)を「PV」「記事本数」に設定していました。

PVは指標の一つとしてよく利用されますが、当然ながら、そこからわかることは、「どれだけアクセスがあったか」ということだけに過ぎません。

例えば、ある記事が100万PVだったとしてその記事を最後まで読んだユーザーは何人いたのか?その記事をきっかけに、ユーザーはどんな行動をとったのか?(他の記事も読んだのか?友達に記事をシェアしたのか??次の日もクックパッドニュースにアクセスしたのか???)を知ることは、残念ながらPV単体ではわかりません。

そこで、クックパッドニュースのユーザーストーリーに紐付いたKPIを再設定することにしました。

まず、クックパッドニュースのユーザーストーリを時系列順に大まかに以下のように分け、そこに紐づく主な数値をリストアップします。

f:id:mura24:20151120190708p:plain

クックパッドニュースはメディアサイトですから、コアの体験は「記事を読むこと」です。コアの体験となる「記事を読むこと」にまつわる数値として「読了率」「回遊率」をKPIに採用しました。

これで追うべき指標が決定し、改善の成果を数値で判断できるようになりました。

施策を決める

指標が決まったので、次はサービス改善のための具体的な施策を決め、タスクに落とし込んでゆきます。

タスク管理は、進捗状況の確認に物理カンバン、タスクの内容詳細検討にGithubのissue(掲示板のスレッドのようなもの)を利用しています。

f:id:mura24:20151120190714p:plain

issueを立てる際は、最低限、目的、仮説(見るべき数値)、変更点やその意図などを記載し、リリース後にチームメンバーがいつでも確認できるようにしました。

施策の振り返り

施策をリリースしたあとは、ユーザーにどのように影響を与えたのか効果測定を行います。

ニュースチームでは、施策のやりっぱなしを防ぎ、振り返りをきちんと行うことを意識づけるために、物理カンバンの「DO」と「DONE」の間に、施策の効果検証をするフェーズとして「CHECK」を置いています。

効果測定は、サービスの規模や行った施策にもよりますが、だいたいリリース後の前後1週間を比較して検討します。

今回は、記事下にある関連記事枠のデザイン改修施策を例に説明します。

「記事のサムネイルサイズが小さく、どんな料理か判別しにくいためユーザーの目に止まらずスルーされているのでは?」という仮説のもと、サムネイルサイズと表示するタイトルの文字数・サイズを変更しました。

f:id:mura24:20151120190720p:plain

施策実施前後の数値の経過をまとめたものが以下です。

f:id:mura24:20151120190725p:plain

これから以下のことが分かります。

  • 関連記事枠のユーザー一人あたりのイベント数上昇
  • 回遊率は微増
  • 関連記事下のランキング枠の数値も上昇(ネガティブな影響なし)

次に、この結果を持って、考えられる仮説や次のアクションを決めるフェーズに移ります。本施策はポジティブな結果が出ましたが、数値が下落してしまった場合は、なにが原因だったのか、初期の仮設に誤りがあったのかなども検証します。

今回は以下の様なことが考えられると思います。

  • UIの最適化を引き続き行う
    • サムネイルサイズ
    • 記事文字数
    • 掲載記事数を5件から増やすor減らす
  • 関連記事枠の体裁をランキング部分など他のコンテンツにも適応させる

仮説検証のサイクルを回す

施策を行った結果、思わしくない結果になってしまうこと自体は問題ではありません。重要なのは、そこからどんな学びを得られたのか、今後サービスにどう活かせるか、ということではないでしょうか。

施策を終えたら、「次はどうするか」「もっといいやりかたはないか」を再度検討し、また次のタスクに着手し、仮説検証のサイクルを回していきます。

以上が、クックパッドニュースで始めた数値改善の取り組みの大まかな流れになります。

チームメンバーが共通のものさし(KPI)で会話をし、効果を確認・検証するサイクルを回し始めたことで、ほんの少しずつですが、数値にもよい影響が出はじめてきました。

おわりに -デザイナーが数値を見る意義-

f:id:mura24:20151120190730j:plain

数値による定量データは、サービスの成長やユーザーの満足度を測る方法のひとつです。

デザイナーが数値に基づいた仮説の上でサービスやUIをデザインすることで、「なんとなくこのほうがよさそう」という感覚的な部分を自身できちんと検討できたり、デザインを改善してゆく際の学びを多く得られるでしょう。

たとえばバナーひとつを例にとっても、どういったストーリーでどれくらいのユーザーがそのバナーを目にするのか、バナーがクリックされたとして、きちんと目的は果たせたのか(コンバージョンに至ったのか)、など数値から分かることはたくさんあります。

クリック率は高く、コンバージョンが低ければ、「バナーと遷移先のイメージに齟齬があったのかもしれないので、遷移先の内容をより強く反映させたデザインや文言を再度検討しよう」というアイデアや、「バナーで訴求していたメッセージが対象ユーザーからかけ離れているのではないか」といった仮説を検証することが可能になるでしょう。

私自身もデザインを提案する際「なぜそのデザインを改善する必要があるのか」「それがどれくらいのユーザーの助けになるのか」「改善した結果どんなよいことがあったか(悪いことがあったか)」といった判断や検証を自分で行えることで、より責任をもってデザインに臨めるようになりました。

よりよいサービスをユーザーに届けるために、日々のデザインに数値を活かしてみてはいかがでしょうか。

デザイナー募集中です!

クックパッドでは、どうしたらユーザーの毎日の料理が楽しくなるかを日々考えながらサービス開発を進めております。ユーザーのくらしを豊かで楽しいものにするデザイナーを募集しています!あなたのご応募をおまちしております。

Graylog ではじめるログ管理

$
0
0

こんにちは。インフラストラクチャー部 セキュリティグループの星 (@kani_b) です。 主に "セキュリティ"や "AWS"といったタグのつきそうなこと全般を担当しています。

Fluentd などのデータコレクタ、Kibana やその他 SaaS による可視化、Kafka, Kinesis, Spark などのストリーム処理といった様々な分野で「ログの処理」がホットですが、アプリケーションのログ (行動ログなど) に関する話題が多くを占めています。

そうしたログの他に重要なのが OS や各種ミドルウェアのシステムログです。これらはトラブルシューティングであったり、セキュリティ上の問題を見つけたり、といったことに使われますが、最低限 syslog でどこかに集約しているだけ、といった例をよく見かけます。 これらのログをきちんと検索可能にし、分析することで、今まで気づかなかったような問題をキャッチすることができるようになります。

ログ管理システムとしては、商用の Splunkなどが特に有名ですが、 今回は OSS のログ管理システムである Graylogについて紹介します。 Graylog は概ね以下のような機能を持っています。

  • ログの収集
  • ログの検索
  • ダッシュボードの作成
  • ログを基にしたアラートの発報

これらの機能をユーザやグループに応じて、権限を分割しながら利用することが可能です。 この記事では、執筆時点で最新安定版の Graylog 1.2.2 について説明します。

Graylog の構成とインストール

ドキュメントに記載されていますが、Graylog は以下のようなコンポーネントで構成されています。

  • graylog2-server
    • ログの受け口であり、API を通じて検索などのサービスを提供します
  • graylog2-web-interface
    • Web UI です。graylog2-server とは API 経由で通信します
  • Elasticsearch
    • ログの格納先として利用されています
  • MongoDB
    • 設定の格納や Dead Letter Queue として利用されています

それぞれ単体のサーバとして動作させても問題ないので、例えば graylog2-server, graylog2-web-interface を複数用意しての負荷分散や Elasticsearch のクラスタ化も可能です。MongoDB についても可能ですが、設定などのデータが格納されているだけですので、クックパッドで利用している環境においては非常に負荷が小さいです。

Elasticsearch ということで、Amazon Elasticsearch Serviceなどを使いたい!と思う方もいらっしゃると思いますが、graylog は master node に HTTP 経由でクエリするわけではなく、graylog2-server が対象の Elasticsearch cluster に join して各種操作を行うようになっているため、現在は利用できません。 適宜、自分で Elasticsearch 環境を用意する必要があります。

試しつつ良さそうであればそのまま運用したい、という方であれば、まず graylog2-server と MongoDB が一緒になったインスタンス + Elasticsearch インスタンス という構成にしておくと後々楽なのではと思います。

インストール方法はディストリビューションのパッケージや Docker image, VM image など色々と用意されていますので、環境に合わせて選択します。 詳細はドキュメントを参照いただくとして、具体的なインストール手順についてはここでは割愛します。

Graylog へのデータ入力

Graylog へデータを入力する方法はいくつかありますが、クックパッドでは現在 Syslog 入力と GELF 入力、そして CloudTrail input pluginを利用しています。 input の設定は、図のように設定画面から簡単に行うことができるようになっています。 f:id:kani_b:20151125184455p:plain

以下では、個々の入力について説明します。

Syslog 入力

Syslog 入力を有効にすると、Syslog メッセージをそのまま受け付けられるようになります。既に rsyslog などで転送設定をしているのであれば、転送先に追加するだけで入力できるようになり、お手軽です。UDP はもちろん TCP にも対応しています。syslog-ng から AMQP で投げたり Apache Kafka 経由でのデータ投入にも対応しているようですが、検証はしていません。 インスタンスへのログインログ、各種エラーメッセージなどのログはこちらで入力すると良いでしょう。

GELF 入力

GELF (Graylog Extended Log Format) は Graylog 独自のログフォーマットで、syslog と比較してデータ長の制限が大きい、データ型を持つ、圧縮など、アプリケーションのログを転送するのに適したフォーマットとして設計されています。 詳細は公式ドキュメントを参照ください。 gelf-rbなどライブラリも用意されているので、アプリケーションから直接投入することも可能です。 また、 fluent pluginを公開している方がおり、Fluentd 経由でデータを投入することもできるようになっています。 クックパッドでは、以前このブログでご紹介した OSSECという IDS のログや WAF のログなどを fluentd 経由で格納しています。

CloudTrail input plugin

Graylog は、入力や出力、ログのパースなどにプラグインが使えるようになっており、 そのうちの一つに CloudTrail input pluginがあります。 CloudTrail の SNS notification を使い、SNS 経由で SQS にメッセージを格納しておくと、このプラグインが対象の S3 オブジェクトを取得・パースして格納してくれます。

ストリームの作成

入力設定を行うとデータが流れ始めますが、そこにはシステムログや CloudTrail のログなど種類の違う様々なデータが流れるため、そのままだと目的のログを探したりダッシュボードを作るのが非常に難しくなります。 そのため、Graylog には、流れてきたログを特定の条件に基づいて振り分けることのできる機能 (Stream) があります。 例えば、syslog input を流すストリームに対して図のような設定をすると、syslog のうち sshd のログだけが流れるストリームなどを作ることができます。 f:id:kani_b:20151125184517p:plain

また、このルールを作成する画面でログのサンプリングを行うことも可能です。 f:id:kani_b:20151125184529p:plain

ダッシュボードの作成

Stream からグラフを作成してダッシュボードに貼り付けることができます。 作成したダッシュボードはこのように表示することができます。 f:id:kani_b:20151125184538p:plain

グラフの種類は Kibana ほどではありませんが、ドキュメントで紹介されているように、アプリケーションやセキュリティログを監視するためのダッシュボードなどを作成するには十分だと思っています。

アラートの発報

個々の Stream にはアラートを設定することができます。 ログに含まれる文字列に特定の文字列があった場合、特定のフィールドを持つログがある場合、などの条件をもとに、さらにx分以内にy回合致した場合はアラート、といった形で、ある程度高度な条件を作ることも可能です。 f:id:kani_b:20151125184551p:plain

また、アラートはメールで送信する他に任意のコールバック先を指定できます。プラグインをインストールする必要がありますが、 HipChat, Slack, PagerDutyなどが利用可能です。

権限管理

ここまでの機能を見てみると、特にログの検索やダッシュボードの作成については「それ Kibana でできるよ」という方も多いのではと思います。 しかし、 Graylog ではこれらの作成、表示に加えて、ユーザごとの権限管理ができるようになっています。つまり、特定のダッシュボードのみ参照できるユーザA、特定のストリームとダッシュボードのみ参照できるユーザB、といった形で権限を分割することが可能です。

また、Graylog のユーザ管理は LDAP/Active Directory と連携することができます。もともとユーザの管理のみ連携されていたのですが、先日発表された 1.2 からグループも利用することができるようになりました。

注意点

Graylog はあくまでログ管理のためのシステムであり、ログを保存するためのシステムではありません。ログの保全は別のものとして考慮し、例えば、テキストファイルとしてポリシ設定をした S3 にアップロードしておくなど、その仕組みを別途きちんと作っておく必要があります。

まとめ

OSS のログ管理システムである Graylog について簡単にご紹介しました。

ログ集約や検索、そして OSS といえば最近では Fluentd などを経由して Elasticsearch に投入し Kibana で可視化する、という構成がとても有名です。 用途や構成をきちんと限定すればとても便利ですが、例えば先述の権限管理であったり、細かな条件付けやパフォーマンスなどを考えていくと無理が生じてくると個人的には考えています。

Graylog のドキュメントに、Graylog の基本的なコンセプトなどについて書かれた章がありますので、興味のある方はご一読ください。

また、今回紹介しきれなかったものとして、以下のような機能も持っています。

  • ログコレクタ
  • Stream からの出力 (syslog -> Stream -> syslog など)

現在クックパッドでは主に IDS や CloudTrail のログ分析などのために利用されていますが、Graylog 含め、ログ管理についてはまだ色々と試しているところでもありますので、より良いノウハウを共有していければと考えています。

最後になりましたが、インフラストラクチャー部ではエンジニアを積極募集中ですので、ログに興味があってもなくても是非ご検討ください。


クックパッドの広告エンジニアは何をやっているのか

$
0
0

クックパッドの広告エンジニアは何をやっているのか

こんにちは、新規広告開発部の武田(@cnosuke)です。 私は、今年の7月より新規広告開発部に配属されました。

例えば花金などに、社外のWebエンジニアの人達とお酒を飲みに行ったり、ひさしぶりに大学時代の友人にあったりして 「最近はクックパッドの広告を担当している」というと、みんな「あぁアドテクね。ふ〜ん」みたいなリアクションをします。

興味があるのか無いのかわかりませんね。そんな時は、だいたい「あぁそうそう、アドテクアドテク〜」と返します。

確かに、私が所属している新規広告開発部は、いわゆる「アドテク」と呼ばれる領域のことは行ってはいますが、 そもそも「アドテク」という用語が完全にバズワード化していますので、具体的に「クックパッドの広告エンジニア」が何をやっているかをほとんどの方はご存じないのではないでしょうか。

そこで、今回はクックパッドの広告エンジニアが普段どういうことをやっているかを、ざっくりご紹介してみたいと思います。

広告もユーザさんに価値を届ける手段である

f:id:cnosuke:20151126161134j:plain

クックパッドの収益の4割は広告収入です。 広告の収益は基本的には「掲出量×単価」で決まります。つまり掲出量か単価、もしくはその両方を増やせば収益は増えますので、 単純に広告の枠をページ中に増やせばそれに応じて収益は増えると考えられます。 ではクックパッドはどうしているのか。収益のために、ただ闇雲に広告の枠を増やしているのかというと、もちろん違います。

クックパッドの広告は、昔から、ユーザさんと広告出稿企業さん、そして私たちクックパッドの3者ともが幸せになる形を模索し続けてきています。 クックパッドを通して、最終的には広告も「価値ある情報」としてユーザさんに届けば、それは広告単価の上昇にも繋がるからです。

クックパッドにはこちらの記事(サービスを通じて日々の嬉しい体験を増幅する)の中で触れられている、登場人物全員が幸せになるサービスを考えるためのEOGSというクックパッド謹製のサービス開発フレームワークが存在していて、社内で広く使われています。実は、このサービス開発フレームワークも、一番最初は広告企画を作るために使われたと聞いています。

昔から、クックパッドは広告について「ただ掲載しさえすればいい」という考えは持たず、あくまでもユーザさんに価値を届ける方法の一つとして考えてきました。 百聞は一件にしかず、具体的にはこちらのページをご覧いただければご理解いただけるかもしれません。

このような、食品系の会社さんと一緒にユーザさんにも価値がある形で広告を考える取り組みは、10年以上前からずっと続けてきています。

しかし、当然、同じ広告商品だけをずっと続けているわけではありません。 技術の進歩やビジネス環境の変化、ユーザさんの嗜好の多様化もあり、クックパッドの広告も日々進化しています。 私達エンジニアの出番ですね。

クックパッドの広告エンジニアは、おおきく分けると3つ

クックパッドの広告のエンジニアは、おおきくわけて3種類の仕事があります。

ざっくり次に説明していきます。

純広告まわりをみるエンジニア

f:id:cnosuke:20151126161117p:plain

部署的には「広告事業部」に所属するエンジニアです。

ナショナルクライアントと呼ばれる、大手の食品や生活用品のメーカーさんと弊社営業・広告企画とともに広告商品を考えて、掲出出来るようにします。

主なものとしては、前述のこちらのページに出ているようなものや、 例えばこんな感じ("豚"の検索結果)のカテゴリジャックと呼んでいるバナー広告類もあります。

カテゴリジャックは、ユーザさんにとっては、探しているレシピの文脈にあわせた広告だけが表示されるので、 広告自体が価値ある情報としてユーザさんに届き、今晩なにを作ろうか、といったメニューを考えるのに役立ちます。 もちろん、広告出稿企業さんとしては、メニューを考えて決めるタイミングのユーザさんに対して自然な形でブランディング出来るという価値があります。

このような広告商品が他にもいろいろあります。 媒体資料はこちらにあるので興味をお持ちの方は是非ごらんください。

エンジニアとしての主な仕事は、現在受け付けている広告の運用業務や、前述のカテゴリジャックのようなクックパッドらしさがあり、 ユーザさんにも価値を感じてもらえるような広告商品を新たに開発することです。

必要なものであれば新しいものもガンガン使いますし、なければ作ります。 一年前には、まだ他に例が少なかった動画広告のスマートフォンWeb上での自動再生を実現するために、 入稿されたファイルをDockerコンテナの中で専用フォーマットに変換する仕組みを作ったりもしました

分析まわりをみるエンジニア

f:id:cnosuke:20151126161125p:plain

部署的には「トレンド調査ラボ」に所属するエンジニアです。

クックパッドには、恐らく日本で最大の日常食に関するデータがあります。 毎日600万人ほどのユーザさんがレシピを探したり、ご飯のことを考えるためにクックパッドを訪れています。 訪れてくださるユーザさんの検索キーワードに現れてくるようなユーザさんの「たべたい」という情報を日々蓄えていって、正しく分析出来る基盤をつくるのが、この領域のエンジニアの仕事です。

具体的には、クックパッドの検索トレンドを分析できる「たべみる」などのサービスを開発しています。また、「じわじわ検索頻度が上昇しているキーワードの発見」のように、 食品メーカーさんに広告企画を提案するためのデータ分析も行っています。

技術的には、Amazon Redshiftを利用した巨大バッチの構築などが特徴的です。 こちらの記事(巨大なバッチを分割して構成する 〜SQLバッチフレームワークBricolage〜)に 詳しく書かれていますので是非ご覧下さい。

いわゆるアドテクっぽい部分のエンジニア

最後に、私が所属している「新規広告開発部」のエンジニアについて書きます。

弊社にはクックパッドそのものだけでは無く、他にもみんなのカフェクックパッド 料理動画といったサイトもあります。 こういったいわゆる本体以外のサイトに対しても統一的に広告を配信出来る基盤を整備したり、アドテクと呼ばれることの多い配信最適化といったことを、私の部署で行っています。

配信最適化のためには、前述のトレンド調査ラボと連携して、ユーザさんひとりひとりに合わせて広告が配信出来るような仕組みを考えたり、ユーザさんのクリックなどのログデータをほぼリアルタイムで活用にまわすことで、より成果が得られやすい広告の掲出比率が高まるようにしたりしています。

まだまだ不完全ではありますが、今後工夫を重ねることによって、広告もそれが必要な人だけに配信出来るようになっていけると考えています。 そもそも、ユーザさんの関心が全く無い広告が出ているということは、私達エンジニアが技術的に解決すべき問題をまだ解決できていない状況とも言えます。 先ずは私たち、担当エンジニア自身がもっとデータサイエンスに詳しくなることから初め、もっと無駄な広告が少なくなり、それぞれのユーザさんに対して適切な広告が「価値ある情報」として届くように試行錯誤を繰り返していきます。

まとめ

本記事ではあまり知られていなさそうなクックパッドの広告のエンジニアのやっていることについて、非常にざっくりですがご紹介しました。

ひとつひとつの領域がかなり広い割に、まだまだ少ない人数でやっています。もちろん、仲間を募集しています!

クックパッドのエンジニアの対外的な情報発信は、技術部のスーパーな話やインフラ部のハイパーな話、サービス開発エンジニアのウルトラな話が目立つと思いますが、 広告エンジニアもストロングだぜ!!!!ということを伝えたくて記事を書きました。伝わっていると嬉しいです。

たかがレシピサイトに何故こんな技術力が必要なのか

$
0
0

こんにちは!クックパッド編集室メディア開発グループ長の @yoshioriです。

たまにネットやイベントなどで「たかがレシピサイトになんでこんな技術力が必要なのか」と言われることがあるので今日はそれに真正面から答えてみようと思います。

例えばどういうところで技術使ってるか

他の人の話はこのブログの他のエントリを見てもらえればわかると思うので、僕の所属しているクックパッド編集室での取り組みの中から今回は料理動画を例に説明します。

Adaptive bitrate streaming での配信

クックパッドで配信している動画は基本的に「料理動画を支える技術」でも触れられている配信プラットフォームを利用しています。

ここでは裏で動画を「低画質」「普通」「高画質」の 3 パターンでエンコードして、回線状況に応じて最適な画質の動画を HTTP Live Streaming (HLS) で配信しています。

また TS ファイルは色々な試行錯誤の結果、 5 秒単位で作成しています。

これにより、ユーザさんは特に何も気にすること無く、最適な画質の動画が見えるようになっています。 なお、トップページでの自動再生時では回線コストを抑えるため、常に低画質の物を使っています。

この辺は動画配信をやってる人間にはあたりまえのことですが、そのためには、最適な技術を知っていて適切な設計が出来ないといけません。

自前で作った iOS プレイヤー

iOS 版クックパッドアプリでの動画視聴は当初は iOS 標準の動画プレイヤーである MPMoviePlayerControllerで実装しました。実際にリリースしてみると当初の想定よりも好評だったのでもっと使い勝手を良くするためにプレイヤーを自前で書き直しました。

例えば、iOS 標準実装のプレイヤーでは常に全画面で再生され、横向きにすると画面の向きも変わります。 しかし、自前実装のプレイヤーではまずインラインで再生されるようにしました。 そして、プレイヤー右下のボタンをタップすると、横向きの全画面へプレイヤーが切り替わります。

実際に料理をする時は iPhone をキッチンなどに置いて使う事が多いはずです。 iOS 標準のプレイヤーはスクリーン回転ロックを解除して iPhone を横に傾けると全画面になります。 iPhone をお使いの方は経験あると思いますがスクリーン回転ロックせずに置いておくと意図せずに縦横が切り替わることがあります。料理の時にそうなってしまうと使いにくいと思い、このプレイヤーでは標準の動きとは違いますが、あえて上記のような動きになるようにしています。

また、動画を見ながらでも手順を読めるように、スクロールさせても動画は固定されて見えるようにしました。 この辺も自前でプレイヤーを書いたので細かく調整出来るところです。

f:id:Yoshiori:20151127192410g:plain

pjax での自動再生

料理動画のページでは一覧から動画を選んだ時、遷移先で動画が自動再生されるようになっています。 これは PC・スマートフォンどちらのブラウザでも共通の動きです。 スマートフォンのブラウザには制約があり、touch などのユーザーイベントでしかメディアを再生することが出来ません。 https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html

つまり window.onloadでは動画再生をさせることが出来ないため、普通に実装すると自動再生は実現できません。 そこで料理動画では、動画の一覧からリンクをタップした時に pjax で画面を書き換えています。 pjax を利用すると、リンクをタップした時のイベントをそのまま利用できるため、そこで動画を再生しています。 これにより見た目ではリンクをタップしたら遷移先で動画が自動で再生されたように見えるようになっています。

もちろん、上記制約の意図はモバイル環境の考慮です。 そのため、むやみに回避すべきではありませんが、動画の一覧から動画をクリックした後は、動画の再生ボタンを押すことは自明です。 そのユーザーの 1 クリックを減らすことが出来ることはユーザーにとっても嬉しい事だろうと思い実装しました。

f:id:Yoshiori:20151127192427g:plain

強烈にアピールするのではなくユーザさんの使い勝手のための技術

さらっと料理動画を例に 3 つほどあげてみました。動画をメインの事業にしているわけでもないのにここまでやっているケースはなかなか無いのではないかなと思います。

別にこれをアピールしたいわけではなく、それぞれユーザーさんにとってメリットがあるからそれを実現するために技術を使っているということです。

技術力がなければ価値なんか届けられない

このブログを読むと色々な記事で「ユーザーさんに価値を届ける」という言葉が出てきます。 あくまでその為の技術であり手段でしかありません。ただし手段でしか無いという言葉は技術力がなくて良いという言い訳には全く繋がりません。

今回は料理動画を例にあげましたがそれ以外のサービスでも様々な技術が使われています。またサービスだけでなく裏側のインフラでも色々な技術が使われています。僕らはプライドを持って「たかがレシピサイト」が普通に気持ちよく使えるように作っていっています。

会社のブログで超個人的な意見を書くのもどうかと思いつつ書きますが、「技術力必要ない」とか言ってしまう人がいる環境ってどうなのでしょうか? 僕はその発言が出てくる事自体が技術を大切にしてない証拠にみえるしそんな環境で働くエンジニアは可哀そうだと思っています。 少なくとも弊社では技術を軽く見ている人は誰もいません。それは先程も書いたように「技術力がなければユーザーさんに価値なんか届けられない」と信じているからです。

ということでクックパッドでは一緒に「たかがレシピサイト」を作ってくれる人を募集しています

ちゃんと自信を持って「俺がこれを作ってる!」といえるサービスを一緒に作りませんか?

管理画面を開発する際に気をつけたこと

$
0
0

クックパッド編集室の加々美です。
現在、食や暮らしのトレンドを発信するメディアであるクックパッドニュースの開発に携わっています。
「総合職で入社した新卒がクックパッドでエンジニアになるまで」というエントリを投稿した2015新卒の土谷と同様に、2014年に新卒として入社後、総合職から研修を経てエンジニアへと転向しました。

今回は、クックパッドニュースの管理機能の改善を行う際に注意した点についてお話します。

自分がその管理ツールを使う人になる

事業体制の変化もあり、現状のクックパッドニュースの管理画面に関して、いくつかの運用上の問題点が指摘されており、その改善を行いました。

管理画面改善の進め方としては
「現状の業務フローの把握」「問題点の把握」「理想の管理画面の設計」
という基本的な手順で取り組みました。

現状把握と問題点洗い出しの方法としてまず思いつくのはヒアリング中心で進めていく方法ですが、あまり枠にはまらず柔軟な対応の多いルーチンワークだったり、各業務がスタッフ間で細分化されていると、ヒアリングだけで業務フローの全てを理解して問題点を整理するのは時間がかかり、消耗しがちです。

そこでオススメの方法は、自分自身が、管理画面を使用する人と全く同じ作業をすることです。※

単純ではありますが、現状把握のためには、この方法が最も正確かつ素早いと思いました。
また、画面が分かりづらかったり、遅かったりして苛立たしいという感情も味わえる上に、エンジニアとして一気通貫した業務を行うことで、現場のスタッフでは出てこないような問題点や改善点を発見することもできます。

現状の業務フローを定義できた時点で、ようやく適切な問題点の洗い出しができるようになり、用意すべき機能が見えてきます。

※具体的には、記事を発注されるライターの業務である、ニュース記事の企画・執筆、編集スタッフとの校正のやり取り。
そして、編集スタッフの業務である、編集者の朝会や編集会議への参加、校正業務や記事の配信設定の業務等を行いました。
記事執筆に関しては、総合職時代にライティングの業務を行っていたことがあったためここまで踏み込んで行うことができました。

現場の意見は再定義して、絵か動くもので改善策を説明する

現場の方から出てくる意見として、例えば
「ここにこのような数字を表示する窓を足して欲しい」「配信設定画面はエクセルのような使用感にしてほしい」
などというものがあります。

もちろん、意見を拾う段階ではこのような言葉にも耳を傾ける必要はありますが、字義通りに受け取るのではなく、「何を、なぜ達成したくて、何が原因でそれができないのか」という意見を相手から引き出して、エンジニア側で問題点と解決策を再定義する必要があります。
例えば「エクセルっぽい使用感」という言葉の裏に隠れている欲求は、実は配信時間枠を固定にせず自由に追加さえできれば達成されるものなのかもしれません。

また、改善後の画面や機能を言葉で説明するのはミスコミュニケーションを引き起こしがちなので、ある程度作りこんだペーパーモックや、似たような動きをするアプリを見せるほうがより正確に伝わり、作業の手戻りも減らせます。 動きの説明をしたり意見をもらいたいときは、切り絵でコミュニケーションをとるのもスムーズでした。

f:id:fkagami:20151127221706p:plain

管理画面もパフォーマンスに気を遣う

管理画面は、運用段階では基本的に非エンジニアが操作することとなり、多少読み込み遅くなってもそのまま放置され、報告されないということがままあります。
管理画面の速度は、運用メンバーの業務効率に直接響く部分であるので、パフォーマンスには十分気を遣いたいところです。

パフォーマンス改善施策の1つとして、N+1クエリ潰しがあります。
N+1クエリとは、あるデータと、その関連データを合わせて取得する際に発生しがちなクエリのことで、例えば、一覧画面に表示するためのデータを取得するために1度クエリが投げられ、そこで取得したデータN個分の関連データ取得のためのクエリが投げられてしまうようなケースを指します。

管理画面は、運用する中で参照したいデータを増やすことが多く、このN+1クエリが発生することがしばしばあります。N+1問題はデータが増えるほど深刻化するので、積極的に対策しておきたい問題です。

この対策には、N+1クエリが発生した時にアラートを出してくれるGemのbulletが便利です。

アラートが出たページではN+1クエリが発生しているので
Controller側で、関連データを予め読み込んでおく(Eager Loading)ようにしましょう。
N+1クエリが検出された時点で潰すようにしておく体制を作っておき、こまめに将来の負債を潰しておくことが大切です。

最後に、Railsに用意されているincludesメソッドなどの、Eager Loading機能ではカバーしきれない、よくありそうな大量のクエリが発生しまうケースに遭遇したので、サンプルコードと共に紹介したいと思います。

下のような、7日間×24時間のタイムライン上に時間枠がセットされており
その時間枠内に記事の配信設定する、というケースです。(分かりやすくするために、変数名や実装は簡略化しています)

f:id:fkagami:20151127221323p:plain

改善前

下の実装では、View上で、ある時間に設定されているArticleモデルを取得していますが
この実装だと7×24のクエリが発行されることとなります。

Controller

defindex@monday = Time.parse(params[:date]) rescueTime.now.beginning_of_week
    @articles = Articles.active
  end

View

- (0..6).each do |date|
  %table
    %thead
      %tr
        %th タイトル
    %tbody
      - (0..23).each do |hour|
        - target_time = monday + date.days + hour.hours

         # このループの中で時間を取得し、取得した時間内に設定をされている記事を取得する
        - target_articles = @articles.where(
            published_at: (target_time).at_beginning_of_hour..(target_time).at_end_of_hour
          )

        = render partial: "article_row", locals: {
                                                    target_articles: target_articles,
                                                    target_time: target_time,
                                                    hour: hour
                                                 }

改善後

コントローラー側に
{< Articleのpublished_at > =>< Articleのid >}
{< Articleのid > =>< Article Object >}
のハッシュを用意しておいて、View上で時間を取得して、その時間に配信設定されているArticleのidを取得、更にそのidのArticleオブジェクトを取得するという実装に変更します。

これにより、SQLに発行されるクエリは、24×7個から2個になります。

Controller

@monday = Time.parse(params[:date]) rescueTime.now.beginning_of_week

target_articles = Article.group("DATE_FORMAT(`published_at`, '%Y-%m-%d %H:%i')").
                    select("id","DATE_FORMAT(`published_at`, '%Y-%m-%d %H:%i') AS date").
                    where(published_at: @monday..(@monday + 1.week))

@date_id_hash = Hash[target_aricles.map{|a| [a.date, a.id]}]

# @id_obj_hash = Hash[Article.where(id: @date_id_hash.values).map{|a| [a.id, a] }]と同義@id_obj_hash = Article.where(id: @date_id_hash.values).index_by(&:id)

Helper

deftarget_articles(date_id_hash, id_obj_hash, target_time)
    # viewで取得した時間を元に、1時間の枠内に配信設定されているArticleのidを取得する
    target_ids = Hash[@date_id_hash.find_all {
                |k,v| Time.parse(k) >= target_time && Time.parse(k) < target_time + 1.hour
              }].values

    # 上で取得したArticle idからArticleオブジェクトを取得する
    target_articles = target_ids.map { |id| @id_obj_hash[id] }
    target_articles
  end

View

- (0..6).each do |date|
  %table
    %thead
      %tr
        %th
        %th タイトル
    %tbody
      - [*(0..23)].each do |hour|
        - target_time = monday + date.days + hour.hours

        = render partial: "article_row", locals: {
                                                    target_articles: target_articles(
                                                      @date_id_hash,
                                                      @id_obj_hash,
                                                      target_time
                                                    ),
                                                    target_time: target_time,
                                                    hour: hour
                                                 }

この変更で、読み込み時間は約80%改善しました。

まとめ

管理画面の開発においても、当事者意識を持つのが大切という点では、ユーザー向けのサービス開発と変わらないと言えるかもしれません。
管理画面はあくまでバックヤードなので、あまり時間をかけられない部分かも知れませんが、これから管理画面を作る方や、改善したい方の一助になれば幸いです。

ウェブ系企業において技術を学ぶことについて

$
0
0

技術部の牧本です。

最近は主に新卒社員研修や新卒採用選考プロセスの設計などに携わっています。

半年ほど前に書いた「クックパッドの新卒研修2015」という記事の中で、みんなが技術を理解することを大事にしたいという旨を述べました。

クックパッドでは、様々な役割の社員が技術を学ぶことを志すケースが多くあり、このブログでもいくつか事例を紹介しています。

本稿では、主にエンジニアではないメンバーが技術を学ぼうというときのモチベーションを分析し、それに対するクックパッドの取り組みについてお話しします。

1. ソフトウェア開発を体験したい

「プログラミングをしてみたい」という話をエンジニア以外の方からたびたび聞きます。 これは、同僚のエンジニアが普段何をしているのかという興味などから来るものが多いです。

この場合は、プログラムを書き、それが動くという体験が重視されます。 その目的にはウェブアプリケーションが適していると考えています。

私の場合、プログラミングを体験してもらう目的のために、簡単なハンズオンを作って公開しており、興味のある人にやってもらっています。 *1

https://github.com/makimoto/handson

これは Ruby on Rails を使って掲示板アプリを作り、それを Heroku にホストするという単純なものです。 とにかく動かすということにフォーカスしていて、技術的背景を省き、単純に書かれた指示通りに作るとものができあがるようになっています。

その他、プログラミング経験の少ない人向けのチュートリアル *2を試してもらうのも良いと考えます。

小さなアプリを作るという経験は、ソフトウェア開発はどういったものか、どういうところでつまずくのか (例えば、単純な typo で半日を棒に振ったことなど、みなさんも経験があると思います) というのを体験できるものだと考えています。

2. 技術の背景を理解したい

技術を学ぶ動機のもっとも大きい一つは、自分の日々の仕事や生活で使う仕組みを詳しく知りたいというものです。 それは、業務上必要に迫られての場合もありますし、興味本位の場合もあります。

この文脈で技術を学びたい人には、業務で関わる話をしたりします。

例えば、ユーザーがログインするという機能はウェブアプリケーションにおいて頻繁に登場しますが、これが実際何をしているかを大雑把に説明すると、以下のようになります。

「ユーザーはログインするために ID とパスワードをウェブサーバに送り、サーバは自身の持つユーザー情報と付き合わせて ID とパスワードの組み合わせが有効であることを確認し、ユーザー情報にもとづいたコンテンツと一緒にユーザーがログインしていることを示す情報を cookie としてブラウザに送り、ブラウザはその情報を保存する。」

ただ、これを口頭で説明するのはつらいので、私の場合はブラウザの開発者ツールで cookie やリクエスト・リスポンスを見てもらったりします。

これを理解していると、「ログインさせなくてもサーバーから任意の cookie を発行すれば、ユーザーを識別できる」「ウェブブラウザ (クライアント) が変わるとログインしているという情報は原則引き継げない」などといったことが導き出せるようになります。

このように、普段使っている技術の背景を理解するということで、業務の精度や速度向上に繋がり、また、チームのメンバー間が共通の言葉で議論することができる下地を作れると考えられます。

3. ツールを使いたい

技術を学ぶという場合の次の観点は、エンジニアが使うツールを使えるようになりたいというものです。

たとえば、クックパッドにおいて、GitHub Enterprise はエンジニア以外にも積極的に使われているツールです。

サービス開発のチームが仕様を検討したり、ユーザーサポートが各部門のエンジニアやマネージャとお問い合わせを受けた内容について議論するのに使われています。

GitHub にはレポジトリのファイルを直接編集して pull request を出す機能があります。 *3そのため、文言を修正したいなどの要求はエディタを開くことなく GitHub で完結できるので、エンジニア以外の人にも扱うことができます。

別の例として、サービスディレクターが仮説の検証のためにデータを分析したいという場合、SQL で直接データを引き出すというケースも多くあります。

クックパッドでは、リレーショナル DB へのアクセスには、phpMyAdmin や phpPgAdmin などの UI と、その利用方法が記されたドキュメントが整備されています。 そのため、ターミナル (いわゆる黒い画面) で操作することなく、データを引き出して適切なフォーマットでダウンロードすることができるようになっています。 *4

このように、各個人ができることの幅を増やすことで、誰かに作業を依頼することが少なくなり、効率的に業務を進めることが期待できます。

4. ソフトウェアエンジニアとして活躍できるようになりたい

最後に、ソフトウェアエンジニアの役割で活躍できるように技術を学ぶという例です。 クックパッドには入社後にソフトウェアエンジニアに転向した社員が何人かいます。

直近だと、2015年4月に新卒として入社した土谷が例として挙げられます。 彼は、入社後の集合研修のあとに技術部に配属され、エンジニアになるための教育プログラムを受けています。 彼がエンジニアリングを学ぶ過程を以下のエントリーで記述しています。

総合職で入社した新卒がクックパッドでエンジニアになるまで - クックパッド開発者ブログ

エンジニア以外の役割の人がエンジニアとして活躍できるようになる道筋は、今までクックパッド社内で人それぞれでした。 土谷の例は、技術教育を体系化する試みの一環でもあり、トレーニングを受ける土谷とトレーナーであるわれわれが一緒に、どのような道筋でエンジニアとして活躍できるようになるのかを模索しています。

おわりに

本稿では、技術を学ぶこととはどういうことなのか考察しました。

技術を活かすことは、エンジニアチームだけの課題ではなく、会社全体で取り組む課題だと考えており、今年度からクックパッドの新卒社員研修では、総合職の新入社員にも技術を学ぶ研修を実施しています。 *5

この記事が公開される日に、2017年のクックパッドの新卒採用の募集が開始されました。 一緒に学び、クックパッドと一緒に成長してくれる方の応募をお待ちしております。

さあ、一緒に次のステージへ | クックパッド株式会社 採用情報

*1:このハンズオンは、クックパッドの内定者やインターンシップ経験者などに対してオフィスの会議室を開放し、個人のプロジェクトを進めたり技術の勉強をする場を提供するエンジニア自習室という取り組みの中で生まれ、主に技術に明るくない人にウェブ開発を体験してもらうという動機で作られました。

*2:たとえば、 Rails Girlsのリソースなど

*3:https://help.github.com/articles/editing-files-in-your-repository/

*4:ここで、MySQL 用の phpMyAdmin と PostgreSQL 用の phpPgAdmin が両方用意されているのは、永続化データのほとんどは MySQL に、一部ログデータなどは PosgreSQL クライアントから利用する Amazon Redshift に保存されているという事情があるからです。

*5:クックパッドの新卒研修2015 - クックパッド開発者ブログでその一端を述べました。

iOS アプリの UI でこれだけはおさえたい、読み込み中の体験を向上させる基本 UI パターン3つ

$
0
0

ホリデー株式会社 *1の多田です。Holiday ( https://haveagood.holiday/ ) というサービスの開発を行っています。

アプリを通してユーザに価値を届けるためには、アプリの細部のインタラクションを軽視することはできません。細かい部分に気を配り使い心地を良くしてこそ、サービスで本当に実現したい価値をユーザにまっすぐ届けることができるためです。

iOS アプリの使い心地を良くするための基本的なインタラクションを以前当ブログで投稿した記事でいくつか紹介しましたが、今回は前回紹介しなかったインタラクションのうち、「読み込み中」の UI の基本パターンについて取り上げようとおもいます。

はじめに:なぜ読み込み中の UI を考えなくてはいけないのか

Holiday iOS アプリでは、基本的にデータはクライアント側で持たずサーバと通信して表示するデータを受け取っており、受け取ったデータを表示するまでにはどうしてもある程度時間がかかります。もちろん、根本的な待ち時間を短くするために、API を高速化することや、レスポンスの最適化、リクエストのタイミングの工夫などは必要不可欠です。ですが、どこまでそのような最適化を行っても、特にモバイルアプリの場合はネットワークが不安定な環境を避けることができないため、読み込み時間がある程度かかる場合のことを想定しなくてはいけません。

読み込み時間がかかる場合にできることは色々と考えられますが、凝ったことをせずとも適切な UI を用いることで読み込み中の体験を良くすることができます。そこで、簡単に実現できる読み込み中の基本 UI パターンを3つ、Holiday iOS アプリでの例とともにご紹介します。


おまけ:ネットワーク環境が悪い状態を開発中に再現するために

シミュレータ上や実機でネットワーク環境が悪い状態を再現するためには、Network Link Conditionerを使うと良いです。


アクティビティインジケータ

f:id:tdksk:20151201234102g:plain

最も基本的な読み込み中の UI は、UIKit で提供されているアクティビティインジケータです。アクティビティインジケータを表示することで、処理が停止せずに進んでいることを示すことができます。

アクティビティインジケータは画面内に馴染むため、データを取得するまでの間に表示するものとして最も手軽で無難な選択肢の一つです。遷移後の画面でデータ取得までの空の画面に表示すべきものがないなら、アクティビティインジケータを表示しておくと画面を見ているユーザを安心させることができます。

APIKitを使ったサンプルコードは下記のとおりです。

// リクエストを送る前にアクティビティインジケータを表示(画面の初期化時に `activityIndicator` をビューにセットしておく必要がある)
activityIndicator.startAnimating()

Session.sendRequest(request) { result in// レスポンスを受け取ったらアクティビティインジケータを非表示
    activityIndicator.stopAnimating()

    // データ取得後の処理// ...
}

HUD

f:id:tdksk:20151201234127g:plain

アクティビティインジケータの他には、HUD が読み込み中の UI として広く使われています。SVProgressHUDMBProgressHUDなどのライブラリを使うことで簡単に導入することができます。

HUD は画面の最前面に覆いかぶさるように表示されるため、どのような画面でも使うことができるという意味では便利なのですが、良くも悪くも目立つので使いどころには気をつけなくてはいけません。例えば画面遷移後のデータ読み込み中に表示するものとしては、前述のアクティビティインジケータのほうが読み込み前後での画面の変化が少なく違和感を与えないため良さそうです。また、HUD を表示している時はユーザの動作を妨げている(ように見える)ため、本当にユーザに何もさせず待たせるべき箇所なのかを考える必要があります。

これらを踏まえると、HUD を使うことに適している場面は、処理が終わることに最も注目しており、処理が終わるまでは画面遷移するべきでないところと言えそうです。そのため、Holiday では読み込み中というよりも以下のような POST や PATCH の処理中の場面で HUD を使っています。

  • ユーザ登録、ログイン処理
    • 成功するまで先に進むことができない
    • 失敗した場合必要であれば入力項目を書き換えて再度実行する必要がある
  • ある程度ボリュームのある内容の投稿
    • 投稿が完了されたことを確認するまではユーザが安心できない
    • 失敗した場合リトライする必要がある

後者の具体例としては、フォトレポ *2を投稿する場面が挙げられます。投稿されたものは投稿する時と同じ画面に表示するため、サーバ側の処理を待たずにクライアント側で先に表示を変えてしまうこともできますが、この場合は処理が完了するまでに時間がかかる *3ことや、処理中であることを明確に伝えたほうが安心感があること、またチャットなどのように短い時間で連投するものではないことなどを考慮し、処理中は HUD を表示して処理が完了した後に投稿内容を反映させる *4方法を取りました。

// リクエストを送る前に HUD を表示
SVProgressHUD.show()

Session.sendRequest(request) { result inswitch result {
    case.Success(let response):
        // HUD を消す
        SVProgressHUD.dismiss()

        // 成功結果を画面に反映する処理// ...case.Failure(let error):
        // HUD でエラーメッセージを表示
        SVProgressHUD.showErrorWithStatus("送信できませんでした。もう一度お試しください。")
    }
}

プレースホルダ

f:id:tdksk:20151201234209g:plain

アクティビティインジケータも HUD も、読み込み中であることはユーザに伝えやすいものの、多用するとうっとおしい印象を与えてしまいます(操作の度に HUD を表示したり、一画面内に複数のアクティビティインジケータを使用したり…)。そのため、ある程度読み込みが発生していることが予期できそうな場所に関しては、読み込み中であることを明示しないというのも一つの選択肢です。

読み込み中であることを予期させるために、プレースホルダを使うと効果的です。プレースホルダを使うことで、データの読み込みの完了を待たずに先にレイアウトだけを画面上に反映し、体感読み込み速度を上げると同時にデータ取得後の画面との差分が少なくすることができます。

プレースホルダは読み込みに時間がかかる画像に対して良く利用されます。画像の読み込みまでプレースホルダ画像を表示するためには SDWebImagesd_setImageWithURL:placeholderImage:メソッドを使うと簡単に実現できます。

Holiday では画像のプレースホルダだけでなく、いくつかの画面でセルのプレースホルダを利用しています。例えばおでかけプラン詳細画面ではプラン内のスポットをコレクションビューセルで複数表示していますが、事前にスポットの数だけ読み込んでおき、プラン詳細画面ではスポットの内容が読み込まれるまでスポットの数だけセルのプレースホルダを表示しています。

overridefunc numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    // 初回ロード時は Plan モデルに紐づく Spot モデルが取得できていないので、事前に Plan モデルに保存されているスポット数 `spotCounts` 分だけセルを表示return plan.spots.count >0 ? plan.spots.count : plan.spotCounts.toInt()
}

overridefunc collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(SpotCollectionViewCell.reuseIdentifier, forIndexPath: indexPath) as SpotCollectionViewCell

    // スポットデータが取得できているときはそのデータでセルを描画し、そうでない場合は空データでセルを描画let spot: Spot? = plan.spots.count > indexPath.item ? plan.spots[indexPath.item] : nil
    cell.updateWithSpot(spot)

    return cell
}

おまけ:文字のプレースホルダ

f:id:tdksk:20151201234340p:plain

セルや画像だけでなく、文字部分もプレースホルダとして表示するパターンも少し前から流行って使われ続けています *5。文字中心のコンテンツだとセルや画像のプレースホルダだとスカスカになるため、データ読み込み後のイメージを想起させるためには有効です。また、Facebook などはプレースホルダ部分の色をアニメーションで若干変化させることにより読み込み中であることを示しています。

Holiday では読み込み中のプレースホルダとしては利用していませんが、おでかけプラン投稿画面などで BLOKKというフォントを用いて文字のプレースホルダを設置しています。


おわりに

今回は iOS アプリの読み込み中の UI の中でも基本的で手軽に実現できる3つの例を紹介しました。もちろんここで紹介したものが正解とは思っておらず、実現したいことを達成するための一つの参考になれば良いと思っています。

なお、Holiday ではプロダクトを良いものにするためにこだわりを持ってサービス開発をしたいエンジニアを募集しています。興味をお持ちいただけた方はぜひご応募ください。

*1:クックパッドの100%子会社

*2:写真とひとことでおでかけプラン作者に感謝のフィードバックを伝えることができるもの

*3:写真のアップロードをバックグラウンドで先に行うと送信後の完了時間は短くなりますが、まだそこまでは実現できていません

*4:処理が完了した時は投稿したものが画面に反映されるため、HUD で成功した旨のメッセージを出していません

*5:Facebook, Slack, Medium, Booking Now, TVShow Time などで見ることができます

Viewing all 726 articles
Browse latest View live