asoview! TECH BLOG

アソビュー株式会社のテックブログ

canvasを使って動体感知。"あの"犯人(犬)を捕まえた!

犬アレルギーだけど犬が大好きな相原(@raihara3)です。

実家で定期的に起こる、ワンコのとある事件の犯人を捕まえるべく
canvasを活用して簡単につくった監視ツールの紹介です。

その名も「トイレ警察24時
名前の通り、トイレの話です。
汚い表現は極力控えていますが、苦手な方はご注意ください。


実家には現在3匹のワンコがいます。
そこで起こるワンコの事件というのは、
犬あるある「他の子の排泄物を口にしてしまう子」がうちにもいるんです...
母性本能で良い事なんですが、みんな成犬なのでやめて欲しい...

犯人はもう分かっています。(なぜ分かったかは省略)
でも、いつも人間の目を盗んで犯行を行う為証拠もなく、注意できずにいます。

そこで思いついたのが「トイレ警察24時」

概要

ざっくり。

f:id:raihara3a:20200113193334p:plain

(3)で差分がなければ(1)(2)を繰り返し、
差分がある場合は、被疑者が近づいては危ないので(4)(5)のような流れになります。
(差分 = 排泄物あり)

仕組み

※この先折り紙を丸めたものが排泄物として登場します。本物ではありません。

通常時の情報を取得

f:id:raihara3a:20200113193611p:plain
監視スタートして、15秒後に現在の状態を基準として取得します

setTimeout(() => {
  standardStatus = document.getElementById('toiret-canvas').getContext('2d').getImageData(0, 0, canvasSize.width, canvasSize.height).data;
}, 15000)

モーション検出

Diffy.jsというライブラリを使用しました。
フレームを少しずらしたWebカメラのスナップショットを取得し、
ハイコントラストの差分があれば"モーション検出"される仕組みです。

f:id:raihara3a:20200113193714p:plain
トイレに末っ子がやってきました。

import { create } from 'diffy.min.js';

const resolution = {
  x: 20,
  y: 15
}

let diffy = create({
  resolution: { x: resolution.x, y: resolution.y },
  sensitivity: 0.2,
  threshold: 21,
  debug: true,
  containerClassName: 'diffy-container',
  sourceDimensions: { w: 250, h: 200},
  onFrame:(matrix) => {
    matrix.forEach((row) => {
      const notWhiteCount = row.filter((color) => {return color !== 255}).length;
      if(notWhiteCount > resolution.x / 2.5) {
        // モーション検出
      }
    });
  }
});

window.diffy = diffy;

今回だとresolutionで指定している20 x 15に映像を分割し、
matrixに白〜黒のカラーコードが配列で渡されます。差分がなければ白が入ります。

モーション検出の条件に設定している割合は、
カメラからの距離や映り込む被写体の大きさによって前後すると思うので要調整です。

差分検出

動きがなくなった5秒後に最初の状態と変わりないか確認します。

const currentStatus = document.getElementById('toiret-canvas').getContext('2d').getImageData(0, 0, canvasSize.width, canvasSize.height).data;

for(let i = 0; i < currentStatus.length; i++) {
  if(standardStatus[i] - currentStatus[i] < -30 || 30 < standardStatus[i] - currentStatus[i]) {
    new Audio('chime.mp3').play();
    hasAbnormal = true;
    return;
  }
}

dataプロパティで取得したRGBAから+-30を許容値としました。
これは白のトイレシートと排泄物の明暗差からざっくり決めました。

f:id:raihara3a:20200113193824p:plain

差分が検出されると人間に向けた通知音を流します。

この段階で近くに人間がいて片付けができればいいのですが、必ずいるとも限らないので...

再度モーション検出

今が取り締まる瞬間です!
hasAbnormal = trueの状態でモーション検出すると、音と共にcanvasから写真のダウンロードリンクを生成します。
音といっても、あまりビックリさせても良くないので知らない犬の鳴き声にしてみました。
(これでもかなり反応する)

new Audio('dog.mp3').play();

const canvas = document.getElementById('toiret-canvas');
const aTag = document.createElement('a');
aTag.innerText = new Date();
aTag.download = new Date();
aTag.href = canvas.toDataURL("image/jpeg");
document.getElementById('evidence-box').appendChild(aTag);

この音に気を引かれてやめてくれるか、人間が駆けつけて止めるか、です。
被疑者の長女がやってきました。

f:id:raihara3a:20200113194043p:plain

しっかり音のする方を気にしてくれてます。

もし止めることができなくても、リンクから画像をダウンロードすればしっかり証拠が手に入ります。

f:id:raihara3a:20200113194216p:plain

排泄物がある状態ではモーションを検出している間、5秒おきに写真を撮り続けます。
その為ただトイレをしにきただけだった、という場合もちゃんと確認できます。冤罪防止。

しかし、いくら折り紙を丸めたと言ってもちょっとリアルだったかな...

さいごに

ワンコに後から注意することはできませんが、
これで「人間は見てないはずなのにバレる違和感」を感じてもらえればと思います...笑

毎回人間の目を盗んで犯行を行うなんて賢いけど、しっかり撮ってるからね...

f:id:raihara3a:20200113194311p:plain

これから取り締まっていこうと思います。

組織とアーキテクチャとマーケットの話

この記事はアソビュー! Advent Calendar 2019 - Qiitaの25日目、最後の記事になります。

メリー・クリスマス

CPOの江部です。アドベントカレンダーもいよいよ最終回となりました。僭越ながらトリを務めさせていただきます。

自己紹介ですが、総じて事業・プロダクト両面において道のないところに道をつくったり、空いてるスペースに走り込んだりする役割を担ってきました。目の前の扉は蹴り破るスタイルを得意としています。

今回のお題ですが、ゼロからの組織立ち上げを経てある程度の組織規模となった現状に至る中で感じたことを吟じてみたいと思います。相当な乱文かつ少し抽象度の高い話になりますが興味のある方はご一読ください。

生産性の高い開発組織とは

生産性の高い組織であるために必要なことはなにかを考えたときに、数年前くらいからマーケット、組織、アーキテクチャの3つが統合できている状態じゃないかなーと考えるようになりました。 ちょっとわかりにくいかもしれませんが簡易的な図で表すと下記のようなイメージです。

f:id:jjebejj:20191224014103p:plain
理想の状態

顧客に対して提供するサービスの境界が明確であり、それをどの組織がオーナーシップをもって作り上げるのか定義されている状態です。

  • 誰の何の課題を解決すべきか明確
  • チームがシステム(コード)に対してオーナーシップを持てている
  • 影響範囲が特定できる
  • スピーディーに変化できる

のではないかと考えています。

逆に、この状態が歪んでいると、様々な組織課題が現れます。 個人的な感覚ですが、これまで私達が直面した組織課題はほとんどこの歪みが原因で発生しているように思います。

例えば。

組織の歪み

f:id:jjebejj:20191224021802p:plain
組織の不和
「フロント」、「バックエンド」など技術レイヤで区切られた組織の場合に起こりやすいかとおもいます。 これにより起こる問題として

  • チームメンバーが向き合う先がバラバラ
  • 案件ごとのアサインで受託マインドになりがち
  • チーム間の足並みや認識が揃わずプロジェクトマネジメントに失敗する など。

アーキテクチャの歪み

f:id:jjebejj:20191224021235p:plain
アーキテクチャの不和
例えば組織はマーケットの境界で区切られているが、システムはモノリシックなケースなど。

  • あるシステムに対して異なる目的をもった複数チームがそれぞれ修正を加えることになり、システムとしての品質を維持するのが困難になる。
  • システムのコードに関する所有権・オーナーシップ・責任が曖昧になる。気づいたらデジタル九龍城がそこに生まれる。
  • 影響範囲がわからない。触りたくない。
  • 触った結果障害が起こる。

統合の順序

ではなにをガイドラインにしてマーケット、組織、アーキテクチャを統合していくのがよいのでしょうか。 答えはそのビジネスやフェーズによって違うのかもしれませんが、すくなくともいまのアソビューで言えば

マーケットに従った組織によってアーキテクチャを変化させる

のが良いのではと考えています。

例えばアソビューの場合、マーケットとしては大きく分けるとゲスト(消費者)、パートナー(レジャー事業者)、代理店(OTA・旅行代理店・媒体など)があり、さらにゲストはチャネル別、パートナーは商品別、代理店は業種別に分けられます。

これらは異なるKPIやビジネスライフサイクルでアプローチされるわけですが、それぞれに対して明確な責任をもったチームが組まれ、そのチームにおいて事業目標達成の手段としてアーキテクチャを最適化していくことにより、マーケット、組織、アーキテクチャが統合されていくというシナリオが理想かと思います。(まさにコンウェイの法則です)

分解ではなく凝集

得てしてスタートアップのサービス開始当初はエンジニア人数も時間も制約があるなかでモノリシックなアーキテクチャで構築されがちです。 それ自体はもちろんそのタイミングでは正解なのですが、事業成長し組織が複雑化するにつれアーキテクチャやコードが最後に変化に取り残されていきます。

そのアーキテクチャを最適化しようとする時、マイクロサービスの導入を検討するわけですが、往々にしていかに最適な要素に分解するか、という視点で議論しようとする向きがあります。誰かが横軸の技術視点で分解、整理することにより技術最適化を図ろうとしてしまいがちですが、そうではなくそれぞれのマーケットに従ったチームが自らのビジネスドメインに基づいて凝集させていくほうが、長期的に整理が付きやすく、実際にそれに向けてプロジェクトが動きだすことが多いです。

ここでいう凝集とは、ビジネスドメインを整理し、それを実現するシステムに必要なサブシステムや関連システムのデータ、機能などを取り込んでいくことにより最適なアーキテクチャをビルドアップしていこうというアプローチです。

一方の分解とは、様々なデータや機能が含まれるモノリスを、ある基準に従って境界線を引きながらマイクロサービスに切り分けることによってアーキテクチャを最適化しようという思考パターンです。

技術視点での分解は、機能的な重複がきれいに整理され一見効率のよいアーキテクチャのように見えますが、往々にして上述のような組織とマーケットとの乖離を起こします。 一方、ビジネスドメインに基づいて凝集させていく考え方であれば、ある程度のデータや機能の重複は随所に起こるものの、それぞれがマーケットや組織と連動した形となり、結果ビジネスやオペレーションを含めた全体視点でみると効率的となります。そして、それによって単なるアーキテクチャの最適化プロジェクトとしてぶち上げられるのではなく、具体的な業績向上、業務効率化を目的としたプロジェクトとして打ち出すことができ、エンジニア以外のチームやメンバーと目線を合わせて進めやすくなります。

(ちなみに、マーケットと表現するとSoE (System of Engagement)に限定された議論に捉えられやすいかもしれませんが、例えば基幹系システムなどSoR (System of Record)的なシステムでも、社内の運用部門をシステムやチームにとっての顧客ととらえれば同様の考え方が適用できます。)

あくまでマーケット起点での組織、アーキテクチャであるべきであって、既存の組織の形や技術的な効率だけを理由にしたアーキテクチャの設計、とくに異なるマーケットにまたがった低レイヤーの統合は、恒常的にアーキテクトチームなどが保守できる体制でなければなるべく避けたほうが懸命です。

さいごに

かなり纏まりのない文章になってしまいました。

まあこれは多分に理想論なので、人がたりないスタートアップだとどうやってもそんなきれいにはいかないよーとかあると思います。 我々も試行錯誤の連続のなかで振り子のように行ったり来たりしながら最適解を探し続けている状態です。

が、いろいろな組織課題に悩まれているマネジメントレイヤーの方々に、少しでも思考の整理や今後の方向性を考える上でなにがしかのヒントにつながればと思い書いてみました。

では、みなさん良いお年を。 来年もアソビューをよろしくお願いいたします!

Spockを使ったユニットテスト

アソビュー! Advent Calendar 2019 - Qiitaの23日目の記事になります。
はじめまして。
アソビュー!のサーバーサイドエンジニアの山野です。
弊社ではSpockを使用したユニットテストを行なっています。
今回はその実装方法について備忘録も兼ね、書きたいと思います。

Spockとは

SpockはGroovyで動作するJava・Groovyアプリケーション向けのテストフレームワークで、
以下のような特徴や機能があります。
・テストケース内がブロックで明確に分かれており、
 テストケース内にテーブル形式でテストデータを定義できるため、テストの内容や仕様が分かりやすい。
・テスト結果確認(アサーション)が簡潔に記述できる。
・Mockの仕組みが標準で用意されている。
http://spockframework.org/

基本のテストコード実装例

テストクラス内のメソッドとブロックは以下のような形で実装します。
・テストクラスはSpockのSpecificationクラスの継承が必要です。
・テストケース内のsetupgivenと宣言してもよく、
 expectブロックを使えばテスト対象の実行と結果確認一緒に行えます。
・またテーブル記述を使ったテストを行う場合は、whereブロックが必要です。(後述します)

class RecommendItemSpec extends Specification{

    def setupSpec() {
        //テストクラスで一回だけ実行される初期化処理
    }

    def setup() {
        //テストケースごとに実行される初期化処理
    }

    def "テストケース"() {
        setup:
        // テストケース内の初期化/前提条件の設定など

        when:
        // テスト対象の処理実行

        then:
        // テスト結果確認

        cleanup:
        // テストケース内の後処理

    }

    def cleanup(){
        // テストケース毎に実行される後処理
    }

    def cleanupSpec(){
        // テストクラスで一回だけ実行される後処理
    }
}

[実装の具体例]
商品の単価と個数を渡すとその合計金額を返却するメソッドのテストケースです。
テスト結果の確認をする際はassertXXXXのようなチェック用メソッドを呼ぶ必要はなく、
通常プログラムのように比較演算子で確認することができます。

def "合計金額計算_when_then版"() {
    setup:
        def charge = 1000
        def number = 5
        def amount = new Amount(charge, number)

    when:
        def result = amount.totalAmount()

    then:
        result == 5000
}
def "合計金額計算_expect版"() {
    setup:
    def charge = 1000
    def number = 5
    def amount = new Amount(charge, number)

    expect:
     // テスト対象実行とテスト結果確認を一緒に行う。
    amount.totalAmount() == 5000

}

テーブル記述(データテーブル)を使ったパラメタライズドテストの実装

・ユニットテストにおいて、一つのテスト対象メソッドに
 複数パターンのパラメータを指定して繰り返しテストを行うたいことはよくあることと思います。
・例えば年齢と性別の情報を受け取り、
 以下のように年代と性別の組み合わせでおすすめ商品を出力仕分ける処理を考えた場合、
 テストパターンとして年齢の境界値テストが必要のため、
 最低12パターンのテストケースが必要となります。

年代        性別     おすすめ商品
10・20代     男性     アイテムA
10・20代     女性     アイテムB
30・40代     男性     アイテムC
30・40代     女性     アイテムD
50・60代     男性     アイテムE
50・60代     女性     アイテムF

通常であれば12のテストケースを書く必要がありますが、
その場合テストコード行数が長くなり可読性が悪く、
また何度も同じようなテストケースを繰り返し書くと、
設定値や期待結果にミスが生まれやすくなると思います。

Spockではこのようなテストを実施する場合、
データテーブルと呼ばれるテーブル形式でテストデータを記述することで、
渡し繰り返しテストを実行できる機能があります。
このデータテーブルを使用する場合はwhereブロックが必要となります。

[実装の具体例]

@Unroll
def "おすすめ商品取得"() {
    setup:
    //テーブルの値(age, gender)をパラメータとしてセット
    def recommend = new RecommendItem(age, gender)

    when:
    def result = recommend.recommendItem()

    then:
    //テスト結果とテーブルの期待値(expected)を比較
    result == expected

    where:
    age | gender   || expected
    10  | 'male'   || 'item-A'
    29  | 'male'   || 'item-A'
    30  | 'male'   || 'item-C'
    49  | 'male'   || 'item-C'
    50  | 'male'   || 'item-E'
    69  | 'male'   || 'item-E'
    10  | 'female' || 'item-B'
    29  | 'female' || 'item-B'
    30  | 'female' || 'item-D'
    49  | 'female' || 'item-D'
    50  | 'female' || 'item-F'
    69  | 'female' || 'item-F'
}

テーブルの一行目(テーブルヘッダ)は各項目の変数名にあたり、
setupブロックやwhen/thenブロックで使用することができます。
テストを実行すると二行目以降のテストデータが順次この変数にセットされ、
テーブル行数分のテストを実行してくれます。
この実装例では、
agegenderの値がsetupブロックのRecommendItemクラスのパラメータとしてセットされ、
thenブロックでwhereブロックのexpected(期待値)の値とテストの結果を比較しています。

このデータテーブルを使用したテスト(パラメタライズドテスト)を行う際は
テストケースに@Unrollをつけるのを忘れないようにしてください。

@Unroll
def "おすすめ商品取得"() {

理由としては@Unrollをつけずにテストを実行すると
下記のように全パターンテストを行なった後にテスト合否結果は返してくれるものの、
何回目のテストで失敗したかは表示をしてくれません。
f:id:ys-yamano:20191220005146p:plain

@Unrollをつけてテストを実行すると、
下記のように各回別のテストの合否が確認できるようになります。

f:id:ys-yamano:20191220005354p:plain

Mockを使ったテストコード実装

DB接続などが伴うユニットテストでDBのデータに依存しないよう
Mockを使うケースがあると思いますが、
Spockでは標準でMockの機能が用意されています。

以下は商品IDを指定してDBから商品情報を取得する処理で、
DBから商品情報を取得するDaoクラスをMock化した実装例です。

[テスト対象クラス]

public class ItemService {
    ItemDao dao;

    public Item find(int itemId) {
        return dao.select(itemId);
    }
}
public interface ItemDao {
    Item select(int itemId);
}
public class Item {
    int id;
    String itemName;

    public Item(int id, String itemName) {
        this.id = id;
        this.itemName = itemName;
    }
    public int id() {
        return id;
    }
    public String itemName() {
        return itemName;
    }
}

[テストコード例]

    def "Mockを使ったテスト"() {
        setup:
        // ItemDaoをMock化
        ItemDao mockDao = Mock()
        def service = new ItemService()

        // Mockオブジェクトにテストデータをセット
        mockDao.select(1) >> new Item(1, "商品1")
        // ItemServicedaoプロパティをMockオブジェクトで書き換え
        ItemService.metaClass.setAttribute(service, "dao", mockDao)

        when:
        def result = service.find(1)

        then:
        result.id() == 1
        result.itemName() == "商品1"
    }

ポイントとしてはモック化したいクラスに
SpockのMockingApi.Mock()メソッドをセットしモックオブジェクトを生成する点と、

ItemDao mockDao = Mock()
//または以下でもOK
def mockDao = Mock(ItemDao)

{メソッド} >> {テストデータ}の記述で
指定したデータを返却するよう振る舞いを定義している点です。

以下の場合ではselectメソッドのパラメータ値が1の場合のみ、
商品ID:1 商品名:商品1のデータが返却されるよう定義しています。

mockDao.select(1) >> new Item(1, "商品1")

以上、簡単ですがSpockの実装方法について書かせていただきました。
内容がご参考になれば幸いです。

Google Optimizeで行うA/Bテストのすゝめ

こちらは、アソビュー!Advent Calendar 22日目の記事になります。

はじめまして!新卒2年目の山内と申します。

特徴的な髪型から、社内ではキノコとしばしば呼ばれています。
現在は、社内基幹システムの運用保守とバックエンド開発をしているのですが、1年目のときにアソビュー!のグロースを目的としたA/Bテストを行っていたので、その時のことを書いていこうと思います。

A/Bテストとは

A/Bテストとは、オリジナルページに変化を加え、オリジナルパターンとテストパターンを同条件で比べて効果をはかるテスト手法になります。

様々なツールがありますが、弊社では、VWO(visual website optimizer)やGoogle Optimizeを利用していました。 今回は、Google Optimizeでの手法を紹介していこうと思います。

テスト準備

さて、いざテスト実装と言いたいところですが、施策を考えなければ実装ができません。
施策を考えるときに重要になるのが、施策の目的・課題・仮説を明確にすることです。

▼ページを特定

まず、テストを行うページを特定します。

▼課題の抽出・仮説出し

次に仮説を考え、そのページにはどのような課題があるかを洗い出します。

私の場合、色々な場面で実際にアソビュー!を使って仮説を出していきました。
外にいるときはこのボタンは目立たないかもしれない。であったり、
お出かけ先で、明日の朝体験したいとなったらどのような情報があったら良いだろうか。 等々あらゆる場面や時間、状況を想定しました。
また、実際に知人にアソビューを使ってもらい、ページの課題のインタビューを行いました。

▼施策出し

最後に、課題を解決する施策を出します。

このように施策を考え出すまでの目的・課題・仮説を明確にすることでテスト後の考察が的確にできるようになります。

いざテスト実装

いざ、テストを実装していきます。
今回は、アソビューのチケット枚数選択画面の「購入する」というボタンに対して実際に行ったテスト例を紹介します。

f:id:mauchi0106:20191221105230p:plain
こちらの「購入する」というボタンの文言を変更していきます。
チケット枚数選択の文言が、「購入する」だといきなり購入がはしるのでは、という不安感があるため、文言を変更することで遷移率が向上するのではないか。という仮説のもと、他に適切な文言がないかを検証するため、下記の文言でテストを行いました。

  • 購入に進む
  • 上記で購入に進む
  • 次へ
  • 枚数を確定する

こちらの4つの文言をOptimizeに反映させ、テストを作成します。
OptimizeではGUIで簡単にボタンの大きさや色、文言を変更することができます。
ただ、この方法だと実際のページ上にDOMが追加されたり、複数ボタンがある際に思わぬ挙動を起こす可能性があるため、 直接Optimize上でCSSを上書きする方法がおすすめです。

Optimize上で実際にCSSを書いてみるとこのような感じで文言の変更ができます。

.button {
  font-size : 0px;
}

.button::after {
  font-size : 14px;
  content : "購入にすすむ";
}

※クラス名は仮です。

f:id:mauchi0106:20191220134705p:plain
「購入に進む」に変更できました。

このようにCSSを上書きすることで、文言の変更を行うことができます。

テスト開始

テストパターンの作成が完了したら、目標の設定や、テスト対象ページの設定、対象デバイス(PC/SP/タブレット)等の選択を行い開始ボタンを押すと実際に開始されます。
ページにもよりますが、2週間ほどテストを行うと十分なOptimizeが十分な結果が取れたと認識し、レポートが作成されます。

f:id:mauchi0106:20191221111900p:plain
今回の結果がこちらになります。 Optimizeによると、「購入にすすむ」が目標に対して、最適である可能性が高いことがわかりました。

効果検証

最も重要なのは効果検証です。

今回の結果から、「購入に進む」というすぐに購入するわけではないという文言を示したことにより、ゲストの不安が払拭され、次ページへの遷移率が向上したと考察する事ができます。

このように結果を元に、なんでうまくいったのだろう、なんでうまくいかなかったのだろうと考察すると思います。
その際に活きるのが、テスト前に明確にした施策を行った目的・課題・仮説です。
うまくいった際は、この仮説のようなゲストが多かった、なので同じ仮説を元に考えると次はこの施策をやってみよう、であったり、
うまくいかなかった際にも、この仮説のようなゲストは少なかった、なので次の施策はこうすれば良いのではないかと考えることができます。

施策の目的・課題・仮説を明確にすることで格段に素早くPDCAを回すことができます。

効果まとめ

1つのテストが終了し、結果を集約することも重要です。

私達は、A/Bテストの成功事例を青シート、失敗事例を赤シートというようにまとめました。

今回の結果をまとめたシートがこちらです。

f:id:mauchi0106:20191221115743p:plain

失敗した際も同じように考察し、シートにまとめます。

f:id:mauchi0106:20191221120443p:plain

変更点は何なのか、目標や仮説は何なのか、結果はどうなったのか、どう考察できるのか、をすべての施策に対してまとめました。
このようにシートにまとめることで、ひと目で過去の具体的なA/Bテストの振り返りや、社内でA/Bテストの知見を広めることができます。

また、A/Bテストのタスク管理チケットや実際の結果、青/赤シートがわかるようにスプレッドシートに集約し、管理していました。
このように一括して管理することで、テスト後のソースコード改修や、変化率、テストの消化状況をモニタリングすることができます。

f:id:mauchi0106:20191221122142p:plain

まとめ

実際に僕は紹介した手法で、2ヶ月間で15個ほどのA/Bテストを実行しました。

うまくいったもの、いかなかったものとそれぞれありましたが、どちらのパターンもシートにまとめ考察することで、アソビュー!を利用するゲストがより使いやすくなるにはどう改修すればいいのか、という知見を貯めることができたと思います。
これからA/Bテストを行おうと思っている方は参考にしていただけると幸いです!

最後に

アソビューでは、一緒にワクワクしながら課題解決の遊びしたい方大募集中です!

hrmos.co

スケールできなかったオフショア開発拠点をなんとかした話

この記事は アソビュー! Advent Calendar 2019 - Qiita 21日目の記事です。
アソビューにてバックエンドエンジニアの服部と申します。
今年の夏よりベトナム開発組織 ASOVIEW VIETNAMのCTOに任命され、組織のスケールアップをメインミッションとして日々奮闘してます。
最近はベトナムにくると「帰ってきた...」というホーム感がでるようになってきました、不思議です。

f:id:takayasu-hattori:20191221102109j:plain:h250
無秩序なバイクのイメージが強いホーチミンですが、テト明けは人も少なく装飾が綺麗です。

はじめに

オフショア開発を初めてみたはいいが、どこかうまくいってない。
開発は進んでるしリリースもできてるんだけど、人の入れ替わりは多いし
どこか日本と現地で噛み合っていないような。。。

アソビューでは2018年にベトナムにオフショア開発拠点を自社で立ち上げおります。
ベトナム開発拠点の立ち上げから1年。
新規開発系のプロジェクトはうまく回っていたが、エンハンスメント案件系の対応チームがこの問題に直面しており、スケールしづらくなってました。
それを少しだけ改善した話。

ここで説明しないもの

スクラムガイドに記載されている事柄について

なんかうまくいってない

ベトナム開発拠点が立ち上がってちょうど1年たった頃くらいから、
日本のメンバーから下記のような声がよくあがるようになってきました。
もちろん自分も同じように言っていた1人です。

  • 依頼通りのものが出てこない
  • 各タスクの進捗がわからない
  • コード品質が悪い
  • プロダクトバックログに積まれたタスクが山ほどあるのにタスクがないと言われる
  • PO/決裁権のある人が知らないないものが開発されている
  • 数ヶ月で人がすぐ辞めていく
    etc...

(これってベトナム組織の問題なの...?というものが多数混ざってますが一旦。)
離職率も上がっているように見え、ベトナム開発組織でなにが起きているのかみんないまいちちゃんと把握できてない状態でした。
ということで、ベトナムに参りました。

何が起きていたか

ベトナムに来て最初やっていたことは、とにかく毎日観察/調査です。

  • 日本側と「どのような」やりとりを「どこで」やってるのか
  • どんなイベント(朝会/定例等)をやっているのか
  • だれがリーダー的なポジションで、だれがコミュニケーションの中心にいるのか
    etc...

訪越して2-3週目くらいたってようやく、どうやらベトナム組織単体の課題ではなく、
日本組織にも大いに課題がありそうということが見えてきました。

下記一例ですが、

  • 複雑なレポートライン
    • 事業をまたがり自由に依頼
    • ブリッジエンジニアを通さない依頼
  • 不確実性の高いタスク
    • 要件定義がきちんとされていないタスク
    • POを通さずに要件定義されたタスク
    • いろんな人が自由に作ったtodoレベルのタスク
    • 何に貢献しているのか説明されていないタスク
  • 柔軟すぎる開発体制
    • 開発計画のない作業体制
    • 作業の振り返りをしていない
    • チーム間のメンバー移動がフレキシブルに行われている

f:id:takayasu-hattori:20191220234144p:plain
レポートラインが多すぎて誰も管理できてない状況に
なかなか渋い状況ですね。。。日本で管理できていると思っていましたが、見えないところでいろんな作業が発生していました。
もちろんベトナム組織からも下記の不満が出てきていました。

  • 案件依頼にルールがない
  • タスクをやってもリリース直前で止められる
  • タスク完了直前で仕様変更がある
  • 事業貢献度がわからない
  • 開発の進め方にルールがない

これではベトナム組織も疲弊するのは当然です。

これ、ほとんどスクラム開発で解決できそう

課題としては、日本組織は開発依頼体制が、ベトナム組織は開発受け入れ体制が整備されていないこと。
スクラムガイドラインに則った開発ができるように整備すれば、両方とも解決できそう。

日本組織では2年ほど前よりスクラム開発を導入し、知見も溜まってきております。
採用/教育含めスクラムマスターをベトナムで急遽起用するのは現状難しそうだと判断し、
CTO兼スクラムマスターとしてスクラム開発を数チームに導入し、改善を図ってみることにしました。

スクラム導入の下準備

とはいえ、「スクラム開発やろうぜ!」といって始められるわけではないので下準備(スプリント0の実施)をしていきます。
巻き込まないといけな人が多いので、実際にスプリントを回し始めるより大変です。

ベトナム開発チーム内での準備

ベトナム開発組織のメンバーは全員スクラム開発をやったことがないとのこと。
スクラムガイド にベトナム語訳があったので、1週間以内に読んでくることを依頼。
また、下記を並行で実施しました。

  • 日報の提出依頼
  • 毎朝、同じ時間に朝会の実施(のちにデイリースクラムに移行)
  • スクラムガイドの読み合わせ
  • スクラムイベントの簡単なシミュレーション
  • タスク管理ツールのルール化

POとの調整

チームの整理および当時のプロダクトバックログや今後のタスクに関してはPOと整理しました。

  • チームの整理
    • ベトナム開発チームの数/メンバー構成は変えない
    • 事業ごとにベトナム開発チームをつけ管理することを合意
  • プロダクトバックログの整理
    • 不要なもの(個人のtodoや決済権を通ってないタスク)は削除
    • 要件定義できていないものはICEBOXへ
    • 必要なタスクの作成
  • タスクの依頼ルートの整理
    • 基本的にPOがタスクを作成する
    • POがOKを出した場合のみ、そのタスクに関してのみ代理でスクラムマスターがタスクを作成することができる(これはスクラムガイドに反してますが今回OKとしてます)
    • メンバー/サポートからの依頼の吸い上げはPOが参加するミーティングでやるやらを判断する

f:id:takayasu-hattori:20191220234026p:plain
メッシュになってたのが嘘のよう。レポートラインが明確化されました

そしてスクラムへ…

初めてのスクラムイベント

スクラムイベントの初回実施時は、どのチームでも質問責めにあいます。
1つ1つの行動や発言に対して

  • なんでやるのか
  • 今のはどう言う意味か
  • こうした方が効率がいいと思う

いろんな意見が出てきますが、ちゃんと意味を説明し、また初回なので教科書通りにやっていくことを伝えます。
とはいえみんなポジティブで、「スクラム開発やってみたい!だから教えて欲しい!」という姿勢なので、
やりにくさや進めづらさはなかったです。

導入してどうなったか...

課題としてあげた部分に関してはほぼ解消。
また以前は個人プレーが割と多いと感じてましたが、イベントをこなすにつれてチームでゴールを達成するにはどうすればいいか。という良い空気も出てきました。

ここ、さらっと書いてしまっていますがスクラム導入までの整理や調整が本当に大変で、導入後は日々みんなで改善を繰り返していくだけです。

f:id:takayasu-hattori:20191220234732j:plain:h250
レトロスペクティブ後のようす。最初は恥ずかしがってましたが最近は楽しそう!

とはいえ、課題はまだまだ

以前よりよくなった!とはいえ開発チームが自走し、かつ組織がスケールしていくには課題がまだまだあります。
日本の遊びをもっと世界に広めたい!、オフショア開発や海外経験してみたい。もしそういうことに興味がある方はぜひ一緒に働いてみませんか?

というわけで、アソビューでは一緒に働ける、世界をワクワクさせたい仲間を募集してます。
hrmos.co

Next.jsアプリをNowで公開する

アソビューAdvent Calendar 2019の20日目の記事になります。

フロントエンドエンジニアの野口です。 今日は、Next.jsアプリを最近話題のNowで公開する方法を紹介します。
ほんとに一瞬で公開できます。 ​

Next.jsとは

​ Reactで簡単にSSR(server side rendering)を可能とするフレームワークです。
SSRはもちろん、高速ページロードのための自動コードスプレティングやHMR(Hot Module Replacement)をサポートしたWebpackベースの開発環境などをデフォルトで用意してくれています。 ​

Nowとは

Nowは、Next.jsを開発しているZeit社が提供するPaasです。
Nowを使うことで非常に簡単にデプロイすることができます。 ​

Next.jsアプリを作成

​ まずはNext.jsアプリを作成します。

create-next-appを使うことで、簡単にアプリケーションの雛形を作成可能です。​
かなり多くのExamples(雛形)が用意されているので、用途に合わせて選ぶことができます。
next.js/examples at canary · zeit/next.js · GitHub

create-next-appを使わない場合でも、Reduxの設定など、単純なReactアプリとは少し異なる設定をしないと行けない時もあり、そんな時にここのExamplesはかなり参考になります。

  • アプリ作成 ​
npx create-next-app {app-name}
  • アプリ起動 ​
yarn dev

http://localhost:3000/にアクセスするとデフォルトで用意されているページを確認できます。 ​

Next.js デフォルト初期起動画面
nextjs prepared page

Nowで公開

​ さきほど作成したアプリを公開していきます。
CLIの場合と、ブラウザでポチポチ操作していく場合をそれぞれ紹介します。 ​

CLIの場合

  • Nowにサインアップ

まずは、こちらからNowにサインアップします。
Githubアカウントで登録できます。

  • Now CLIをインストール
npm i -g now
  • Now login

以下コマンドを実行し、サインアップ時に登録したメールアドレスを入力。

now login
> We sent an email to hoge@hoge.co.jp. Please follow the steps provided
  inside it and make sure the security code matches Gentle Tasmanian Devil.
  ⠦ Waiting for your confirmation

​ メールの認証を求められるので、届いたメールの認証ボタンを押します。 認証完了すると以下のメッセージが表示されます。 ​

✔ Email confirmed
> Congratulations! You are now logged in. In order to deploy something, run `now`.
  • Nowにデプロイ

これで準備は完了です。
あとは以下のコマンドを実行するだけでデプロイされます。

now
> Deploying ~/Documents/work/next-now-sample under {account name}
> Using project next-now-sample
...
> Ready! Deployment complete [2m]
- https://next-now-sample.now.sh

https://next-now-sample.now.shにアクセスするとNext.jsアプリが公開されているのを確認できます。 ​

GUIの場合

​ コマンドではなく、画面を操作するだけでもデプロイできます。

  • サインアップ

CLIと同じく、こちらからサインアップします。

  • Nowのセットアップ

デプロイ対象のリポジトリを選択。 ​

対象のリポジトリを選択
select target repository

  • テンプレートを選択

続いてテンプレートを選択します。今回の場合は、Next.jsを選択。

フレームワークを選択
select target framework

その後、プロジェクトネームを設定し、デプロイを実行。
しばらくすると、こんな感じでデプロイが完了します。

デプロイ完了画面
done deploy
​ ​ ボタンをぽちぽち押していくだけで簡単にデプロイできます。 ​ ​ ​

最後に

Nowは無料枠でいろいろできます。
個人的にいいなと思ったのは、カスタムドメインの設定。これも無料でできます。
デプロイも100/per dayなので十分です。

てっとり早くアプリを公開したいときにはかなりいい選択肢になるんじゃないかなと思います。

おしまい。

精算システムについて

アソビュー Advent Calendar 2019の19日目。
こんにちは。アソビューで精算システムやSREチームを担当している並木です。
マイクラ歴3年になるのに、いまだに豆腐建築から抜け出せません。
建築センスをください。

簡単な経歴

・SIerとして、銀行の勘定系システムを担当
・Webベンチャーの営業っぽい雑用係を担当
・アソビューにJOIN
元々銀行のシステムを担当していたこともあり
アソビューでは主に精算周りのシステムに関わらせていただいております。
今回は、精算システムにおける計算処理の注意点などを書きたいと思います。
※アソビューのシステムに関連する部分もありますが、基本的には一般論として書かせていただきますので、ご容赦ください。

法改正対応

記憶にも新しい

消費税の改正 8% → 10%

実際にシステムにどんな影響があるか?

税率計算の処理

  • 増税のたびに影響範囲の確認、修正規模が多くなりがちな悪い実装例
  • 影響範囲は変わらないが、修正規模が小さく、結果テストなどに掛かる工数も減らせる良い実装例
悪い実装例
  • ※言語はCOBOLにて実装した場合のイメージとなります。あくまでイメージです。

料率をDBでの管理ではなく、code にベタ打ち固定で記載している場合は
全ての固定箇所に修正が必要になり、修正範囲の確認から修正、テストと掛かる工数も大きくなりがちです。
このような例はミスも起こりやすく、「お金を取り扱うシステム」としてはよくない例となります。

  WORKING-STORAGE  SECTION.
     01  ONIGIRI     PIC 999 VALUE 150.
     01  SYOUHIZEI    PIC 9999.
     01  ZEIKOMI     PIC 9999.
     01  ZEIRITSU    PIC 9   VALUE 8.  -- 消費税率 計算時は /100 で使用
~~
         COMPUTE     SYOUHIZEI =  ONIGIRI * (ZEIRITSU / 100).
         COMPUTE     ZEIKOMI =  ONIGIRI + SYOUHIZEI
~~

実際に計算してみると
150円のおにぎりの消費税を固定で8%税率で計算し
それを加算することで税込金額として算出。

   150 + 12 = 162円 

が計算結果となります。

良い実装例

レコードの構成イメージ

f:id:yusuke-namiki:20191215221008p:plain

DBで料率を管理し、増税時に掛かる作業としては

最新レコードの挿入(inesrt)
直近レコードの終了日の更新(update)

のみであり、このDBを呼び出している箇所全てに適用されるため
影響範囲の特定も容易で、テストも機械的に実施できることになります。

  WORKING-STORAGE  SECTION.
     01  ONIGIRI     PIC 999 VALUE 150.
     01  SYOUHIZEI    PIC 9999.
     01  ZEIKOMI     PIC 9999.
     01  ZEIRITSU    PIC 99.  -- 消費税率 計算時は /100 で使用
~~
     select zeiritsu
       from db-syouhiziei
        where now() between start_day and end_day 
-- 今日がどの税率なのかを判定させる条件
     MOVE zeiritsu TO ZEIRITSU

         COMPUTE     SYOUHIZEI =  ONIGIRI * (ZEIRITSU / 100).
         COMPUTE     ZEIKOMI =  ONIGIRI + SYOUHIZEI
~~

これであれば今の日付を見て、最新の税率を取得して計算しているので
税率の最新レコードの insert と一つ前のレコードの update 処理さえ忘れていなければ
実際の計算処理には影響が出なくなります。

帳票類(レシート、振替用紙などなど)

消費税(税率8%) ¥108 

といった形で固定で出力している場合には、計算処理同様に
最新の税率を取得して出力するなどの工夫が必要になります。
キャッシュレスに先んじてペーパレスは事前に対応していることが多かったため
そこまで大掛かりな回収は必要ではなかった認識でいます。

今回は触れていませんが 軽減税率の場合のレシート出力については、政府からも対応例が出ていますね。 https://www.gov-online.go.jp/tokusyu/keigen_zeiritsu/jigyosya/kubunkisai.html

まとめ

いかがでしたでしょうか。
今回は消費税のことにのみ触れていますが、法改正に関わるシステム改修に関して
一部ではありますが、サンプルを記載してみました。

昨今では法改正に関わるシステム改修がテレビなどでも取り沙汰されるようになりましたので
「どんなことしてるんだろう?」の理解の一助になれば幸いです。

最後に

記載した内容について、開発と運用の責任者を募集中です!
※COBOLは必須スキルではありません。むしろいらな・・ry
成長著しいベンチャーで社内基幹システムの開発・運用責任者大募集! | アソビュー株式会社