2013年12月31日火曜日

軽い気持ちでコードを書いたらフルボッコされた

職場でフィボナッチ数を求めるプログラムぐらい書けるという話題が出た。
テキトーにコードを書いたらアルゴリズムやらテスト手法やら突っ込みを受け、
大変よい勉強になったので備忘録として残す。

最初に書いたプログラム

再帰を使用。 諸先輩から計算量どうなってるのと突っ込みを受ける。

<?php

class FibCalc {
static function getFib( $num ) {
if (intval($num) < 0  || !is_int($num)) {
   throw new Exception('Invalid argument');
}
if ($num < 2) {
return $num;
}else {
return self::getFib( $num - 2) + self::getFib( $num - 1 );
}
}
}

class FibTest extends PHPUnit_Framework_TestCase
{

public function testFibZero() {
$this->assertEquals( 0, FibCalc::getFib(0) );
}

public function testFibOne() {
$this->assertEquals( 1, FibCalc::getFib(1) );
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testMinusOne() {
FibCalc::getFib(-1);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testString() {
FibCalc::getFib("test");
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testNull() {
FibCalc::getFib(NULL);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testFloatZero() {
FibCalc::getFib(0.0);
}

/**
* @note http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0
*/
public function testFromWikipedia() {
$answers = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946);
foreach ($answers as $i => $value) {
$this->assertEquals($value, FibCalc::getFib($i));
}
}
}

再帰をやめたプログラム

過去のフィボナッチ数の直近2件を保持。
これならと社内に公開したところ、メソッド名が安直、イテレータにしたほうがまだわかりやすいと指摘を受ける。

<?php

class FibCalc {
private $index;
private $pastValues;

function __construct() {
$this->index = 1;
$this->pastValues = array(0,1);
}

public function getNext() {
$ret = array_sum($this->pastValues);
array_shift($this->pastValues);
$this->pastValues[] = $ret;
$this->index++;
return $ret;
}

public function getIndex() {
return $this->index;
}



static function getFib( $num ) {
if (intval($num) < 0  || !is_int($num)) {
   throw new Exception('Invalid argument');
}
if ($num < 2) {
return $num;
}

$calculator = new FibCalc();
do {
$result = $calculator->getNext();
} while ( $num > $calculator->getIndex() );
return $result;
}
}

class FibTest extends PHPUnit_Framework_TestCase
{

public function testFibZero() {
$this->assertEquals( 0, FibCalc::getFib(0) );
}

public function testFibOne() {
$this->assertEquals( 1, FibCalc::getFib(1) );
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testMinusOne() {
FibCalc::getFib(-1);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testString() {
FibCalc::getFib("test");
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testNull() {
FibCalc::getFib(NULL);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testFloatZero() {
FibCalc::getFib(0.0);
}

/**
* @note http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0
*/
public function testFromWikipedia() {
$answers = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946);
foreach ($answers as $i => $value) {
$this->assertEquals($value, FibCalc::getFib($i));
}
}
}

インタフェースを改修

ついにimplements Iterator。
ついでにテストもデータプロバイダを利用。
<?php

class FibCalc implements Iterator{
private $index;
private $pastValues;

function __construct() {
$this->rewind();
}

function rewind() {
$this->index = 1;
$this->pastValues = array(0,1);
}

function current() {
return array_sum($this->pastValues);
}

function key() {
return $this->index;
}

function next() {
$ret = $this->current();
array_shift($this->pastValues);
$this->pastValues[] = $ret;
$this->index++;
return $ret;
}

function valid() {
if ($this->index < 0) {
return false;
}

return true;
}




static function getFib( $num ) {
if (intval($num) < 0  || !is_int($num)) {
   throw new Exception('Invalid argument');
}
if ($num < 2) {
return $num;
}

$calculator = new FibCalc();
do {
$result = $calculator->current();
$calculator->next();
} while ( $num > $calculator->key() );
return $result;
}
}

class FibTest extends PHPUnit_Framework_TestCase
{

public function testFibZero() {
$this->assertEquals( 0, FibCalc::getFib(0) );
}

public function testFibOne() {
$this->assertEquals( 1, FibCalc::getFib(1) );
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testMinusOne() {
FibCalc::getFib(-1);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testString() {
FibCalc::getFib("test");
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testNull() {
FibCalc::getFib(NULL);
}

/**
* @expectedException Exception
* @expectedExceptionMessage Invalid argument
*/
public function testFloatZero() {
FibCalc::getFib(0.0);
}

/**
* @dataProvider providerFromWikipedia
*/
public function testFromWikipedia($input, $output) {
$this->assertEquals($output, FibCalc::getFib($input));
}

/**
* @note http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0
*/
public function providerFromWikipedia()
{
return  array( 
array( 0,    0),
array( 1,    1),
array( 2,    1),
array( 3,    2), 
array( 4,    3), 
array( 5,    5), 
array( 6,    8), 
array( 7,   13), 
array( 8,   21), 
array( 9,   34), 
array(10,   55), 
array(11,   89), 
array(12,  144), 
array(13,  233), 
array(14,  377), 
array(15,  610), 
array(16,  987), 
array(17, 1597), 
array(18, 2584), 
array(19, 4181), 
array(20, 6765), 
array(21, 10946)
);
}
}




2013年11月19日火曜日

アプリの継続開発に必要な仕組み


アプリを開発していると、アプリにバグが入ったり、サーバをメンテしたりするときがある。そんなときにアプリが備えていると安心な仕組み。

アプリのバージョン制御(強制バージョンアップ)

万が一、深刻なバグのあるアプリをリリースしてしまっても、そのバージョンを利用不可にできる。

用意するもの:APIサーバ


方法1 (サーバ寄せ)
  1. アプリがAPIにバージョン番号を送る
  2. APIでバージョン番号を見て、バージョンアップが必要ならURLを返す
  3. アプリはURLが返された場合、AppStoreやGooglePlayに遷移する

方法2 (アプリ寄せ)
  1. アプリはAPIにリクエストする
  2. APIは現在利用可能なバージョン番号下限とバージョンアップURLを返す
  3. アプリはAPIが返したバージョンよりも自身のバージョンが低ければ、AppStoreやGooglePlayに遷移する。
方法2ならjsonやxmlの静的ファイルをサーバに置くだけで実現できる。


メンテナンスモード

DBを止めて作業するとき(機能追加によるテーブルの変更、DBのスケールアップなど)に必要。ユーザにメンテ中であることを知らせることができる。


用意するもの:APIサーバ

実現方法
  1. アプリは起動時にAPIにアクセスする
  2. サーバからの応答が503だった場合、アプリは「サーバがメンテナンス中」と判断し、しばらくAPIサーバとの通信をしない状態にする。
    レスポンスヘッダにRetry-Afterがあれば、その時間までメンテナンスモードになるのが理想。





2013年10月14日月曜日

Using Amazon SNS Mobile Push APIs 日本語訳

日本語訳したドキュメント


Amazon Simple Notification Service
Developer Guide (API Version 2010-03-31)

http://docs.aws.amazon.com/sns/latest/dg/mobile-push-api.html

Using Amazon SNS Mobile Push APIs

Amazon SNS モバイルPush APIを使うためには、APNsやGCMなどのPush通知サービスの事前準備を行っておく必要があります。詳細情報についてはPrerequisitesのページを参照。
Amazon SNSのAPIを使ってモバイル機器へとPush通知を送るためには、最初にCreatePlatformApplicationアクションを実行しておく必要があります。CreatePlatoformApplictionアクションはPlatformApplitionArmを返すので、これを使ってCreatePlatformEndpointアクションを実行し、最終的に通知実行に必要なEndpointArnを取得できます。

EndpointArnの用途は

  • PublishアクションにEndpointArnを指定して通知メッセージを送信する
  • SubscribeアクションにEndpointArnを指定してトピックにEndpointを紐付ける

以下はAmazon SNSのモバイルPush APIについてのAPI一覧とその解説です。
APIDescription
CreatePlatformApplicationAPNsやGCMのようなPush通知サービスをサポートするplatform applicationオブジェクトを作成し、モバイル機器を登録できるようにします。
APIの戻り値としてはPlatformApplicationArnがあり、これはCreatePlatformEndpointアクションで利用されます。 詳細はAmazon SNS  APIリファレンスの、CreatePlotformApplicationの項目を参照。
SetPlatformApplicationAttributesplatform applicationオブジェクトに各属性値をセットします。
詳細はAmazon SNS APIリファレンスのSetPlatformApplicationAttributesの項目を参照。
GetPlatformApplicationAttributesplatform applicationオブジェクトの属性値を取得します。詳細はAmazon SNS APIリファレンスのGetPlatformApplicationAttributesの項目を参照。
ListPlatformApplicationsAmazonSNSがサポートしているPush通知サービスに関して、AWSアカウントで作成済みのplatform applicationオブジェクトの一覧を取得します。詳細はAmazon SNS APIリファレンスのListPlatformApplicationsの項目を参照。
DeletePlatformApplicationplatform applicationオブジェクトを削除します。 詳細はAmazon SNS APIリファレンスのDeletePlatformApplicationの項目を参照。
CreatePlatformEndpointAmazonSNSがサポートしているPush通知サービスに関して、モバイル機器用のエンドポイントを作成します。CreatePlatformEndpointアクションには、CreatePlatformApplicationアクションが返すPlatformApplicationArnを入力します。このアクションが返すEndpointArnは、Publishアクションで通知メッセージをモバイル機器へ送信する際に使われます。詳細はAmazon SNS APIリファレンスのCreatePlatformEndpointの項目を参照。

SetEndpointAttributesエンドポイントへモバイル機器やアプリに関する属性をセットします。詳細はAmazon SNS APIリファレンスのSetEndpointAttributesの項目を参照。
GetEndpointAttributesエンドポイントに紐づく情報を取得します。詳細はAmazon SNS APIリファレンスのGetEndpointAttributesの項目を参照。
ListEndpointsByPlatformApplicationAmazonSNSがサポートしているPush通知サービスに関して、エンドポイントをリストとして取得します。詳細はAmazon SNS APIリファレンスのListEndointsByPlatformApplicationの項目を参照。
DeleteEndpointAmazonSNSがサポートしているPush通知サービスに関して、エンドポイントを削除します。詳細はAmazon SNS APIリファレンスのDeleteEndpointの項目を参照。

まとめ

  • 通知を送るにはEndpointArnを指定する
    • もしくはエンドポイントとトピックを紐付けておく
  • EndpointArnを作成するには事前にPlatformApplicationArnを作っておく
    • PlatformAPplicationArnは各通知サービスごとに作成する
      • ADM、APNs本番、APNs砂場、GCM
  • 作成したエンドポイントには端末やアプリの情報を紐付けておくことができる
  • エンドポイントは削除できる(ただし、EndpointArnの指定が必要)
  • platform applicationを削除した際に、紐づくエンドポイントも連動して削除されるかは不明

2013年10月13日日曜日

Amazon SNS Mobile Push Notifications のDeveloper Guide 日本語訳


日本語訳したドキュメント


Amazon Simple Notification Service
Developer Guide (API Version 2010-03-31)

http://docs.aws.amazon.com/sns/latest/dg/SNSMobilePush.html




以下、訳した文章

Amazon SNS Mobile Push Notifications

Amazon SNSによりモバイル機器上のアプリへ通知メッセージを送ることができます。AWS上の“モバイルエンドポイント” へ送られた通知メッセージは、モバイル機器上ののアラート文言、バッジ、サウンドなどの形態で受け取られることになります。 モバイル機器への通知サービスがとして、以下に挙げるサービスが利用できます。
  • Apple Push Notification Service (APNS)
  • Google Cloud Messaging for Android (GCM)
  • Amazon Device Messaging (ADM)

以下の図はAmazon SNS がモバイルエンドポイントへ送られた通知メッセージが如何にしてモバイル機器へと届けるのか、その全体図を示します。

訳者注: Publisher = 通知の送信側 ,    Subscriber=通知を受信するモバイル機器

APNsやGCMのような通知サービスは、モバイル機器とのコネクションを保持しています。アプリとモバイル機器が通知サービスに登録されると、通知サービスはデバイストークンという端末を識別する情報を発行します。Amazon SNSはPublisher(通知送信側)からの通知要求を受け付けるために、デバイストークンを使ってモバイルエンドポイントを作成します。Amazon SNSがPublisherに変わって通知サービスとの通信するため、通信に用いるSSLの秘密鍵などの情報はAmazon SNSに前もって登録しておきます。
通常のエンドポイント指定の通知送信に加えて、”トピック”という機能を利用できます。 モバイルエンドポイントはトピックを購読することができ、トピックに通知メッセージを送ることでトピックに紐づくモバイルエンドポイントすべてへ通知を送ることができます。このため、AmazonSQS, HTTP/S、メール、SMSなどエンドポイントの種類が異なっていても、1つの通知の送信はトピックへ送信依頼の処理1回で済ませることができます。Push通知がメールなどの他のサービスと異なるのは、モバイル機器がメッセージを受信するまでの間にAppleやGoogle などの通知サービス提供者が入っていることです。以下の図はAmazon SNSのトピックを購読するモバイルエンドポイントの例です。 モバイルエンドポイントはAPNsやGCMを通じてアプリに通知メッセージを送りますが、メールなどその他の場合には直接SNSトピックから通知メッセージが送られます。


まとめ


  • 基本的に通知送信側はモバイルエンドポイントを宛先として知っていればよい
    • デバイストークンはAmazonSNS側で管理
    • SSL証明書などのパラメータは前もってAmazonSNSにアップロードしておく
  • 通知の送り先の指定方法として2種類ある
    • エンドポイントに送信
    • トピックへ送信(トピックに紐づくエンドポイントに送信)
  • トピックを使うと通知の種類をまたがった通知が簡単に実現できる
    • 例)iOSアプリにはAPNs、AndroidアプリにはGCM、PC版利用者にはメール
    • 例)iOSアプリ利用者にAPNsとメールを両方送る





2013年1月3日木曜日

残念なメンバーシップの改善方法

目的

標準的な管理手法を理解し、マネージャによるコントロールがしやすい状態にしたい。
そのために、自分が日頃何に気をつければよいか明文化し、それを守る。

改善策

なにをやるか・やらないかの確認

  • スコープ:このプロジェクトは何を作るのか
  • 職責:“自分の”役割と説明責任を理解する
  • 見積もり: 1日8時間、自分の力量を過信せずバッファ込みで

やるべきことをどんな順番でやるか

  • プロジェクトのマイルストーンは何か?いつまでに必要か?
  • 自分の成果物は何か?
    • 不確定なこと、失敗しそうなことはないか?
    • 大変なこと、面倒なことは別の方法で代替できないか?

状況の変化に対してどのように対処するか

  • 成果とスケジュールがベースラインからずれるとき、報告が必要
    • 相手が状況を理解し、対処策の判断できる状態になるまで伝えきる
    • リスクは被害の深刻さと発生確率を評価し、優先度付できるようにする

2013年の目標

はじめに

2012年はリスク管理ができず、いろいろと残念なことがありました。仕事だけに楽しみを見出すことの危険性がよくわかりました。

2013年の目標

  • 楽しいと思えることを1つ以上見つけ、仕事以上にそれを大事にする

評価方法

2013年12月31日に「今年は人間らしく生きた?」と自分に問い、「肯定」の回答が得られること。