開発生産性向上の取り組みについて

id:eitoball です。

この記事は、Misoca Advent Calendar 2017 の記事として書いています。

昨日、NGK2017B 昼の部 で「開発生産性向上の取り組みについて」という内容のLTをしてきました。今回は、LTで話したことについて書いていきます。

開発生産性向上の取り組みとは?

いきなりですが、

If I had 8 hours to chop down a tree, I would spend 6 of those hours sharpening my axe. (もし8時間、木を切る時間を与えられたら、そのうち6時間を私は斧を研ぐのに使うだろう。)

という言葉をご存じでしょうか?第16代アメリカ合衆国大統領エイブラハム・リンカーンが残したと伝えられている名言の1つです。

斧で木を切る際、斧の刃をしっかり研いでよく切れるようにすると早く効率よく木を切ることができるようになりますね。物事に取りかかる際には、しっかり準備をして取りくむと生産性が上がるという意味です。

Misoca では、開発生産性を向上させる、もしくは、停滞させないようにいろいろな取り組みをしてます。具体例としては、

という取り組みをしています。以下の技術ブログの記事はこの取り組みの成果になります。

継続的な向上

Misoca では、以前から有志がJenkinsとrrrspecと私に書かれているような CI 環境のビルド時間を短縮するなど生産性の向上に単発的に取り組んでいました。木を切り続けていると鋭かった斧の歯はだんだん鈍ってしまうように、開発チームの規模が大きくなるに従い実行するテストの数が多くなっていき、早かった CI 環境はだんだんと遅くなり、不満が上がるようになりました。

そのため、2017年5月頃より、生産性向上の取り組みを機能開発と同じようにプロジェクトとして、期間と人数を決めて、継続的に実施するようになりました。プロジェクトなので、最初に インセプションデッキ を書いて、事後検証をして終了します。

開発生産性向上のプロジェクトでは、基本的に期間を優先しています。2週間と決めたら成果が出る・出ないに関わらず、終了します。成果の出すのが難しいテーマの場合は、期間に合ったスコープ(ゴール)を決めながら、段階的に進めていきます。 CI のビルド時間の短縮の場合、

といった取り組みを行っていき、20分以上だったビルド時間を約5分程度まで短縮しました。

まとめ

物事を効率的に進めるには、事前準備と継続的な取り組みが必要です。Misoca では、開発生産性向上を機能開発と同様に扱って実施しています。

明日は、 merotan こと @renyamizuno_が「すごーい」何か書いてくれるそうです。楽しみです。

自動esaやり機とクリーンアーキテクチャ

🎄 Advent Calendar

もう12月ですね。
あっという間に、今年も残り1ヶ月となりました。

f:id:kokuyouwind:20171129095835j:plain

12月といえばクリスマス。
そしてアドベントカレンダーです。

f:id:kokuyouwind:20171129095858j:plain

そんなわけで、今年はMisocaでもアドベントカレンダーを作りました!

この記事はMisoca Advent Calendar 2017の1日目です。
中の人がそんなにいないので、同じ人が何度か出てくることになります。
途切れず25日完走できるか、乞うご期待。

あ、ちなみに初日の担当は@kokuyouwindです。
BOOTHで技術同人誌を頒布してるのでよろしくおねがいします。
コミケにも持っていきます。初日のキ-53aです。

🐤 esa.io

Misocaでは情報共有にesa.ioを使っています。
ポストをWIPで作ってから書き足していけるため、気軽に情報をアウトプットでき大変便利なツールです。

✍️ 寄せ書きポスト

Misocaでのesaの使い方の一つに「枠だけ作ったポストに各自が書き足す」場合があります。
寄せ書きみたいですね。

例えば、議題を持ち寄って話すミーティングでは、議事録のポストを作っておいて各自が議題を書き足していきます。
こうすると、当日は書いてある内容に沿って進行できますし、議事録の手間をだいぶ軽減できます。

また各自の日報についても、WIPで作って書き足していくスタイルの人がいたりします。
これは好みが分かれますが、自分は帰り際に記憶を掘り起こす必要がないのでこの方が好きです。

🔄 ポストの自動作成

WIPで書き足していく方法はかなり便利なのですが、「誰がポストを作るか」という問題があります。
特に週次で開催されるミーティングの場合、毎週だれかが作るのは結構面倒です。
また、うっかりすると2人が重複して新しいポストを作ってしまいます。

既にあるポストに書き足すだけで良いのが理想的なので、決まった周期で自動的にポストを作れると便利そうです。
残念ながらesa.ioにはそういった機能はありませんが、esa APIを使えば外部からポストを作ることができます。

そういった経緯で作ったのが自動esaやり機(esa_feeder)です。

🐣 自動esaやり機

自動esaやり機ではesa.ioの投稿テンプレートにタグを付与することで自動作成の指定を行います。

  • #feed_* をつけると指定した曜日に記事が作られる
    • #feed_mon で月曜日、#feed_tueで火曜日、etc...
    • #feed_wdayで平日指定(祝日を除く月〜金)
  • #slack_* をつけると指定したSlackチャンネルに記事作成通知
  • #me_* をつけると指定ユーザでポストを作成
    • 未指定だと esa_bot ユーザで作られる
  • ユーザごとの投稿テンプレートに対応
    • この場合は #me_* タグの指定に関わらず、そのユーザでポストが作られる

例えばtemplates/週次報告/%{Year}-%{month}-%{day} #feed_thu #slack_generalという投稿テンプレートがあった場合、

  • 毎週木曜日に 週次報告/2017-11-30 などのポストが作られる
  • 作ったポストがSlackの #general チャンネルに通知される

という挙動になります。

f:id:kokuyouwind:20171130163541p:plain

から

f:id:kokuyouwind:20171130163029p:plain

が作られる感じです。

⚙ 仕組み

内部処理は非常に単純で、当日の曜日に該当するタグをクエリとして、APIでテンプレートを取得し、それぞれのテンプレートごとにポストを作成しているだけです。
この処理はthorタスクで起動できるようにしており、Heroku Schedulerで毎日9時に実行するようにしています。

当初はデータベースにポストの作成設定を持たせたりWebUIをつけたりしようと考えていたのですが、必要な機能を絞った結果、非常にシンプルな構成になりました。

🌏 クリーンアーキテクチャ

esa_feederは単機能の小さなリポジトリですが、せっかく新しいリポジトリを作ったので、試験的にクリーンアーキテクチャに沿ってレイヤを切り分けてみました。

以下の画像はThe Clean Architectureのブログ記事からの引用です。

f:id:kokuyouwind:20171129105311j:plain

クリーンアーキテクチャではこの図のようにビジネスルールを中央に置き、フレームワークや外部APIなどの詳細には直接依存しないようにします。

📁 ディレクトリ構成

esa_feederbundle gemコマンドで初期構成を作ったため、lib/esa_feeder以下に主要なコードが配置されています。
lib/esa_feeder以下のディレクトリ構成は以下のとおりです。

esa_feeder
├── entities
│   └── esa_post.rb
├── use_cases
│   ├── feed.rb
│   └── source_tag.rb
├── gateways
│   ├── esa_client.rb
│   └── slack_client.rb
└── version.rb

entitiesは図中のEntities(黄色の層)に対応しており、関心の対象になるクラスを配置します。
今回はesaのポストを表わすEsaPostを作っています。

use_casesは図中のUse Cases(赤色の層)に対応しており、アプリケーションの振る舞いを扱うクラスを配置しています。
今回は「日付に該当するタグを取得する」と「指定したタグ群の記事を作成する」という2つの振る舞いクラスを作っています。

gatewaysGateways(緑色の層)とExternal Interface(青色の層)に対応しており、外界とやりとりするコードを置いています。
今回はesa.ioとSlackのAPIを利用するため、それらのクライアントを置いています。

もちろん、ユースケースではesaのテンプレートを取得したり記事を作ったりする必要があり、gatewayに配置されたクラスのインスタンスを知る必要があります。
しかしクリーンアーキテクチャの原則では内側のレイヤから外側のレイヤに依存することはできないため、ユースケースの初期化時に依存性注入(Dependency Injection, DI)するようにしています。

module EsaFeeder
  module UseCases
    class Feed
      def initialize(esa_port, notifier_port)
        @esa_port = esa_port
        @notifier_port = notifier_port
      end

      def call(tags)
        # ...
        post = esa_port.create_from_template(template, feed_user)
        # ...

🙆 メリット

クリーンアーキテクチャにしたことで各クラスの責務が明確になり、テストが書きやすくなりました。
特にユースケースが外界と接続する部分は必ずDIされているため、allow_any_instanceなどを使わずmockでテストでき、意図しない動作を起こしにくくなっていると思います。

# 直接依存しているとこうなりがち
before do
  allow_any_instance_of(Esa::Client)
    .to receive(:posts)
    .and_return(posts)
end
subject { described_class.new.call }

# DIするとこう書ける
let(:esa_client) { double('esa client', posts: posts) }
subject { described_class.new(esa_client).call }

また変更で影響を受ける層が限られるため、機能追加や修正の見通しが良くなります。

例えば#me_*タグでポストの作成ユーザを変えれるようにした際は、

  • #me_*という特殊タグの追加」というEntities層の変更
  • 「ポストを作る際に作成者を切り替える」というUse Cases層の変更

にとどまり、Gateways層には影響を与えずに修正できました。

🙅 デメリット

当然ですが責務をかなり細かく切り分けることでクラス数も多くなるため、コード量自体は多くなります。
またテストコードではモックが増え、特にUse Cases層ではGateways層とのやり取りを全てモックすることになるため、今回のようにAPIを順に叩くだけだと「これは何のテストだっけ…」という気持ちになってきます。

今回は実験的な意味もありクリーンアーキテクチャに沿った設計にしましたが、この規模のリポジトリではメリットよりもデメリットが大きいかもしれません。

❓ 迷い

クリーンアーキテクチャには境界をまたがるデータについて、以下のように書かれています。

典型的には、境界をまたがるデータは、シンプルなデータ構造だ。 (中略) われわれは、ズルをして、エンティティやデータベースの行を渡すべきではない。

これはもっともな気もするのですが、ユースケースgatewayから返ってきた値を毎回entitiesに変換するというのも筋が悪いような気がします。
そもそも緑色のInterface Adapter層は

ユースケースとエンティティにもっとも便利な形式から、データベースやウェブのような外部の機能にもっとも便利な形式に、データを変換する

のが責務と書かれているので、ここがEntitiesへの変換まで受け持ったほうが見通しが良くなるのではと思い、現状のesa_feederではgatewaysからentitiesを触っています。
自分の理解が浅いというのもありますが、このあたりは結構どうするか迷っている部分です。

💪 今後の課題

自動esaやり機はesa.ioとSlack通知を前提に作っていますが、Gatewayを切り替えれば他の情報共有アプリケーションで同様の記事作成を行ったり、通知先をSlack以外に切り替えたりできるはずです。
このあたりはRubotyの仕組みを参考にしてプラグイン方式にできないかと画策しています。

また細かい話ですが、議事録を事前に作ると記事中の日付が作成日になってしまうため、実際に会議を行う日とズレてしまいます。
このあたりをうまくサポートできる機能が付けられないかなーというのも考えています。

💬 余談

自動esaやり機を作るにあたりesa APIで「テンプレートから記事を作る機能」が欲しいなーと思い問い合わせたところ、その日のうちに実装完了メールが来てびっくりしました。
その後もパラメタ周りの問題を問い合わせたら1時間後には修正がリリースされていたりして、最高のスピード感でした。

📢 宣伝

Misoca Advent Calendar 2017、明日の担当はころちゃんです。

Misocaではesa APIをハックしたいエンジニアを募集しています!

📱MisocaアプリのiPhone X対応

mzpです。こんにちは。

MisocaのiPhoneアプリiPhone Xに対応しました。

🎨 デザイン変更

単純に最新のXcodeでビルドしなおしてみたが、iPhone Xのホームインジケータとボタンが被ってしまった。

f:id:mzp:20171116135049p:plain:w200

こういった画面はデザインを変更し、ボタンの下に余白を設けた。

f:id:mzp:20171116135803p:plain:w250

同様の位置にボタンがある画面がいくつかあったので、それらの画面も修正した。

📝更新通知画面

今回のバージョンから以下のような更新内容を通知する画面を導入した。

f:id:mzp:20171116140943p:plain:w250

実機で動かしてみたら、想像以上に格好よくて楽しかった。この写真のときは不具合修正のところに虫のピクトグラムを表示していたが、虫が気持ち悪いと言われたのでリリース版では魔法の杖に変えた。

f:id:mzp:20171115110809j:plain:w400

この画面を表示するかどうかの判断は以下のようにしている。前回起動したときとバージョンが異なっている場合は、この画面を出すようにした。

enum UpdateType {
    case initial
    case update
    case normal
}

class UpdateInfo {
    private let key = "latest_version"

    func requestUpdateType(f: (UpdateType) -> Void) {
        if !Auth.isLoggedIn() {
            // 未ログイン状態なら初期画面を表示する
            UserDefaults.standard.set(currentVersion, forKey: key)
            f(.initial)
            return
        }

        if currentVersion == previosVersion {
            // 前回起動したときと同じバージョンなら通常処理をする
            f(.normal)
        } else {
            // 前回起動したときと違うバージョンなら更新通知画面を出す
            UserDefaults.standard.set(currentVersion, forKey: key)
            f(.update)
        }
    }

    private lazy var currentVersion: String? = {
        guard let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
            return nil
        }
        guard let version = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
            return nil
        }
        return "\(shortVersion)-\(version)"
    }()

    private lazy var previosVersion: String = {
        return UserDefaults.standard.string(forKey: key) ?? ""
    }()
}

👈文書ボタンの長押し

文書作成ボタンやタブバーを長押しすると、作成する文書の種類を選べるようにした。

f:id:mzp:20171116151905j:plain:w250

これは UILongPressGestureRecognizer で実現している。

let view = navigationItem.rightBarButtonItem?.value(forKey: "view") as? UIView
view?.addGestureRecognizer(
  UILongPressGestureRecognizer(target: self, 
                               action: #selector(DocumentsViewController.addLongpressAction(_:))))

📢採用

MisocaではiPhoneアプリ開発に携わりたいエンジニアを募集中です。

特定のブランチをもとに本番同様の動作確認ができる「レビュー環境」の話

こんにちは。tkykです。

みなさん、コードレビューしていますか?今日はMisocaのレビュープロセスで用いられている、とっても便利な「レビュー環境」について紹介します。

Misocaのレビュー体制とその課題

MisocaではPull Request(以下、PR)ベースの開発体制をとっており、必ず他のエンジニアによるPRのレビューを経てから、masterへマージすることになっています。

レビュー時に動作確認をするには、エンジニア各自がローカル環境にブランチをチェックアウトして行うのですが、時にはそれだけでは不都合なケースもあります。

  • 非エンジニアにも動作確認をしてほしい
  • 動作確認をするための条件を整えたい
    • 最終的にはマージされないコードを一時的に追加したい
    • 依存するライブラリのバージョンを変更したい
    • RAILS_ENV=production でビルド・実行したい
    • などなど

このようなケースに対応するために、「レビュー環境」という仕組みを作りました*1

レビュー環境とは

レビュー環境とは、特定のブランチに基づいて構築された、独立したアプリケーションの実行環境です。専用のURLが割り当てられ、本番環境と同様に動作しますが、サーバリソースは独自に確保されており、自由な操作によるテストが可能です。

レビュー環境の使い方はとても簡単です。例えば今、 awesome-feature ブランチで新機能の開発を行っているとしましょう。このブランチを元にレビュー環境を構築するには、 review/[任意の名前] という名前のブランチをリポジトリにpushします。

git push origin awesome-feature:review/awesome

構築は自動で行われ、完了するとslackにそのレビュー環境専用に割り当てられたURLが通知されます。このURLにはGoogle認証によるアクセス制限がかかっていますが、Misocaの開発メンバーなら誰もが自由にアクセスすることができます。

(当初はPRが作られるたびに自動で環境を構築する方式も検討しましたが、すべてのPRが専用の環境を必要とするわけではなく、リソースの無駄が大きくなるので、ブランチ名で明示する方式になりました)。

仕組み

レビュー環境の正体はDockerコンテナです。コンテナの管理はAWS ECS(Elastic Container Service)で行っています。

全体の構造は次の通りです。GitHub Webhookを起点にAWS CodeBuildでDockerイメージの構築を行い、AWS Lambdaでコンテナの起動やslackへの通知を行います。

(prprについては過去の記事をご覧ください)

f:id:tkykmw:20171107151244p:plain

今回はCodeBuildの設定と、ECSを管理するLambdaの処理内容について、少し詳しく説明します。

CodeBuildによるDockerイメージの構築

CodeBuildではDockerイメージの構築と、ECR(Amazon EC2 Container Registry)への登録を行います。一連の処理の流れは、公式ドキュメントのサンプルとほぼ同じです。

# appspec.yml

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Building the Docker image...
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG -f config/docker/Dockerfile.review .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - curl -X POST -d "{\"repo_name\":\"$IMAGE_REPO_NAME\",\"image_tag\":\"$IMAGE_TAG\"}" https://xxxxxxxx.execute-api.$AWS_DEFAULT_REGION.amazonaws.com/build-review-container

イメージのタグとして、レビュー環境の名前(ブランチ名の review/[この部分])を割り当てます。ファイル中では $IMAGE_TAG という変数の部分です。

ECRへのイメージ登録が完了したら、後続のLambdaを実行するために、API Gatewayに対するHTTP POSTリクエストを発行します。このとき、レビュー環境の名前をパラメータとして渡します。

LambdaによるECS管理

ECSでは、コンテナの定義情報(タスク定義)と実行情報(サービス)を分けて管理します。実際に起動されるコンテナは「タスク」として管理されます。

レビュー環境を構築するためのLambdaの処理は、次の通りです。

  1. タスク定義を作成・更新する
  2. サービスを作成・更新する
  3. (必要な場合)既存のタスクを終了する

順にコードを見ていきます。Lambdaのランタイムはnode.js 6.10を使用し、エラー処理は省いています。

タスク定義

タスク定義は単純なJavaScriptオブジェクトとして作成し、registerTaskDefinition で新規作成または更新します。

/*
 * タスク定義には、
 * - Dockerイメージのパス
 * - ポートマッピングの設定
 * - 環境変数の設定
 * - ログ出力の設定
 * - etc.
 * が含まれる
 */
const taskDefinition = (repository, tag, hostname, port) => {
  return {
    containerDefinitions: [ {
      family: taskName(tag),
      image: `000000000.dkr.ecr.ap-northeast-1.amazonaws.com/${repository}:${tag}`,
      portMappings: [ { hostPort: port, containerPort: 3000, protocol: 'tcp' } ],
      entryPoint: [ 'bin/launch' ],
      name: tag,
      environment: [
        { 'name': 'RAILS_ENV', 'value': 'production' },
        { 'name': 'APPLICATION_HOST_WITH_PORT', 'value': hostname },
        { 'name': 'MYSQL_DATABASE_PRODUCTION', 'value': `misoca_review_${tag}` }
      ],
      logConfiguration: {
        logDriver: 'awslogs',
        options: { 'awslogs-group': 'ECS', 'awslogs-region': 'ap-northeast-1', 'awslogs-stream-prefix': 'misoca-review' }
      },
      memoryReservation: 256
    } ],
    networkMode: 'bridge',
    placementConstraints: [],
    volumes: [],
    essential: true,
    volumesFrom: []
  };
};

/*
 * タスク定義の更新 or 新規作成
 * `event` はCodeBuildからLambdaに渡されたイベントオブジェクト
 * ホスト名とポート番号はタグ名をもとに生成する前提
 */
ecs.registerTaskDefinition(
  taskDefinition(
    event.repo_name,
    event.image_tag,
    hostname(event.image_tag),
    port(event.image_tag)
  ), (err, data) => {
  updateOrCreateService(event.image_tag);
});

タスク定義の内容に変更がない場合、毎回更新する必要はないのですが、処理を単純化するために常に registerTaskDefinition を実行するようにしました。

サービス

タスク定義の作成(更新)に成功したら、その定義を参照するサービスを作成(更新)します。レビュー環境一つにつき、一つのタスク(=コンテナ)を起動したいので、desiredCountは1に設定します。

const updateOrCreateService = (tag) => {
  ecs.describeServices({ services: [serviceName(tag)], cluster: CLUSTER }, (err, data) => {
    // 削除されたサービスは 'INACTIVE' ステータスとして見える
    if(data.services.length > 0 && data.services[0].status !== 'INACTIVE') {
      ecs.updateService({
        cluster: CLUSTER,
        service: serviceName(tag),
        taskDefinition: taskName(tag),
        desiredCount: 1
      }, (err, data) => {
        stopRunningTask(tag);
      });
    } else {
      ecs.createService({
        cluster: CLUSTER,
        serviceName: serviceName(tag),
        taskDefinition: taskName(tag),
        desiredCount: 1
      });
    }
  });
};

サービスが作成されると、タスクの起動はECSが自動で行ってくれます。しかしすでにタスクが起動していた場合、そのままでは新たなイメージがロードされないので、stopTask で起動済みタスクを終了します。するとECSが新たなイメージとタスク定義に基づいて、タスクを再起動してくれます。

/*
 * 常に1件のみ、実行中であることを想定する
 */
const stopRunningTask = (tag) => {
  ecs.listTasks({
    maxResults: 1,
    cluster: CLUSTER,
    desiredStatus: 'RUNNING',
    serviceName: serviceName(tag)
  }, (err, data) => {
    ecs.stopTask({
      task: data.taskArns[0],
      cluster: CLUSTER,
      reason: 'Review deploy'
    });
  });
};

まとめ

いかがでしたでしょうか。

Dockerコンテナによる軽量な実装と、AWS APIを活用した構築の自動化によって、細かな目的ごとに、本番同様に動く環境を作れるようになりました。今ではレビュー目的に限らず、エンジニア各自が自由な実験を行うためにも用いられ、開発生産性の向上に寄与しています。

採用

Misocaでは日々の開発生産性の向上に取り組みたいエンジニアも募集中です!

*1:この名前と機能は、Heroku Review Appsにインスパイアされたものです

開発合宿で野尻湖に行きました

こんにちは。Misocaの「合宿神(がっしゅくしん)」、もといコロチャン(@corocn) です。

f:id:monoooki:20171025154753j:plain

まあ、いきなり「合宿神」って言われても、「合宿神って、なに?」ってなると思いますので「合宿神」について説明します。

f:id:monoooki:20171025154822j:plain

合宿の、神です。

参考: 東京都内のおすすめ銭湯&交互浴を銭湯神ヨッピーが伝授! | SPOT
(※銭湯神に許可を頂いております)

今回はそんな合宿神コロチャンが、 先日、長野のLAMPで2泊3日の開発合宿を行いましたので、その報告を書きたいと思います。

野尻湖の近くでしたが、天候が悪めで、寒かったです。
そういえばRubyKaigi2017も台風が来てましたね。私のせいでは、ありません。

f:id:monoooki:20171025154843j:plain

目標と成果

今回の合宿では、3つのグループに分かれて、それぞれのテーマに取組みました。 我々のグループですが、数年後の軽減税率制度の話題もあり、税計算を学びたいという機運が高まってきているため、

「「「源泉徴収税を計算できるライブラリを作って公開するぞ!!」」」

という目標で取り組み、無事公開しました。

f:id:corocn:20171017174103p:plain

1日目

名古屋から特急しなの&しなの鉄道に乗って移動しましたが、合計3時間以上かかりました。長野の北部なのでなかなか遠いですね。

f:id:monoooki:20171025154940j:plain

LAMPに到着。

f:id:monoooki:20171025155004j:plain

荷物だけ置いて、会議室に移動します。

f:id:corocn:20171017181520j:plain

会議室は木をベースにした作りで、合宿ッ!って感じですね。
ちなみに、宿泊所と会議室は車で数分ほど離れていますので、検討されている方はご注意を。

開発

さっそく、3班に分かれて開発。

f:id:monoooki:20171025155028j:plain

源泉徴収といっても色々な種類があり、全部は合宿期間中に収まらないため、「報酬・料金」の計算ロジックを組むことに焦点をあてました(これは事前に決めてました)。

まずは国税庁のホームページを見て、皆でロジックを確認していきます。 資料がわかりにくかったので友人の税理士に聞いてみたところ、「これを見るといいよ!」と言われ、次のようなURLをもらいました。ありがとうございます。

「この金額の時はいくらになる」というような例が載っていたので、それをテストケースとしてTDD的に実装を進めることにしました。

開発環境

税の気持ちが分かってきたので、アーキテクチャを決めました。 Misocaでは普段使ってないTypeScriptで書こうぜ!! という提案があり、いいねいいね! となったので、採用されました。

TypeScriptはwebpackやbabelを入れる必要がないので、スッキリしています。 テストはavaとCircleCIで回しました。手軽で良いですね。

夕食

雨に打たれながら、炭をバーベキューを食べました。

f:id:monoooki:20171025155157j:plain

焼きすぎてしまいましたね。

食後

寝室がドミトリー形式なので作業できず、宿泊所のロビーで静かにコーディング。

一人1種類の計算ロジックを組んで、この日は終了です。

2日目

朝食

LAMPの朝食はモーニングビュッフェ形式になっており、パンを食べました。

米が食べたいとダダをこねましたが、有料みたいなのでやめました。

開発

国税庁の分類分けを参考に、イレギュラーケースを実装していきました。
Ⅳ 税額の求め方(平成29年分)(※PDF)

馬主やプロボクサーなど、職種によって計算方法が異なります。面白いですね。 どうしようもなくなって、banushiproBoxer というメソッドを実装することにしました。

昼食

大雨と寒すぎて散策する元気がなく、近所の喫茶店でそばを食べました。

f:id:monoooki:20171025155414j:plain

LAMPの方にストーブを出してもらいました。

f:id:monoooki:20171025155443j:plain 文明の利器に感動するワレワレ

開発

昼食後もワイワイ開発をしました。

f:id:monoooki:20171025155546j:plain

f:id:monoooki:20171025155559j:plain

夕方

夕方ごろになったらだいぶ晴れてきました。

f:id:renya-mizuno:20171024151906j:plain

f:id:renya-mizuno:20171024151949j:plain

それっぽい景色になって満足です。

f:id:renya-mizuno:20171024151924j:plain

夕食

LAMPの中で宴会。 「ナラタケ」というとても貴重なキノコをだしてもらいました。とても貴重な味がしました。
それはそれとしてポテトが美味しかったです。

食後

夜は昼と打って変わってガンガン晴れてました。 星が綺麗でした。

f:id:corocn:20170928215844j:plain

f:id:renya-mizuno:20171024152340j:plain

3日目

朝食は2日目と同じだったので割愛です。

晴れた

祈ったらめっちゃ晴天になりました。 僕達が求めていた野尻湖はこれだ!

f:id:corocn:20171017181716j:plain

開発

npm publish して、デモページを作ったり、発表資料を整えたりしました。

昼食

LAMPではランチにハンバーガーを提供しています。

lamp-guesthouse.com

6種類あって、僕は "始まりのLAMPバーガー" を選びました。
めっちゃ天気が良いお外でLAMPバーガーを食べました。美味しかった!

f:id:monoooki:20171025155654j:plain

発表会

名古屋オフィスと繋いで、発表会を行いました!!

f:id:monoooki:20171025155748j:plain

f:id:monoooki:20171025155756j:plain

まとめ

  • 思いのほか寒く、9月の長野は防寒具が必須ですね。
  • 馬主やプロボクサーの方に税金計算についてお伺いしたいです。
  • あなたのプルリク待ってます!

採用

Misocaでは二代目合宿の神を募集しています!!

Misocaではワイワイコードを書くのが大好きなエンジニアを絶賛募集中です!!

🍣キーキャップ

こんにちはmzpです。

先日開催されたRubyKaigi 2017に参加し、ノベルティとしてMisocaロゴ、Rubyロゴ、寿司の3種類のキーキャップを配りました。

今回は、このキーキャップを作ったときの話を紹介したいと思います。特に寿司の話をします。

💸要約: 注文した会社とかかった費用

キーキャップの作成はWASD KeybordsのCustom Art Cherry MX Keycapsで作りました。

計450個を発注し、約560ドルかかりました。数があったおかげか、サイトに掲載されている金額よりも安くなりました。

👣作った経緯

デザイン決定

ノベルティとしてキーキャップを作ることにしたので、デザイン投票を行ないました。その結果、候補になかった寿司に投票が入りました。

f:id:mzp:20171018170001p:plain

「なぜ寿司??」という疑問が社内の各所からでてきて、混乱が生じました。

f:id:mzp:20171018170329p:plain

f:id:mzp:20171018170953p:plain

WASD Keyboardsへの発注

WASD Keybordsの大口注文要のフォームから見積りを行ない、予算を確定させた後、発注しました。この際、予算の都合で寿司デザインがなしになりそうになりましたが、協議の末、注文することにしました。

f:id:mzp:20171018170352p:plain

寿司画像はいらすと屋の中トロマグロを合成して作りました。せっかくなので貼っておきます。

f:id:mzp:20171018170715p:plain:medium

RubyKaigiまであまり時間がなかったので間に合うか不安でしたが、かなりすばやく製造・配送が完了しました。 詳細なタイムテーブルは以下の通りです。

  • 9/6 注文
  • 9/8 製造完了・発送
  • 9/11 日本到着
  • 9/12 UPSに依頼して、会場に直接搬入してもらうよう依頼する
  • 9/17 RubyKaigi会場へ搬入

RubyKaigi当日

RubyKaigi当日にブースで配布しました。 かなり盛況で、2日目で在庫が尽きそうになりヒヤヒヤしました。

f:id:mzp:20171019113404j:plain

f:id:mzp:20171019113407j:plain

⭐️ふりかえり

よかった

  • 在庫がつきるほどの人気があってよかった。
  • 使い方の説明のためにキーボードを持っていったのはよかった。

よくなかった

  • チェリー軸のキーボードにしか刺さらないので、HHKB用のアダプタがあったほうがよかったかもしれない。
  • 黒(キーキャップの色)で赤(寿司やRubyの色)は発色があまりよくなかった。

📢採用

Misocaではノベルティ作りに興味のあるエンジニアの方の応募もお待ちしております。

RubyKaigi 2017 にみんなで参加して、Misoca ブースを初出展しました

こんにちは、松江オフィス勤務の @hidakatsuya です。急に寒くなってきて通勤が辛い季節になってきましたが、Misoca はリモートワークができるので体調や気分や気候に合わせて自由に選択できるので助かります。

さて、少し間が空いてしまいましたが、今回は 9/18-20 に広島で開催された RubyKaigi 2017 のお話です。

Gold Sponsor

Misoca は RubyKaigi 2017 に Gold Sponsor として協賛しました。今回は代表の豊吉をはじめ、総勢14名で参加してきました。

Image uploaded from iOS.jpg (5.7 MB)

なお、昨年の RubyKaigi 2016 は Drinkup Sponsor として協賛させてもらいました。

tech.misoca.jp

勉強会参加補助を使ってみんなで参加

Misoca には勉強会への参加補助制度があります。RubyKaigi においては業務扱いで参加できますし、参加費や交通費は全て会社負担です。そのおかげで積極的に RubyKaigi に参加できますし、その開催地(今回は広島)を楽しむこともできて最高ですね。来年は仙台だそうですよ。楽しみですね。

お好み焼きです。 Image uploaded from iOS (3).jpg (7.9 MB)

ブースの様子

さて、今回初めて RubyKaigi で Misoca のブースを出展しました。

f:id:hidakatsuya:20170918112522j:plain

キーキャップのノベルティが大好評ということもあり、本当にたくさんの方にお越しいただきました。お越しいただいた皆さん、ありがとうございました!

f:id:hidakatsuya:20170918112611j:plain (一番人気はやはり「Ruby」。キーキャップの他にキーシールも配布)

2日目からは、Misoca のリモートワーク風景を見ていただこうと、zoom の端末をブースに置いて実際の作業風景を紹介しました。こちらもたくさんの方に足を止めていただき、Misoca の文化とも言えるリモートワークを皆さんに知っていただけたと思います。

f:id:hidakatsuya:20171018023111j:plain

Misoca のリモートワークについては過去のエントリーもご覧ください。

ふりかえり

Misoca では KPT を使って頻繁にふりかえりを行っています。今回の RubyKaigi への参加も例に漏れず、終了後参加者で集まってワイワイとふりかえりました。その中で挙がった意見を少し紹介してみます。

Keep(良かったこと)

  • 寿司キーキャップ、好評で良かった
  • はじめてのRubyKaigiで楽しかった(by 非エンジニア)
  • 知り合い増えた
  • ブースでのオフィスの中継に足を止めてくれた人がいて嬉しかった

Problem(悪かったこと)

  • キーキャップ足りなかった
  • 英語がんばりたい
  • Misocaの紹介ぐらいは英語でできるようにしたい
  • もう少し早めにブースの準備(企画とかノベルティとか)すれば良かったね

Try(やりたいこと)

  • 参加だけじゃなく LT でもいいから発表したいね
  • ブログ書こう

ということで、来年も今年以上に RubyKaigi を盛り上げていきたいと思っています。その際は、今回のふりかえりを踏まえて、もう少し早めにみんなで楽しく考えていきたいと思います。

最後に

開発メンバーは毎日楽しく Ruby を書いています。そして、その Ruby の国内最大のイベントである RubyKaigi は Misoca とメンバーにとって大切で楽しみなイベントの一つです。Rubyコミュニティへの感謝と僅かながらの貢献として、今後もなんらかの形で RubyKaigi を盛り上げていければと思っています。