Cookpad Ruby Hack Challenge

f:id:koichi-sasada:20170629153022p:plain

技術部の笹田です。Ruby インタープリタの開発をしています。最近は Fiber まわりを10年ぶりにいじってます。

2017/08/30, 31 に、Cookpad Ruby Hack Challenge というイベントを行いますので、その宣伝をさせてください。

Cookpad Ruby Hack Challenge とは

クックパッドで Ruby インタプリタを Hack しよう!

クックパッドをはじめ、多くのウェブアプリケーション開発でプログラミング言語 Ruby が利用されています。Ruby で書かれたプログラムを動かすときは Ruby インタプリタで実行します。

Cookpad Ruby Hack Challenge は、この Ruby インタプリタに対して機能を追加したり、改良したり、性能向上させたりする方法、つまり Ruby インタプリタを Hack する方法を、二日間かけてお伝えするイベントです。

イベント概要

二日間かけて、Ruby インタプリタをハックします。一日目は共通課題として、用意する資料を手順どおりに進めて頂きます。二日目は発展課題として、Ruby インタプリタに残る未解決問題に取り組んで頂きます。両日とも、講師は Ruby コミッタの笹田が務めます。

また、二日目には特別企画として、クックパッド以外の Ruby 開発者とも交流できる場を用意したいと思っています。Ruby という言語や、Ruby インタプリタに対する疑問や意見がある人は、この機会にぜひ議論してもらえればと思います。

学生の方には、ぜひ夏休みのアクティビティの一つとして楽しんで貰えると幸いです。

なお、本イベントは、以前ご紹介した社内イベント Hackarade: MRI Internal Challenge を、よりわかりやすくブラッシュアップしたものになります。

こんな方に来てほしい

  • Ruby インタプリタの Hack がしてみたい方
  • Ruby プログラムは書けるけど、どうやって動いているのか知りたい方
  • プログラミング言語 Ruby および Ruby インタプリタを普段開発しているような人達が、何を考えているのか知りたい方
  • 難易度の高いプログラミングに挑戦してみたい方
  • 夏休みの思い出が欲しい方

共通課題(1日目)のゴール

  • Ruby のソースコードの構造を知る
  • Ruby のビルドができるようになる
  • Ruby の中身を弄ることができるようになる

発展課題(2日目)のゴール(できれば)

  • 未解決問題を解決する
  • 実際に Ruby インタプリタへの貢献を体験する
  • 開発コミュニティへの参加を体験する

スケジュール

注意:時間等は変わる可能性があります。

開催前

  • 7/27 (木) 募集締め切り
  • 7/29 (土) 参加者抽選決定
  • Gitter を用いたオンライン予習サポート(希望者)

8/30 (水) 一日目

  • 10:00 オープニング
  • 10:30 ハックに必要となる事前知識の講義
  • 12:00 ランチ
  • 13:00 共通課題
  • 16:00 発展課題の紹介と割り振り

8/31 (木) 二日目

  • 10:00 発展課題の開始
  • 11:30 まつもとゆきひろ氏 特別講演
  • 12:00 Ruby開発者を交えてのランチ
  • 13:00 Ruby開発者との Q&A セッション
  • 14:00 発展課題の再開
  • 18:00 打ち上げパーティー

なんでクックパッドで開催するの?

前節までは、募集ページそのままの内容でした。これだけではなんなので、本イベントを開催する動機について、少し触れておきます。

いくつかあるのですが、まずは IT エンジニアコミュニティへの還元です。クックパッドは「毎日の料理を楽しみにする」という目標のために、多くの IT 技術を活用しています。我々は OSS としてプロダクトを公開したり、この開発者ブログでの情報発信をすることで、エンジニアコミュニティへの還元を行っていますが、本イベントはその一環です。おそらく、本イベントに興味を持つ方は、向上心があり、これからのエンジニアコミュニティを牽引して下さる、かもしれない、方々だと思うので、そのような方々へ支援することは大事なことだと思っています。

笹田個人の動機としては、このようなイベントをきっかけに言語処理系といったシステムソフトウェア開発に興味を持つ人が少しでも増えてくれたり、優秀な Ruby コミッタが増えてくれると嬉しいです(Ruby にはまだまだ改良の余地がありますが、人手が足りていません)。最近、システムソフトウェアといったコンピュータの「裏方」に興味を持ってくれる人が少なくなっているような気がします。いろいろな理由があるかと思いますが、一つは難しそう、といった「とっつきづらさ」があるのではないかと思います。本イベントを通して、そのようなハードルを越えるお手伝いができればと思っています。

このような思いを実現するためには、長期的に継続して活動を行っていく必要があると考えており、本イベントはその第一歩です。うまくいけば、第二弾や、別テーマでの開催も検討できるのではないかと思っています。そもそも、クックパッド1社でやってもスケールしないので、もっと広げたいと思っており、夢(だけ)は広がります。というわけで、まずは本イベントがうまくいくといいなぁ。

おわりに

というわけで、Cookpad Ruby Hack Challenge のご紹介でした。 ご興味とお時間がある方は、ぜひご応募ください。

UICollectionView の Layout で悩んだら

こんにちは、サービス開発部の氏です。
主にiOSのクックパッドアプリの開発を担当しています。

UICollectionViewLayout みなさん使ってますか?
UICollectionView でレイアウトを組む際、実際触り始めると実装するための選択肢が複数あり、どれが最適なのか悩ましい場面に遭遇する人もいるのではないかと思います。
今回は、自分が業務で触れた際に得た知見について軽くお話したいと思います。

UICollectionVIewLayout とは

UICollectionView は Cellのサイズや余白等のレイアウトを管理するため、プロパティとして、 UICollectionViewLayout を所持しています。
この UICollectionViewLayout に手をいれることによって、レイアウトを好きな形に変更することができます。

レイアウトを組み立てるときの複数の選択肢

実際に UICollectionViewLayout をいじろうとすると、大きく分けて三つの選択肢が出てきます。

  1. UICollectionViewFlowLayout を調整する
  2. UICollectionViewDelegateFlowLayout を実装する
  3. UICollectionViewLayout (Custom) を作成する

つづけて、各Layoutで出来ること、出来ないことを挙げていきたいと思います。
どんなレイアウトの組み上げ方をすればよいか等、判断に困った際の参考にしていただければ幸いです。

1. UICollectionViewFlowLayout を調整する

一つ目は UICollectionViewFlowLayout をそのまま利用する方法です。
InterfaceBuilderで UICollectionView を設置すると、初期値としてこの UICollectionViewFlowLayout が設定されています。
UICollectionViewFlowLayout では、CellやHeader/FooterのSize等がプロパティとして用意されており、それを変更するだけで良い感じに組み上げてくれます。

let flowLayout = UICollectionViewFlowLayout()  
let margin: CGFloat = 3.0  
flowLayout.itemSize = CGSize(width: 100.0, height: 100.0)  
flowLayout.minimumInteritemSpacing = margin  
flowLayout.minimumLineSpacing = margin  
flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)  
let collectionViewController = CollectionViewController(collectionViewLayout: flowLayout)  

ですが、Cellの大きさを決める itemSize では、動的な変更が行なえません。
全てのCellを同じ大きさで表示するのであれば、UICollectionViewFlowLayout を利用すると良いでしょう。

2. UICollectionViewDelegateFlowLayout を実装する

二つ目は UICollectionViewDelegateFlowLayout を実装する方法です。 UICollectionViewDelegateFlowLayoutUICollectionViewDelegate を継承した Protocolになっており、各種便利メソッドが用意されています。
基本的には、 UICollectionViewFlowLayout のプロパティと同等なものが準備されています。

extension CollectionViewController: UICollectionViewDelegateFlowLayout {  

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {  
        if indexPath.row % 3 == 0 {  
            return CGSize(width: 100.0, height: 100.0)  
        }  
         return CGSize(width: 60.0, height: 60.0)  
    }  

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {  
        return UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)  
    }  

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {  
        return margin  
    }  

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {  
        return margin  
    }  
}  

このレイアウトの利点としては、 indexPath の情報を参照出来るため、動的なCellのサイズ変更が可能です。
ですが、Protocolとして用意されているものでしか変更を行うことが出来ないため、アニメーションを伴う変化には余り適していないと思います。

3. UICollectionViewLayout (Custom) を作成する

最後は UICollectionViewLayout を継承した独自レイアウトを作成する手段です。

自由にレイアウトを組める反面、今までに挙げた2通りの様に良しなにレイアウトを組んでもらえません。
CellやSectionなど各要素の配置先を計算する必要があり手間がかかりますが、その分動的なサイズ変更やレイアウト変更を好きなように行うことができます。(自分で書くので当然ですが…)

UICollectionViewLayout を継承して利用するには、下記の処理を実装する必要があります。

collectionViewContentSize: CGSize

UICollectionViewcontentSize を返します。
UICollectionView は、この contentSize をもとにスクロール量を判断します。
その為、ここでは表示させたい要素に応じた正確な contentSize を返さないと思った通りの位置までスクロールをしてくれません。

layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?

IndexPath に応じたCellの UICollectionViewLayoutAttributes を返します。
UICollectionViewLayoutAttributesIndexPath に応じたセルのレイアウト属性です。
この layoutAttributes にCellのサイズと座標を指定しておくと、指定通りの座標に表示されます。
この中でCellのサイズ計算等を行う場合、時間がかかる処理などがあるとカクつきの原因となります。
よくある方法として、prepare() でレイアウト情報を先に計算して配列などに用意しておきここではその情報を返すだけとするケースが多いです。

layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?

範囲内に含まれる UICollectionReusableView (cellやsupplementary view)の UICollectionViewLayoutAttributes の配列を返します。
基本的には、その範囲に含まれる layoutAttributesForItem(at indexPath: IndexPath) を取得してくる形になるでしょう。

アニメーションについて

レイアウトを作っていると、アニメーションを求められるケースがそれなりにあるかと思います。
レイアウト変更時のアニメーションは下記の様な形でアニメーションを行うことができます。

collectionView.setCollectionViewLayout(newLayout, animated: true)  

他には、Cellの生成時や削除時のレイアウト属性を返すメソッドがあり、それを実装することで insertItems, deleteItems でもアニメーションをさせる事ができます。

  • initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath)
  • finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath)

また、「UICollectionViewController, UINavigationController の組み合わせでのみ」という制限がありますが、
UICollectionViewControlleruseLayoutToLayoutNavigationTransitions の値を true にしてpushさせると UINavigationBar と連携した遷移が可能です。

let viewController = UICollectionViewController(collectionViewLayout: newLayout)  
viewController.useLayoutToLayoutNavigationTransitions = true  
navigationController?.pushViewController(viewController, animated: true)  

UICollectionViewController 以外への遷移アニメーションは、 UIViewControllerAnimatedTransitioning を実装してあげると良いでしょう。

まとめ

UICollectionView のレイアウトを作るに当たって、ほとんどのケースでは UICollectionViewFlowLayoutUICollectionDelegateFlowLayout で事足りるかと思います。

独自レイアウトを採用するケースとしては、行ベース、グリッドベース以外のレイアウトが必要なケースや、各要素のレイアウトが頻繁に変化する場合に必要になってきます。(カバーフローのようなものだったり)

UICollectionView はiOS10から prefetchUICollectionViewFlowLayoutAutomaticSize 等の新しい機能も追加され、表現の幅も増えています。

クックパッド内で利用するのはまだ少し先かもしれませんが、ユーザーがより良い体験を提供できるよう常に心がけていきたいですね。

Android開発のコードレビューbotを乗り換えた話

モバイル開発で利用しているコードレビューbotを最近乗り換えた話をします。

コードレビューbotとは

コードレビューbotはPull Request(以下PR)に対して、静的解析した結果などをコメントする機能を持つプログラムの事を指します。 コードレビューbotを導入すると、些末な内容はbotが勝手に指摘してくれるため、レビューワーがより重要な内容のレビューに時間を使うことが期待できます。 有名なサービスにHoundSideCIなどがあります。

Android開発でのレビューbotの役割

CookpadのAndroid開発では、下記の項目をPR毎に実行しています。

今まではこれら全てをDokumiと呼ばれるツールで行っていました。(上記の通りDokumiではコードレビューだけではなくdeploygateへのアップロードなども受け持っていたため、 コードレビューツールと呼ぶのが適切かもしれません)

Dokumi時代

CookpadのiOS/Android開発では、自社製のDokumiというレビューbot(レビューツール)を長年愛用してきました。詳しくは下記のエントリに紹介されています。
Dokumi (日本語)

長い間お世話になってきたDokumiも、利用していく間にいくつか問題点が生まれました。

  • 依存関係が難しい
    • OSSであるdokumiと社内で利用しているdokumi-cookpad-customというmoduleの関係性が複雑だった
  • セットアップが難しい
  • デプロイ手順が煩雑(gemifyされていない)
  • Dokumi以外の仕組みで動かしてる機能もあり、それらを統合したい
    • Dokumiはプラグイン機構がなく機能追加が容易ではない
  • dokumiの設定がAndroidの開発レポジトリと別で扱いづらかった
    • dokumi-cookpad-customレポジトリ内に設定が管理されていた

このような理由で、iOSの開発チームが@giginetを中心にDangerへの乗り換えを進めていました。 それに続くようにAndroid開発の環境もDangerに移行する運びになりました。 Android版Cookpadのアプリの開発環境をDokumiからDangerに移行する作業は@_litmon_が対応してくれました。

Dangerの特徴

Danger - Stop Saying "You Forgot To…" in Code Review

  • Ruby製
  • 多くのCIシステムをサポートしている
  • 多くのコードホスティングサービスをサポートしている
  • 設定をDangerfileとしてレポジトリに含める

コアは小さく、様々な機能はプラグインによって提供されています。 Dangerのプラグインは充実しているとはいえませんが、iOS環境は比較的揃っている印象です。Android関連ではFindBugs,AndroidLint,JUnitのプラグインがあります。

pluginの作成

DokumiからDangerへ移行するに当たりfindbugsプラグインがDanger側に無かったため、findbugs-dangerを作成しました。 Dangerはpluginのテンプレートをコマンドラインで作成できるなどサポートが手厚く、下記の記事を読み進めていくとrubyやruby-gemsのエコシステムに詳しくなくてもハードルは高くない印象でした。

Creating your first Plugin

Dangerの導入

導入手順は下記のページにまとめられていますが、簡単に紹介します。

Getting Set Up

Dangerはgemとして公開されています。gem install danger としても利用できますが、bundlerを経由した利用が推奨されているのでGemfileをプロジェクト直下に用意しましょう。

# frozen_string_literal: true
source "https://rubygems.org"

gem 'danger'

danger init を実行すると初期セットアップが行われDangerfileが作成され、セットアップウィザードのようなものが表示されます。

bundle install
bundle exec danger init

ひたすら長文のメッセージが流れてくるので、enterで進めていきましょう。

  • Step 1はDangerFileを作成したというメッセージが表示されます。
  • Step 2はbot用のGithubアカウントを作成を促されます。クールなアイコン画像の設定をすることを忘れてはいけません。
  • Step 3でbotアカウントでアクセストークンを作るように言われます。Publicなレポジトリの場合 public_repo の権限だけで問題ないそうです。
  • Step 4でCI側の設定を求められますが、詳しい案内はないので、setting-up-danger-to-run-on-your-ciを見ると良さそうです。
  • 全てのセットアップが完了したら、CIからbundle exec dangerを実行する様にセットアップしましょう。

danger init で作成されるDangerFileはとてもシンプルなので必要に応じてカスタマイズしましょう。導入に成功するとbotがPRに対してコメントを投げてくれるようになります。

※ FindBugsの結果からdangerが指摘した例

CookpadのDangerfile

2017/6末地点で、Android開発で利用しているDangerfileを公開します。導入の際はぜひ参考にしてください。

####
#
# github comment settings
#
####
github.dismiss_out_of_range_messages

####
#
# for PR
#
####
if github.pr_title.include? "[WIP]" || github.pr_labels.include?("WIP")
  warn("PR is classed as Work in Progress") 
end

# Warn when there is a big PR
warn("a large PR") if git.lines_of_code > 300

# Warn when PR has no milestone
warn("A pull request must have a milestone set") if github.pr_json["milestone"].nil?

# Warn when PR has no assignees
warn("A pull request must have some assignees") if github.pr_json["assignee"].nil?

####
#
# Findbugs
#
####
findbugs.report_file = "your_module/build/reports/findbugs/findbugs.xml"
findbugs.gradle_module = "your_module_name"
findbugs.report(true)

####
#
# Android Lint
#
####
android_lint.gradle_task = "your_module:lint"
android_lint.report_file = "your_module/build/reports/lint/lint-result.xml"
android_lint.filtering = true
android_lint.lint(inline_mode: true)

まとめ

Cookpadにおけるコードレビューbotの役割や、ツールを乗り換えた経緯、Dangerの特徴などを紹介などをしました。コードレビューbotをもし導入されていないのであればDangerはおすすめ出来ます。手軽にセットアップが可能なのでぜひお試しください。