Developers Summit 2015とDevelopers Summit 2015 Summerで発表してきました #devsumi #natsumi

もう数ヶ月前のことですが・・・、Developers Summit 2015で発表してきました。 そして、先日のDevelopers Summit 2015 Summerにて発表&表彰をして頂きました。

デベロッパーズサミットというと数年前の自分からしたら神々がそれぞれのマサカリでもって斬り合いする天下一武道会みたいなもので、まさか自分が登壇することになるとは(しかも2回も連続で)夢にも思っていませんでした。 今でも想像するだけで脇に変な汗かくくらい鮮明に覚えていますが、この壇上から300人くらいの方々に話すというUXはとてつもなかったです。高校時代はバンドをやっていましたが、人の前で演奏しているときの感覚とすごく近い体験でした。色々なコミュニティの勉強会で登壇させていただくことはありましたが、それらとは全くの別物でした。程よい緊張感と楽しさが同居する感じ。もうそんな機会はないと思いますが、何か話せるネタを作ってまたダメもとで応募させていただこうと思います(2年連続とかでは登壇出来ないルールぽいけどw)

デブサミ当日は、エンジニアの大先輩(当時入社面接してもらた)の @kawanetさんや、前職の上司、先輩、同僚、大学時代の友人、勉強会コミュニティで知り合った方々、知り合いたくさんがきてくださりとても精神的にリラックスして発表することが出来ました。ありがとうございました!!!

そのときのスライドがコチラです。

www.slideshare.net

夕方のセッションで早朝のセッションよりも参加率が高く、また、業務を早めに切り上げてこられる方がちょうど参加できる枠であった(と思われる)ため、また、あえて共感を頂きやすいような言い回しをしたりして、、、会場票をたまたま多く頂くことが出来まして・・・、その結果、まさかの賞を頂くことができました。色々な偶然のファネルが積み重なった上なのだと思いますが、ありがとうございました。 codezine.jp

そして、昨日、7月29日のDevelopers Summit 2015 Summerにて、受賞者枠?で、もう一度登壇させていただく機会をいただきました。 当日は、ドレスコードは無しと書いてあったので、気楽にいつもどおりの格好で、半袖短パンでいったところ、スーツな方々がほとんどで、TPO力の低さを露呈してしましました。

受賞者枠として頂いた時間が約20分であり、前回の160枚のスライドを短縮で発表することが出来そうになかったので、別の発表をさせていただきました。 (スライド内では、小さく検証しろとか、MVPつくれとか言う割に、こと自分事になると肥大化したスライドしか作れないという・・・・)

www.slideshare.net

授賞式の際に簡単にはお話させていただきましたが、一貫して伝えたかったことは、実際に現場でものを作っている人が一番偉いとされるようなチームを作りたかった。これにつきます。 今後もエンジニアが輝けるような場を作るために精進していきたいと思います。

関係各所の皆々様に素晴らしい機会を沢山いただけたことに感謝します。ありがとうございました。

「Devlove現場甲子園2014 日本シリーズ編」で発表してきた。 #devlove

先日開催されましたDevLOVE現場甲子園2014 日本シリーズ編 〜東西開発現場の集結〜 - DevLOVE | Doorkeeperで発表させて頂きました。 会場でのMVP投票にて1位と2票差で2位でした。投票して下さった方々、本当にありがとうございました!

発表資料をはっつけておきます。

あと、補足で社内でリーンスタートアップについて説明する機会が最近多いのでそのために作った資料ものっけときます。

リーンスタートアップ概論

ZapierでSlackを佐野ひなこちゃんで埋め尽くす #apijp

本ポストは下記のアドベントカレンダー13日の記事になります。

Web API Advent Calendar 2014 - Adventar

ところでみなさん、Zapierってご存知でしょうか?

The best apps. Better together. - Zapier

一部界隈では便利ツールとして凄く有名ですが、ご存知ではないかたのために簡単に説明いたします。 APIAPIをコーディング無しで連携させることが出来るハブサービスになります。 Yahoo PipesとかIFTTTのようなイメージです。 ただ、連携可能なサービスがものすごく多く、300以上あります。SlackやTwitterInstagramFacebook、JIRA、Trello等有名どころは大抵連携できます。 Twitterでファボッたツイートを自動的にインスタペーパーに登録したりとか、そういう連携です。 あとはWebhookも出来るので、例えばTwitterで「パイルダーオン!」とツイートするとそれがCIサーバとWebhookで連動し、ビルドが開始するとか出来ます。

ということでやってみた。

左がインプットでそれを右のサービスで何かをするという構造です。プルダウンで対応している各種サービスが出て来ます。今回でいうとインプットがInstagramで、それのアウトプットがSlackになります。

f:id:i2key:20141212181509p:plain

f:id:i2key:20141212181528p:plain

それぞれ各サービスの認証を行います。

f:id:i2key:20141212181557p:plain

Instagram側のSearch条件の設定をします。今回はハッシュタグで #佐野ひなこ に該当するものとしました。やろうと思えばもっと細かく設定できます。どれくらいかというと、InstagramREST APIの戻り値であれば何でも条件にすることが出来ます。

f:id:i2key:20141212181654p:plain

次に出力先であるSlackへの投稿設定を行います。ここでは Slackの #test というチャンネルに投稿するようにしています。また、前述しましたが、InstagramREST APIで取得可能な値であば何でも入力アシストで候補にしてくれます。凄く便利。これ対応しているサービス300個くらいすべてこういう用な入力補完があります。Slackへの出力値として、「スタンダード解像度の画像のURL」を指定しています。

f:id:i2key:20141212181628p:plain

下記が便利な入力補完になります。

f:id:i2key:20141212181642p:plain

これで設定完了。実行すると!!!!!

佐野ひなこちゃんきたああああああああああああああああああああああああああああああああああああ!!! かわいいいいいいいいいいいいいいいいいいいいいいい

f:id:i2key:20141212181709p:plain

これでInstagramの #佐野ひなこ ハッシュタグに投稿がされるたびにSlackにひなこちゃんが流れてきます。 はかどるわああああああああああああああああああああ。

便利な時代になりましたねー。

Hello Swift

Social.frameworkとAcount.framework使ってTwitter投稿とTableViewへのタイムライン取得&表示でSwiftをハローワールドしました。

xcodeでコード補完が殆ど効かなくて苦行でした。

コードをおいときます。(実行環境はxcode6-betaなので、いつ動かなくなってもしらんです) https://github.com/i2key/HelloSwift

こんな感じね。

f:id:i2key:20140606202738p:plain

f:id:i2key:20140606202735p:plain

AndroidのBitmapを扱う際のTIPSメモ

Androidの実装をOSのバージョンが2.xの頃からしておらず最近のコーディングの仕方をキャッチアップしているので、メモを残して行きます。

  • 画像キャッシュ
  • メモリキャッシュ
  • ディスクキャッシュ
  • 大きなBitmapをメモリに読み込むときに気をつけること
  • inPreferredConfigで画像の質を下げメモリ使用量を減らす
  • inPurgeableでGC時に解放されるようにする
  • inJustDecodeBoundsとinSampleSizeでメモリ読み込み時にメタ情報のみを先に読み込み、1/nサイズにしたものをロードする
  • Bitmapオブジェクトのリサイクル

画像キャッシュ

メモリキャッシュ

Twitterのタイムラインを実装するような時に、APIレベル12以前は下記のように画像キャッシュをしていたと思います。WeakHashMapのがいいかもですが。

public class ImageCache {

    private static HashMap<String,SoftReference<Bitmap>> cache = new HashMap<String,SoftReference<Bitmap>>();

    private ImageCache() {
    }

    public static Bitmap get(String key) {
        if (cache.containsKey(key)) {
            Log.d("cache", "cache hit!");
            return cache.get(key).get();
        }
        return null;
    }

    public static void put(String key, Bitmap image) {
        cache.put(key, new SoftReference<Bitmap>(image));
    }

    public static void remove(String key){
        cache.remove(key);
    }
}

しかしながら、APIレベル12からLruCacheというものが導入されたことにより、画像キャッシュはもっぱらそっちのようです。同じクラスを書き換えてみます。

public class ImageCache {

    private static final int CACHE_SIZE_BASE = Build.VERSION.SDK_INT > 10
            ? 5 : 1;
    private static final int CACHE_SIZE = CACHE_SIZE_BASE * 1024 * 1024;
    private static LruCache<String, Bitmap> sLruCache;
    static {
        sLruCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    private ImageCache() {
    }

    public static Bitmap get(String key) {
        return sLruCache.get(key);
    }

    public static void put(String key, Bitmap bitmap) {
        if (get(key) == null) {
            sLruCache.put(key, bitmap);
        }
    }

    public static void remove(String key) {
        sLruCache.remove(key);
    }
}

ディスクキャッシュ

GoogleがDiskLruCacheのサンプルコードを公開しています。こちらを参考にしましょう。

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html Use a Disk Cache The sample code of this class uses a DiskLruCache implementation that is pulled from the Android source. Here’s updated example code that adds a disk cache in addition to the existing memory cache:

https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java

大きなBitmapをメモリに読み込むときに気をつけること

写真系のアプリを作っていて画像をメモリに読み込むことが多いのですが、何も考えずに作るとすぐメモりが一杯になり落ちる(OutOfMemory)。そんなときの対策。

BitmapFactory.Optionsでいろいろやりくりする

http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

inPreferredConfigで画像の質を下げメモリ使用量を減らす

ARGB_8888からRGB_565にすると大体メモリは半分くらいになる。

http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888

EnumValue Desc
ALPHA_8 Alphaのみ8bit
ARGB_4444 A,R,G,Bそれぞれ4bit
※クオリティがPoorという理由でAPIレベル13から非推奨
ARGB_8888 A,R,G,Bそれぞれ8bit
RGB_565 R,G,Bを5,6,5bit

inPurgeableでGC時に解放されるようにする

decodeFileやdecodeResourceをするときに指定しておくと、ここで生成されたBitmapはAndroid上のメモリが足りなくなった場合に解放されるようになります。

inJustDecodeBoundsとinSampleSizeでメモリ読み込み時にメタ情報のみを先に読み込み、1/nサイズにしたものをロードする

Googleの公式ページに乗っています。

http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object

下記のように、inJustDecodeBounds = trueにすることでDecode時にメモリに画像がアロケートされるのを防ぐことができるようです。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

これで、まずは画像のメタ情報(縦横幅等)のみ取得し、画像を何分の1にリサイズして取得するかを計算します。下記、calculateInSampleSizeメソッド参照。 そこで取得したinSampleSizeをOptions.inSampleSizeにセットすることで、次にDeocodeするタイミングで1/inSamplesizeにリサイズされた画像データがメモリにロードされます。 コードは以下。(Google本家サイトから引用)

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Bitmapオブジェクトのリサイクル

Bitmapオブジェクトを使う場合は、使い終わったらrecycleをすること。これをしないとメモリ上から解放されない。これはBitmapFactoryでdecodeする際の実装が、2.x系ではNativeHeap領域に画像メモリを確保するため、それを解放するための明示的なコールになる(3系からはJaveHeap領域に画像メモリを確保するようになった)。

つまり、Bitmapオブジェクトのメモリ解放処理は、DalvikのGCC++レイヤでのdeallocの併用なので、recycleメソッドで明示的に教えてあげないとダメという理解。(間違ってたら教えて下さい・・・)

http://developer.android.com/reference/android/graphics/Bitmap.html#recycle()

コードでは下記のようにリサイクル。

//インスタンス化
Bitmap bitmap = BitmapFactory.decodeXXXXX();

//解放
if(bitmap != null){
     bitmap.recycle();
     bitmap = null;
}

//もっかい使う
if(bitmap.isRecycled()){
     bitmap = BitmapFactory.decodeXXXXXX()
}

Play2.1.3でplay testをしてもテストを認識してくれない不具合について

Play2.1.3 Javaで発生したけど、こうするとなおるよ。Play2.1.3 Scalaはしらぬ。
@kara_d さんに教えてもらたのでメモ。

Build.scalaのmainに以下を追加

    testOptions in Test ~= { args =>
      for {
        arg <- args
        val ta: Tests.Argument = arg.asInstanceOf[Tests.Argument]
        val newArg = if(ta.framework == Some(TestFrameworks.JUnit)) ta.copy(args = List.empty[String]) else ta
      } yield newArg
    }

こんな感じ

  val main = play.Project(appName, appVersion, appDependencies).settings(
    // Add your own project settings here
    javaOptions in Test ++= Seq("-Xmx1024m","-XX:MaxPermSize=128m", "-Dfile.encoding=UTF-8", "-Dconfig.file=conf/application_dev.conf"),
    testOptions in Test ~= { args =>
      for {
        arg <- args
        val ta: Tests.Argument = arg.asInstanceOf[Tests.Argument]
        val newArg = if(ta.framework == Some(TestFrameworks.JUnit)) ta.copy(args = List.empty[String]) else ta
      } yield newArg
    }
  )

スマホアプリ向けのAPIサーバの品質ガイドライン

ふと思い立ってかいた。

スマホアプリを開発する際、mixiさんが公開しているようなスマホ向けガイドラインと同様にAPIサーバでも、ある程度の確認用のガイドラインを個人的に設けていますので、メモとして公開してみます。クラウド環境の登場により誰でも簡単にサービス公開できるようになりましたが、結局のところインフラ構築コストが無くなるだけであり、サーバ品質は自身で担保しないとなりません。

前提

  • ガイドラインの目的
    • リリースしてから、問題が発見されるようなことが無いように、サービスを運用する上で確認すべきことをリスト化しています。機能要件の試験は済んでいることを前提とし、運用レベル確認のためのリストになっています。当然、品質目標はミッションクリティカルなレベルの品質ではなく、toC向けの無料スマホアプリレベル品質になります。
  • 対象
  • 環境
    • AWS等のスケーラブルなクラウド環境。私がAWSユーザなので、AWSベースに表記が一部なっています。
  • 前提
    • 結合試験レベルまでの機能レベルテストは完了していること。条件網羅系試験、境界値試験、同値分割、フルパス等一般的な試験は実施済みであることが前提です。

全体的な機能確認

  • 実データ確認
    • 本番環境、若しくはそれに近い実データでオンライン処理、バッチ処理を実行させ基本機能が実現できることを確認
  • オンライン・バッチ連動
    • オンライン処理データをバッチ処理で加工し、またオンライン処理で使う等の場合、オンライン→データベース→バッチ処理→データベース→オンラインとデータが正しく引き継がれ、処理が実行されること
  • 業務処理・運用処理連動
    • マスタメンテ等の運用系機能からデータを設定し、それがオンライン処理側で利用できることが確認できていること。
  • アプリ連携
    • アプリ to アプリ、アプリ to ブラウザ、ブラウザ to アプリのようにアプリ連携するようなサービスはそれらの全パスを網羅し確認出来ていること
  • 環境差分
    • 開発環境と本番環境の差による確認が出来ていること。
    • パス等は全て本番に向いていること。
    • 外部API接続アカウントは本番用になっていること。
  • 国際化
    • アプリ、サーバ、バッチ全てにおいて国際化が行われていること。
    • ここで言う国際化は文言だけではなく、データフォーマット、文化による振る舞いの変化等も含む。

☆あるある:本番データを入れると動かない。本番環境の0件データ状態だと落ちる。
☆あるある:オンライン側の処理の入力チェック仕様とマスタメンテの入力チェック仕様が不整合。

対外接続確認

  • 疎結合
    • 接続先外部サービス(TwitterFacebook等)が落ちている場合、その影響を受け、自身のシステムが利用停止にならないこと。
    • 接続先からエラーが出た場合に、丸め込み等が出来ていること。
  • 性能確認
    • 接続先サービスの性能限界(TwitterAPIでいうRateLimit)を把握しており、それにあわせた対応実装がなされていること。
  • 規約遵守
    • 接続先サービスのプラットフォームポリシーガイドライン等に準拠していることが確認できていること。|

☆あるある:Facebookプラットフォームポリシーガイドラインに準拠せずにアプリ登録をBANされる。

性能確認

  • 単性能
    • スケール前の最小構成にて、レイテンシーが許容値以内である確認が出来ていること。
  • 最大性能
    • スケール前の最小構成にて、性能の最大値を把握できていること。
    • ボトルネックになる部分を把握していること。
  • 過負荷
    • 負荷をかけた状態にて、通常の処理の確認が出来ていること。
  • 最大データ量
    • 想定される最大データをDB等に設定し、機能の確認が行えていること。

☆あるある:データが多くなりバッチ処理時間が伸び、後続バッチ、またはそれを必要とするオンライン処理の開始時刻に間に合わなくなる。
☆あるある:大量データのCSVアップロード、ダウンロードでセッションタイムを超えたときに切断してしまう。

拡張性確認

  • スケール
    • スケーラブルな実装になっていること。
    • ElastiCache、DynamoDB等オートスケールしないものに対するスケール性の担保の仕方が決定し、対策が講じられていること。
    • EC2起動時に必要な全てのサービスが起動するようになっていること
    • スケールインする際に必要なログ等のデータはストレージに退避するようになっていること

☆あるある:push用のバッチもAPIサーバで稼働しており、スケールさせるとバッチ処理がサーバ毎に重複起動し同じプッシュがユーザに複数配信される。

可用性確認

  • 耐障害性
    • 障害時に復旧するための手順があり、それの確認が出来ていること
    • 障害時に自動復旧する仕組みが講じられていること。
  • SPOF
    • あるサーバの停止がシステム全体を止めてしまわないこと。
    • あるモジュールの停止がサーバを止めてしまわないこと。

☆あるある:オンライン処理とバッチ処理を同一サーバ上にで実行している場合、バッチの過負荷によりオンライン側が停止。

運用確認

  • バックアップ
    • 必要なデータがバックアップ出来ていること、またそれをリカバルする方法が決まっており、確認が出来ていること
  • 監視
    • システムの状態を監視する仕組みがあること。
    • 不具合の解析に必要な情報がログから取得出来るようになっていること。
  • 日廻し試験
  • 月廻し試験
    • 上記同様
  • 特定日試験
    • 祝日、祭日、閏年等により振る舞いを変える場合、その動作が確認出来ていること。
  • 問い合わせ運用確認
    • データ削除等のユーザからの問い合わせによる運用がある場合、運用担当者を交え、運用手順に従い運用を実施出来ることを確認できていること。
    • ユーザ問い合わせへの調査に必要な情報がログから取得出来るようになっていること。
  • ログ運用
    • ローテーション、退避。
  • 長時間運用時の資源
    • サーバの再起動無しで長時間運用され続けている状況で、資源(メモリ、CPU、ディスク、..etc)の枯渇を起こさないことが確認できていること。

☆あるある:ログローテートが出来ておらず(またはファイルサイズ見積もりが甘く)、サーバ容量を圧迫し、サーバ停止。

セキュリティ確認

上記の確認ポイントを確認しつつ、バグが見つかった場合は、以下のような基本動作の徹底が必要です。

バグ発生時の基本動作

開発終盤になってくるとバグチケットの山になり、それに埋もれた終わりの見えない状態になると思いますが、そんなときこそ、基本動作を徹底することは大切です。バグでましたーなおしますー、こっちでも出ましたーなおしますーという場当たり的な修正ではなく、品質を作り込むことを意識しないといけません。今回のさいごの仕上げ部分に閉じず、どの試験でも同じスタンスが必要です。

  • バグに対して、以下の切り分けを行うこと
    • このバグを発見するための試験観点があり、それによって検出された場合
      • その場合はハッピーですね。摘出すべくして、摘出した。試験項目がヒットしたということです。
    • このバグは既に試験実施済みの観点なのにも関わらず、検出された場合
      • これは、完全に試験すり抜けなので、何故すり抜けたかを原因を確認し、必要であれば、その観点で再試験が必要です。実施済み観点でバグが出てしまうと、今までやった試験自体の信頼性を疑わざるを得ない状況になってしまいます。
    • このバグが試験をしていない観点のバグであった場合
      • 観点漏れです。試験観点を追加し、追加試験の実施が必要です。
  • 横並びチェック・歯止め・真の原因の追求
    • また、不具合に対してはある程度真の原因を追求し、その原因に対する歯止めの実施、および影響範囲を洗い出し横並びチェックを実施すべきです。横並びチェックとしては、究極的に、同じ開発者が同じ勘違いを複数箇所でしていた場合は、その開発者のコミットしたソースを全部確認する(自動or手動)ことになります。こういう泥臭いことを行わないと、結局、デグレード、同じようなバグの発生が止まらない、とかとか、結局バグ発生曲線が収束しないのです。そして、同じような確認を何度も再帰的に行うので、テストを自動化しておけば良かったということになります。