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

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

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に従うコードになるんじゃないかと思っています。なので、今度設計する時や同僚のコードレビューする時に抽象度にフォーカスしてみてください、新たな発見があるかも知れません!

SQL を一定間隔で実行する

夜ご飯んンンンンンンンンンンンンンンッ!食べれないやつ😐
夜ご飯んンンンンンンンンンンンンンンッ!食べれないやつ😐

こんにちは、バックエンドエンジニアの @akira です。
データを自動で更新するなど、 SQL を一定間隔で実行したいケースがあり、今回はその方法を調べてみました。

前提条件は以下です。

  • PostgreSQL 9.3~
  • PGPASSWORDhost, port, username, dbname は省略
  • 以下の SQL を実行
update "Fruits" set updated_at = now() where id = '123';

bash

watch -n n COMMAND で COMMAND を n 秒ごとに実行できます。

COMMAND 部分はダブルクオートで囲むこともできます。
watch -n 10 "ls -l" # 10秒ごとに ls -l を実行

上述のクエリを実行する場合は次のようになります。

watch -n 60 psql -c "update \"Fruits\" set updated_at = now() where id = '123';"

エスケープがちょっと辛いですね。
watch の COMMAND 部分をダブルクオートで囲むと、さらに大変になりそうです。

psql

PostgreSQL 9.3 から \watch メタコマンドが追加されました。12 系でも使えます。

参考:\watch [ seconds ]

QUERY; \watch n で QUERY を n 秒ごとに実行できます。
先ほどのクエリを実行する場合は次のようになります。

update "Fruits" set updated_at = now() where id = '123'; \watch 60

エスケープ不要で、可読性も高いですね。

おまけ:redis-cli

redis-cli -r n -i s COMMAND で COMMAND を s 秒間隔で n 回実行できるようです。
-i を指定しない場合は即時実行のようですね。

参考:Continuously run the same command

さいごに

MySQL も少し調べてみたのですが、Event Scheduler で実行する方法しかヒットしませんでした(他にやり方をご存知でしたら是非教えてください🙏)。

「何かを実現したい」という意思がないと、上記のやり方を知る機会はなかったのかなと思っています(「30 分に一度、手動でコマンドを実行すればいいや」では決して調べることはなかったでしょう)。

業務でこのような機会を逃さずに、一つ一つ積み上げていきたいですね。

私たちテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。
ぜひ コーポレートサイト までお気軽にご連絡ください!

pip install Flask-Injector ができない! を解消した

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

こんにちは。バックエンドエンジニア / SRE のまのめです。
先日、弊社のテスト環境「 tako 環境 」が焼けなくなり(*)、それについて調査・解決したことをまとめました。

* tako 環境を焼く = 社内用語で、テスト環境を作成/更新すること

状況

python3.5 で動くアプリケーションの tako 環境 を焼く際、 ansible のタスクが失敗するようになりました。

ansible のログを追うと、以下のようなエラーが出力されていました。

ERROR: You need Python 2.7 or 3.4 to install the typing package.

----------------------------------------

:stderr: Command \"python setup.py egg_info\" failed with error code 1 in /tmp/pip-buikd-XXXXXX/typing
You are using pip version 7.1.2, however version 20.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

複数回、複数環境で試したのですが、どうやら Flask-Injector の pip install ができなくなっており、 typing というモジュールの install でコケているようでした。

原因

先程も述べたように、アプリケーションは python3.5 系で動いています。
このアプリケーションで Flask-Injector を利用していますが、ある程度歴史もあるため若干バージョンが古めでした。

また、この現象が起きたのが 2020 年 7 月 10 日 で、 typing モジュールはその日にアップデートがありました。
ここで、typing モジュールの このときの最新版(3.7.4.2)の Project description に新しくこんな注意が記載されていました。

For package maintainers, it is preferred to use typing;python_version<"3.5" if your package requires it to support earlier Python versions. This will avoid shadowing the stdlib typing module when your package is installed via pip install -t . on Python 3.5 or later.

typing - pypl

さらに、 Flask-Injector の該当バージョンのモジュールの requirements を見たら、以下のようになっていました。

install_requires=['Flask', 'injector>=0.10.0', 'typing'],

flask_injector/setup.py at v0.10.1

はい、 Flask-Injector のこのバージョンでは、 typing のバージョンが固定されていませんね。

つまり、

Flask-Injectortyping のバージョンが固定されていないために最新版が入る
=> typing の最新版は python3.5 で入れられなくなった
=> pip install がコケる

ということだったのです。

そして解決へ

typing のバージョンをこちらが定義する requirements で無理やり固定してもよいのですが、ほかのモジュールでまたこういう事があると面倒だな…と思っていました。 そこで試しに、 pip install --upgrade pip setuptools をしたら、直りました。

このとき急いでいたこともあり、なぜそれで解決するのかを詳しく調べませんでしたが、おそらく setuptools がいい感じに働いてくれるのだと予想されます。 なので、 pip install する直前に setuptools をアップグレードするように ansible のタスクを修正し、 tako 環境 は無事直りました。

感想

こういったモジュールが古くて起こる不具合には、今回のようにその場しのぎな解決ではなく、根本的な解決であるモジュールのアップグレードで対応したいです。
しかし、緊急性が高い場合はひとまず今回のように処理し、時間をかけてモジュールのアップグレードの検証を進めて、根本解決できるといいなと思いました。

サービスが大きくなっていくとなかなかモジュールのバージョンにまで目を向けられなくなっていきますが、こういうことが本番で起きる前に、定期的に振り返る時間を作っておきたいですね。

エンジニアの「ドキュメントよりコード書きたい」を本気で解決してみた!

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

こんにちは、みんなのマーケットでCTOをしている戸澤です。

今回は、長年に渡って整備できなかった開発ドキュメントを、全員で整備する取り組みをはじめた、という内容です。

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

課題

テクノロジー本部の長年の課題の1つに、「ドキュメントがない」 というものがあります。 ドキュメントがないと、

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

開発や環境構築などのわからないことは、Slackや口頭で聞く必要があります。 この状況では、教える側がその対応に都度時間を使う必要があり、また、内容も記憶頼りになり正確かどうか保障ができません。 特に教える側は往々にして忙しいことが多く、聞く側からすると、聞きづらい雰囲気の場合があり、ゆえに、聞く側が自己解決を試み時間がかかったり、聞かずに放置されてしまうということが起こります。

反対に、ドキュメントがあると、

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

聞く側が自主的に探せるようになり、また、探せなかったとしても、教える側が「xxxで検索してみて」と、キーワードを伝えることで解決できます。 それにより、教える側が都度対応していた時間を減らすこと、聞く側の聞きづらさの解消ができます。 また、内容が記憶頼りではなくなり、内容の間違いや忘れを防止できます。 さらには、教える側の使える時間が増えるため、ペアプロなどより価値の高い時間の使い方ができるようになります。

このようなメリットがあるとわかっていても整備が進まないのが、ドキュメントです。

どうすれば、ドキュメントを充実させていくことができるのでしょうか。

そんな中、ある方から次のようなアドバイスをもらいました。

転機

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

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

たしかに、コードとドキュメントどっちに時間を使うべきか迷ったり、一人で書こうとして整備が進まなかったドキュメントですが、みんなで同時に書くのであれば、時間の設定や全員の責任として、書くことができるはずです。

これは良いのではないか、ということで実施してみました。

やってみた

実施にあたって、次の3つのポイントを決めました。

  1. ドキュメントのフォーマット
  2. ドキュメントの項目
  3. イベント化する

順番に、 f:id:curama-tech:20200710184343p:plain

まずはゴールを決め、そこに求められるドキュメントのフォーマットを決めます。 今回達成されるべきゴールは、そのドキュメントを他の開発チームが読んで理解できることです。 現在の状態では、フォーマットを詳細に決めてもそれを全て守ることに頭を使ってしまうので、ゴールが達成に必要な必要最低限のものだけにします。

記事は、機能単位の粒度で1つの記事にまとめます。 記事が長くなる可能性がありますが、それよりも記事が分散し探す手間が増えたり、更新漏れの発生を防ぐためにまとめます。 今後、記事が長くなってから分割することを考えればよく、まずは記事を書くことに集中します。 その他、事実と感想を分けることや、図や表を使ってわかりやすくすることなど、基本的なことだけ決めます。

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

次に、ドキュメントの項目出しをします。 チーム毎にこれまで扱った開発内容のドキュメントを書いていくため、まず項目の抜け漏れがないか確認をするため、同じ項目を複数名で同時に作成してしまうことを防ぐために行います。 どの項目から作成に着手するかはチームで判断して大丈夫です。 ただし、2,3回...と継続して取り組んでいき、どの項目もいずれ書くことになるので、優先度は決めなくても問題ないと思います。

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

最後に、ドキュメントを書くことをポジティブに認識していけるように、この取り組みをイベント化していきます。 また、ゴールが達成されているかの確認と、成果を出すことに意識を向けるために、ほかの開発チームからレビューをもらうようにします。 ドキュメントは全員で作成、更新していくものであり、全員が参加すること、という意識付けでもあります。

レビューが終わったら、全員で簡単にドキュメントの概要を発表しあいます。

結果

「xxxで検索してみて」と言えるタイミングが増えました。 結果として、教える側が都度付きっきりで教えていた時間を、他のことに使えています。 また、ドキュメントのレビュー時に、レビュワーが初めて知る内容もあり、そのタイミングからすでに理解が深まっていく様子でした。

今年4月に1回目を開催し、毎月1回開催し、6月までに計3回開催しました。

ドキュメントは増えてきましたが、カバレッジをもっと上げるために今後も月1回で継続開催し、カバレッジが高まってきたら数ヶ月に1回の開催のように、頻度を落としてもいいと思います。

おわりに

そもそもドキュメントがないとドキュメントの有用性を理解できなく、ドキュメントが書かれないという悪循環に陥ると思いますが、ドキュメントのカバレッジが上がり、日常的に使うようになってくると、逆にドキュメントがないことが目立つようになってくると思います。 そうなると、このような取り組みをしなくても、その気持ち悪さから自然とドキュメントを作成し、そこを補完しようとする気持ちに私はなると考え、今後はカバレッジが上がるにつれて、ドキュメントを書くカルチャーも根付いてくるのではないかと思いました。