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

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

Railsプロジェクトを引き継いでから安定させるまでに行ったこと

nazoです。現在は厳密には攻殻機動隊(システムユニット)ではないのですが、開発に関する大きな動きがありましたのでまとめてみたいと思います。

何の話?

LMND というシステムがあるのですが、これの開発を急ピッチで進めたいという事で急遽移動することになり、快適な開発環境になるまでに行ったことについての話になります。

LMNDのサービスの詳細についてはここでは割愛させて頂きますが、システム的にはRails4系にMySQLという、少し古いものの一般的な構成で作られたものになっております。2018年9月に吸収合併を行ったのですが、その際に開発体制が完全内製に切り替わってしまったため、引き継ぎがほとんどない状態から体制が切り替わってしまったという状態になります。社内の開発者が先に割り当てられていたのですが、困っているので助けてくれということで私が途中から入ることになりました。

作り直すかどうか

事前情報では「やばかったら作り直してもいいよ」とは言われたのですが、作り直しというのは一見楽しそうですが難しい問題を多く抱えています。

  • サービスをやり直すわけではないのでデータは既存のまま使えるようにしないといけない
  • 機能が多く、かつそれらが契約上廃棄できるものではない場合、それらを全て同様に移植しなければならない

一方で、それでも作り直さないといけないという場合も存在します。

  • コードの汚さがあまりにどうしようもなく、何をするにもほぼスクラッチで開発するしかないような状態
  • フレームワークなどが一般的ではないまたは独自のもので、今後のアップデートに対応することがほぼ不可能な状態
  • 予算が十分にあり、かつ作り直しのビジョンが明確にある場合

ただし予算があるケースの場合以外は、最終的には全て作り直すとしても一部分から作り直すとか新しいものと古いものを共存できるようにするとか、様々な手法があると思います。規模の大きなアプリケーションの場合、そのアプリケーションが解決しようとしているもの(事業)が何かを適切に見極め、交通整理を行うことでどこをどう作り直すべきなのかが見えてくると思います。

今回のプロジェクトはRails製で、コードが読みにくい箇所が多いもののRailsそのものを魔改造しているというわけでもなく、作りながら書き直すことが十分に可能なレベルでしたので、基本的には作り直さないという方針を採ることにしました。

続きを読む

polidogさんをお招きして社内勉強会をしました

こんにちは、今年新卒で入社したエンジニアの中村です。

6月28日(金)にパーティーハード株式会社のpolidogさん(@polidog)をUUUMにお招きし、Symfony使いの為の勉強会を行いました!!

4月からSymfonyを使い始め、苦しんでいた私にとってもすごく有意義な時間となりました。

f:id:chibiProgrammer:20190702114021j:plain
写真右奥がpolidogさんです

Symfonyとは、Webアプリケーションフレームワークで、 弊社ではクリエイターポータルサイト「CREAS」の開発にSymfonyを使っています。 普段の開発では柔軟なSymfonyアプリケーションを作ろうと意識しても、 実際どう設計すればいいのか分からないことが多く、今回の勉強会で意識するべきところを教えて頂きました。

以下勉強会の資料になります。

speakerdeck.com

弊社のエンジニアもSymfonyに興味がある人が多く、勉強会の後の質疑応答では沢山質問にお答えしていただきました。 以下、質疑応答で出た内容になります!

  • SymfonyのJob Queue はないのか?

    - BCCResqueBundle
    - JMSJobQueueBundle
    
  • Security.ymlが解決されるタイミングは?

  • Security.bundleってcomposerとかじゃなくてSymfonyで提供されているのか?

    - composerである。Symfonyのコアチームが提供している。
    
  • 1コントローラーつき1メソッド(コンストラクタを除く)とした場合、CRUD等の機能はどのように分けているか?

    - ディレクトリで切っていって、名前空間で分けている。
    

今回の勉強会でリファクタを強く意識しましたが、polidogさん曰く 頑張ってリファクタしすぎるのは良くない、動いているコードが最終的には正解だと思っているが、 変化させるタイミングの時にちょっとづつ綺麗にしていくのが大事とのことだったので、 日頃から少しづつ意識して今あるコードを綺麗にしていきたいなと思いました。

今回はSymfonyについて色々知ることができ、とても楽しい会となりました!

polidogさん、お忙しい中お越しいただきありがとうございました!

www.wantedly.com

AWS Summit Tokyo 2019 に行ってきました。

こんにちは、エンジニアのオークボです。
会社のカンファレンス参加制度を利用して先日、幕張メッセにて行われたAWS Summit Tokyoに参加をしてきました。この記事はカンファレンスでの忘備録兼まとめを書きたいと思います。

aws.amazon.com

youtu.be ダイジェストはこちら

経緯

今回カンファレンスに参加したきっかけは、業務で毎日AWSを触るため、もっと知見を深めたいと思ったのと、以前社内ブログに、先輩エンジニアのめるさんが RubyKaigi2019 に行ってきた。という記事を上げていたのを見て触発された部分もあったからです。 

system.blog.uuum.jp

内容

以下が私の参加したセッションのリストです。

- AWS環境における脅威検知と対応
- Edge Servicesを利用したDDoS防御の構成(AWS WAF/Shield)
- 「ネットワークデザインパターンDeep Dive」
- Security Best Practices on S3
- Startup Architecture of the year 2019 ピッチコンテスト
- サービスメッシュは本当に必要なのか、何を解決するのか
- Amazon SageMaker で実現する大規模データのための分散学習とワークフロー
- Fate/Grand Orderにおける大規模なデータベース移行と負荷試験

ピックアップメモ

こちらは、一番面白かった「AWS環境における脅威検知と対応」 のメモです。

  • 脅威の検知をするためには以下の4つのサービスを組み合わせて使うと良い

    • Amazon GuardDuty
    • AWS Config
    • AWS CloudTrail
    • Security Hub
  • Security Hubは セキュリティ関連等の状態を一覧表示できるのでとても便利

    • 現状パブリックプレビューとなっていてお試しができる様子
    • 脅威が見つかったインスタンスなどに対する各種アクションが行える。以下はslackに通知をするカスタムアクションのサンプル
  • セキュリティワークショップという擬似的に攻撃を行って各種対応の訓練などができるツールもある

aws.amazon.com

github.com

awssecworkshops.jp

このように、どのセッションも普段の業務に直結するような内容が話されていましたし、AWSを利用している各社の事例が紹介されていました。

感想

どのセッションも超満員で熱気がすごかったです。インフラ関連ツールを展示をしている企業さまなどが多く、今まで知らなかった有意義な情報の交換ができました。
セッションは、サイレントセッション(イヤホンとレシーバーにて講演を聞く方式)は初めてでしたが、広い会場で皆がイヤホンをして画面を見上げているのはなんとも不思議な体験でした。
AWS Summit をより楽しむには、もっとAWSを活用してゆき、自分の中に疑問を貯めていくと良いのだろうなと感じました。

まとめ

以上が、簡単でしたが、AWS Summit Tokyo 2019に参加をしてきたレポートでした。
来年はパシフィコ横浜にて開催されるようです。一年後に向けてもっとAWSを活用して、勉強して行くぞ~。


最後に

UUUM では、AWSなどのインフラ関連技術に興味があったり、カンファレンスに参加するのが大好き! 技術大好き! というようなエンジニアを募集しています!! 詳しくは以下のリンクをご覧下さいませ。

www.wantedly.com

「僕」でもわかるSEO対策、基礎の基礎の基礎

こんにちは UUUMでエンジニアしてるながいです。

題名の通り「僕」でもわかるSEO対策です。ちなみに「僕」はまだパソコンを初めて買ってから1年のキラキラ✨✨✨✨✨✨、ペーペーの「僕」です!

なんでこの記事を書くかというと個人的に気になるから、それだけです。サイトのSEO対策をしてみたかったので、、、

SEO対策すればそれなりのサービスでも人が来るんじゃない?という安直な考えからきています。

逆に言えばすごいサービスでも人に知られなければ使われることはないわけですし。多分、たぶん、tabun、、、

さて!

そもそもSEOってなんの略?

これよくSEOって耳にはしますけど実際なんの略なの?ってところから

こういう使ってるけど知らないとか、読み方違う系の言葉が無限にこの界隈にはありますよね。

ちなみに僕はREADMEを「レドメ」ってなんなんって4ヶ月くらい思いながら読んでました!

Search Engine Optimization:検索エンジン最適化

らしいです。これ知ってる人どれくらいいるのかな、、、僕はもちろん知りませんでした!!

まぁでも読んで字のごとくですね、SEOってそういうものだよね確かにって感じがしますね。

Googleとかの検索エンジン上位に表示されるように自分のサイトを最適化して行こう!って感じですね。

どういうアルゴリズムでGoogleは検索上位を作ってるの?

Googleは色々な要素で上位に表示する項目を決定しているようです。その数は200以上!!!

そして最も重要な要素と言えるのが「被リンク」と「コンテンツ」と言われるものらしいです。

まず被リンクってなんだよって感じです。これは自分のサイト以外に表示されている自分のサイトのリンクいわゆるURLの事のようです。

例えば自分のサイトを誰かがブログでシェアしてくれたりしたとします。その際に、リンクを貼ってシェアすることになると思うのですがそれがいわゆる「被リンク」と言われるもののようです。

しかしこの被リンクには良質なものと低質なものがあるようで、自然な被リンク、例えばシェアなどが良質なものとされるようです。低質なものというのは関係のないリンクのみが羅列掲載された相互リンク集などのコンテンツを指すようです。

この低質なリンクというのはSEO効果を下げてしまうようなので、良質な被リンクだけを増やすようにした方がようさそうですね。とはいえシェアしてもらうにはコンテンツが重要ですので被リンクに関しては余計なことをしなければ良いという感じでしょうか。

となるとエンジニアリングサイドで対策できるのは「コンテンツ」に関してかと思われます。具体的にはそのコンテンツのどこを評価しているのでしょうか?

  • Expertise(専門性)

  • Authoritativeness(権威性)

  • Trustworthiness(信頼性)

どうやらGoogleはこの3つを評価すると明言しているらしいです。

ちなみに日本で使われてる検索エンジンの90%以上がGoogleらしいのでとりあえずGoogleでの対策を押さえておけばいいと思います。というか他のエンジンの対策してる人はいるんでしょうか。

まぁ要するにこのGoogleの評価基準から考えるとコンテンツはユーザーファーストでなおかつ、ユーザーにとって質の高いコンテンツを評価しますよってことかと思います。

具体的にどんな感じでコンテンツを作ったら良いかを次に考えて行きましょう。

どんなコンテンツにすれば良いの?

まずどのキーワードでページをアルゴリズムに評価されるかを考えなければいけません。

原則として1ページ1キーワードという概念があるようです。

例えば「UUUM 所属youtube」って検索した人の上位に次ページを出したいなと思ったらUUUMに所属するyoutuberの一覧が載ってるページを見たいわけで、多数のyoutuberのそれぞれの所属事務所が見たいわけではないはずです。

なのでUUUM所属のYouTuberというキーワードでコンテンツを作らないと「UUUM 所属YouTuber」には引っかからなくなるのです。

そしてさらにコンテンツ量(文字数)が大事なようです。文字数の少ないページは低品質なページとみなされるようです。

確かに検索してて読み応えのないページってあんまり上にほうに出てこないですよね。

具体的には800~1000文字以上あれば低品質なページとはみなされないようです。

比較的難易度の高いキーワードでも2,000~4,000文字程度のコンテンツが上位に表示されているらしいです。

「UUUM 所属YouTuber」だったらそんなに難易度の高いキーワードとも取れないきがするので1200文字くらいでいいんじゃないでしょうか?(適当です)

HTMLタグの重要性について

さてコンテンツの作り方の方針が決まったところで細かなHTMLのタグなどの対策をしていきましょう。

重要なのはtitleタグh1タグだろうと素人ながらに軽く知っています。実際やはりそのようですね。しかしここでもう一つ超重要タグがあるようです。それがmetaタグ

とりあえず一つずつ追っていきましょう。

titleタグ

これは検索したるしたらそもそもみなさんみる見出しっぽいあれになるやつです。Googleくんもここを重要に見てるらしいですよ。

<title>ここにページのタイトルを記述</title>

ポイントは簡潔でキーワードを含めること

そして30文字程度にすること。目立たせること。

また他サイトとの重複を避けることも大切です。

以上を踏まえて、先ほどの「UUUM 所属YouTuber」の例だとしたら、「【UUUM所属】のYouTuber一覧!6000人越え!?人気YouTuberが勢揃い!!

とかでいいんじゃないでしょうか?(適当です)

h1タグ

これもまた重要なポイントです。

<h1>ここに見出しを記述</h1>

そして1ページ1キーワードの原則どおり1ページに対してh1タグは1つしかつけてはいけません。

というのが常識かと思ったのですがGoogleは何個あっても問題はないと声明しているらしいです。

ケースバイケースといったところでしょうか。

そして内容は「キーワード」を入れた、コンテンツに合った「簡潔な文」で作成しましょう。

これは超重要ですね。

SEOを知らないとなんか文字で隠したいしh1にしとくかとかいうノリでつけがちですが、h~系のタグは意識してつける必要が非常にありそうです。

metaタグ

これは僕も見落としていたのですがmetaタグもめちゃくちゃ重要ですね。

metaタグにも色々あると思うのですがこれはmeta description についてです。

<meta name="description" content="ここにページの説明を記述" />

よく検索した結果一覧を見るとtitleのしたに内容の軽い説明見たいのがありますよね。それがこれです。

あの文を見て僕は結構クリックするか決めるのでそういう意味合いが強いかと思います。

直接的に表示順位とは関係はなさそうですね。

クリックしたくなるような説明書きをかければ良さそうです。

ただ一つ注意点があり、pcとspでは表示される文字数に違いがあるようです。

SP 50文字程度
PC 120文字程度

のようですね。どちらに重きを置きのか、もしくはスマホとPCで変更できる機能を作るのも吉ですね。

まとめ

ここまで調べたことを書いてきましたがとりあえずはこの辺りに注意をしてサイトを作れれば初心者としては十分なSEO対策ができるのではないでしょうか?

もちろんSEOというのは奥がめちゃくちゃ深いものなので突き詰めていけばキリはないと思います。

手っ取り早くサイトの閲覧数を稼ぐために最低限のことはしておきたいですよね。

エンジニアとしては作ることが楽しいですけど、こういうコンテンツも重要視していかなければどんなにいいサービスも作ってもらえなくなってしまいます。

エンジニアとしていろんな方面へ手を出してみることが大事ですね〜

www.wantedly.com

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

こんにちわ。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