Lento con forza

大学生気分のIT系エンジニアが色々書いてく何か。ブログ名決めました。

Webサービスのアジャイル論

と、まぁ、こんな大仰なタイトルを付けてしまって申し訳ないですが、僕はアジャイラーなわけでもないです。思ったことを書きなぐってみました。

アジャイル

アジャイルアジャイル、と結構もてはやされている気がします。僕が「アジャイル」と言う言葉を初めて聞いて、少し調べてみた時のイメージはこうでした。

ウォーターフォール?スパイラル?なんて面倒。仕様なんてどうせ書いたって読まないんだし、さっさとコード書いちゃえばいいじゃん。動く物が一番だよ。コードを書こう。Let's write more code!

結論から言うと、この認識は結構間違ってたわけですけど、少し、ほんの少しだけ本当のアジャイルを知り、まぁこういう所もあるよなぁと思ってきています。

ソフトウェアは誰のための物か

答えは簡単です、ソフトウェアは使う人のためにあります。業務系のソフトウェアであれば、日々の業務を少しコンピューターに肩代わりしてもらうために。TwitterFacebookのようなSNSであれば、日々の生活を少しだけ豊かに、楽しくするために。LINEのようなアプリは、もはやインフラと化していて、使う人に取っては必要不可欠なものになっています。
この、「使う人」即ちユーザーにソフトウェアを提供することによって価値を届けることが、僕達エンジニアの仕事でもあり、使命でもあるわけです。アジャイル開発宣言には、このように書かれています。

プロセスやツールよりも 個人と対話 を、
包括的なドキュメントよりも 動くソフトウェア を、
契約交渉よりも 顧客との協調 を、
計画に従うことよりも 変化への対応 を、
価値とする。すなわち、左記のことがらに価値があることを
認めながらも、私たちは右記のことがらにより価値をおく。

ここでいう顧客がユーザーは同じ事を指しています。
この開発宣言の左記と右記を比べると、左記は開発者目線で、右記はユーザー目線で利益になるような事が書かれているように思います。
アジャイル開発とは、 顧客(ユーザー)の価値を一番に考えた開発手法 と言っていいでしょう。ソフトウェアはユーザーのためにあるのですから、ここがちゃんとしていないといいソフトウェアができないのも頷けるし、時代がアジャイルの流れになってきた事にも頷けます。

Webサービスにおいては、開発者が最強のユーザー

僕の好きな漫画「電波教師」に出てくるキャラクターの漫画家である天上院 騎咲先生は、同書内で天上院先生が書いている漫画の「終末学園」について、「自分がこの漫画の最大の理解者で一番のファンだ」と言っていました。(ややこしい・・・。この漫画は本当に好きなので、是非読んで欲しいです。)
Webサービスにおいて、開発者は最強のユーザーです。しかし、業務改善系のソフトウェアだとこうは行きません。エンジニアが直接業務をする事はないからです。Webサービスの場合だと、普段から自分でサービスを使う事ができます(出来る場合が多いです)。気に入らない所があれば自分で直す事ができますし、最新版をいち早く使う事だってできます。一般ユーザーにはない開発者のメリットを上げ始めたらキリがありません。

ソフトウェアのゴールはユーザーに価値を与える事であり、ソフトウェアをユーザーに届けることではありません。

開発者自信がユーザーになることができるWebサービスの世界では、開発者がハードユーザーになることによって、更にアジャイルの効果を高める事ができるでしょう。まぁ、独りよがりになってはいけないですが・・。

アジャイルは全然適当じゃない

アジャイル開発」と聞いて、何を思い浮かべるか?
この答えは、僕は結構長い間、「テキトーな開発」でした。
この認識の人は僕だけだといいんですが、ちゃんと勉強すると全然違っていました。
今、「アジャイル開発」と聞いて、何を思い浮かべるか? という質問をされると、こう答えるでしょう。

「ユーザーに価値を届ける事を考え、その価値を最大化するための開発手法」

なんでこんな事を書いたか

本当はもっと書きたい事がたくさんあるんですけど、色々と足りません。主に知識と経験です。そうなんです、買ったけど読んでいない、買おうと思ってるけど買っていないアジャイルの本がたくさん積まれているのです。作りたいWebサービスも、アプリも、どんどん溜まっていくし、その割に土日は怠けているし。そんな自分へやる気を出させるために書きました。もっとちゃんとアジャイル勉強します。サービスも作ります。

結局これは、自分のために書きました。 このエントリは全然アジャイルじゃありませんでした。残念。

毎日コードを書いていきたい。

毎日コードを書いていきたいものだ。

最近のGithubはひどい。

f:id:kouki_dan:20150614001315p:plain

なんていうか、2月くらいからどうしたんだ自分。

緑でまっ緑にすることを目指して頑張って行きたい。

UIDocumentInteractionControllerでクラッシュ

Swiftが熱いですね!
Objective-Cなんてもう忘れ去れるくらい最近Swiftしか書いてないです。
ということで、Swiftを使ってちょっとハマった事をメモ。

外部アプリにファイル転送

外部アプリにファイルを転送するときに便利なものに、UIDocumentInteractionControllerがあります。よく見る、Open In...ってやつですね。これを普通に使うと

    @IBAction func showOpenIn(sender: AnyObject) {
        if let url = NSBundle.mainBundle().URLForResource("pdf_name", withExtension: "pdf") {
            let dic = UIDocumentInteractionController(URL: url)
            dic.delegate = self
            dic.presentOpenInMenuFromBarButtonItem(openInButton, animated: true)
        }
    }

このようなコードになると思います。
しかし、このまま実行すると、iBooksを選択することまではできるのですが、選択した後落ちてしまいました。
ちなみに、シミュレーターだとUIDocumentInteractionControllerがうまく動かないため、実機で実行する必要があります。

こうすればOK

上記のコードで落ちてしまう原因は、関数が終了した段階でGCdicインスタンスを開放してしまうことが原因のようです。そのため、今回はクラスのインスタンス変数にインスタンスを保持する事で解決しました。コードにすると以下のようになります。

    var dic:UIDocumentInteractionController?
    @IBAction func showOpenIn(sender: AnyObject) {
        if let url = NSBundle.mainBundle().URLForResource("pdf_name", withExtension: "pdf") {
            dic = UIDocumentInteractionController(URL: url)
            dic?.delegate = self
            dic?.presentOpenInMenuFromBarButtonItem(openInButton, animated: true)
        }
    }

cocos2d-xで尋常じゃない数の線を引きたい時

最近cocos2d-x v3.3でゲーム作ってます。
ゲームの実装で、タッチの動きに合わせて線を引きたかったのですが、素直に実装したらとんでもないことになりました。

DrawNodeで実装

まず最初は、タッチ位置が移動する度にDrawNodeのdrawSegumentを呼び出す方法で実装しました。コードにするとこんな感じです。

    auto drawNode = DrawNode::create();
    this->addChild(drawNode);
    
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = [this](Touch* touch, Event* event) {
        auto pos = touch->getLocation();
        this->prevPos = pos;
    
        return true;
    };
    listener->onTouchMoved = [drawNode, this](Touch* touch, Event* event) {
        auto pos = touch->getLocation();
        drawNode->drawSegment(prevPos, pos, 10, Color4F(1,1,1,1));
        
        this->prevPos = pos;
    };
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);

これで一応要件は満たせます。後はcocos2d-xが内側でいい感じに処理をしてくれればいいなーって考えてたんですけど、実行するとこんな感じに。

f:id:kouki_dan:20150211184054p:plain

注目すべきは左下です。

左下のフレームレートとGL Vertsの数がヤバイことに。
元は60フレームだったのに、線の描画が重くて10フレームまで落ちてます。
これじゃあ使えないなぁと思い別の方法を探索。

RenderTextureで実装

DrawNodeは毎回線全部を描画しているのが問題なので(多分)、別の方法で描画してあげれば処理が早くなりそうです。
調べた感じ、RenderTextureを使えばこの問題を解決できそうだったのでこれを採用することに。
グラフィックの世界の専門用語はよくわからないですが、オフスクリーンレンダリングと呼ばれる手法らしいです(間違ってたらごめんなさい)
DrawNodeで線を描画して、RenderTextureにレンダリング といった処理を繰り返す実装にしてみました。

以下コードはこんな感じ。

    Size size = Director::getInstance()->getVisibleSize();
    auto renderTexture = RenderTexture::create(size.width, size.height);
    renderTexture->setPosition(Vec2(size.width/2, size.height/2));
    this->addChild(renderTexture);
    
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = [this](Touch* touch, Event* event) {
        auto pos = touch->getLocation();
        this->prevPos = pos;
        
        return true;
    };
    listener->onTouchMoved = [renderTexture, this](Touch* touch, Event* event) {
        auto pos = touch->getLocation();
        
        renderTexture->begin();
        auto drawNode = DrawNode::create();
        drawNode->drawSegment(prevPos, pos, 10, Color4F(1,1,1,1));
        drawNode->visit(); // RenderTextureにレンダリング
        renderTexture->end();
        
        this->prevPos = pos;
    };
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);

こちらの実行結果はこんな感じです

f:id:kouki_dan:20150211185230p:plain

見た目は一緒ですが中身と速度は全く違います。

フレームレートが30になってしまったのですが、最初から30だったので問題なし(?)です。
RenderTextureを使うと30よりあがらない制限があるのかな・・?詳しくは調べてないからわからないです。

線を描画することによって処理が重くなっている様子もなく、ゲームにも使えそうです!
DrawNodeが重いわ!!って方はRenderTextureを使って描画する方法もお試しください!

WebStormをVim化できない?

なんかWebStorm学生無料になってるじゃないですか!! 全然知りませんでした。

とりあえず、インストールして、Vimキーバインドを適用しようとしたんですが。

Browse repositoriesからインストールできない。

Browse repositoriesから、IdeaVimを検索して、インストールをしようとしたんですが、うんともすんとも言いません。
なんでなんだろうなー。って思いましたけど、悩むのが嫌だったので別の方法で入れちゃいました。

jarファイルを直接インストール

JetBrainsのプラグインページから直接jarファイルを落として、Install plugin from disk...から、直接jarファイルをインストールしちゃいました。

この方法では問題なくインストール可能。

なんでプラグインマネージャーからインストールできなかったんだろうなぁ・・・。

Swiftでのユニットテストでクラスが見えない問題

XcodeでProjectを作った時にデフォルトで作られるテストケースでのユニットテストで、少し(だいぶ)はまったのでメモして起きます。

Testsファイルから自作クラスが見えない?

Testsファイルから、自作クラスをインスタンス化しようとすると

f:id:kouki_dan:20141115094605p:plain

このように、クラスが見えなくインスタンス化できない状態になってしまいました。

Targetがプロダクト用とテスト用で別だからできないのかなぁ。と思いつつ、不便だなぁ。とずっと思ってたんですが・・。

解決策

テストしたい対象のswiftファイルのTargetsに、~~Testsも追加してあげれば解決します。

ファイルを作成する時に、ここのTargetsを

f:id:kouki_dan:20141115095107p:plain

このように、チェックを入れるのを忘れないようにすれば~~Testsからでもクラスが見えるようになります!

f:id:kouki_dan:20141115095212p:plain

既存ファイルの場合・・・

既存ファイルの場合でも、簡単にTargetを変更可能です。

f:id:kouki_dan:20141115095452p:plain

File Inspector内の、Target Membershipのチェックを変更することで、ファイル作成時に設定したTargetを自由に変更する事が可能です!

まとめ

よく考えてみたら当たり前だけど、なかなか気付きにくいんじゃないかなぁって思います。
classとmethodをpublicにしないとユニットテストできねーじゃん!!とか最初思ってたんですが、そんなことはなかったです。

Swiftで簡易APIクライアント

AFNetworkingを使った簡易クライアントをSwiftで書いてみました。

Objective-CのライブラリをSwiftで利用

Cocoa Podsかなんかで適当にAFNetworkingを入れておきます。

SwiftでCocoaPodsを使う - Qiita

APIClientを作成

AFHTTPSessionManagerのサブクラスを作る形でAPIクライアントを作成します。
AFHTTPSessionManagerはiOS6以下では利用することができないので、iOS6以下をターゲットにしている方は以下の方法は使えません。

MyClient.swift

import UIKit

class MyClientRequestSerializer: AFHTTPRequestSerializer{
    override func requestWithMethod(method: String!, URLString: String!, parameters: AnyObject!, error:NSErrorPointer) -> NSMutableURLRequest! {        
        var path:NSString = URLString + "?"
       
        var dictParameters:Dictionary<String, String>
        if (parameters != nil){
            dictParameters = parameters as Dictionary<String, String>
        }
        else{
            dictParameters = Dictionary<String, String>()
        }
        
        for key in dictParameters.keys{
            let value = dictParameters[key]
            
            path = path.stringByAppendingFormat("&%@=%@", key, value!)
        }
        
        let req = super.requestWithMethod(method, URLString: path, parameters: nil, error: error)
        
        return req
    }
   
}

class MyClient: AFHTTPSessionManager {
   
    class var sharedInstance : MyClient {
        struct Static {
            static var instance : MyClient = MyClient(baseURL:NSURL(string:"https://api.github.com/"))
        }
        return Static.instance
    }
    
    override init(baseURL url: NSURL!, sessionConfiguration configuration: NSURLSessionConfiguration!) {
        super.init(baseURL: url, sessionConfiguration: nil)
    }
    
    override init(baseURL url: NSURL!) {
        super.init(baseURL: url)
        let requestSerializer = MyClientRequestSerializer()
        self.requestSerializer = requestSerializer
        // If you need a responseSerializer, you can write it
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func yourAPI(){
        self.GET("users/kouki-dan/repos",
            parameters: ["none":"none"],
            success: { (task, response) in
                println(response)
            },
            failure: { (error) in
                println("error")
        })

    }
    func yourAPI2(id:NSString){
        //...
    }
    //.......
}

APIクライアントはこのような感じになります。
yourAPIの所でエンドポイントを記述して、実際にHTTPリクエストを呼び出しています。

APIを利用する時は

func yourFunc(){
    let sharedClient = MyClient.sharedInstance
    sharedClient.yourAPI()
}

のように使います。

簡単な例をgithubにあげときました。

kouki-dan/SimpleAPIClient · GitHub