UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

インターンで初プルリクエストを出してマージされるまで

こんにちわ。2019年4月15日にUUUMにインターンとして入社したれとるときゃりーです。

インターンに入るまで、個人でサービスを作るなどして開発はしていましたが、実際の会社に入ってコードを書くのは初めてでした。

ちょうどUUUMに入って任された一つ目のタスクが終わったので、振り返りを込めてブログに執筆していこうと思います。

f:id:retoruto-carry:20190529170529p:plain
プルリクがマージされたところ

前提条件

laravel+nuxt.jsのプロダクトにアサインされました。

どのような実装を任されたか

外部のRSSを受け取って、それをサービス内で表示するようにします。

※ RSSとは

RSSは、ニュースやブログなど各種のウェブサイトの更新情報を配信するための文書フォーマットの総称である。( wikipediaより引用)

どのように実装したか

※ コードは一部省略しています

実装の方針

  • バックエンド側は、RSSを受け取り、タイトルやサムネイル画像のURLなど必要な部分のみを抜き出してAPIとして返します

  • フロント側は、バックエンド側のAPIを叩いて表示します

バックエンド側(laravel)

RSSを受け取る

ライブラリを使えば簡単でした。willvincent/feedsを使用しました。

サムネイル画像は簡単に取得できませんでした。記事中の一番最初に出てくるimgタグの中のsrcを取ってくる必要がありました。

画像のパース部分は、最初は正規表現で実装していました。

// 記事の本文の中からimgタグを抽出して画像URLを取得
$pattern = '/<img.*?src\s*=\s*[\"|\'](.*?)[\"|\'].*?>/';
if (preg_match($pattern, html_entity_decode($feedItem->get_content()), $match)) {
    $image = $match[1];
} else {
    $image = null;
}

しかしこの実装だと、記事側でのタグの表記揺れがありうまくいかないときがありました。

正規表現はつらいのでやめました😇

HTMLパーサーのライブラリammadeuss/laravel-html-dom-parserを導入して解決しました。

// 記事の本文の中からimgタグを抽出して画像URLを取得
$image = HTMLDomParser::str_get_html(html_entity_decode($feedItem->get_content()))->find('img')[0]->attr['src'];

テスト

テストは書いたことがなかったので、かなり苦戦しました。 他のテストを参考に、laravelとPHPUnitのドキュメントを見ながら書きました。

上述した、willvincent/feedsをモックする必要があったので、中身を少し読む必要がありました。

個人でサービスを作っている中では、ライブラリのコードをまったく見たことなかったので新鮮でした。

「Qiitaなどで調べるより、直接コードを見たほうが早いので癖をつけたほうが良い」ということを先輩に教えてもらいました。

エディタのコードジャンプ機能も、この時教えてもらいました。(今まで知らなくて1年位やってきたので便利すぎて泣きました😭)

書いたテストはこんな感じです。

<?php
namespace Tests\Unit\ControllerTest\Api\V1;
use Mockery;
use Tests\TestCase;
use App\Http\Controllers\Api\V1\HogeController;
use Feeds;
class HogeControllerTest extends TestCase
{
    private $hogeController;
    /**
     * {@inheritdoc}
     */
    public function setUp()
    {
        parent::setUp();
        $this->hogeController = new hogeController();
    }
    /**
     * Test Get List Hoge
     *
     * @dataProvider listHogeDataProvider
     * @param array $hoges データセット
     * @param array $expect 期待値
     * @return void
     */
    public function testFetchListHoge(array $hoges, array $expect)
    {
        $fetchedTopics = [];
        foreach ($hoges as $hoge) {
            $simplePieItem = $this->mock('SimplePie_Item');
            $simplePieItem->shouldReceive('get_content')->andReturn($creatorTopic['content']);
            $simplePieItem->shouldReceive('get_title')->andReturn($creatorTopic['title']);
            $fetchedTopics[] = $simplePieItem;
        }
        $feed = $this->mock('SimplePie');
        $feed->shouldReceive('get_items')->withAnyArgs()->andReturn($fetchedTopics);
        $feed->shouldReceive('get_title')->andReturn('title');
        Feeds::shouldReceive('make')->with(Mockery::any())->andReturn($feed);
        $response = $this->json('GET', 'api/v1/hoge');
        $response->assertJson($expect);
    }
    /**
     * @return array
     */
    public function listHogeataProvider()
    {
        return [
            "正常" => [
                [
                    [
                        'content' => '&lt;figure class=&quot;block-image&quot;&gt;'
                            . '<img src="https://example.com/uploads/img1.png" alt="image" width="auto" height="auto">'
                            . '&lt;/figure&gt;',
                        'title' => 'title1',
                    ],
                    ...
                ],
                [
                    'title' => 'title',
                    'hoges' => [
                        [
                            'title' => 'title1',
                            'image' => 'https://example.com/uploads/img1.png',
                        ],
                        ...
                    ],
                    ...
                ]
            ],
            

        ];
    }
    /**
     * @param $class
     * @return Mockery\MockInterface
     */
    private function mock($class)
    {
        $mock = Mockery::mock($class);
        $this->app->instance($class, $mock);
        return $mock;
    }
}

DataProviderを利用することで綺麗にまとまりました。とても便利ですね。

フロント側(nuxt.js)

記事の表示

プロジェクト内はアトミックデザインを意識している構成でした。

アトミックデザインは、詳しくなかったのでいろいろと調べてみましたが、結局良くわかっていません。

変更前

もともと記事を表示するためのカードがあったので、これを再利用したいと考えました。

CardList.vue

<template>
  <div>
    <ul>
      <li v-for="post in posts" :key="post.id">
        <card v-bind="{ post }" />
      </li>
    </ul>
  </div>
</template>

<script>
import Card from "~/components/molecules/Card";

export default {
  name: "CardList",
  components: { Card },
  props: {
    posts: {
      type: Array,
      default: () => []
    }
  }
};
</script>

Card.vue

<template>
  <a :href="`posts/${post.id}`">
    <div v-lazy:background-image="post.image" />
    <div>
      <text-title :value="post.title" />
      <div>
        <text-date :value="publishDate" />
      </div>
    </div>
  </a>
</template>

<script>
import moment from "moment";
import TextTitle from "~/components/atoms/TextTitle.vue";
import TextDate from "~/components/atoms/TextDate";
import { Post } from "~/utils/entities";
import timeFormat from "~/config/timeFormat";

export default {
  name: "Card",
  components: {
    TextTitle,
    TextDate
  },
  props: {
    post: {
      type: Object,
      default: () => new Post({}),
      validator: obj => Post.keys === obj.keys
    }
  },
  computed: {
    publishDate() {
      return moment(this.post.publish_date, "YYYY/MM/DD").format(timeFormat);
    }
  }
};
</script>

変更後

変更前は、Postという記事のオブジェクトの構造に依存していました。そのため、CardList側でStringやNumberに分解してCardに渡してあげることで、Cardを再利用可能なものにしました。

CardList.vue

<template>
  <div>
    <ul>
      <li
        v-for="content in parsedContents"
        :key="content.id"
      >
        <card
          :link="content.link ? content.link : 'posts/' + content.id"
          :image="content.image"
          :title="content.title"
          :publish-date="content.publishDate"
        />
      </li>
    </ul>
  </div>
</template>

<script>
import Card from "~/components/molecules/Card";
export default {
  name: "CardList",
  components: { Card },
  props: {
    contents: {
      type: Array,
      default: () => []
    }
  }
};
</script>

Card.vue

<template>
  <nuxt-link-and-atag-wrapper :to="link">
    <div v-lazy:background-image="image" />
    <div>
      <text-title :value="title" />
      <div>
        <text-date :value="publishDate" />
      </div>
    </div>
  </nuxt-link-and-atag-wrapper>
</template>

<script>
import TextTitle from "~/components/atoms/TextTitle";
import TextDate from "~/components/atoms/TextDate";
import Tag from "~/components/atoms/Tag";
import NuxtLinkAndAtagWrapper from "~/components/molecules/NuxtLinkAndAtagWrapper";
export default {
  name: "Card",
  components: {
    NuxtLinkAndAtagWrapper,
    TextTitle,
    TextDate,
    Tag
  },
  props: {
    link: {
      type: String,
      default: ""
    },
    image: {
      type: String,
      default: ""
    },
    title: {
      type: String,
      default: ""
    },
    publishDate: {
      type: String,
      default: ""
    },
    categoryId: {
      type: Number,
      default: null
    }
  }
};
</script>
クリックすると内部または外部リンクに飛んでくれるコンポーネントを作成

渡されたリンクが、内部リンク("/hoge"など)か、外部リンク("https://example.com"など)を判断して、aタグかnuxt-linkでラップしてくれるコンポーネントを作りました。

関数型コンポーネントを利用しています。

NuxtLinkAndAtagWrapper.vue

<script>
export default {
  functional: true,
  render: function(h, { props, children, data }) {
    function isInternalLink(path) {
      return !/^https?:\/\//.test(path);
    }
    if (isInternalLink(props.to)) {
      return h("nuxt-link", data, children);
    } else {
      delete data.attrs.to;
      data.attrs.href = props.to;
      return h("a", data, children);
    }
  }
};
</script>

テスト

このプロジェクトのnuxt.js内ではほとんどテストが書かれていませんでした。

フロントのテストを書くのも初めてだったので、いろいろ調べながら書きました。

テスト対象 TextDate.vue

<template>
  <p>{{ value | readableDate }}</p>
</template>

<script>
import moment from "moment";
import timeFormat from "~/config/timeFormat";
export default {
  name: "TextDate",
  filters: {
    readableDate(str) {
      if (str == "") return "";
      return moment(str, "YYYY/MM/DD").format(timeFormat);
    }
  },
  props: {
    value: {
      type: String,
      default: ""
    }
  }
};
</script>

テスト TextDate.spec.js

import { shallowMount } from "@vue/test-utils";
import TextDate from "@/components/atoms/TextDate.vue";

describe("TextDate.vue", () => {
  let textDate;

  test("Setup correctly", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(true).toBe(true);
  });

  test("props", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(textDate.props().value).toBe("2019-01-01 00:00:00");
  });

  test("日時が人間に読みやすいように変換されて表示される", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: "2019-01-01 00:00:00"
      }
    });
    expect(textDate.text()).toBe("2019/01/01");
  });

  test("空文字を渡すと、何も表示されない", () => {
    textDate = shallowMount(TextDate, {
      propsData: {
        value: ""
      }
    });
    expect(textDate.text()).toBe("");
  });
});

フロントエンドのテスト項目は、どのようなものが良いのか、正直イマイチわかっていません....。

感想

まず、プロジェクトにアサインされてから、実際に中身を読むと分からないことが多くて大変でした。

理解するためには、テストやアトミックデザイン、Vuex、DI、Trait、サービスコンテナなど、使ったことのない技術や概念について勉強する必要がありました。

また、他の人が書いたコードを読むと勉強になることがとても多く楽しいです。

これは個人でコードを書いていてもあまり得られない経験です。

いろいろ調べて悩みながらやっていたら、実装に2週間半くらいかかってしまいました・・・。もうちょっと早く実装できるようになりたいです。

まだまだ分からないことがたくさんあるので、勉強していきたいです。

これからも頑張っていきます〜💪

www.wantedly.com

春の開発合宿に行ってきました!

こんにちは!今回担当させていただきます、4/1にインターンとして入社しましたながいです!

はやくも2ヶ月が経とうとしてしまっているわけでですが今回春の開発合宿に行かせていただきましたことを記します!

今回の日程は5/19~5/20の1泊2日もしくは0泊2日の合宿です。

この開発合宿はそれぞれが好きなテーマで開発をしていきます。

新しい技術の吸収、既存サービスの効率化などみんなテーマは多種多様!

そして最後にみんなの前で成果発表するというUUUM攻殻機動隊恒例行事です!

初参加の僕はとっても楽しみ!!

今回のお宿は湯河原にあります「おんやど恵」!!!

事前に調べた感じとっても良さそう!

温泉も楽しみ〜〜

www.onyadomegumi.co.jp

1日目

10:00 

みんなで六本木に集合してお車で湯河原へ向けて出発!

f:id:johnmanjiro3rd:20190522155556j:plain
海老名サービスエリアでx-tapioka

13:00 

旅館到着です!

早速会議室に集合して各自今回の開発合宿の目標を発表!!

ちなみに僕はweb-socketを使ったチャットを開発します。

14:00  

開発着手!!!!!

f:id:johnmanjiro3rd:20190522160429j:plain 自分のお部屋で開発する人、会議室で開発する人みんなそれぞれ黙々と開発してます!

21時間後の11:00に発表ということだけ決まっており、あとは各自で己の限界と向き合うのみ!!

次第に皆静寂へとかわりそこにただ聞こえるのは川のせせらぎと爆音BGM。

集中している緊張感がひしひしと伝わってきました。

18:00  

旅館といえば夕飯!夕飯といえば旅館!みんな大好き夕飯の時間です!!

f:id:johnmanjiro3rd:20190522160834j:plain
なんとも豪華!!普段一人暮らしの学生には食べられない、夢の御膳、、、、

19:00

夕飯で腹を満たしたらまた各自すぐに開発へ!!

ここから朝食の8:00まではどんな時間を過ごしてもOK

温泉へ行くもよし、ひたすら部屋にこもるもよし、みんな集中して取り組んでいまいした。

みなさん集中力おそるべし、、、

眠くなったら足湯につかりながら開発なんてこともできちゃいます!!

しかも効果抜群!捗る捗る!

ashiyu.hasEmpty? //true
ashiyu.filter(seat => seat.empty?).length // 3

足湯大人気!!

2日目

8:00

朝を迎えました。一睡もせずに頑張った人もちらほら、、、

自分は3時間ほど寝てしまいました✨

f:id:johnmanjiro3rd:20190522164032j:plain

みなさん開発合宿の朝っぽい〜

食べ終わったら発表時間までラストスパート!!

11:00

ついに発表!!! f:id:johnmanjiro3rd:20190523202445j:plain

みなさん本当にすごくて圧倒するばかりでした、、、

あの短時間でなぜこんなすごいものが?というものばかりでした。

みなさんのレベルの高さがすごく伺えました。

ちなみに自分の開発目標であるチャットは一応できました。ダメなポイントを挙げればキリはないですが、、、

まとめ

まず2日間に渡り開発に集中できる素晴らしい環境を整えてくださった施設の皆様ありがとうございました。

合宿を通してみなさんとの距離も近くなったなと思います!

技術が大好きな人たちがたくさんいて素晴らしい環境だなと実感しました!

貴重な時間を過ごせて非常によかったです。

これからも日々精進していきたいなと改めて思うばかりです。

f:id:johnmanjiro3rd:20190522185118j:plain
最後にみんなで集合写真

明日からまたがんばろう!

www.wantedly.com

Kaggleで銀メダルを獲得しました!

こんにちは、UUUMで主にデータ分析業務をしている成松です!

先日(3ヶ月ぐらい前)ソロで参加したElo Merchant Category Recommendationで銀メダルを獲得したので、今回はこちらの振り返りをしたいと思います!


コンペ概要

Eloというブラジルのカード会社が主催したもので、クレジットカードの購買データを使ってRoyalty Scoreを予測するという回帰問題でした。データとしては、各カードの属性が記載された学習用データと検証用データ、そしてカードそれぞれの購買履歴データが記載されたトランザクションデータが主に提供されました。

予測対象であるRoyalty Scoreは、0を中心とした正規分布のような分布と-33付近に外れ値が存在するという分布になっていました。

f:id:n_narimatsu:20190516140107p:plain

評価指標には比較的外れ値の影響を受けやすいRMSEが設定されていたため、外れ値をどう処理するかが非常に重要となりました。

問題設定が分かりやすく評価指標もシンプルで、データサイズもさほど大きくなかったので、初心者でも気軽に参加できたコンペだったのではないかと思います。


スコアの変遷

まず、コンペ終了までに私のスコアがどう変化したのかをご紹介します!

f:id:n_narimatsu:20190517122619p:plain

コンペの序盤 ~ 中盤にかけて運よくスコアを順調に下げることに成功したのですが、後半はほとんど何もできませんでした。


初サブミット!

コンペに参加してすぐにベースとなるモデルを作り始めました。テーブルコンペだったので、深く考えずLightGBMを使いました。また、特徴量はカード属性を表すカテゴリカルデータの他に、購買履歴のデータから購買頻度や金額などを集計し特徴量としてくわえました。

そうして作ったファーストモデルをKaggleに提出したところ、スコアは3.873でした。 この時点で、ほとんどの参加者は3.75 ~ 3.80にいて0.001の精度を競っていました。ですので、この3.873というスコアはめちゃくちゃ低かったです。


Kernelを写経

ファーストモデルを提出した後、このモデルを改良して順位をあげると躍起になっていました。ちょっと特徴量を加えたり、パラメータを調整すれば簡単にスコアが上がるだろうと考えていたのですが、ほとんど改善されませんでした。

そこで、自分のモデルを改良することを諦め、こちらのKernelを写経しました。 自分よりスコアが優れているモデルをそのまま写経したので、当たり前ですがスコアが上がりました!

この時点で順位は上位20%ぐらいだったと思います。


特徴量を大量生産

その後、モデルそのものの改良は一旦ストップし、特徴量を大量生産するフェーズに移行しました。始めのうちはローカルPCでゴリゴリ作っていたのですが、メモリが足りなくなる事や計算処理に時間がかかりすぎる事があり、途中からこちらの動画を参考にBigQueryを使ってました。

最終的には300個ぐらいの特徴量を作ったと思います。その中でも「どのカード(ユーザ)がどのお店で何回購入したか」のマトリックスをNMFで次元圧縮した特徴量はめちゃくちゃ効果的でした!

import pandas as pd
import numpy as np
from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype
from sklearn.decomposition import NMF


N_COMP = 20


# データ読み込み
feats = ["card_id", "merchant_id"]
h_trs = pd.read_csv("../input/historical_transactions.csv", usecols=feats)
h_trs = h_trs.dropna()


# sparse matrixの生成
card_merchant = h_trs.groupby(feats).size().reset_index()
card_merchant.columns = ["card_id", "merchant_id", "count_"]

card_c = CategoricalDtype(sorted(card_merchant.card_id.unique()), ordered=True)
merchant_c = CategoricalDtype(sorted(card_merchant.merchant_id.unique()), ordered=True)

row = card_merchant.card_id.astype(card_c).cat.codes
col = card_merchant.merchant_id.astype(merchant_c).cat.codes
sparse_matrix = csr_matrix((card_merchant["count_"], (row, col)), \
                           shape=(card_c.categories.size, merchant_c.categories.size))


# NMFによる次元削減
model = NMF(n_components=N_COMP, init='random', random_state=0) 
embedded = model.fit_transform(sparse_matrix)

df = pd.DataFrame(embedded, columns=["NMF_comp{}".format(i) for i in range(1, N_COMP+1)])
df["card_id"] = person_c.categories.values


この特徴量と他の300もの特徴量の組み合わせにより、順位が一気に5位まで上がりました!

f:id:n_narimatsu:20190516181823p:plain


アンサンブル

12月中旬あたりぐらいになると、新たな特徴量を作るためのネタが切れてしまいました。
新たな特徴量を作るぐらいしかスコア改善の方法が分からなかったので、1月中旬ぐらいまでかなり伸び悩みました。

悩んだ結果、モデルを複数作ってそれらをアンサンブルしようと決めました。 Kaggleでは、複数のモデルをアンサンブルすることで精度を上げるという手法がよく使われます。各モデルがもつ特有の弱点を他のモデルで補い合うことで精度が向上しやすいのではと思われます。

私の場合、複数個のモデルを作るにあたり下記のようなことを考えました。

  • 外れ値を予測する問題として扱ったらどうか?
  • 外れ値以外は、外れ値なしで学習したモデルの方が精度が良いのではないか?

最終的には2つの学習モデルを新たに作り、合計3つのモデルをアンサンブルしました。

f:id:n_narimatsu:20190517123746p:plain


コンペ終了

3つの学習器によるアンサンブルモデルを作った後も試行錯誤をしたり、raddarさんの神Kernelを参考にしたものの、これ以上スコアが上がることなく3ヶ月間のコンペが終了しました。

Private LBでは多少のShake Downをくらったものの、4129チーム中196位に踏みとどまり銀メダルを獲得しました!!!

f:id:n_narimatsu:20190517120336p:plain


まとめ

Eloコンペは、本気で最後までやりきった初めてのコンペだったので、無事に銀メダルを獲得できてよかったです!日々投稿され続けるKernelやDiscussionをキャッチアップし続けるのは骨が折れましたが、その分多くの学びを得ることができたと思います。今後はテーブルコンペに限らずいろんなコンペに参加して、2019年内にKaggle Masterになることを目標に頑張っていきたいと思います!

www.wantedly.com

webサーバーとアプリケーションサーバーの関係を調べてみた

UUUMでエンジニアインターンをしている鷲見です。弊社では社内システムの一部をRailsを使って開発しています。なので今回はRailsで開発する上で必要な知識を一部書いてきます。

HTTP

私たちがwebページブラウザがwebサーバーに対してコンテンツを要求し、webサーバーは要求されたコンテンツをブラウザに対して返します。HTTPはこの一連のやりとりにおける通信のルール(プロトコル)です。この通信のルールが世界標準で決められているため、私たちはどの種類のブラウザであっても、世界中のどんなwebサーバーにもアクセスできるのです。

URI

URIとはwebに存在する情報を名前をつけて識別するためのルールのことです。URIを使うことでwebに存在する全ての情報は一意のもので表現することができるようになるます。HTTPはURIを使ってwebサーバーからほしいコンテンツを指定することができます。URIとURLの違いとしてはURIが情報の識別するルールに対し、URLがURIにのっとり情報の場所を示したものになります。

webサーバーとアプリケーションサーバー

webサーバー

webサーバーはブラウザからのコンテンツのリクエストを受け取り、ブラウザにレスポンスを返す役割のが役割です。このときのリクエストがHTML、CSS、画像ファイルのような更新しない限り同じ表示コンテンツを表示する静的なwebコンテンツだった場合、webサーバーが処理してレスポンスを返します。そしてクライアントごとで表示内容を変化させる処理が必要な動的なwebコンテンツの場合、webサーバーはアプリケーションサーバーへとリクエストを送ります。アプリケーションサーバーから返ってきた結果をレスポンスとして返します。webサーバーとして NginxとApacheなんかが有名です。

アプリケーションサーバー

アプリケーションサーバーは私たちが作ったRailsアプリケーションを動かしてくれるものです。webサーバーから送られてきたリクエストをアプリケーションサーバーからRailsアプリケーションに伝え、Railsアプリが処理した結果をwebサーバーに返します。ローカル環境下での開発の場合はPumaのようなRails用のアプリケーションサーバーのみを立てますが、本番環境ではwebサーバーをRailsアプリケーションの手前に置くことで、静的なコンテンツの処理を負担させることが多いです.RailsのアプリケーションサーバーとしてRainbows、Pumaなんかがあります。

Rack

RackはRuby製のフレームワークとアプリケーションサーバーの間に入り、互いをつなぐ役割をしてくれます。送られてきたHTTPのリクエストをサーバーはRackを使用しアプリにも理解できる形に変換して伝えてくれる。逆にアプリからのレスポンスはRackを通じてHTTPに変換されてサーバーに返ります。Rackを使用することでサーバーとフレームワークの組み合わせが自由になります。

全体の動き

ここまでのことをまとめるとブラウザから送られてきたリクエストをwebサーバーが受け取り、静的なwebコンテンツだった場合レスポンスをブラウザに返します。しかし、リクエストが動的なwebコンテンツの場合webサーバーでは処理せず、アプリケーションサーバーに送り、アプリケーションサーバーがRailsアプリにRackを通して伝えます。結果を先ほどとは逆の順番でブラウザに返します。

具体的に言うとブラウザからNginx、NginxからPuma、PumaからRackを通してRailsアプリに伝わります。レスポンスはこの逆の順序となります。

おわりに

プログラムは書いていけば理解が膨らみますが、知識関連はブログみたいに書かないと中々定着してくれないなーと常々思いましたー。

www.wantedly.com

ターミナルをインスタ映えするPokemonに変えよう!

こんにちは。2月入社の井上です。

今回のブログでは技術的なことではなく日常的な話題をしようと思うのですが、みなさんターミナルは何を使ってますか?

macデフォルトのターミナル、tmux、iTermなどなど、たくさんのターミナルがありますね。

日常用の僕のオススメはhyperです。

hyper.is

オススメの理由ですが、みなさんはターミナルに何を求めますか?

僕は✨✨インスタ映え✨✨です。

なんとこのhyper, ポケモンのpluginがあるんですよ!

f:id:iammyeye1:20190425123401p:plain

どうです??

インスタ映えじゃないですか??

hyperを導入してインスタ映え系エンジニアになっちゃいましょう!

hyperをインストールする

brew caskまたは公式サイトHyper™から入れちゃいましょう!

brew update
brew cask install hyper

hyper.jsを編集する

hyperをアプリケーションとして開くと、homeに.hyper.jsが生成されます。

そいつを編集しましょう。

vi .hyper.js

ファイルの下の方にプラグインの設定部分があるので、追加と書かれている部分を追記します。

    // for advanced config flags please refer to https://hyper.is/#cfg
    pokemon: 'random',   #追加
    pokecursor: 'false',   #追加
    unibody: 'false',  #追加
    poketab: 'true',   #追加
  },

  // a list of plugins to fetch and install from npm
  // format: [@org/]project[#version]
  // examples:
  //   `hyperpower`
  //   `@company/project`
  //   `project#1.0.1`
  plugins: ['hyper-pokemon'],  #追加

  // in development, you can create a directory under
  // `~/.hyper_plugins/local/` and include it here
  // to load it and avoid it being `npm install`ed
  localPlugins: [],

  keymaps: {
    // Example
    // 'window:devtools': 'cmd+alt+o',
  },
};

自分の.hyper.jsの設定はこれです!! gist:2e6c6953ff03c0f8c22c0392f2f76ef4 · GitHub

追加部分の簡単な説明

    pokemon: 'random' #壁紙のポケモンの種類を指定
    pokecursor: 'false',   #trueだとカーソルをポケモンにしてくれるらしい(僕の端末では動かなかったです)
    unibody: 'false',  #headerの部分の色を壁紙と揃えるか
    poketab: 'true',   #タブの部分でgifのポケモンアイコンを表示するか

使ってみる

そうすると、ポケモンのテーマがこんな感じで適用されるはずです!

f:id:iammyeye1:20190425125548p:plain

先ほどのセットアップでは、hyperの起動のたびにポケモンが変わる仕様になっています。

またcommand + Shift + u で、好きなタイミングでポケモンを変更することができます。

最高!!

今は関東地方のポケモンのみが壁紙になっているようですが、次に壁紙になる地方は投票で決めるっぽいです!!

## Vote the next Region

Vote for the Pokémon Region you want to see themes from next.<br/>
In essence, this poll will determine the creation order of all Pokémon Regions.<br/>
The poll will be kept alive until the project's completion, when all **600+** themes will be available.

投票状況↓

https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Johto https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Hoenn https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Sinnoh https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Unova https://m131jyck4m.execute-api.us-west-2.amazonaws.com/prod/poll/01BMH8W2ETBFXQ9H6PSS0X9VZ8/Kalos

以下に投票用リンクを貼っときます!!

ジョウト地方

ホウエン地方

シンオウ地方

イッシュ地方

カロス地方

終わりに

hyperは普通のターミナルですので、日常でターミナル使いたいな〜って思った時にポケモンのプラグイン入れてサクッと使ってみてください!!

ターミナルはhyper、君に決めた!




参考記事

Hyper Store - hyper-pokemon

爆速でターミナルをポケモンにする - Qiita


www.wantedly.com

RubyKaigi2019 に参加してきました

こんにちは、趣味エンジニアのめる(@c5meru)です!
UUUMに入社して1年と2ヶ月が経とうとしていますが、自分が入社した時、現場のエンジニアは5人くらいしか居なかったのに、いま数えてみたら現在22人もいるようで、そのスケールの違いに圧倒される日々を送っています。

さて、自分は先日、会社のカンファレンス参加制度を利用して RubyKaigi2019 に参加してきました。
今回が初めての RubyKaigi なので書きたいことがたくさんあるのですが、こちらのUUUMエンジニアブログでは、1日目に行われた、まつもとゆきひろさんのKeynoteでお話があった内容について書かせていただきたいと思います!

The Year of Concurrency by Matz

f:id:c5meg1012:20190420115628j:plain

  • Rubyは「だいたいのことにはいい」
  • チームやプロダクトが大きくなるとデメリットが見えてくる
  • 他の言語と比較して遅いのは、マルチコアを活用できていないから
  • Github cookpad Airbnb などもRubyを使っている
    • これらよりパフォーマンスを求められるサービスはあまりない
    • だから「十分に速い」とも言える
  • パフォーマンスが良くないっていう人もいるけど、特筆する例はTwitterの一件だけ
    • しかも古いバージョンであった1.8を使い続けていたケース
  • Rubyは今までも性能の改善を続けてきたし、Ruby3でも改善を進めている
    • Ruby3のポイント
      • 静的解析
      • パフォーマンス改善
      • Concurrency

静的解析

  • プロジェクトが大きくなってくるとテストのサイズや実行時間が長くなって苦痛になってくる
    • Matzはテスト嫌い、できれば書きたくない
    • DRYじゃないし
    • でも人類は間違いのないプログラムをまだ書けないから仕方がない
  • JSのTypescriptや、他の言語でもtype hintingがある
  • Matzは型宣言が嫌い、できれば書きたくない
    • DRYじゃないし
    • コンピュータに仕事させられてる感じがする
    • だからここは妥協したくない
    • なくてもプログラムを書くことはできるから
    • けどできるようにするために進めている
      • 型定義シンタックス .rbi
      • 静的型チェッカー
        • Stripe さんの Sorbet と soutaro さんの steep がある
        • rbiに対応できるように進めている
      • 型宣言を書かなくても型チェックができるように進めている
        • Gems で型定義ファイルを配布すれば、既存の型情報はカバーできるから

パフォーマンス改善

  • alibabaでもRubyは使われているらしい
  • メモリがボトルネック
    • なのでGC周りの改善、キャッシュの改善をすすめている
  • CPUもボトルネック
    • 昨年はMJITを導入、メリットもあったがデメリットも多くあった
    • Ruby2.6ではベンチマークで使っているファミコンエミュレータは2.8倍早くなる
      • RailsなどのWebアプリケーションは、たくさんメソッドがあってコンパイルする量が多くなるので遅くなる
      • PHP8のパフォーマンス改善も同様だそう

Concurrency / 並行性

  • IOボトルネック
  • 現状の Thread/Fiber にも課題を感じている
    • オブジェクトを今からイミュータブルにはできない、過去の経緯があり破壊的変更になってしまう
    • Guild(仮称)をつかう
      • ゲーム業界から反対が出た(このClass使ってるんだけど)
    • frozen objectのようなイミュータブルなものだけ送れるように
    • GuildはJSでいうwebworkersみたいなもの
    • Guildは慣れるまでは使いづらい、IOボトルネックを解消するにも使いづらさがある
    • シングルスレッドのNode.jsはどうしているのか
      • ノンブロッキングIO
      • V8のパフォーマンス
    • AutoFibers(仮称)でIOブロッキングを避けられるようにすすめている
      • Goはgoroutine
      • Elixirはprocess
    • Rubyはそういうのを当初考えていなかった、破壊的変更を避けるために、気をつけながら改善している

メンテナンス方針

  • 互換性を維持しながら改善するのは難しい
    • Ruby3、その先も、いろいろ改善してRubyをGreatにしたい
    • Frozen String Literalsは3.0がデフォルトにはまだできなかった
    • emacsからもってきた '?' literalsも、3.0では消せなかった
    • バッククオートも、3.0では消せなかった
    • キーワード引数は、3.0で変わる
      • もともと直感的でなくわかりづらいと思ってた
      • 静的型付けと相性が悪そう
      • 大きな非互換になる見込み
        • 2.7から警告を出すそう
    • numbered block parameter
      • すでに trunk では導入されているが、議論が続いている
      • まだやらない
    • パターンマッチング
      • 昨日 trunk にマージされた
  • Rubyは生き残らなければならない
    • わたしたちはRubyが好きだし
    • Rubyのプロダクトがすでにたくさんあるし
    • わたしたちはRubyでご飯食べてるし
      • でもみなさんは、JSでもPythonでも使ってもらっていいんですよ
      • わたしたちコミッターは、Rubyがなくなったら本当に困る
    • よりよい、よりたのしい、より生産的な言語に
      • Rubyを使うことがメリットになるように
    • たまに後悔するような実装もしてしまう
      • キーワード引数、Threadなど
      • ユーザーが多いから直しづらい
      • もう間違えないようにしなくては、と慎重にやっている
      • みなさんの意見をないがしろにしたくない
      • 決まってからでは変えられないので、決まる前にどんどん意見を言ってほしい
    • 最近はもどかしいけれど、Matz自身は設計だけ考えて、実装は他のコミッターにやってもらってる
      • Cが書ける人がいたらぜひ参加してほしい

その他

Keynoteの次のセッション「Ruby 3 Progress Report」では、上記よりもさらに踏みこんだ、Ruby3の開発進捗がお話しされました。
Matzさんとは対照的に、自分はテストも静的解析も好きなので(笑)、特に静的解析の仕組みは非常に興味深く、Ruby3が今から待ち遠しくなりました!
上記にもあったキーワード引数の破壊的変更では、この変更だけのために何度も何度も開発者会議が開かれたという話を聞いて、日頃わたしたちが使っているRuby(などのプログラミング言語)は、たくさんの方が時間をかけて作ってくださったものなんだな、ということを実感しました。ありがたや...!
また、RubyGemsのアカウント乗っ取りが多発しているという注意喚起もあり、RubyGemsアカウントを持っている方は、必ず2段階認証を設定してほしいということでした!

まとめ

セッション以外のことについては、個人ブログの方に書かせていただきましたが、全体的にお祭り感もあって非常に楽しいカンファレンスでした!
RubyKaigiをもっともっと楽しむには、Rubyのしくみを読んだり、(関連するセッションが多い)mrubyを押さえておいたり、英語力をがんばって高めておくと良いだろうな、と思ったので、今後参加される方は参考にしていただければと思います!


www.wantedly.com

GraphQLをはじめよう!

皆さんは普段、WEBでAPIを作っていますでしょうか? 最近はリッチなUIやスマートフォンアプリの開発が当たり前になってきたので、 APIを作ることが多いのではないでしょうか。

APIの開発を楽にするために、GraphQLを使ってみませんか?

現状のAPI開発の悩み

現在APIを作るときは、REST設計で開発を行うことが多いと思います。 実際開発中に以下のような悩みを持ったことはないでしょうか?

  • API多すぎ
  • 仕様がよく変わる
  • ドキュメントがメンテできない
  • もうRESTのAPI作るの飽きたよ…

そんなあなたにぜひGraphQLを! GraphQLを使えば楽しいAPI開発が待っているかも!

GraphQLって何?

答: A query language for your API. (出典: GraphQL )

以上です!(^o^)

……要するに、SQLみたいにクエリを書いて、サーバにリクエストして、 自分が欲しいデータを取って来たり、変更するということができるものです!

GitHub GraphQL API を試そう!

GraphQLがどういうものなのかを知るためには、GitHubが公開しているGraphQL APIを試してみるのがわかりやすいと思います。

そこで、GitHubのAPIを使ってGraphQLがどのようなものなのか試してみましょう!

GraphQL API Explorer | GitHub Developer Guide

上のURLにアクセスして、右上の Sign in With GitHub からログインしましょう。 ログインするとすぐに使用できるようになります。

f:id:m-suzukix:20190418192948p:plain

左上がクエリエディタになっていて、ここにサーバにリクエストするクエリを書いていきます。 右側は、サーバからのレスポンスが表示されます。

自分のアカウント情報を取得する

ではクエリエディタに下のものをコピーして貼り付けて、実行してみましょう!

  • クエリ
query {
  viewer {
    name
  }
}

実行は 再生ボタン みたいなのがあるので押すと実行されます。

f:id:m-suzukix:20190418192929p:plain

実行すると下の様なレスポンスが返ってくると思います。

  • レスポンス
{
  "data": {
    "viewer": {
      "name": "masa"
    }
  }
}

ここにさらに、アバターURLを取ってみましょう!

  • クエリ
query {
  viewer {
    name
    avatarUrl
  }
}
  • レスポンス
{
  "data": {
    "viewer": {
      "name": "masa",
      "avatarUrl": "https://avatars2.githubusercontent.com/u/47341025?v=4"
    }
  }
}

追加で登録してあるアバターのURLが取得できました!

このように自分が欲しい情報に合わせて、クエリを書くことでレスポンスを調整することができるのがGraphQLの特徴になります。 SQLのSELECT文をイメージしていただくと、わかりやすいのではないかと思います。

query とは?

ここで、先程からエディタに書いてたものは、どのような構造をしているのかを見ていきましょう。

まず query についてです。

query {
  viewer {
    name
  }
}

query はデータを取得するリクエストであることを示しています。 SQLではデータを取得する時に必ず SELECT を書くと思いますが、GraphQLでは必ず query を書くことになります。

この query の中には viewer という指定がありますが、 これは viewer というデータを返してくれというリクエストになります。

上の query をSQLで書くと下のような感じになります。

SELECT name FROM viewer

viewer の中身

viewer の中には nameavatarUrl など指定ができましたが、他にはどんなものが指定できるのか気になると思います。

これを調べるために、このAPIコンソールには便利なドキュメントビューワーが付属されています。

エディタの右端に Docs というボタンがあるので押すとドキュメントを見ることができます。

f:id:m-suzukix:20190418194312p:plain

開いたら、viewer を検索してみましょう!

f:id:m-suzukix:20190419114854p:plain

すると Query.viewer が出てくると思います。 これをクリックすると、詳細が見れます。

f:id:m-suzukix:20190419114914p:plain

認証中のユーザ情報が返ってくることがわかりました! その下に TYPEUser! というのがあるので、TYPEについて説明します。

Type

型の指定のことです。これはClassのようなものとイメージするとわかりやすいです。 ルールは以下のようになっています。

type 型名 {
  名前: 型
}

型を構成する Field を書くことができます。 先程の User! の中身を見てみましょう。

f:id:m-suzukix:20190419114936p:plain

FIELDS にどんなデータなのかの指定があります。 先程指定した name , avatarUrl だけ定義してみると下の形になります。

type User {
  name: String
  avatarUrl: URI!
}

後ろに ! があるのは NULL不許可 を示しています。 avatarUrl を指定すると必ず URI型 のデータが返ってきます。 URI型 もドキュメントからどんな型なのか見ることができます。URIの定義に則った文字列が返ってくることがわかると思います。

データを更新する

APIではデータを作成、更新、削除したりすることがありますが、 GraphQLではこれらは mutation で指定する形になります。

mutation {
  ここにどの操作を行うかを書く
}

リポジトリにスターを付けてみる

addStar というスキーマが用意されているので、これを使うとGitHubのリポジトリにスターを付けることができます。

まずドキュメントを使って仕様を確認してみましょう。

f:id:m-suzukix:20190419114952p:plain

Mutation.addStar が表示されます。

f:id:m-suzukix:20190419115002p:plain

ARGUMENTS に指定する引数が書かれています。 addStar には inputAddStarInput! が必須であるとわかります。 (AddStarInputの後ろに ! が付いているからです)

f:id:m-suzukix:20190419115012p:plain

AddStarInput の中身を見ていくと、starrableIdID型 で必須ということがわかります。 ID型 はGraphQLの組み込み型で、通常はbase64エンコードされた文字列が使われるようです。

このIDはリポジトリごとにユニークなものが振られています。 スターを付けるためには、このリポジトリのIDを取得する必要があります。

以下のクエリで取ってこれます。

query {
  viewer {
    repository(name: "pokemon") {
      id
    }
  }
}

このクエリは自分が所持しているリポジトリの名前が pokemon であるものを取得するものになります。

レスポンスは以下になります。

{
  "data": {
    "viewer": {
      "repository": {
        "id": "MDEwOlJlcG9zaXRvcnkxODAyNTUzNDk1="
      }
    }
  }
}

ここで取ってこれたIDを使います。 スターを更新する構文は以下になります。

mutation {
  addStar(input: { starrableId: "MDEwOlJlcG9zaXRvcnkxODAyNTUzNDk1=" }) {
    clientMutationId
  }
}

これを実行するとスターが付くかと思います! 仕様通りに addStar の引数に input: { starrableID: "ID" } の形式で指定を行っています。

GraphQLでは addStar のような構文でも 必ず返り値を指定する ことになっています。 しかし更新系のスキーマでは返り値が不要なこともあるかと思います。 そこで、clientMutationId という指定をすることで、NULL の返り値をレスポンスとして受け取ることができるようになっています。

このスキーマのレスポンスは以下の様になります。

{
  "data": {
    "addStar": {
      "clientMutationId": null
    }
  }
}

この他にも、スターを外したり、PullRequestにコメントを付けたりするスキーマなどが用意されています。 ドキュメントを調べて実際に使ってみると、GraphQLが何となくわかってくるかと思います。

実装するには

各プログラミング言語用のライブラリが用意されているので、これを使うことでGraphQLの実装を行うことができます。

Code | GraphQL

また、Applo EngineAWS AppSyn のようにGraphQLを使う環境が既に用意されていて、後はデータやスキーマを用意するだけで簡単に利用することができるサービスもあります。

まとめ

GitHubが公開しているGraphQLを使ったAPIのご紹介をしました。 GraphQLはまだまだWEB上で知見があまりなく、自分でGraphQLを作る時にどうやって実装して良いのか悩むことがあるかと思います。

そんな時には既に GitHubのAPIの仕様を参考にして開発してみるといい感じに実装ができるのではないかと思います!

www.wantedly.com