パルカワ2

最近はFlutterをやっています

27歳になった。

良かった

26歳は、Androidアプリエンジニアになったし、シニアエンジニアになったし、仕事ではサーバサイドのコードを全く書かなくなった。
変化に耐えれる力が、エンジニアとして生きていく上で自分に必要な事だと思っていたんだけど、ここ1, 2年でやることが大きく変化していて、そこは自分でも納得出来るくらいには出来るなと感じるようになった。

変化に耐えれる力とは、環境の変化だけではなくて、仕様の変化に耐えれるという意味で、コード上でも言えると思う。今まで途中から関わったプロジェクトはテストのカバレッジが高いものばかりだったので、既存のコードが大量にあるけどテストが全くなく一向に増えない状態からテストが少しずつ増えていく状態にするのは経験がなく、そこを経験出来てよかった。まだまだ少ないけど、チームメンバーのおかげで確実に増えてきている。

また1つのクラスにコードがまとまりがちだったのを、クラスの責務をちゃんと分けていきましょうという話をチームメンバーにして、それもうまく機能しつつあると思う。

気になる

幅広くやることを増やした結果、Androidの知識は元々ないし、サーバサイドの知識は忘れていくしで浅くなってきたと感じる。「こういうのあるんじゃないですかね?」とか「こういう感じだったような?」みたいな会話が明らかに増えた。

こうする

Android一本に絞るぞという気持ちでいる。

pebble time round買った

軽くて本物の時計っぽいスマートウォッチ欲しいと思ってたら年末に公式サイトで安売りしていたので買った。見た目もかわいいし最高。

歩数はほとんど見てないけど、睡眠時間は見ている。寝起き良いわ〜って時は、Deep sleepが3時間超えてるじゃん!すごい!みたいに一人で盛り上がっている。

アプリは、タイマーアプリと Tomato というポモドーロテクニックのアプリ入れてるくらい。

弱点をあげるならTomato使ってる時にバイブレーションが弱くて時々気づかないのとiPhone見てる時は震えないでほしいくらい。
総合的には大変満足しています!!!!!!!1
ちなみに公式サイトだと10%のクーポンもらえるので使うと良さそう。

追記

ウェーブハンディワイパー最高便利

家の机まわりの掃除したい時、でかい掃除機を出すしかなくてだるくて仕方がなかった。しかもディスプレイに付いた埃とか取れない。なので、会社で使っているハンディワイパーを買った。
今回買ったのは、ケースが付いていて置き場に困りにくく机の近くにおけるので、アプリをビルドしてる時とかちょっとした隙間時間にササッと掃除出来て最高便利。最高だったので、Amazon定期便で取替シート買うようにした。

ウェーブハンディワイパー 本体+1枚(ケ-ス付)

ウェーブハンディワイパー 本体+1枚(ケ-ス付)

ウェーブ ハンディワイパー 掃除用 取替えシート 8枚

ウェーブ ハンディワイパー 掃除用 取替えシート 8枚

最近読んだ本

1分間リーダーシップ―能力とヤル気に即した4つの実践指導法

1分間リーダーシップ―能力とヤル気に即した4つの実践指導法

読み返した。
新1分間リーダーシップというのが、新しく出たっぽいのでそれも読んでおきたい。

新1分間リーダーシップ

新1分間リーダーシップ


10分の面談で部下を伸ばす法

10分の面談で部下を伸ばす法

寝る前にざっくり読み返した。

酒に酔った勢いでかはわからないけど、いつの間にか買っていて、読んでみるととても良かった。「伝える力」というのも一つの技術力であるな、と感じる。

5巻以内の最高の漫画知りたいとか言ってたら教えてもらった。やれやれだぜという気持ちで読み進めた。

最近読んだ本

途中までしか読んだことがなかったので最後まで読めてよかった。

仲の良い友達とともに大金のかかったゲームに挑戦! ゲームクリアのコツは“友達を疑わないこと”。だが、一度の裏切りが疑念を生み、ゲームは息づまる心理戦となる! 金か友か? 人の心をゆさぶる究極の頭脳ゲーム漫画、誕生!

マンガボックスで読んでいて、続きが気になったので買った。

「この世界はマンガ、僕が主人公なんだ」

中学二年生の津乃峰アリスは、クラスメイトの星野宇宙から「自分達がいる世界はマンガで、僕が主人公である」と告白された。そう言われてみると、確かに見える「フキダシ」。常に“何者か”に“読まれている”状態での学校生活…アリスのプライベートも、心の中も、すべて丸見えに? そしてその先にある「真相」とは? IKKI新人賞・イキマン出身の実力派新人作家、超待望の初単行本!

面白かった。紙で読んだほうがよかったかもしれない。2巻完結。

MVPアーキテクチャを使いたいという話をチームにした。

最近Androidアプリを開発していて、チームメンバーみんなでテストを書いたりしているわけなのですが、いかんせんUIテストを書くことになりがちで自分も含めみんなが疲弊しているなぁと感じていました。

せっかくみんながテストを書こうとしていて、非常に良い状態なのにこのままだとテストを書くことが疲れること(=やりたくないこと)になると思えたので、色々考えた結果、チームメンバーにMVPアーキテクチャを使いたすぎる!とプレゼンをしたので、資料を公開します。

チーム向けに話したので、補足です。

  • 全てのアプリで、MVPアーキテクチャを必ず使うべきという話ではない
  • Roboletric 3.xは、Activityを起動出来たりするわけですが、今のプロジェクトではだいぶ頑張らないとそれが動かないので、その機能は使っていません
  • hisaichi5518/minne-android は、僕が勝手にMVPアーキテクチャでminneの一部機能を書き直したやつなので公開できません
  • 資料を作って話すのだいぶめんどいけど、伝わりやすいと思うので良さそう

以下、参考にしたリンクです。

https://github.com/rallat/EffectiveAndroid
http://tomoima525.hatenablog.com/entry/2015/08/13/190731
http://konifar.hatenablog.com/entry/2015/04/17/010606

こういう感じの話をもう少し膨らませてDroidKaigi 2016で話したかったんですが、CFPの提出に間に合わなかったので諦めました。こちらからは以上です。

Retrofitでテストするときは、MockClientを作る

RestAdapter.BuildersetClient()できるので、テスト時はそこにテスト用のClientをセットしてあげる。そのテスト用のClientをMockClientとしました。

本当は、Clientをわざわざ作るのがめんどくさいなと思って作らない方針で行こうとしたんですが、基盤チームの人にsetClientする方法があるでと改めて指摘されたあと考えなおして、Clientを作るのはだるいけど作ったほうが後々楽でわかりやすいなと思えてきたので作りました。

流れとしては、以下のような感じを想定しています。

  • @BeforeでMockClientをセットする(正確にはRestAdapterをセットする)
  • @Testで なにをどうモックするか指定する
  • @Afterで指定したモックをクリアする
// MockClientTest.java
public class MockClientTest {
    MockClient mMockClient = new MockClient();
    @Before
    public void setUp() throws Exception {
        // RestAdapterを作ってセット
        ApiClient.getInstance().setRestAdapter(RestAdapterFactory.newMocking(mMockClient));
    }

    @After
    public void tearDown() throws Exception {
        // 次のテストで引き継がないようにclear
        mMockClient.clear();
    }

    @Test
    public void testMock() throws Exception {
        // ここでAPIのbodyはなにを返すか指定する
        mMockClient.mock("/v1/reviews/100.json").to(200, "reviews/ok.json");
        Review review = ApiClient.getInstance().create(ReviewsService.class).fetch(100);

        assertThat(review.getStar()).isEqualTo(5);
    }

    @Test
    public void testMock_route() throws Exception {
        MockClient.Route route = mMockClient.mock("/v1/reviews/100.json").to(200, "reviews/ok.json");
        ApiClient.getInstance().create(ReviewsService.class).fetch(100);

        assertThat(route.called()).isEqualTo(true); // mockは呼ばれたか?
        assertThat(route.times()).isEqualTo(1); // 呼ばれた回数
    }

    @Test
    public void testMock_regex() throws Exception {
        // 正規表現でも可能
        MockClient.Route route = mMockClient.mock("/v1/reviews/\\d.json").to(200, "reviews/ok.json");
        ApiClient.getInstance().create(ReviewsService.class).fetch(100);

        assertThat(route.called()).isEqualTo(true);
    }
}
// MockClient.java
public class MockClient implements Client {
    List<Route> mRoutes = new ArrayList<>();
    Route notFoundRoute = new Route("404").to(404, "response/not_found.json");

    @Override
    public Response execute(Request request) throws IOException {
        for (Route route : mRoutes) {
            Response response = route.match(request.getUrl());
            if (response == null) {
                continue;
            }
            return response;
        }

        return notFoundRoute.newResponse(request.getUrl());
    }

    public Route mock(String urlPath) {
        Route route = new Route(urlPath);
        mRoutes.add(route);

        return route;
    }

    public void clear() {
        mRoutes = new ArrayList<>();
    }

    public class Route {
        Pattern mUrlPathRegex;
        int mStatus;
        int mTimes;
        String mJsonPath;

        Route(String urlPath) {
            mUrlPathRegex = Pattern.compile(urlPath);
        }

        public Route to(int status, String jsonPath) {
            mStatus = status;
            mJsonPath = jsonPath;

            return this;
        }

        public int times() {
            return mTimes;
        }

        public boolean called() {
            return mTimes >= 1;
        }

        private Response match(String url) throws IOException {
            Matcher matcher = mUrlPathRegex.matcher(url);
            if (!matcher.find()) {
                return null;
            }

            return newResponse(url);
        }

        private String getContentType() {
            return "application/json";
        }

        private Response newResponse(String url) throws IOException {
            // https://github.com/gfx/Android-Helium/blob/master/app/src/test/java/com/github/gfx/helium/TestUtils.java#L22
            byte[] body = TestUtils.getAssetFileInBytes(mJsonPath);

            mTimes++;
            return new Response(
                    url,
                    mStatus,
                    "mocked-response",
                    new ArrayList<Header>(),
                    new TypedByteArray(getContentType(), body)
            );
        }
    }
}

テストが並列で走ることとかはまだ考えてないです。
Clientを作るのめんどいと思ったけど、案外すぐ出来たので、そういうの良くないなって思いました。