くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

GitHub Actions もしものときの timeout

みんなのマーケットの バックエンド/SRE を担当している、マノメです。

今回は、GitHub Actions で起こった障害に巻き込まれて失敗に気づいた、という例をご紹介したいと思います。

なお本記事は、6/10 に開催した 「失敗に学べ!くらしのマーケットの開発「失敗」LT 会 vol.1」 での LT 内容を再構成したものです。

f:id:curama-tech:20200821162334p:plain

GitHub Actions の活用

この記事を読んでいる方は既にご存知かもしれないですが、GitHub Actions は、以下のような特徴がある超便利な CI/CD ツールです。

  • GitHub 謹製の CI / CD ツール
  • 基本無料 (月ごとの時間制限あり、それ以降は課金)
  • 実行環境は Linux / Win / Mac を選べる
  • 様々な言語が動かせる (Docker イメージもいける)
  • OS や言語のバージョンをマルチビルドできる
  • シークレットキーの設定も簡単
  • docker-compose が使える (最近知った)
  • yaml でタスクを書くだけで作れる

ほかにも特色はいろいろありますが…。

f:id:curama-tech:20200821162420p:plain

弊社では、この GitHub Actions をプロダクトの一部でほんのり活用しています。
テストなどがコケている場合、master ブランチにマージできないようになっており、コードの品質をある程度保てるようにしています。

突然、Actions が終わらなくなる

ある日、突然 GitHub Actions のタスクが終了しなくなりました。

f:id:curama-tech:20200821162431p:plain

これによって、困ることがあります。

  • 追加でお金がかかるようになるかもしれない!
    • Actions は、一定の時間以上は課金制になってしまう
  • Pull Requests がマージできない

結局調べた結果、Nodejs のダウンロードが起因する障害でした。
(この時起きていたことの詳細はこちら: actions/setup-node/issues/132)

要するに、Nodejs の ダウンロードができなくなり(503)、Actions の「Use Node.js 12.x」が止まっていました。 これによって、タスクがリトライを繰り返し、なかなかエラー終了しない状態になっていました。

timeout を設定しよう

もしも Actions に何かあった時のために、タイムアウトする設定を入れると安心できます。
Actions 全体でかかった時間が計測されているので、その時間にリミットをかけられる機能です。

Actions はタスクを yaml で表現します。
この中で、build のオプションとして timeout-minutes を設定するだけでタイムアウト値を設定できます。

f:id:curama-tech:20200821162510p:plain

しかし、私はここでさらに失敗しました。

タスクにかかる時間が 1 分弱であるのに対してタイムアウト値を 1 分に設定していましたが、 コードの量や Actions 内のネットワーク環境の影響などでタスクの時間が 1 分を超えることがある、というのを考慮できていませんでした。
そのため、全然 Actions が Success にならなくなっていました。

Actions のタスクの実行時間は、状況に応じて変わります。
実行時間は適切なバッファをもたせつつ、適切な時間を設定するようにしましょう。

まとめ

今回は Github Actions をクリティカルなものに使っていなかったため、影響がほぼなかったのが幸いでした。
こういう細かい設定をちゃんと設定して、もしものときに備えておきましょう。
そして、どんどん GitHub Actions を活用していきましょう!

くらしのマーケットの開発チーム紹介!

みんなのマーケットでPdMをしているハットリです。

今回はくらしのマーケットを開発しているテクノロジー本部のチームを紹介していきたいと思います。

テクノロジー本部では

機能をより使いやすく

より、「探しやすく」「予約しやすく」「払いやすい」ものにしていくために、新規カテゴリの追加、既存カテゴリの機能改善を進めます。

「オススメできる」カテゴリをより多く

本当に友人にオススメできるカテゴリをよりたくさん増やしていくために、新規カテゴリの追加、既存カテゴリの既存改善を進めます。

をミッションにくらしのマーケットの新規機能開発や運用保守を行っています。


f:id:curama-tech:20200814161637j:plain

チーム紹介

🚀 カテゴリチーム

くらしのマーケットの各カテゴリの売上を成長させるために、市場調査からプロダクトの企画・改善案の発案、運用の改善、また、そのKPI管理まで一気通貫して行うチームです。

車内清掃サービスや土嚢積み(浸水対策)といったカテゴリの新設や社会情勢に合わせた特集・キャンペーンの企画も行っています。

チームメンバーからの一言
売上の成長をミッションに担うため、他部署をも巻き込んでいく必要があり、高い調整力が求められます。 そういうシビアな面もありますが、メンバーそれぞれ異なる得意分野を活かして、お互いにアドバイスしたり助け合っていける空気感のチームです



💳 決済・認証チーム

くらしのマーケットの決済に関する開発を行うチームです。

通常のECサイトでの支払いはオンライン決済が最も一般的ですが、出張訪問サービスの業界ではまだまだ現金というアナログな決済手段がメジャーになっています。
この業界を仕切るプラットフォーマーとして、利用者(出店者・ユーザー)の体験向上のために、業界を変えていく責任があります。

キャッシュレス導入を進め、お金の流れをスムーズに、そして透明にしていきます。

チームメンバーからの一言
少数精鋭のチームなので、役割に留まらず責任を持ってコミットできる方を求めてます!



🛠️ リフォームチーム

くらしのマーケットのリフォームカテゴリの利便性の拡充を行うチームです。

巨大な住宅リフォーム市場には、商品を含めた作業料金の総額が不明瞭である点や、小規模な作業においても見積もりに多くの工数が発生する点など、多くの課題が存在しています。

トイレリフォームや給湯器交換といったカテゴリを「商品+工事費込の値段」で比較検討でき、煩雑な見積もりをすることなく、作業料金を事前に把握できます。

チームメンバーからの一言
見積もりなんかいらない!リフォームもパッケージ化の時代だ!今までになかったそんなサービスを一緒に作りましょう!



🤝 マッチングチーム

くらしのマーケットの現在の仕組みでは解決できていない課題を解決するチームです。

「料金がわかりやすい」「人とサービスで選べる」というバリューを担保しつつ、会員登録から作業までの流れを簡略化することでニーズを満たします。

チームメンバーからの一言
新しい仕組みを一から作っています!ローンチ後のユーザーの反応が待ち遠しいです!



🚅 即応チーム

くらしのマーケットの特定のプロジェクトを持たず、他のチームが自分たちのやるべき開発に集中できる環境を作り、足りない役割を補うチームです。

具体的には依頼タスク(バグ修正、繁忙期対策、CM連動企画)、他部署からの開発要望、他チームの開発ヘルプをしています。

チームメンバーからの一言
33%が新卒、40%が海外の方です。新メンバーはまずこのチームに入隊し、広くいろいろな部署との開発に関わることでくらしのマーケットの仕組みを理解します。各国の先輩がいるので日本語のケアもできます!



🔍 QAチーム

くらしのマーケットの全ての機能に不具合がないこと、サービス利用者が「〇〇にくい」ではなく「〇〇やすい」状態であることを検証するチームです。

サービス利用者が何かを気にせず使える状態になっていることは、プロダクト本来の目的が達成されるために必要な前提条件であり、この条件を満たした上で、3つの観点(製品・データ・利用/操作)で品質保証に尽力しています。

詳しいお仕事紹介記事はこちら

チームメンバーからの一言
昼はQA、夜はやきとり屋!元ヤンのマリと、昼はQA、夜は家事代行、元パティシエのザキで頑張っています。開発エンジニアより圧倒的に人数が少ないのでメンバー絶賛募集中です!



最後に

6チームに分かれて開発を行っていますが、ミッションを達成するという同じ気持ちを持って開発を進めています。

テックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。

ぜひコーポレートサイト までお気軽にご連絡ください!

要件定義で、すったもんだ

みんなのマーケットでUI/UXデザイナーをしているミソサクです。

入社5年目です。みんなのマーケットに来るまではアパレルEC業界でデザイナーをしていました。この記事にあるように、私は入社して1年くらいは自分の思い描くような活躍ができていませんでした。今回はそんなトライアンドエラーの日々の「失敗」をご紹介します。

本記事は、6/10に開催した「失敗に学べ!くらしのマーケットの開発「失敗」LT会 vol.1」でのLT内容を再構成したものです。

要件定義で、すったもんだ

はじまり

今から5年ほど前、入社してちょっとたったころ「カレンダー機能」というものを作る機会を頂きました。

メンバーはエンジニア2人とデザイナー1人の、計3人でした。要件定義は全員未経験でした。

そこで、デザイナーの私が人生で初めて要件定義をやる事になりました。 右も左も分からなかったので「まぁ、なんとかなるだろう」という感じでとにかく手を動かし始めました。

そしてすぐ、ものすごい困りました。

ものすごい困りました

何もかも不確定なまま手を動かしはじめてしまい、ものすごい困りました。たくさんある選択肢から「何を基準に決めたらいいのか」全くわかりませんでした。しかも「アプリをメインとした機能としたいのにアプリ開発者がいない」「使いたいデータが求めている形ではない」などいろんな問題が立ちふさがりました。

現状を無視してとにかく理想の形を描きましたが、いろんな問題が複雑に絡み合っていて、全てをうまくまとめるのはとても困難でした。

しかし「早く決めなきゃ」という焦りからその状態のまま手を動かし続け、それっぽいデザインを完成させました。焦りとは裏腹に1ヶ月もかかってしまいました。エンジニア2名にデザインを見せた所・・・

1年以上かかってしまいそう しかもこれじゃあ要件を満たしてないよ

考え直し。。。

というかもう、どうしたらいいか分からない。。。

完全に行き詰まりました。。。

転機

そんな時、一冊の本をすすめられました。

 「ユーザーストーリーマッピング」(Jeff Patton 著、長尾 高弘 訳 O’REILLY)

この本の内容をざっくり言うと「機能を全部ポストイットに書き出して、要求を満たす機能を見極めて、最小限でリリースしましょう」という事でした。例えば乗り物を開発する場合、時間をかけて自動車を作ってユーザーにいきなり提供するのではなく、まず最低限の移動手段であるスケボーを短時間で作って提供し、ユーザーの声を聴きながら機能を追加していき、最終的に自動車にすればいいという考え方でした。

やってみた

藁にもすがる思いで、エンジニア2人にも協力してもらい、全員で本の通りに機能を整理してみました。

「要求を満たす最低限の機能」という線引きをすることで以下の変化がありました。

  • 必須だと思い込んでいた機能は実際には必須ではないことが分かった

  • たくさんある選択肢の中から、本当に必要なものがどれなのか判断できた

  • 問題解決も含めた、開発の優先順位が見えるようになった

  • 初回リリースまでの時間が圧倒的に短くなった

また、ユーザーストーリーマッピングをすることで以下のメリットが生まれました。

  • チーム内で共有の言語、認識ができた

  • 全体像のうち、どこの開発をしているのか簡単に把握できた

  • チーム外のメンバーへの説明がしやすくなった

できる人がいなくてどうにもならなかった「アプリ開発」は、後でできる人が入社したら追加開発するという前提で「まずは基盤となる仕組みを作る」という判断ができました。そして無事、後から入社したアプリエンジニアと機能を作りあげました。

以上、はじめての要件定義の失敗談でした!

最後に

当たり前なんですが、やったことがないことに挑戦するときは、素直に本などで勉強すべきだと痛感しました。そしてユーザーストーリーマッピングは開発だけじゃなく「本当に必要なモノはなんなのかを整理する」という点で、いろんな事に活用できる手法なので、非常におすすめです。

QAのお仕事紹介

はじめまして。QAエンジニアのザキです。

ここ数年、QAってなに?QAエンジニアの仕事って?、と聞かれる機会が増えました。 それだけ興味・関心が高まっていると思いつつ、認知度については業界を通じた課題があるのかなと一喜一憂の日々です。

今日は簡単ながら、みんなのマーケットのQAについて、お話したいと思います。

まずは自己紹介

f:id:curama-tech:20200725163337p:plain

自己紹介も無事?に済んだところで、早速QAのお仕事紹介スタートです!

QAの仕事って何ですか?

テスト設計(テストケースの作成)とテスト実施が業務のメインです。 テスト作業と端的に言っても、仕様要件や影響箇所の把握、テストスケジュールの計画、リソース調整など様々あります。

新しい機能のリリース、既存の機能の修正も、それらが組み込まれる事によって他の機能へ影響が無いか、開発予定の内容詳細に抜け漏れの観点が無いか、内部的なオペレーション(運用面)は整備されている・混乱しないか、開発プロセスは守られているのか等。

その他、社内メンバーからの仕様や不具合の問い合わせを一次受付しています。 問い合わせをQAで切り分けた後に、不具合となった場合はエンジニアへ対応依頼を行います。 この不具合修正はQAテストを経てリリースされ、リリースまでの期間はQAで不具合管理を行います。

作業自体はテストに括られますが、意識しなければならない事はテスト外にも及びます。 プロダクトが成長し大きくなるにつれ、守備範囲は増えます。大変ですが、それもQAの醍醐味かなと思います。

どれくらいの件数をテストしますか?

月ごとに変動しますが、1人あたり毎月約500件〜1,000件のテストケースを消化します。 不具合やフィードバックが無ければ、この件数で終了しますが、再テストや切り分け調査なども入るので、それで考えると1人あたり毎月700〜1,200件程度になるのではないでしょうか。

f:id:curama-tech:20200725154942j:plain

メンバーは何名ですか?

今現在は2名です。2人とも宮崎オフィスのメンバーです。 (メンバー募集中です、是非ご応募お待ちしております)

テストって地味なイメージですが?

確かに、見た目に派手な仕事ではありませんが、見方を変えるとQAテストがリリース前の最終局面になります。 我々のテストを通ったものが世の中にリリースされる、そう考えると不思議と辛さよりも使命感に燃えます。 地味に見えても大事なポジションに変わりなく、逆にそれを担っているからこその誇りもあります。

プロダクトがより多くの人に安心して利用され、より一層好まれるよう、最後の一瞬まで気を抜かずに粛々とテストを行います。 精神論ではなく、そういう気持ちでテスト出来る人がQAであるべきかな(向いている)と思っています。 とは言え、リリース期限ギリギリの状況で「わぁぁぁ」となる事もありますよ、人間なので ^^;

f:id:curama-tech:20200725155217j:plain

お伝えしたい事やお話したい事はたくさんありますが、今日のところはこの辺で。 今後も定期的に記事を書いていきますので、乞うご期待ください (。・・。)

読みやすいコードを書く必殺技

こんにちは、バックエンドエンジニアのカーキです。 今日は自分が思う読みやすいコードの秘密を紹介したいと思います。 いきなり答えを言っちゃいますが、読みやすいコードを書く必殺技はコードの抽象度を揃えることです。

抽象度とは

抽象度というのはものを見る時の視野の広さや高さのことです。

例えば、「富士山はどこにあるの?」という質問の答えは 地球 --> アジア --> 日本 --> 静岡県 のどちらでも答えられると思います。 この例では 地球 が一番高い抽象度で、静岡県 が一番低い抽象度です。私達は普段質問の文脈によってどの抽象度で答えるかを無意識で選んでると思います。

以下のシナリオを想像してみましょう。 僕個人的にマックが大好きなのでマックで注文する時のシチュエーションにします。起きることはだいたいこんな感じでまとめられると思います。

f:id:curama-tech:20200722115146p:plain
抽象度

👆の画像のように注文するアクションを高い抽象度(左側)から低い抽象度(右側)にわけられます (もちろん記載されてる以外の分け方も全然あると思います)。

一番左に寄せられてるマックで注文するが抽象度マックスのアクションで、財布を出すレベルのものは一番低い抽象度にいることがわかると思います。抽象度が違うものを比較してみると、抽象度が高いものは遠くからの目線で見られて、低いものは比較的に近い目線から見られてるのがイメージできると思います。

抽象度の違いをイメージできたところで、コードを読みやすくすることと抽象度はどう関係あるのかについて深堀したいと思います。

コード例

早速ですが、また例から始めます。今回は上記のマックで注文した時のキッチンの店員さんの動きをシミュレーションしてみましょう。 注文は ソーセージエッグマフィンセット、飲み物は ホットコーヒー 前提で書いています。 本来ならOOPでちゃんと書ける部分いっぱいあると思いますが、抽象度の説明をシンプルにするためほぼ疑似コード程度で書いていますのでご了承ください

抽象度がバラバラのコードが混ざってる場合

// メインの関数 (一番高い抽象度)
function prepareSausageEggMuffinSet(): SausageEggMuffinSet { 
    let continueToastingMuffin = true;
    let muffin = new EnglishMuffin();
    while (continueToastingMuffin) {
        muffin = toastMuffin();
        if (muffin.color) === "GoldenBrown" {
            continueToastingMuffin = false;
        }
    }
    
    let sausage = new Sausage();
    addSaltAndPepper(sausage);
    sausage = makeRoundAndFlat(sausage);
    while (!sausage.isCooked) {
        cook(sausage);
    }
    let egg = new Egg();
    while (!egg.isCooked) {
        cook(egg);
    }
    const cheeseSlice = new CheeseSlice();
    const sausageEggMuffin = new SausageEggMuffin(
        sausage, 
        egg, 
        muffin, 
        cheese
    );
    
    const potato = new SlicedPotato();
    const eggForHashedPotato = new Egg();
    const seasonings = [new Salt(), new Pepper()];
    let hashedPotato = new HashedPotato(
        potato, 
        eggforHashedPotato, 
        seasonings
    );
    let continueCookingHashedPotato = true;
    while (continueCookingHashedPotato) {
        hashedPotato = cook(hashedPotato);
        if (hashedPotato.color) === "Golden" {
            continueCookingHashedPotato = false;
        }
    }
    const coffee = CoffeeMaker.brew();
    return new SausageEggMuffinSet(muffin, hashedPotato, coffee);
}

上記のコードを一度読んで見てください。抽象度バラバラのコードが同じところに混ざってると思います。一番高い抽象度の prepareSausageEggMuffinSet の実装に中レベルの toastMuffin もいれば、すごく抽象度低レベルのマフィンの色の比較も入っています。コレによって prepareSausageEggMuffinSetのコードが長くなり、読みにくくなってると思います。

抽象度を揃えた場合

まずは prepareSausageEggMuffinSet だけ最初に揃えて見ましょう。単純にソーセージエッグマフィンセット を作るためには ソーセージエッグマフィンハッシュポテトコーヒー が必要なので、 それぞれを作る処理を prepareSausageEggMuffin, prepareHashedPotato, prepareCoffee 関数で表します 。この一階層の抽象化を挟むことで ソーセージエッグマフィンを作るハッシュポテトを作るコーヒーを作る アクションそれぞれが独立化され、疎結合になっていると思います。

実際のコードはこんな感じになります。明らかにこっちのコードの方が読みやすいかと思いますがいかがでしょうか?

// メインの関数 (一番高い抽象度)
function prepareSausageEggMuffinSet(): SausageEggMuffinSet { 
    // 中で呼ばれてる全関数がprepareSausageEggMuffinSetより一段階低いレベルの抽象度
    const sausageEggMuffin = prepareSausageEggMuffin();          
    const hashedPotato = prepareHashedPotato();           
    const coffee = prepareCoffee();                 
    return new SausageEggMuffinSet(
        muffin, 
        hashedPotato, 
        coffee
    )
}

これだけだと ソーセージエッグマフィンセットは作られないので次は prepareSausageEggMuffin, prepareHashedPotato, prepareCoffee それぞれの実装をしてみましょう。 残りのコードはこんな感じになります。

function prepareSausageEggMuffin (): SausageEggMuffin {
    const muffin = toastMuffin();             
    const egg = cookEgg();
    const sausage = cookSausage();
    const cheeseSlice = new CheeseSlice();
    return new SausageEggMuffin(sausage, egg, muffin, cheese);
}


function prepareHashedPotato(): HashedPotato {
    const potato = new SlicedPotato();
    const eggForHashedPotato = new Egg();
    const seasonings = [new Salt(), new Pepper()];
    let hashedPotato = new HashedPotato(
        potato, 
        eggforHashedPotato, 
        seasonings
    );
    while (!hashedPotato.isCooked) {
        cook(hashedPotato);
    }
    return hashedPotato;
}

function prepareCoffee(): Coffee {
    const coffee = CoffeeMaker.brew();
    return coffee;
} 


function toastMuffin(): EnglishMuffin {
    let muffin = new EnglishMuffin();
    while (!muffin.isPerfeectlyToasted) {
        toast(muffin)
    }
    return muffin;
}

function cookEgg(): Egg {
    let egg = new Egg();
    while (!egg.isCooked) {
        cook(egg);
    }
    return egg;
}

function cookSausage(): Sausage {
    let sausage = new Sausage();
    addSaltAndPepper(sausage);
    sausage = makeRoundAndFlat(sausage);
    while (!sausage.isCooked) {
        cook(sausage);
    }
    return sausage;
}

結論

抽象度を揃えた場合は明らかに抽象度バラバラで書いた場合よりコードが読みやすくなっていて、各関数の役割もしっかりしてることがわかるかと思います。 上記の例から言いますと、抽象度バラバラの場合一目でオーダー入った時にどんなことをやってるかはわかりづらいと思います。高抽象度の関数 (prepareSausageEggMuffinSet)にいきなり低抽象度のコード (ソーセージや卵の具体的な作り方など) が書かれてるので prepareSausageEggMuffinSet が複数の理由で変わる可能性が高まっています (コーヒーの作り方が変わっても変わりますし、ソーセージの味付けが変わっても変わります)。一方で、抽象度揃えてる例の場合 「ソーセージエッグマフィン作って、ハッシュポテト作って、コーヒー作ってまとめて返す」とすぐにわかるようになってるかと思います。具体的「こうやってコーヒー作る」とか「こういう味付けする」など prepareSausageEggMuffinSet に書かれていないのでコーヒーの作り方やソーセージの味付けが変わったとしても prepareSausageEggMuffinSet へ影響がないようになっています。

当記事は「コードを読みやすくする」視点で書きましたが、上記のように抽象化するメリットは読みやすさだけではなく様々あると思います:

  1. 責任の制限: 抽象度を揃えることで責任の線がハッキリするので、Godクラスが生まれにくくなります。
  2. テスタビリティ: 抽象度を揃えることでコードがモジュール化され、単体テストしやすくなります。
  3. 変更への耐えやすさ・拡張性: 抽象度揃った場合自然とコードがある程度疎結合になっているので既存機能の変更や拡張はしやすくなります。 例えば、記載の例でソーセージの味付けを変えたい時に、抽象度揃えてない例の場合関係ないところ (例えばコーヒーの作り方) にも影響してしまう可能性があります。抽象度を揃えた例だと、メインの関数 prepareSausageEggMuffinSet に変更無し、prepareSausageEggMuffin 自体にも変更無しで、cookSausage の変更だけで済みます。

最後に

よくオブジェクト指向プログラミングでSOLIDという単語が出てくると思いますが、個人的にはSOLIDを意識してコードを書くより、抽象度を意識してコードを書いた方が自然とSOLIDに従うコードになるんじゃないかと思っています。なので、今度設計する時や同僚のコードレビューする時に抽象度にフォーカスしてみてください、新たな発見があるかも知れません!