企業ブースで実コード公開をした話 #phpcon2016

この記事はVOYAGE GROUP techlog / Advent Calendar 2016の記事として書いています。

こんにちは、@pro_shunsukeです。

VOYAGE GROUPはPHPカンファレンス2016にスポンサーとして協賛させていただきました。PHPカンファレンス2016に関してはこのブログの中で事前告知発表のあとがき、また企業ブースを出して学んだことなどを記事として掲載しています。今回でPHPカンファレンス2016に関する記事は4つ目という事になります。

さて、上記の記事にも詳しく書かれているのですが、弊社ではスポンサーとしてプレゼン発表や企業ブースを出展させていただきました。どちらもとても多くの方に興味を持っていただきました。今回はその中でも特に興味を持っていただいた 企業ブースでの実コード公開 をした際の知見ついて書こうと思います。実コードを見せるに至った経緯や当日の反応、また全体を通しての反省点を記事として残していこうと思います。

なぜ実コードを見せる事になったのか?

カンファレンス当日は弊社の@dkkomaによる「老舗メディアが改善に取り組んでいる話」という発表が行われました。

speakerdeck.com

17年間運営しているECナビというサービスについて、歴史を踏まえてどのように改善に取り組んできたのかについて発表しました。 しかしやはり発表だけではどうしても綺麗事のように聞こえてしまう部分があり、より具体的な取り組みが分からないのでは?という事になりました。そこで 実際に改善したコードの具体例を紹介 することで、見に来ていただいた人に1つ1つの地道な改善を実感していただきたいと思いました。

どのようなコードを選んだのか?

実際にプロダクトで使われているGithubの Pull Request(PR) を3つ選び、差分を見ていただきながら改善の様子を紹介したり、レビューの様子を紹介しました。3つのPRは、発表の中でもありましたKAIZEN会*1で出たコードを選びました。また、 ドメイン知識の必要のない外向けに分かりやすい内容のもの を選びました。

当日紹介した実コードの例

f:id:pro_shunsuke:20161217154120p:plain

当日の反応

とても興味をもっていただいた人が多く、さまざまな反応をいただきました。

反応していただいた意見

以下のような意見を多くいただきました。

  • 「分かる。大変だよね」
  • 「どうやったらうちの組織でも改善出来る体制になるだろう?」

地道な改善作業に対する共感の声がありました。

また、改善するのは大切だけれどなかなか手が出せない、どうしたらそのような体制づくりが出来るだろう?という相談も多かったです。難しい相談ですが、1つは長くサービスを続けていく上では継続的な改善が必要だという事に対するプロデューサーの理解があった事が大きかったと思います。

どのような人に興味を持っていただいたか?

興味をもっていただいた人は30代, 40代くらいのベテラン感のある人が多く、若者にはあまり響かなかったようでした。やはり「改善」に焦点を当てていたこともあって、業界の経験が豊富な人により響いたようでした。

全体としての反省

基本的にはとてもよかったのですが、いくつか反省点もありました。

体制について

予想を上回る反応をいただいた事はとても嬉しい事でしたが、 当初想定していた体制では興味を持っていただいた人全員には説明しきれなかった という事がありました。

最初は説明者1人とモニター1台で望みましたが、徐々に来場者が多くなるに連れて説明が間に合わなくなりました。当日は臨時で説明者を増やし 3人体制 にしたことで、ピークのランチタイムにはちょうど良くなりました。

お客さんの要望について

例えば以下のような要望がありました。

  • 「プロジェクト全体のディレクトリ構造はどうなっているの?」

当初はPRだけを見せるつもりでいたので、ディレクトリ構造までは見せることが出来ませんでした。しかし後になって考えてみたところ、ディレクトリ構造くらいなら見せても良かったのでは、ということになりました。 どこまでを見せてよく、どこまでは見せてはいけないのかは最初にバチッと決めておいた方がよかった です。

最後に

反省点はあったものの、当日は予想を上回る人に興味をもっていただきとても嬉しかったです!次回同じような機会がありましたらこの反省を活かして、より良いブースを出展出来たらと思っています。

また、発表やブースを見に来ていただいた方々に 少しでも改善の文化が伝わっていただけたら と思っています。

それでは明日の記事もお楽しみに!

*1:KAIZEN会についてはこちらの記事でも詳しく紹介しています。 ECナビ KAIZEN会を実施しました

CTOからの挑戦状2016 2ndを手伝った際に書いたPythonコードを晒してみる。

この記事はVOYAGE GROUPのAdvent Calendar 2016 11日目の記事として書かれています。 techlog.voyagegroup.com

みなさんこんにちは。ara_ta3 と言います。
今回はVOYAGE GROUPとサポーターズで行ったイベント
CTOからの挑戦状 2016 2nd にて採点したり、
学生向けに解答を用意したりしたのでその時の話をします。

supporterz.jp

参加された学生の方に参考になれば幸いですし、
今回はPythonでのLintや型ヒント + mypyの実装も記載しているので、
それらの使い方の参考にもなれば幸いです。

CTOからの挑戦状 2016 2nd とは?

  • 問題を解いて賞金が得られる
  • 問題はLV1, LV2, LV3がある
  • LV1は早い者勝ちで賞金 が得られる
  • LV2, LV3はCTO含む3人のエンジニアがコードを読み、評価順に基づいて賞金が得られる
    • 例えば
      • 変数や、メソッド、関数の命名がある程度適切か
      • 適度に処理がメソッド、関数に切り分けられているか
      • テストケースは適切か
    • みたいなところを見たりしました。

問題について

LV1 ~ LV3の問題はgistに記述していて、それぞれ下記URLになります。
問題はピザや寿司の割引クーポンの最適化問題という感じですね。

Level.1 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

Level.2 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

Level.3 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

これらの問題を自分も解いてみたので、
そのコードで気をつけた所などをつらつらと書いてみようかと思います。
Pythonの解答数が最も多かったので、私もPythonで書いてみました。
Versionは3.5.2です。

書いてみたコード

準備

Python詳しくなかったので、開発環境周りをとりあえず調べてみました。

[PR]Pythonの環境については別途Qiitaで記事も書いているので是非ご覧いただければと・・・ qiita.com

ざっとやったこととしては

みたいなところです。

LV1

問題は簡単に言うと、
複数の割引クーポンを使ってピザ・寿司の支払金額を出来る限り少なくすると言ったものでした。

問題
Level.1 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

私の解答
CTO Challenge 2016 2nd LV1 · GitHub

主に書いたコードは下記のような感じです。
単純に高い割引額のクーポンから使って行けば良さそうですね。
とりあえずLV1のケースをテストしました。
for文の中くらいのスコープなら適当な変数名でもいいけど、
少し広めなスコープのときは妥当そうな命名をつけるようにしました。

また、"準備" の部分に色々書いたりしましたが、
このタイミングではあまり複雑じゃなかったのでmypyは入れませんでした。

lowest_amount_for_using_coupon = 1000


def select_optimum_combination(amount, coupons):
    if amount <= lowest_amount_for_using_coupon:
        return []
    sorted_coupons = sorted(coupons, reverse=True)
    rest = amount
    used_coupons = []
    for c in sorted_coupons:
        if rest < c:
            break
        rest -= c
        used_coupons.append(c)

    return used_coupons


def test_select_optimum_combination_case1():
    selected = select_optimum_combination(1000, [500, 500, 200, 100, 100, 100])
    assert selected == []


def test_select_optimum_combination_case2():
    selected = select_optimum_combination(1210, [])
    assert selected == []


def test_select_optimum_combination_case3():
    selected = select_optimum_combination(1210, [500, 500, 200, 100, 100, 100])
    assert selected == [500, 500, 200]


def test_select_optimum_combination_case4():
    selected = select_optimum_combination(1530, [500, 500, 200, 100, 100, 100])
    assert selected == [500, 500, 200, 100, 100, 100]


def test_クーポンが降順になっていなくても割引額が高い順に割り引いてくれること():
    selected = select_optimum_combination(1530, [500, 200, 100, 500, 100, 100])
    assert selected == [500, 500, 200, 100, 100, 100]

LV2

LV2では少し仕様が増えました。
入力がLV1では金額でしたが、メニューに変わりました。
また、クーポンの使用回数に制限が加わりました。
さらにメニューの種類によって使えるクーポンが増えました。
めんどくさいですね

問題
Level.2 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

私の解答
CTO Challenge 2016 2nd LV2 · GitHub

CouponSelectorというサービスクラスにロジックを入れました。
テスト済のLV1で使った関数はそのまま利用したかったので、
CouponSelectorクラスのクラスメソッドとしてリファクタリングしました。
このタイミングで型ヒントを入れてみました。
メソッドの引数にCouponやMenuクラスを含むDictやListなどの少し複雑な型を渡したくなり、
それら以外は渡さないで欲しいことを明示的にわかるようにしたくて入れました。
Python3.5.2現在では型ヒントだけではPythonの処理系はなにもしてくれないのですが、
mypyで意図しない型の入力がないか確認しています。

coupon_selector.py

from typing import List, Dict
from functools import reduce
from collections import Counter
from menu import Menu

Coupon = int


class CouponSelector():
    def __init__(self,
                 limitation_of_coupons: Dict[Coupon, int],
                 coupons_available_for_pizza: List[Coupon],
                 lowest_amount_for_using_coupon: int
                 ) -> None:
        self.limitation_of_coupons = limitation_of_coupons
        self.coupons_available_for_pizza = coupons_available_for_pizza
        self.lowest_amount_for_using_coupon = lowest_amount_for_using_coupon

    def select_optimum_combination_by_menus(
            self,
            menus: List[Menu],
            coupons: List[Coupon]):

        amount = reduce(lambda amount, m: amount + m.price, menus, 0)
        available_coupons = _filter_limited_coupons(
            coupons,
            self.limitation_of_coupons
        )
        if not _pizza_exists(menus):
            available_coupons = [c for c in available_coupons
                                 if c not in self.coupons_available_for_pizza]
        return self.select_optimum_combination(amount, available_coupons)

    def select_optimum_combination(self, amount: int, coupons: List[Coupon]):
        if amount <= self.lowest_amount_for_using_coupon:
            return []
        sorted_coupons = sorted(coupons, reverse=True)
        rest = amount
        used_coupons = []
        for c in sorted_coupons:
            if rest < c:
                break
            rest -= c
            used_coupons.append(c)

        return used_coupons


def _pizza_exists(menus: List[Menu]):
    return any(m.is_pizza() for m in menus)


def _filter_limited_coupons(
        coupons: List[Coupon],
        coupon2limit: Dict[Coupon, int]):
    counter = Counter(coupons)
    for c, n in counter.items():
        counter[c] = min(coupon2limit[c], n)
    return list(counter.elements())

menu.py

from typing import Dict, List, Tuple


class Menu(object):
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def is_pizza(self):
        return type(self) == PizzaMenu


class PizzaMenu(Menu):
    def __init__(self, name, size, price):
        super(PizzaMenu, self).__init__(name, price)
        self.size = size


class SideMenu(Menu):
    def __init__(self, name, price):
        super(SideMenu, self).__init__(name, price)

テストケースはこちら

LV3

LV3ではセットメニューという概念が加わりました。
クーポンを使うよりもセットメニューの方がお得など、なんとなくありそうな仕様ですね。
セットメニュー特定のサイドメニューが無料になるものもあれば、
任意のサイドメニューが無料になるもの、
具体的な割引額が決まっているものもあります。
またセットメニューを頼める時間帯が決まっているものもあるようです。
仕様がめんどくさいですね。

クーポンの割引額とセットメニューの割引額をそれぞれで出して両者を比較すればいいかなと考えました。
なので比較する部分を除くと、LV2から追加されたのはSetMenuのディスカウント額を計算するプログラムのみです。

問題
Level.3 - 2016 2nd - Challenge CTO of VOYAGE GROUP · GitHub

私の解答
CTO Challenge 2016 2nd LV3 · GitHub

SetMenuDiscounterというサービスクラスを作成しました。
処理の流れは
注文されたメニューと注文された時間から利用可能なセットメニュー一覧を取得し、
一覧の中で最も高い割引額のセットメニューを決めて返す。
と言ったものです。
工夫したのは、クーポンが利用可能か判定する部分を関数で表現しました。
仕様に1種類しかないので、抽象化するにはまだ早いかなと考えたためです。
今見返してみて、 SetMenuDiscount クラスのどのメソッドを見るべきが悩んだので、
privateメソッドかどうかも意識して書けばよかったなと思いました。

from datetime import datetime
from typing import List, Tuple, Callable
from menu import Menu


class SetMenuDiscounter():
    def __init__(self, discounts: List[SetMenuDiscount])->None:
        self.discounts = discounts

    def decide_discount(self,
                        ordered: List[Menu],
                        current_time: datetime
                        )->Tuple[SetMenuDiscount, int]:

        availables = [d for d in self.discounts
                      if d.is_available(ordered, current_time)]
        if not availables:
            return (None, 0)

        best, *rest = availables
        best_discount = best.calculate_discount(ordered)
        for d in rest:
            p = d.calculate_discount(ordered)
            if best_discount < p:
                best = d
                best_discount = p
        return (best, best_discount)


class SetMenuDiscount(object):
    def __init__(self, name: str,
                 required_menus_combinations: List[List[Menu]],
                 available_time_rules: List[Callable[[datetime], bool]],
                 discount_target_menus: List[Menu],
                 discount: int = None)->None:
        self.name = name
        self.required_menus_combinations = required_menus_combinations
        self.available_time_rules = available_time_rules
        self.discount_target_menus = discount_target_menus
        self.discount = discount

    def is_available(self, ordered_menus: List[Menu],
                     ordered_time: datetime)->bool:
        return self.in_available_period(ordered_time) \
            and self.is_matched(ordered_menus)

    def is_matched(self, ordered_menus: List[Menu])->bool:
        ordered_menu_set = set(ordered_menus)
        for combination in self.required_menus_combinations:
            c = set(combination)
            if c.intersection(ordered_menu_set) == c:
                return True
        return False

    def in_available_period(self, ordered_time: datetime)->bool:
        if self.available_time_rules is None:
            return True
        return any(map(lambda fn: fn(ordered_time), self.available_time_rules))

    def get_target_discount_menus(self, ordered_menus: List[Menu])->List[Menu]:
        t = set(self.discount_target_menus).intersection(set(ordered_menus))
        return list(t)

    def has_amount_discounts(self)->bool:
        return self.discount is not None

    def has_menu_discounts(self)->bool:
        return len(self.discount_target_menus) > 0

    def calculate_discount(self, ordered_menus: List[Menu])->int:
        price = 0
        if self.has_menu_discounts():
            menus = self.get_target_discount_menus(ordered_menus)
            prices = map(lambda m: m.price, menus)
            m = max(prices, default=0)
            price = max(price, m)
        if self.has_amount_discounts():
            price = max(price, self.discount)
        return price

テストケース

まとめ

CTOからの挑戦状 2016 2ndの際に書いたLV1, LV2, LV3のコードを晒してみました。
それぞれほんの少しですが、意図や工夫した点を書いてみました。
工夫した点を少しまとめると

  • coreなコード(ここで言うとcoupon selector)に仕様的なものが含まれないようにしました。
    • クーポンの具体的な割引額や使える枚数の制限など。
  • パフォーマンスを考えるよりも処理の流れがわかりやすくなるように心がけました。
    • クーポンが1億枚手元にあるからそこから選ぼうということはあまりないかなと考えてます。
  • 変数などの命名は出来る限りわかりやすくするようにしました。
  • Lintや型チェックなどを入れてテストを書かなくてもエラーがある程度わかるようにしました。

説明がわかりづらい、ここなんでこんなクソ実装なんだ!
とかありましたら是非我らがAJITOでお話でもできればと思うので、お声がけください!w

voyagegroup.com

感想

  • テストケースに不備があったらごめんなさい・・・指摘頂ければ幸いです。
  • あまり触ったことないコードに触るとき形(コーディング規約やLintなど)から入るといい感じにそれっぽくかけるから良い
    • ただし、エディタ設定沼にはまらないように注意
  • CTOからの挑戦状のコード書くの大変・・・w
  • 型ヒントとライブラリで頑張るならコンパイルでわかる(言語がサポートしてくれている)方が個人的にはいいなぁ

Google SpreadSheetをサーバーレスっぽく使ってみる

これは VOYAGE GROUP Advent Canlendar 2016 の 9日目のエントリです。

VOYAGE MARKETINGの @katzchum (ちゃむ)です。

先日のLTのネタで作成したアプリの技術的なフォローです。
過去記事ですが、LTの様子はこんな感じです。

techlog.voyagegroup.com

今回のLTの中でつくってみた系として発表したのが、
チェックインされた場所の一覧を時系列に検索するアプリです。

check map-in

https://k2tzumi.github.io/check-mapin

LTおもしろネタの題材として過去のチェックインリストを紹介する為に、視覚化を行うアプリケーションとして作成しました。 *1
(データの元ネタは自虐的なものなので、敢えて明言しませんが。。)

システム構成

LTなのでわざわざコストをかけて環境を構築するのもなんだかな〜と思いserverless チックなシステム構成としました。

Web Servergithub.io
ApplicationJavaScript
(Framework : JQuery(-ui))
Cache system WebStorage
* sessionStorage
* localStorage
BaaS Google Map API v.3
Rakuten WEB SERVICE(楽天トラベル系API)
Database Google SpreadSheet
(Query language : Google Visualization API)
Data seed データ収集 / Scraper (Google Chrome Extension)
データ集計・加工 / COUNTIF, SUMIF, VLOOKUP, DATEDIF(Google SpreadSheet)
スクレイピング / IMPORTXML(Google SpreadSheet)

Webサーバーもgithub.ioを利用して構築手間を省く、ずぼらっぷりとなりました。
アプリケーションの配信の手間もはぶけて楽チンです。

データベース周り

視覚化対象のデータは楽天トラベルの過去予約照会から引っ張ってきました。
データはGoogle SpreadSheetへ入力していきました。
ただデータの入力方法を考える必要がありました。
自身が考えていたよりもデータ件数が多かったのと、チェックインの履歴と住所情報を紐付けるのに、照会ページのUI(HTML構造)では手数が多く難しそうでした。

スクリプトを組んでデータ収集することも考えましたが見送りました。
認証をパスしないといけないのが手間なのと、ワンオフで問題ないのでコストが見合わないとの判断です。

ここでも手間を省くために以下のアプローチを採用しました。

  1. Google Chromeで楽天トラベルへアクセスし、extensionで必要なデータだけ抽出する
  2. Google SpreadSheetへ入力して、データの加工及び補足情報の追加はGoogle SpreadSheet側に任せる

1のデータ抽出はScraperを利用しました。
Scraperを利用すれば、認証後のページでもちょこっとしたスクレイピングは難なくこなせます。
使い方は、抜き出したいページの要素をXPathで指定するだけです。

こんなページを f:id:katzumi:20161127141011p:plain

こんな感じで抜けます。 f:id:katzumi:20161127141052p:plain

指定するXPath自体も Chromeのデベロッパーツールで調べられます。
前回の記事でも書きましたが、Chromeはこういう所でもやっぱり便利です。

2のデータ加工はスプレッドシートの関数で対応出来ます。
収集した予約履歴ではそのままではGoogle SpreadSheet側に正しい日付として判断されないので、そちらの加工等にも利用しました。
続いて補足情報の追加は IMPORTXML というExcelにはない便利な関数を利用します。

=IMPORTXML("http://techlog.voyagegroup.com/entry/2016/07/25/080000", "//title")

とすると

モバイルファーストなサービス開発におけるDockerの活用術 - VOYAGE GROUP techlog

指定したURLのタイトルを取得できたりします。

こちらの関数を利用してデータ加工で抜き出した施設IDをRakuten WEB SERVICEのREST API のエンドポイントURLを組み立てて呼び出します。
レスポンスをXML形式で取得できるのでXPathで必要な項目のみを抽出します。

同じ施設IDが複数存在した場合を考慮して、ユニークな施設IDのみを別シートにして IMPORTXML で補足情報を読み込んでおいて、実際のデータは VLOOKUP で補足情報を関連付けしておきました。
その他、チェックイン回数など事前集計できるものについては集計関数で計算を行っておきました。

最終的にはこんな感じのデータを作成しました。

f:id:katzumi:20161127142850p:plain

フロント周り

フロント周りは日付範囲のスライダー(jQRangeSlider)を利用したかったので JQueryを採用しました。
JQueryでゴリゴリとバックエンドのAPIを呼び出しして地図表示を行っています。

GoogleSpreadsheetにはデータアクセス用のAPIが幾つか用意されていますが、今回はSQLライクに参照ができる Google Visualization API を利用しました。

  var query = new google.visualization.Query('//spreadsheets.google.com/tq?key=' + key + '&pub=1');

  query.setQuery('SELECT B, E, MAX(N), MAX(O), SUM(M), COUNT(F) WHERE ((G >= ' + min_serial + ' AND G <= ' + max_serial + ') OR (J >= ' + min_serial + ' AND J <= ' + max_serial + ')) GROUP BY B, E ORDER BY MAX(O) DESC');

上記がクエリ発行部分になります。
基本的な選択、集計、ソートを行うことができます。
ただやはり複雑なサブクエリの発行や複数シートを跨るクエリ発行までは行えないので、一工夫が必要となります。
上述のデータベース側の対応で記載したとおり、検索やデータ出力し易い様にVLOOKUP関数等でデータを加工しておくことでカバーが出来るかと思います。

今回の実装例では、Google SpreadSheetの関数とGoogle Visualization APIのクエリを組み合わせを行って

  • 全期間でのチェックイン回数(滞在期間)
  • 対象期間でのチェックイン回数(滞在期間)
  • 対象期間内でのチェックイン回数順位

を表示させることを実現しています。

施設名と住所の一覧をGoogle Visualization APIから取得した後の地図(住所のピン)の表示はGoogle Map APIを利用しています。
住所のピン表示は住所から緯度経度をGeocoding APIで求めてから行っています。 *2
ピンをクリックして施設名のリンク表示する際もRakuten WEB SERVICEから情報を取得して行っています。

データ件数が多いので、そのままAPIを呼び出すと、 query over limit に引っかかるので2つの対策を行っています。

  1. APIをレスポンスをキャッシュする
  2. キャッシュヒットミスした場合にWait追加

レスポンスキャッシュはWebStorageを利用しました。
KeyValueでキャッシュすることができますので、APIの検索キーをハッシュ化してKey指定、 value はAPIのレスポンスのJSONを stringify して格納しました。
Javascript でwait処理をUIをロックさせない方法で実装するのは悩んだのですが、APIの呼び出し中の回数をカウントして、閾値を超えた場合にAPIを遅延実行する秒数を調整する方法を取りました。
ここら辺の query over limit を超えない範囲で、うまくAPIの並列性を保つ方法をもしご存知の方がいらっしゃいましたら、 @katzchum宛 もしくはブクマコメで指摘して頂けると助かります。

感想など

  • 課題・反省点
    jsでAPIをごりごり呼び出ししているので Deferred の嵐になってしまった。
    もう少しスッキリかけると良いかな〜と。
  • 得られたもの
    Google SpreadSheetを使えたのは、色々学びがありました。
    Google SpreadSheet自体でスクレイピングができたのは嬉しい発見でした。
    今回は利用しませんでしたが、Spread Sheet APIでデータ更新を行えばバックエンドサービスとして色々広がるなと感じました。
    又、Spreadsheetに入力していたデータをSQLで参照させるのはMicrosoft Accessっぽい感じがして面白かったです。
    今回の様な簡単なシステムでサーバーを調達するまでもないものにはフィットするのでは?と感じました。
  • LTをやってみた感想
    ランキングも発表したのですが、Slackのonairチャネルで先読みされて面白かったです。 皆 AJITING大好き。橙を根城にしている90年台のネタが通じる素敵な おじ様 猛者達がいることを再確認できました。

明日のエントリもお楽しみに!

*1:開発動機として、最近ロケーション情報を利用したアプリが増えてきているのでジオコーディングをやってみるか〜と思って作ってみました。

*2:Rakuten APIでもgeocodeが取れたりしますが、検証の為に敢えて住所緯度変換を行っています。

社内カフェのススメ

これは VOYAGE GROUP Advent Canlendar 2016 の8日目のエントリです。

お暇なときにお読みください。

今回は弊社の社内Bar AJITO...ではなく、社内カフェ Garden について書きます。

ガーデンとは

一言で言うと、「バリスタ常駐型の社内カフェ」です。

バリスタさんがドリップしてくれたおいしいコーヒーを、なんとたった200円で飲むことができます。驚きです。

ホット/アイスに加えて水出しコーヒー(一晩かけて水で抽出したコーヒー)というものもあります。

おしゃれなサイトとInstagramがあるのでぜひ見てみてください。

Garden

動画がおしゃれ。

Instagram (@officecafe_garden)

ちなみに先週のメニューはこんな感じでした。

f:id:saxsir256:20161207194151j:plain

豆がなくなると、また新しい種類が入ったり入れ替わりもあるので飽きずに通えます。

オススメのコーヒー

せっかくなのでバリスタさんにオススメの豆を聞いてみました。

  • 一般の店だと飲めないような海外の豆が飲める
  • しかも200円
  • 味は浅煎りから深入りまで、満遍なく揃えている

ということで、どれもおすすめらしいですが一番はなんですか?と聞いてみたところ エチオピア がオススメとのこと。

コーヒーの概念を覆す

どことなく紅茶のようで、苦手な人でも飲みやすい

コーヒーが苦手な人でも、これなら飲めるという人が多い

by バリスタのJさん

コーヒーを飲んでる間にクルー(社員)同士で軽く仕事の話をしたり、詰まってた実装のひらめきが降ってくることもあったり、私自身は日に1, 2回くらいコーヒーを飲んでます。

カフェインが苦手な人向けにデカフェ(カフェインレス)のコーヒーも用意してあったりします。

ちなみに個人的なオススメは

  • コスタリカ

    • 浅煎りコーヒーが好きになったきっかけ
  • ベラフィンカ(水出し)

    • 二日酔いの朝に効きます
  • エチオピア

    • 迷ったときのエチオピア

です。

f:id:saxsir256:20161207194108j:plain

美味しいコーヒーがお手頃価格で飲めるのはもちろん、バリスタさんがいつも頼む豆とか好みを覚えてくれているのはとても嬉しいです。

あと、私はタバコを吸わないのですがふとした時にコーヒーでも飲みに行くかーと席を立つきっかけになるので重宝しています。

というわけで、社内カフェのススメでした。

イニシャルコスト0円で導入できるらしいので、興味がある方はホームページを見てみると良いかもしれません。

Garden

PHPカンファレンスに企業ブースを出して学んだこと #phpcon2016

本記事は VOYAGE GROUPのAdvent Calendar 7日目の記事として書かれています。


企業ブースを出して学んだこと3行まとめ

  • カンファレンスに何かを出すことはWebサービスを考えることと同じだった
  • 議事録大事
  • 早めに動き出すの大事

こんにちは!
ECナビでエンジニアをやっています、yukimine です。

1ヶ月ほど前にも 告知ブログ を書きましたが、VOYAGE GROUPはPHPカンファレンス 2016でスポンサーとしてプレゼン、ブース出展をさせていただきました。

9月上旬に社内でPHPカンファレンスの運営チームが発足され、僕もその一員として準備を進めました。
本記事では、そのとき 何をやったかどんな風に進めたか 、そして 何を学んだか を書いていきます。

(写真は当日の企業ブースの様子です) f:id:yuk4420:20161205015004j:plain

何をやったか

  • ターゲットとコンセプトの決定
  • プレゼン発表者のアサイン
  • 当日配布されるチラシの製作
  • 企業ブースの企画・運営
  • 企業ポスターの製作

僕たちが一番最初に行ったのは ターゲットとコンセプトの決定 です。
PHPカンファレンスは幅広い年齢層の方が参加されていますが、どんな人にどんなことを伝えたいのかを決め、それに沿ってプレゼンや企業ブース、チラシやポスターを考えることで、 VOYAGE GROUPってこんな会社なんだVOYAGE GROUPの話を持って聞いてみたい と興味・関心に繋げられればと思っています。

ターゲットとコンセプトが決まったら、プレゼン発表者に誰をアサインするか話し合いました。
決め方はいろいろあると思いますが、僕たちは、業務内容や経歴、性格から 想いの乗ったプレゼンをしている姿が思い浮かぶか を基準に話し合いました。

どんな風に進めたか

  • 体制
    • アドバイザー 兼 予算取り 1人
    • プレゼン発表者 1人
    • デザイナー 1人
    • 企画・運営 3人

体制でよかったと感じたことは、 デザイナーがアサインされた ことです。
特に今回は コンセプトに沿って制作物を用意したかった ので、運営チームにデザイナーがいるのは百人力でした。
デザイナーの方にはミーティングにも参加していただき、 コンセプトと制作物のすり合わせ を入念に行いました。
またネームカードの用意など、細かい点にも配慮していただき、運営チームのエンジニア陣からは感謝してもしきれないくらいです。

  • 発足時期
    • 9月上旬(PHPカンファレンス 2016の約2ヶ月前)
  • スケジュール感
    • ターゲットとコンセプトの決定
      • 1ヶ月
    • 制作物やプレゼンの準備
      • 1ヶ月

発足時期はカンファレンスの約2ヶ月前と、個人的には早めの印象を受けますが、これは大正解でした。
カンファレンスまで余裕があったので、ターゲットとコンセプトの決定に1ヶ月割くことができ、その間に 仮説を立てる → 社内へのヒアリング といったサイクルを回すこともできました。

  • 議事録は欠かさず録る
    • Google ドキュメントを利用

当たり前のことかもしれませんが、 議事録は絶対録った方がいい です。
「先週のミーティングで何話したっけ?」といったときに役立つことはもちろん、 次回の運営チームのための大切な資産 になります。
強くてニューゲーム 、したいじゃないですか。

学んだこと(まとめ)

運営チーム発足からカンファレンス終了までを振り返ってみると、 プレゼン、企業ブース共に好評だったりスケジュール通り準備を進められたり運営チーム全体でユーザー目線での議論ができたり と大成功だったのではないかと思います。
同時に課題もたくさん見つかりましたが、それもきちんと 議事録に残っています

今回学んだことをまとめて締めさせていただきます。

  • ターゲットを考えて、ターゲットに何を伝えたいかを考えて・・・カンファレンスに何かを出すことはサービスを考えることと一緒だった
  • 早めに動き出すの大事
  • 議事録大事

本記事中の運営チームという言葉は、VOYAGE GROUPのPHPカンファレンス運営スタッフという意味であり、PHPカンファレンス自体の運営チームやスタッフ様とは関係ありません。

勉強会はピザとビール以外の夢を見るか

これは VOYAGE GROUP Advent Canlendar 2016 の5日目のエントリです。

こんにちは。いろいろやっている sakmt です。 お手すきの時間にお読みください。

栄華を極めた黄金コンビ

世に存在する数多の勉強会, 懇親会, LT会, ……。 今宵もどこかで誰かが、ビールを片手にピザを頬張り、技術談義に花を咲かせていることでしょう。

そう、ビール🍺とピザ🍕。 シリコンバレーのビアバッシュからの流れを汲むこのスタイル、近年web界隈で急速に広まりつつあります。

王者の光と影

先日、エンジニアを志す学生と話している中でこんな言葉がありました。

「最近はどの企業さんに行ってもピザを出してくれるんで、今週ピザ3回目ですよ(笑)」

どこもかしこもビールとピザ。猫も杓子も、ビールと泪と男と女とピザ。 我々は少し、ピザに頼りすぎているのではないか?

いや、別にピザが嫌いなわけではありませんよ。 むしろ好きです。 味の種類もあるし、あの分割された円形はコミュニケーションが生まれやすい形をしていると思います [要出典]。 大した準備も必要ないし、ゴミもかさばらない。 何より適度な油分と塩分が、とても良くビールに合う。

しかし前々から思っていたんですが、ピザって食べ物としての完成度が低いくないですか? 食べやすさよりも作りやすさに主眼を置いた形。 手がベタつくのでウェットティッシュあたりが無いと正直しんどい。 特にLT会などでは、ベタついた手でPC/Macを触らぬよう最新の注意を払う必要があります。

そこで勉強会, 懇親会, LT会などに出す食事を、今一度考えてみることにしました。

※ 最強を決めよう!という意図ではなく、それぞれの選択肢についてメリット・デメリットを明らかにしよう、というまったりとした趣旨です

勉強会食、その品格

「勉強会における食」という文脈では、考察するべき点は多数あります。

  • 美味しいか
  • 味に飽きないか, 個々人の好みに対応できるか
  • 個人が摂取する数量の調節が容易か
  • 主催者側で数量の調節が容易か
  • 箸, 取り皿, 醤油皿, ウェットティッシュなどの準備が必要か
  • 片付けが容易か
  • 時間が経って冷めるなどしても品質が大きく損なわれないか
  • 食べるときに大きな音がしないか
  • 水滴が服やマシンにはねないか
  • 匂いが強烈ではないか
  • 手が汚れないか
  • 金額が高すぎないか

などなど……

これらを踏まえた上で、さっそく様々な食べ物について見ていきましょう!

ピザ

f:id:sakmt:20161205233422p:plain

画像の出典: ドミノピザ

  • 安定感  ☆☆☆☆☆
  • 値段   ☆☆☆☆
  • 手の汚れ ☆

予約の楽さ, 品揃えの豊富さ, 値段の手頃さなどなど……。 勉強会食において、前述した幾つかの欠点も霞むほどの圧倒的存在感を放っています。

寿司

f:id:sakmt:20161205233627j:plain

画像の出典: 銀のさら

  • 華やかさ  ☆☆☆☆☆
  • クオリティ ☆☆☆☆
  • 値段    ☆

寿司とビールというのもまたオツなものです。 ちなみにアサヒス○パ○ドライは「刺身に合うビール」というコンセプトで作られた、ってマンガで読みました。 稀にいる魚介類がだめな方は卵とガリをご賞味ください。

個人的には、醤油は各々が醤油皿に出すよりも、最初にネタの上からかけてしまう方が効率が良くて好きです。

出前弁当

f:id:sakmt:20161205234251j:plain

画像の出典: マイドーイかどや

  • 守備力  ☆☆☆☆☆
  • 技の種類 ☆☆☆☆
  • 柔軟性  ☆

予め人数の確定している勉強会では鉄壁の守備力を誇ります。 ひとり一つのお弁当は食べる順番も、食べる量も、食べるスピードもコントロール自由です。 多くの場合はタンパク質, 食物繊維, 炭水化物のバランスも良いものになるでしょう。 そう、予め人数が確定していれば。

オードブル

f:id:sakmt:20161205234448j:plain

画像の出典: ほっともっと

  • パーティ感 ☆☆☆☆☆
  • 揚げ物   ☆☆☆☆
  • 準備    ☆

最近は近くのファミレスで作ったものを注文できるようです。 筆者の記憶によると、オードブルとは揚げ物とパスタが9割を占めるものです。 取り皿や箸を別途準備する必要があることもあるので注意です。

スーパーやコンビニの惣菜

f:id:sakmt:20161205234619j:plain

画像の出典: 魚辰

  • 計画の不要さ  ☆☆☆☆☆
  • アットホーム感 ☆☆☆☆
  • 華やかさ    ☆

事前に何も考える必要がないのが最大の利点です。 そして大概安いです。 複数人で買い出せば買い物中もワイワイできます。 良く言えば「宅飲み」をしているかのようなアットホームな雰囲気に包まれます。

お菓子

f:id:sakmt:20161205235048p:plain

画像の出典: ブルボン

  • アドオン力 ☆☆☆☆☆
  • 甘いもの  ☆☆☆☆
  • 食事    ☆

良くも悪くも食事にはなりません。 これまで挙げたヤツらと組み合わせて使われることが多いです。 ポテチは手が汚れるので、ハッピーターンやカントリーマアムのような個包装タイプのものが喜ばれるでしょう。

生ハム原木

f:id:sakmt:20161205235229j:plain

画像の出典: N-Styles

  • インパクト ☆☆☆☆☆
  • 塩分    ☆☆☆☆
  • それ以外  ☆

1万円台〜3万円台するようです。 到着してから2,3日常温に慣らす必要があるようです。 塩分が豊富です。

ちなみに弊社の AJITO では生ハム原木を召喚した実績があります(これが言いたかっただけ)。

マグロ解体ショー

f:id:sakmt:20161205235406j:plain

画像の出典: 鮪名人

  • コンテンツ力 ☆☆☆☆☆

弊社内LT会で一度出た案です(これが言いたかっただけ)。 どう考えても勉強会よりもマグロ解体ショーがメインのコンテンツになってしまうため却下されました。 数十万円かかりますが、一度見てみたい&食べてみたいものです。

まとめ

勉強会で使えたり使えなかったりする情報をお届けしました。 え!?勉強会を開催したいけど場所がない!? そんな皆さまへ朗報です!

VOYAGE GROUPには「無料シェア会議室 PORT」という仕組みがあり、 セミナーや勉強会の目的であれば定時以降に 会議室を無料で借りることができます! (これが言いたかっただけ)

voyagegroup.com

また、ピザや寿司をスポンサーとして差し入れしたりもしています。 気になる方はお気軽に @tech_voyage までご連絡ください!

VOYAGE GROUP Advent Canlendar 2016 、明日の記事もお楽しみに!

2016年は技術力評価会の話がわりと好評だったので、2017年は積極的に事例を紹介していきたい

これは VOYAGE GROUP Advent Canlendar 2016 の1日目のエントリです。

CTOやってる @makoga です。昨年は大トリでしたが今年はトップバッターをゲットしました。2年ぶり4回目。

今年は技術力評価会の話を聞かれる機会がたくさんあり、わりと好評だったと思います。
このような仕組みを5年以上継続している組織は他ではあまり聞かないので、今後は積極的に事例を紹介していきたいなと考えています。

このエントリでは公開済みのものについての社外の方のコメントと、私の登壇予定を紹介します。

3行まとめ

  • 新卒のブログエントリが368ブクマ獲得
  • そのエントリ経由でのインタビュー記事が347ブクマ獲得
  • RSGT2017で登壇決定

新卒のブログエントリが368ブクマ獲得

2016年4月に入社したエンジニアが書いたエントリがhotentry入りしました。

techlog.voyagegroup.com

はてなブックマークTwitterには下記のようなポジティブなコメントがありました。とても嬉しい。

  • 仕事柄様々な企業の人事評価制度を見ているけど、なかでもこの VOYAGE の「技術力評価会」は非常に良くできた仕組みだと思う
  • すごい(こなみかん)
  • “その妥当性を他人に伝えるためには(仮に)組織的な決定事項や自分の判断ではないとしても「自分の言葉で」説明できなければなりません” つよそう
  • 半年でやったことを説明して評価してもらうみたいなやり方がいいと思うんだよね。
  • 自身の成果を人に説明できる能力って少なくとも日本のエンジニア苦手なイメージあるから、こーゆーの素敵。「なぜやるか」ってのより意識して仕事するようになるし。
  • この仕組み本当に良いなぁと強く思う。お互いのスキルが明確になるのが良い。
  • なぜやるのかを意識する文化いいな
  • これはお互いに成長できそうで良い
  • こういう評価制度いいですね。理解してるつもりのこともいざ人に説明すると難しいですよね。
  • なんで?を説明するのマジで大事なので良い取り組みだと思いました
  • 凄く良いですね。当たり前だけど自分の言葉で説明って大事。
  • 良好なチームができてて、目的と結果の明度が高いから出来ることだなぁ。
  • なぜって忘れられたり置いてけぼりにされることが間々ある要素だけどそこを意識できる良い仕組みだと思う
  • 自分できちんとなぜ?を考えて説明する機会を与えるのはとても良さそう。「言われたからやりました」とか言ってくる人が減るだろうし。
  • 技術に限らずだとは思うけど、人に自分の言葉で説明できるって大事だよね
  • エンジニアの視点が変わるというか、そろうような感じを受けていいなぁと思いました。

そのエントリ経由でのインタビュー記事が347ブクマ獲得

上記エントリのおかげでSELECKからお声がけいただき記事にしてもらいました。これまたはてなブックマークTwitterでポジティブなコメントが多くて嬉しい。

seleck.cc

  • 対象者 * 90分以上も工数かけるという判断と、みんな人の評価をするのは嫌という前提に立っているところが良い。
  • エンジニアにとって良さそうな会社だな。
  • 「事業には当たり外れがあるからこそ、評価制度が重要」これホント大切だと思う(震え声)
  • “評価資料も、結果もすべてGitHubで社内に公開” よさそう
  • 「欲しいと言われたら何でも作るの?そんなエンジニアは、うちでは評価されないよ」から、チームの決定事項に個々人が責任をもつことをしっかり根付かせているのが良いと思った。
  • 点数っていうものは評価を短縮してわかりやすくする代わりに無思考にする手段の1つだと思っていて、なくしたのは素晴らしい
  • 評価全公開はすごいな…。誠実であろうという意志を感じる
  • 自分からどこを評価して欲しいかプレゼンしてもらう場って有用だよなぁとほんと思う。
  • 素晴らしい取り組み。"運営する人の覚悟と、経営陣がそこに時間を投資する覚悟があれば、どの職種にでも活かせる"
  • “「評価者を育てる」という視点” これができてる会社ってあるのだろうか?成長しようとしない評価者に評価してもらうのはモチベーション下がるよなあ。
  • この制度ほんと今年イチですごいと思った
  • うちもそうだけど、こういうのはエイヤッと作るものではなくて少しずつ社内メンバのコンセンサスを取りながら文化として醸成していくものだよね。いい感じ
  • 良い評価者を育てるという視点がGood

RSGT2017で登壇決定

上記2つが公開されてから社外の人と何回か話をしました。同じ文章を読んでも受け取り方が違うことが多く、どれも良い議論ができました。

どこかで発表したいなと思っていたときに Regional SCRUM GATHERING Tokyo 2017 を教えていただきました。たしかに組織変革とプロセス改善というテーマであれば参加者に面白い話ができるかもと応募し、ありがたいことに採択してもらいました。

confengine.com

このイベントではどのように改善してきたのかにフォーカスを当てて話をしたいと考え、絶賛ネタ推敲中です。

来年に向けて

まずは Regional SCRUM GATHERING Tokyo 2017 で話をし、その後ニーズがあれば弊社の大会議室(100人規模)や自慢の社内バーAJITOで再演&質疑応答できればと考えています。
ご興味ある方はブコメやTwitter、または直接お声がけくださいmm


明日はいつも私に新しい刺激をくれる @katzchang です。楽しみですね!