TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

こんにちは、BASEのフロントエンドチームでエンジニアリングマネージャーをやっている松原( @simezi9 )です。 私は最近ではマネージャーとしてコードを書くことよりもチームの編成や採用などをメインに業務を行っているのですが、 そんな中でチラっと書いたコードで見事に落とし穴にハマって失敗をしたのでその共有記事です まえがき BASEのフロントエンドチームは現在15名ほど(うち業務委託5名)で運営されています。 この人数は今後もどんどん増えていく予定なのですが、目下全社的にリモートワークになっている事情も手伝ってメンバー同士の関係性が希薄になってしまう懸念を持っていました。 BASEの中では常に複数のプロジェクトが走っているのですが、それぞれのプロジェクトにフロントエンドエンジニアは2〜3名ずつ配置されています。 そんななかでアサインされた人同士がフロントエンドエンジニア同士であるにも関わらず、お互いのことをよくわからないという状態が今後おきうるというのは組織として問題があると考えpeer 1on1という制度を開始しました。 peer 1on1 peer 1on1は組織によってはシャッフルランチなどの名前で開催されてもいるようですが、要は「ランダムに選出されたメンバー二人で1on1をする」という時間です。1on1とかっこよく言っていますが、つまるところお互いを知る雑談の場を作るというものです。 現在は週1回30分の時間を取って行っています。BASEでは最近全社的な雑談の場所として Discord が用意されましたが、 Discordの気軽なボイスチャット機能との親和性も高く、それなりに盛り上がって運用されています。 (1対1で話すのは少し疲れるという話もあり、3人単位に変えたりといろいろと運用は試行錯誤しています) そしてこのpeer 1on1を始めるにあたって、メンバーをランダムに組み合わせるためのツールをGoogle Apps Scriptでパパっと書きました。 こんな感じでA列に今いるメンバーを入力しておき、メンバー生成ボタンを押すと毎回の二人組が作られていきます 発生する問題 このシートを作った当初は、「(見た目はさておき) またいっちょツールを作ってしまったぜ・・・ ( 実コード10行程度) 」などと悦に入っていたのですが、いざ運用を始めてみてしばらく回数を重ねるこんな苦情が寄せられるようになります 最初はまあ、ランダムだしこんなこともあるでしょ!ぐらいに考えていたのですが、 対象メンバーが10名を超えるような状況でこの偏りは流石におかしいということで調査を始めるとすぐに原因を突き止められました。 バグを抱えたシャッフルの実装と修正 このシートの実装は メンバーの一覧をシートから取得 その配列をシャッフル 結果列にそのまま出力 というシンプルなフローになっていました その核となるシャッフルのロジックは以下のようなコードになっていました。 const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.sort(() => Math.random() - 0.5); この実装自体は素朴なもので、ほとんどの人に直感的に理解できるものです。 Math.random の動作は 0 以上 1 未満 (0 は含むが、 1 は含まない) の範囲で浮動小数点の擬似乱数を返します というものなので、そこで得られた結果から 0.5を引けば、正の数、負の数が擬似乱数から均等に生成されることを期待できます。 あとはこれをArray.sortに渡すことで無秩序なソート操作が行えるので、配列がシャッフルされるというものです。 実際に、このコードは数回試してみると見た目上は正しく動作します 試しに検証コードをchromeのコンソールで実行してみたら以下のようになります。 const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let tmpArr; tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); //  [1, 6, 4, 7, 5, 9, 2, 8, 3] tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); // [8, 5, 1, 6, 9, 7, 2, 4, 3] tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); //[8, 3, 1, 6, 4, 9, 2, 5, 7] それっぽく動いているような気がします。 ただこのコードは組み込みのsort関数の比較関数の結果をランダムにすればシャッフルされるという前提に立って実装されていますが、考えてみると、この想定自体が随分といい加減です。 大体のソート処理は、一定のルールに基づいて2つの要素を取り出し、その要素同士を比較関数で比較し、結果に応じて並べ替えるという操作を繰り返し行います。 そのソート処理の内部実装は処理系に依存していますが、例えばChromeのJSエンジンであるV8の実装では Timsort を利用しています。Timsortはざっくりいうと、マージソートをベースにして、いくつかのソートアルゴリズムのアイデアを取り入れて性能を改善したアルゴリズムです。 ここで問題になるのが、先程の比較関数に乱数を利用する考え方では、ソートアルゴリズムの詳細を無視して比較関数さえランダムにすればすべての要素が乱雑に配置されるという想定をしていることです。実際にはその想定は間違っていて、操作後の配列の分布は大きく偏ります。 Will it shuffle? というサイトではその偏りを視覚化して見ることができます 以下の画像はそのスクリーンショットですが、比較関数をRandomにした場合の結果の偏りが大きいことがわかります 特に先頭部分、末尾部分があまりシャッフルされていない結果になっています (なぜそうなるのかという原理的な部分は wikipediaなどでも解説 があります) ではどうすればいいのかというと、こうしたシンプルなアルゴリズムの研究はしっかり行われているのでちゃんとその知識を拝借しましょうということで、今回のツールでは フィッシャーイェーツのアルゴリズム を利用したものに変更しました。 このアルゴリズムは、とても素朴なものでランダムな順番の数列を生成してそのとおりに要素を並べるだけのものではありますが、 ソートするなどの余計な処理を挟まない分、計算量的にもとてもスマートで正しく動きます こちらのアルゴリズムでの結果も先程のWill it shuffle?のサイトに用意されており、配置の偏りが少ないことが視覚的にわかります。 他にもこのフィッシャーイェーツの改良アルゴリズムなどいくつか手法は開発されているようでしたが、今回のようなちょっとしたツールにはこれで十分そうでした。 結果 圧倒的改善 をうみました 😭 コードの見た目がシンプルだからといって、正しく動いてる保証は無く、 特にアルゴリズムを適応するような操作についてはきちんと動作を考える必要があると改めて反省をする機会になりました。 このシャッフルの操作については、最初のバギーな実装を紹介しているサイトが検索結果にも多数出てくるため結構ハマってしまいそうなケースが多そうだなと思います。 私自身もシャッフルの偏りというような内容の記事を以前に見た記憶はあったのですが、自分で実装して罠にハマっていることに気づいたのは指摘された後でした。 あまりフロントエンドとは関係のない内容ですが、これでメンバー同士の交流も円滑に行っていくことができそうです。 採用情報 | BASE, Inc. 採用情報-フロントエンドエンジニア 採用情報-Webアプリケーションエンジニア
アバター
こんにちは! BASEのInformation System(情シス)に所属している横山です。 さっそくですが、BASEでは毎月全社定例を行っています。去年から続くコロナ禍の中、以前までは当たり前のようにやっていたオフラインでの集会も難しくなっています。そしてWork From Homeの継続に伴い、全社定例はより重要なイベントになっています。 「今後すぐに在宅勤務がなくなることはないだろう。」 「月に一度の全社定例がより大事なコミュニケーションの場となるので、アップデートしたい。」 そんな想いで全社定例をより良くするために本格的な配信環境を整えて開催しました。 今回はその様子を振り返りたいと思います。 今までの全社定例では コロナ禍の今、100人以上の社員が出社し集まることは難しくなってからは、Zoomのウェビナー機能を使ってやっていました。 Zoom ウェビナーを使った全社定例 こちら特別なことは一切していないのでZoomのウェビナーライセンスのみあれば可能です。 zoom.us ウェビナーを使った全社定例は簡単に始めることができるので手軽ではありますが、 スライドの内容や話しての感情が伝わるように登壇者や司会者の顔だけでなく体も映したい 登壇者も各自のPCやマイクを使っているので人によって画質や音質にばらつきをなくしたい 長時間になると単調な映像だとつらいので、スイッチングやワイプの切り替えなどの仕掛け、より良いデザインにして、見ていて楽しい映像にしたい こういった課題がありアップデートをすることになりました。 年初会に向けて 去年の11月下旬に全社定例のアップデートの話があり、ターゲットを1月の年初会に合わせて準備をはじめました。Slackでは、#pj-broadcast_system が立ち上げり、配信に詳しい社内メンバーにアドバイスをもらいながら進めることとなりました。 12月上旬、機材が届きはじめたので、年末年始の休みに入るまでに二日ほど情シス含む関係者で集まり、機材の設置やどういう構成で配信しようかと試行錯誤しました。 試行錯誤している様子 1 試行錯誤している様子 2 最終的に、年初会はこんなイメージでやることになりました。 背景スライド、プレゼンスライド、登壇者が左下にワイプで抜かれていて、進行の際に司会者が右下に登場します。 配信イメージ みんなで構成を考えつつ、必要な機材を洗い出しました。 機材 主な機材はこちらになります。 カメラ x 1 CANON XA40 (レンタル) 今後必要になるかもしれないマニュアル設定ができ、外付けマイクや会場音声を利用できるようXLR入力に対応、レンズ管理などは大変なのでレンズ一体型のビデオカメラを探しました。業務用でありつつボタンが最小限でタッチ操作メインのXA40にしました。 最初はを購入する予定でしたが、買う前に一回レンタルして使ってみて決めようということでレンタルしたところ、利用する頻度も多くないし、このままレンタルで良さそうという結論になりました。レンタルだと一日 3,000円/台です。 三脚 x 2 E-IMAGE EK630 カメラも業務用であること、携帯性 < パン/ティルトのスムーズや安定性を優先し、それでいて安価な三脚です。元々X40を二台購入する想定だったので三脚も二台購入しています。 グリーンバック x 1 Elgato Green Screen 折りたたみ式になっているので収納ケースを開けて、下から引っ張るだけです。収納ケースがそのまま土台になるのでセットも片付けも数秒で終わります。 スイッチャー x 1 Blackmagic Design ATEM Mini Pro マルチビューモニターが欲しかったのでProを購入しました。 マイク x 2 SHURE SM58SE スイッチ付きモデルにしてます。届くまでカメラのレンタルでついてきたマイクでテストしていましたが、SHUREのマイクに変えたらノイズがほとんどなくなりクリアに聞こえるようになりました。 照明 x 2 LPL ライトバンク LB-604 L18893 なんと社内のメンバーが貸与してくれました。 上記の他に、ケーブル、変換アダプタ、コードリールなど細々した周辺機器も購入しました。 構成 結果、このような構成になりました。 最背面には背景用スライドがあり、その上にプレゼン用スライドと左下の登壇者のワイプを被せ、OBS側でクロマキー合成した司会者を被せています。 スライドと登壇者が被ると見にくかったりスライドに集中できなかったりするのであえて左下に置いてます。 司会者は登壇者が話している時はグリーンバックの前から移動して映らなくします。 配信イメージ(詳細) まとめた構成図になります。 構成図 登壇者はスライドのスピーカーノートが表示されているPCを見ながら話し、配信しているZoomに参加したPCで配信画面とチャットを確認することができます。 司会者は進行する時のみカメラに入り、登壇者が話している時はカメラに映らない場所で待機します。 XA40のカメラマンは登壇者の写り具合を確認しながらカメラを微調整します。Webカメラは固定です。 また、進行に合わせて背景用スライドを操作する人もいます。 設定 ATEM Mini Pro プレゼン用スライドをアップストリームキーで設定します。 パレット > アップストリームキー1 > DVE フィルソース:Camera 2(プレゼン用スライド) 位置:ちょうどいい位置に サイズ:ちょうどいいサイズに プレゼン用スライド(アップストリームキー) 登壇者のカメラはダウンストリームキーで設定します。 パレット > ダウンストリームキー1 > DVE フィルソース:Camera 3(登壇者カメラ) マスク:映る場所に合わせて上下左右の数値を調整 本来ダウンストリームキーはテロップやロゴなど常に映っている部分の編集に利用するようなのですが、今回はそこにカメラを設定してます。マスクした中にちゃんと映るようにカメラも調整します。 登壇者カメラ(ダウンストリームキー) OBS 次は配信PCでOBSを起動し、ATEM Mini ProとWebカメラをPCに接続します。 OBSでATEM Mini Proの映像と司会者を合成します。 OBS > ソース > + >映像キャプチャデバイス デバイス:Blackmagic Design OBS > ソース > + >映像キャプチャデバイス デバイス:HD Web Camera OBS ソース > Webカメラ > フィルタ > エフェクトフィルタ > + > クロマキー 細かい設定はデフォルトから変えていません。 クロマキー あとは二つの映像を重ねて、サイズ、位置を調整します。 Zoom 今回はウェビナーではなく、通常のミーティングに配信PCで参加します。 最初は、ビデオ設定 > OBS Virtual Camera にして配信していましたが、 画面共有 > 詳細 > 第2カメラのコンテンツ でカメラの映像を画面共有する方が画質が良くなりました。 Zoom 第2カメラのコンテンツ 当日 年末年始休暇に入る前に機材は設置し、映像や音声の確認をしました。 あとは当日を迎えるだけです。 本番前 本番1時間くらい前にOBSを操作するPCが固まり、Webカメラの映像を取り込めなくなりました。 OBS、PCを再起動しても改善せず。 調査してても間に合わないのですぐに別のPCにOBSを入れて設定しました。 本番始まってからはもう突き進むしかないのですが、配信画面でよく見ると本来のスライドとは少し色味が変わっていることに気付きました。薄いグレーを使っている箇所はほぼ見えなくなってしまいましたが、それでも何とか配信自体は止めることなく、致命的な事故もなく終えることができました。 全社定例の配信映像 課題 顔だけでなく身振り手振りも見やすいかたちで映すことができ、映像としてもイメージしていたものに近いので、概ねやりたいことはできたのですが新たな課題がたくさんあります。 リハーサルはちゃんと本番のスライドで確認する リハーサルが12月21日で本番が1月5日で年末年始を挟むので仕方ない部分もありつつ、リハーサルが早すぎて本番用スライドで確認できなかった もっと画質を良くしたい スライドの色が微妙に変わってしまう 薄いグレーは消える/見にくくなる クロマキー合成したときに腕まわりに黒いぼやぼやが映る グリーンバックに映った影? などなど、次の配信までに少しずつでも改善していきたいです。 あと設置、片付けにも時間がかかってしまうので、ゆくゆくは専用の部屋や配信スペースを用意したいですね。 まとめ 個人的には他社さんでの配信事例も見ていたので、いつかやってみたいと思っていました。 高価な機材はプライベートで触ることが難しいですし、とても楽しみでした。 実際に色々試行錯誤している時間はとても楽しく、夢中になってやっていたような気がします。 私を含めてほぼ経験がないメンバーで手探りでここまでやってきましたが、それ故に改善する点はまだまだたくさんあります。 専門性が高いのでこれからもっと勉強し、もっと良い全社定例にしていきたいと思います。 おまけ 緊急事態宣言の発令もあり、2月の全社定例は従来のようにZoomのウェビナー機能を使って実施しました。その際、こちらのSlackに投稿したメッセージがニコニコ動画風に流れるChrome拡張機能を使ってみたらとても盛り上がりました。 chrome.google.com
アバター
こんにちは。BASE BANK 株式会社 Dev Division にて、 Software Developer をしている東口( @hgsgtk )です。 BASE 株式会社では、New Relic 株式会社のプレスリリースで発表されている通りオブザーバビリティプラットフォーム「New Relic One」を導入しています。 newrelic.com 私が所属している BASE BANK 株式会社のプロダクトチームでも New Relic One を活用しています。当チームでは AWS や GCP などのインフラ構成管理に Terraform を利用しております。New Relic One での設定情報も Terraform でのコード管理をすると次のような利点が得られて便利です。 設定内容がコードとして可視化される 意図しない設定変更を切り戻したい場合に Terraform の機能で戻しやすい 当記事で紹介する内容は New Relic One 特有なものもありますが、3rd Party 製の Terraform Provider を利用する際に一般的に当てはまる内容も含みます。具体的に紹介する内容は下記となります。 TerraformのNew Relic Providerを使う 当記事で前提するアカウント体系とディレクトリ構成 main.tf での初期設定 3rd party Providerを利用したmoduleを作成する際の注意点 GitHub ActionsによるCI/CD Pipeline 複数環境はjobs.<job_id>.strategy.matrixで対応 Job outputsでのjob間の値受け渡し terraform init/validate/plan結果のPRコメント Slack通知 おわりに TerraformのNew Relic Providerを使う New Relic を Terraform 管理するための New Relic Provider が用意されています。 https://registry.terraform.io/providers/newrelic/newrelic/latest/docs 具体的な始め方は Getting Started with the New Relic Provider にて解説されています。当記事では CI/CD Pipeline の用意等工夫点があった箇所をピックアップして紹介します。 当記事で前提するアカウント体系とディレクトリ構成 BASE BANK チームでは次の 3 つのアカウントを用意して New Relic One を活用しています。 BASEBANK-production BASEBANK-staging BASEBANK-development 環境ごとに 3 つのアカウントを用意しています。Terraform のディレクトリ構成としては環境ごとにサブディレクトリを切る構成としています。 . ├── dev // BANK-development ├── modules // 全環境共通モジュール ├── prd // BANK-production └── stg // BANK-staging ディレクトリ構成の特徴としては、 環境ごとにディレクトリを分け tfstate も環境ごとに持つ module を利用し環境共通の構成定義は module 内に定義する といった点があげられます。 main.tf での初期設定 terraform init によって初期化し最初の main.tf を用意しますが、ここでは次の内容が含まれます。 required_provider として newrelic/newrelic の定義 tfstate の保管箇所の定義 New Relic Provider の設定定義 terraform { required_version = " ~> 0.14.3 " # 1 . required_providerとして`newrelic /newrelic `の定義 required_providers { newrelic = { source = " newrelic/newrelic " version = " ~> 2.14.0 " } } # 2 . tfstateの保管箇所の定義 backend " s3 " { bucket = " sample-bucket " key = " terraform.tfstate " region = " ap-northeast-1 " } } # 3 . New Relic Providerの設定定義 provider " newrelic " { account_id = var.account_id } variable " account_id " { type = string } tfstate は AWS の S3 のバケットに保管しています。BASE BANK チームでは development/staging/production の 3 つの AWS アカウントを管理しているのでそれぞれのアカウントに tfstate のバケットを用意しています。 New Relic Provider の設定定義では New Relic の API Key を必要とします。 https://registry.terraform.io/providers/newrelic/newrelic/latest/docs/guides/provider_configuration New Relic API Key は「 Getting Started with the New Relic Provider 」のガイダンスのとおりに作成します。 Terraform 実行時の参照方法は variable・環境変数の 2 つの方法がありますが、CI/CD 環境から注入しやすい点で環境変数 NEW_RELIC_API_KEY を利用しています。 ちなみに newrelic/terraform-provider-newrelic の内部実装では下記の Provider 定義でこの挙動を実現しています。 provider := &schema.Provider{ Schema: map [ string ]*schema.Schema{ // (省略) "api_key" : { Type: schema.TypeString, Optional: true , DefaultFunc: schema.EnvDefaultFunc( "NEW_RELIC_API_KEY" , nil ), Sensitive: true , }, https://github.com/newrelic/terraform-provider-newrelic/blob/8933a37ccf09137a87643a7da33648012a33c360/newrelic/provider.go#L44-L49 3rd party Providerを利用したmoduleを作成する際の注意点 Terraform の仕様は required_providers を省略した場合、 registry.terraform.io/hashicorp/ を参照することが仕様なため、 hashicorp/newrelic を使おうとしてしまいます。 Note: If you omit the source argument when requiring a provider, Terraform uses an implied source address of registry.terraform.io/hashicorp/. This is a backward compatibility feature to support the transition to Terraform 0.13; in modules that require 0.13 or later, we recommend using explicit source addresses for all providers. https://www.terraform.io/docs/configuration/provider-requirements.html#source-addresses しかし、実態があるのは newrelic/newrelic であるため、module を新規作成する際はそれぞれの module ごとに明示的に指定する必要があります。 terraform { required_providers { newrelic = { source = " newrelic/newrelic " } } required_version = " >= 0.14 " } GitHub ActionsによるCI/CD Pipeline ここからは、GitHub Action での CI/CD パイプラインを紹介します。特徴としては 2 点になります。 Pull request を作成すると自動で PR コメントに Plan 結果が記録される GitHub Pull Requestへのコメント main ブランチへマージしたタイミングで自動で Apply される GitHub Actionsでの自動Apply 全量の GitHub Actions の yml ファイルを先に掲載いたします。 name : tfapply on : push : paths-ignore : - 'docs/**' branches : - main pull_request : paths-ignore : - 'docs/**' jobs : terraform-plan-apply : name : terraform plan apply strategy : matrix : env : [ dev, stg, prd ] runs-on : ubuntu-latest defaults : run : shell : bash steps : - name : Checkout uses : actions/checkout@v2 - name : Terraform setup uses : hashicorp/setup-terraform@v1 with : terraform_version : 0.14.3 - name : Define Configuration id : config run : | if [[ $ {{ matrix.env }} = 'dev' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_DEV }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_DEV }} elif [[ $ {{ matrix.env }} = 'stg' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_STG }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_STG }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_STG }} elif [[ $ {{ matrix.env }} = 'prd' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_PRD }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_PRD }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_PRD }} else echo 'unsupported matrix environment' exit 1 fi - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} aws-secret-access-key : ${{ steps.config.outputs.aws-secret-access-key }} aws-region : ap-northeast-1 - name : Terraform fmt id : fmt run : terraform fmt -recursive -check continue-on-error : false - name : Terraform init id : init run : terraform init working-directory : ./${{ matrix.env }} - name : Terraform validate id : validate run : terraform validate -no-color working-directory : ./${{ matrix.env }} - name : Terraform lint uses : reviewdog/action-tflint@master with : github_token : ${{ secrets.github_token }} reporter : github-pr-review fail_on_error : true working_directory : ./${{ matrix.env }} - name : Terraform plan run : terraform plan -no-color id : plan working-directory : ./${{ matrix.env }} env : NEW_RELIC_API_KEY : ${{ steps.config.outputs.new-relic-api-key }} # https://github.com/hashicorp/setup-terraform#usage - uses : actions/github-script@v3 if : github.event_name == 'pull_request' env : PLAN : "terraform \n ${{ steps.plan.outputs.stdout }}" with : github-token : ${{ secrets.GITHUB_TOKEN }} script : | const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }} #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` <details><summary>Show Plan</summary> \`\`\`${process.env.PLAN}\`\`\` </details> *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ matrix.env }}\`, Workflow: \`${{ github.workflow }}\`*`; github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : output }) - name : Terraform apply if : github.event_name == 'push' run : terraform apply -auto-approve working-directory : ./${{ matrix.env }} env : NEW_RELIC_API_KEY : ${{ steps.config.outputs.new-relic-api-key }} - name : Slack notification (applying success) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && success() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Success to apply terraform-newrelic, check it! SLACK_COLOR : good SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} - name : Slack notification (applying failure) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && failure() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Failed to apply terraform-newrelic, check it! SLACK_COLOR : '#bd3232' SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} この GitHub Actions の yml ファイルの前提として定義している secrets は以下です。 KEY 内容 AWS_ACCESS_KEY_ID_DEV dev 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_DEV dev 環境の AWS IAM User のアクセスシークレット AWS_ACCESS_KEY_ID_STG stg 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_STG stg 環境の AWS IAM User のアクセスシークレット AWS_ACCESS_KEY_ID_PRD prd 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_PRD prd 環境の AWS IAM User のアクセスシークレット NEW_RELIC_API_KEY_DEV dev 環境の New Relic API Key NEW_RELIC_API_KEY_STG stg 環境の New Relic API Key NEW_RELIC_API_KEY_PRD prd 環境の New Relic API Key SLACK_WEBHOOK Slack の incoming webhook URL AWS_ACCESS_KEY_ID や AWS_SECRET_ACCESS_KEY は、tfstate を S3 に保管しているため設定しています。これらを発行している IAM User は当該 S3 へのアクセス権限のみを付与したものとなっています。 resource " aws_iam_policy " " terraform-newrelic-ci-user-policy " { name = " terraform-newrelic-ci-user-policy " path = " / " description = " Allows users to manage terraform-newrelic state file. " policy = jsonencode ({ Version = " 2012-10-17 " Statement = [ { Sid = "" Effect = " Allow " Action = [ " s3:PutObject ", " s3:GetObject " ] Resource = [ aws_s3_bucket.terraform - state - newrelic.arn, " ${aws_s3_bucket.terraform-state-newrelic.arn}/* " ] } , ] }) } 以降、上記の GitHub Actions ファイル内での詳細を紹介していきます。 複数環境は jobs.<job_id>.strategy.matrix で対応 BASE BANK チームでは前述したとおり、development/staging/production の 3 アカウントを用意しています。それぞれの環境に対して CI/CD パイプラインが組むために GitHub Actions の syntax jobs.<job_id>.strategy.matrix を利用しています。 docs.github.com # (省略) strategy : matrix : env : [ dev, stg, prd ] # (省略) - name : Terraform init id : init run : terraform init working-directory : ./${{ matrix.env }} # (省略) directory 構成は前述したとおり dev/stg/prd とサブディレクトリを切っている構成としており各ディレクトリ配下に main.tf をおいています。 . ├── dev // BANK-development ├── modules // 全環境共通モジュール ├── prd // BANK-production └── stg // BANK-staging working-directory にて matrix で指定した環境名を指定することで dev での実行の場合は dev ディレクトリ配下となるようにしています。 jobs.<job_id>.strategy.matrix を使用した場合は dev/stg/prd へのフローは並列に実行されます。dev/stg を先に確認してから prd を実行したいニーズが強い場合はデメリットとなりますが、New Relic の設定管理ではそのデメリットは許容しうると考えこの構成としています。 Job outputsでのjob間の値受け渡し GitHub Actions では Job outputs という syntax を用いることで、job 間の値の受け渡しができます。具体的には jobs.<job_id>.outputs という syntax です。 docs.github.com この機能を用いて matrix ごとに使用する設定情報のハンドリングを行っています。 環境ごとに set-output で outputs を定義 id=config で定義した outputs を別ステップで参照 # Step1. 環境ごとにset-outputでoutputsを定義 - name : Define Configuration id : config run : | if [[ $ {{ matrix.env }} = 'dev' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_DEV }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} // ... (省略) else echo 'unsupported matrix environment' exit 1 fi # Step2. id=configで定義したoutputsを別ステップで参照 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} aws-secret-access-key : ${{ steps.config.outputs.aws-secret-access-key }} aws-region : ap-northeast-1 echo ::set-output name=key::value とすることで Job outputs に値を設定できます。当該 job に id を設定することで以降のステップで参照できます。 aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} terraform init/validate/plan結果のPRコメント 毎回 PR 作成ごとに手元で plan した結果をコメントに貼るのは大変なので、自動で PR コメントに記載してくれるようにしています。当記事内では hashicorp/setup-terraform 内の usage にあるサンプルを活用しています。 github.com ここでは、terraform init/validate/plan の結果を先ほど紹介した Job outputs から取得できる標準出力を活用して、PR コメント内容を作成しています。 - uses : actions/github-script@v3 if : github.event_name == 'pull_request' env : PLAN : "terraform \n ${{ steps.plan.outputs.stdout }}" with : github-token : ${{ secrets.GITHUB_TOKEN }} script : | const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }} #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` <details><summary>Show Plan</summary> \`\`\`${process.env.PLAN}\`\`\` </details> *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ matrix.env }}\`, Workflow: \`${{ github.workflow }}\`*`; github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : output }) 注意点としては -no-color オプションを付けておかないと通知内容が文字化けしてしまいます。 - name : Terraform plan run : terraform plan -no-color # (省略) Slack通知 rtCamp/action-slack-notify を用いて成功時・失敗時に Slack 通知を行っています。 github.com 成功時の Slack 通知はこのようになっています。 - name : Slack notification (applying success) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && success() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Success to apply terraform-newrelic, check it! SLACK_COLOR : good # https://base.slack.com/services/1633237925184?updated=1 SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} この設定で以下のように Slack 通知できます。 slack通知結果 おわりに New Relic One を活用する際に Terraform の初期設定を紹介いたしました。3rd Party Provider を利用する際の初期設定や GitHub Actions を用いた CI/CD Pipeline の作成事例として参考になれば幸いです。 New Relic 等を活用したオブザーバビリティの実践によるサービス品質の向上に興味のある方は、ぜひカジュアルにお話しましょう。 @hgsgtk に DM 頂いても構いません。 open.talentio.com では、また来月もこちらのブログでお会いしましょう。
アバター
社長室の米田です。 本日20:00より、「#BASEとnote 急成長サービスならではの技術課題と組織課題を語る」と題し、note CTOの今さん、BASE CTOの川口の対談イベントを開催いたします。 Zoom開催ですので、途中参加/退出等も気兼ねなくご参加いただけます。 お申込み枠にまだ余裕がありますので、ご興味のある方は下記URLよりお申し込みください。 base.connpass.com
アバター
こんにちは。UIデザイナーの野村です。 前回 に続き、デザインリサーチの取り組みについて述べたいと思います。 (デザインリサーチって何?と疑問に思った方は、前回の記事の「デザインリサーチとは」の項を参照いただけると幸いです。) 主に、社内で企画・実施したデザインリサーチワークショップについて書かせていただきます。 前置き・プロボノプロジェクトの参加(社外) 社内での活動を書く前に、その活動の下敷きとなったプロボノプロジェクトでの経験に軽く触れたいと思います。 (プロボノとは、大まかにいうと「ボランティアの社会貢献」のようなものです。詳しい意味は Wikipedia記事 などをご参照ください。) 2020年春ごろに、社外で実施されたデザインリサーチプロジェクトに参加しました。 プロジェクト概要・成果: https://preview.studio.site/live/xNWYkmXqlB 一般の方を対象に、デザインリサーチの手法に則ってインタビューや分析を行う、というプロジェクトです。 テーマは「リモートワークを行う人の、仕事や生活の実態を探ること」であり、特定のプロダクトや機能について調査するようなものではありません。 このプロボノプロジェクトでは「リモートワーク実態」をテーマとしていたわけですが、これを「ネットショップ運営実態」に置き替えて、似た流れでリサーチを行ったら、BASEサービスの開発にとって有効な知見(ユーザへのより深い共感)を得られそうだと考えました。 継続的なデザインリサーチを検討 上記プロボノプロジェクトで得た知見と、商品オプションAppプロジェクトでのリサーチ試行経験( 前回記事 参照)を踏まえ、 機能開発プロジェクトとは別の括りで定常的なリサーチプロジェクトを行う形 が有効なのでは?と考えて、それを実践する方法を検討をしました。 定常リサーチプロジェクト案 大まかに、以下のような形を想定しました。 隔月か四半期ごとくらいの頻度で、有志を募って数名でインタビュー形式のリサーチを行う。インタビュー対象者数は5名程度か、多くても10名くらいが目安。通常業務に支障が出ない頻度・労力を意識する。 大テーマを「ネットショップ運営の実態や困りごとを探る」として、BASEの利用状況に限らず多様な角度からの情報を集める。 小テーマは都度、その時々で稼働しているプロジェクトに合わせてアレンジする。(例えば、「Instagram販売Appの改善プロジェクトが動いているなら、Instagramの活用実態について特にフォーカスする」というように。) このような、インタビュー主体のリサーチプロジェクトの実施をひとまずの目標としました。 デザインリサーチワークショップ 書籍やセミナー等からある程度はリサーチインタビューのノウハウを得られますが、いきなり実ユーザへのインタビューを始めるのでなく、なんらかトレーニングをしておきたいところです。 また、私が1人でリサーチを実施して知見を蓄えても、チームとしてのデザインワーク向上には繋がりにくいように思われます。 周りから「何かよくわからないことをやってる」と思われながら続けていくのも心理的に辛いので、あらかじめ周囲からの「意義あることをしている」という共感を得られたら望ましいですね。 そんな想いから、まずは社内でワークショップを行うこととしました。 外部の専門家を講師に呼んで、トレーニングをしてもらう。 デザインチームメンバーに参加してもらい、「何をしているのか」「どんな意義があるのか」について知ってもらう。 という二点を適えるべく、企画を進めます。 幸いなことに、 書籍「デザインリサーチの教科書」 の著者である アンカーデザイン社 の木浦さんと個人的な繋がりがありましたので、ワークショップ講師をお願いし、引き受けていただけました。 参加人数は14名で、4チームに分かれて以下のような流れでユーザインタビュー実施しています。 (オンラインで完結するよう計画しています。) DAY1(3時間):インタビュー設計 宿題期間(2週間):インタビュー協力者手配とインタビュー実施 DAY2(3時間):分析・考察 【DAY1 設計】 【宿題・インタビュー手配&実施】 【DAY2 分析・考察】 ワークショップの成果物としては、デザインチャレンジ(課題発見)が10個と、課題解決案が20個程度作成されました。(以下、その一例です。) デザインチャレンジの例 課題解決案の例 インタビューを行うためのユーザ手配がなかなかに大変だったのですが、大変だった故に意義も大きかったように感じます。ある意味、ワークショップの中で最も意義ある部分だったかも知れません。 通常業務の中でユーザとの直接的な対話を行おうとしても、スケジュール的要因、費用対効果面等から、なかなか実行はしづらいものです。慣れないことを行うことへの心理的ハードルもあります。 今回「ワークショップの宿題」という形でデザイナーとユーザの直接的な交流の場を作れたことは一つの大きな成果でした。 ワークショップ実施後に参加メンバーから感想を募ったのですが、「楽しかった」「勉強になった」というコメントを多数いただいており、デザインリサーチに対してポジティブな印象を持ちつつ知見を得てもらえたようです。 「ショップオーナーになりたくなった」「ショップを開きたくなった」といった声も出ており、ユーザに対する深い共感を得られたことも窺い知れます。 ワークショップの成果と今後の継続 ワークショップの実施にて、以下の2つの目的を達しました。 - リサーチの計画から分析までの一連の流れを経験する - 社内メンバーにデザインリサーチ活動の内容と意義を伝える 次の段階としては、単発のワークショップではなく継続的なリサーチプロジェクトとしての流れを作るべく、計画・検討を進めています。 まとめ 社内でのワークショップ(および、その下敷きとなったプロボノプロジェクト体験)から、デザインリサーチの実施・進行についていくらか知見が身についてきました。まだまだ試行錯誤の必要を感じますが、規模を変えたり新たな手法を取り入れたりしつつ、リサーチ活動を継続していきたいと考えています。 また、ここまでの経験から、デザインリサーチ活動は開発プロジェクトの中に組み込むよりも、独立したプロジェクトとして行う形が弊社には向いてそうだ、という感触も得ました。 これらの知見・経験を活かせるよう、今後のプランを練っているところです。 定性リサーチ文化を根付かせるべく、いろいろ楽しく試していきたいと思います。
アバター
こんにちは。UIデザイナーの野村( @nomjic )です。 一年と少し前(2019年終盤ごろ)からデザインリサーチを業務に組み入れようと試行錯誤をしています。 本記事では、昨年の夏頃にリリースした商品オプション Appでの、開発過程における試行と成果についてお書きします。 商品オプション Appプロジェクトについて 商品オプション Appとは、2019年第4四半期から2020年第3四半期にかけて開発したBASEの商品管理機能です。 このAppの機能そのものや、開発の過程全体について詳しく知りたい方は こちらのBASE U記事 や、 こちらのBASEBook記事 を参照いただけると幸いです。 本記事では、このプロジェクト内で実施した模擬的なユーザリサーチやプロトタイプ検証、簡易ユーザビリティテストといったリサーチ活動(の試行)についてお話ししたいと思います。 デザインリサーチとは デザインという行為は、大まかに言うと「共感 - 制作 - 検証」の繰り返しで構成されるのですが、一般的なデザイン業務においては「制作」部分に重点が置かれがちです。 そのため「共感」「検証」への注目(および注力)が薄くなる傾向にあります。 作ることに注力しすぎて、制作の下支えとなる「観察・共感」や成果に対する「測定・検証」を蔑ろにしてしまっては、プロダクト開発におけるデザインの有効性が半減してしまいます。 意図的に、かつ計画的に「共感」「検証」部分へフォーカスしていきたいと以前より思っておりまして、 デザインリサーチ と呼ばれる活動が私の目指すところに近いようなので、この言葉を掲げて試行して参りました。 なお、リサーチという言葉は「調査」の意味で使われることが多いですが、デザインリサーチにおけるリサーチは調査よりも 研究 や 探求 といった意味合いが強いように思われます。 少々拡大解釈かもしれませんが、 アイデア創出やプロトタイピングも、デザインリサーチの一環である と捉えています。 ちなみに書籍「 デザインリサーチの教科書 」を参照すると、デザインリサーチという言葉は以下のように説明されています。 プロダクトをデザインするためのリサーチ。つまり人々や社会などプロダクトが置かれる状況を理解するためのリサーチを「デザインリサーチ」と呼ぶことが多い。この場合、デザインリサーチはプロダクトのデザインプロセスの一部であると捉えることができる。 デザインリサーチの教科書 「2.1.1 本書におけるデザインリサーチ」より トライ① 仕様策定のための模擬的なユーザリサーチ 商品オプション Appの機能は、大まかに言うと「購入時に商品の色やサイズを変えたり付属品つけたりと、カスタマイズを行う」なのですが、購入体験にダイレクトに影響する機能であり、同時にショップオーナーさんの細かいニーズに柔軟に対応し得る機能であるため、慎重に仕様を策定する必要がありました。 そこで、実際にショップを運営しているオーナーさんの意見を聞いて、数多ある要求のどこにフォーカスしていくかを検討したい... と思ったのですが、社外から広く意見を集めて分析するのは時間がかかりすぎるため、日頃よりオーナーさんから直接たくさんの意見を集めているCXメンバー、およびネットショップ運営経験のある社員に対してヒアリングを行いました。 リサーチ内容 ヒアリングの流れは以下の通りです。 - まずデザイナーが、暫定仕様から大まかなUIワイヤフレームを書き起こします。(以下の画像はそのワイヤフレームの一部)   ワイヤフレームをレビューしてもらう形で、ショップオーナーのニーズや仕様の改善点を探り、ホワイトボード上に書き出します。 メインの質問役は仕様策定者であるPMが行い、デザイナー(私)はメモ取り役兼サブ質問役を行いました。 聞き出した内容をシートに整理してプロジェクトメンバーにシェアします。(以下の画像は、整理したシート) ヒアリングの効果 生の現場に近い声を聞くことで、 どのニーズにフォーカスするか、どの機能を切り捨てるか... といった判断の根拠が明確になりました。 リサーチ結果の影響で仕様が大きく変わるようなことはありませんでしたが、根拠が明確になることで以降のUIデザインが進めやすくなったことは間違いないです。 トライ② デザイン過程でのプロトタイプ検証 仕様策定後、UIデザインの詳細を詰めていくわけですが、ところどころで「実装して操作しないと良し悪しが判断できない」という局面が訪れます。 そのような場合、大抵はFigma等のプロトタイピングツールを使って画面遷移モックアップを作り、操作感を試しつつデザインを進行していくのですが、それではうまくいかない場合もあります。 文字入力やドラッグ&ドロップ操作などの、ある程度複雑なインタラクションになると単純な画面遷移モックアップでは検証できません。 かといって十分な検証をせずに実装に入ってしまうと、実装過程で大きな手戻りが生じるリスクがあります。 今回は、デザイナー側でコード(HTML/CSS/JS)をガリガリと書いて入力UI込みのモックアップを作り、他のデザイナーに操作してもらって反応を見る、ということを行いました。(開発対象のUI全てではなく、操作感に懸念のある画面のみモックアップを作成しています。) モック作りにはだいぶ手間取りましたが、操作感に懸念を残さず先へ進むことが出来ました。 デザイナーがある程度はコーディングができる場合は、ちょっとした操作感の検証程度であれば積極的にコードを書いて、フロントエンド技術への理解を深めつつモック作りをしていけたら好ましいと考えています。 トライ③ 実装過程での簡易ユーザビリティテスト UIデザインFIX後、エンジニアによるフロントエンド実装が50%程度に達したあたりで、一度簡易的なユーザビリティテストを行いました。(実際のBASEユーザに操作してもらうのではなく、デザイナー自身およびその同僚が操作してテストするという、非常に簡易的なテストです。) リリース直前やリリース後の急な修正要件発生を避けるべく、なるべく早い段階でのテストを行いました。 当然、実装途中であるためにテスト対象である画面はまだUIが実装されてなかったり、またはUIだけがあってデータと紐づいていなかったりしています。 このような、ところどころ未完成の状態でテストを実施しています。 実装済みの箇所は実働データを使用し、それ以外の箇所はペーパープロトタイプやデザイン過程で試作したモックアップHTMLを使用するなど、ツギハギして一連のUIを組みつつテストをしました。 まずは自分で操作して違和感あるところを探し、次に隣席の社員に同様に操作してもらって観察しています。 画面だけの操作に限らず、「商品を撮影して登録する」「注文を確認して商品を梱包する」といった作業も込みで流れ確認するために、ダミーの商品や梱包用の封筒なども用意して一連の流れを試しています。 【用意したダミー商品(タッセルを付けられるネームプレート)】   • 自ら一連の操作・作業を体感する。 • 他者の操作を客観的に観察する。 この二つを行うことで、 UIのフローおよび作業の流れで大きく躓く箇所はないか? という点と、 画面間での文言やレイアウトのつながり方に違和感はないか? の確認ができました。 従来であったらリリース直前まで洗い出せなかったような要修正点をこの段階でいくつか洗い出すことができ、リリース直前のドタバタを軽減する一助となりました。 振り返り 仕様策定段階での模擬インタビュー、デザイン制作段階でのプロトタイプ検証、実装段階での簡易ユーザビリティテスト、という3つのトライを各フェーズで行いました。 それぞれ、以下のような意義があったことを確認しています。 ■ 模擬インタビュー: 「なぜこれを開発するのか」「何を目指すのか」「最低限必要なのは何か」といった指針が明確になり、数ヶ月におよぶ開発期間の中で仕様やデザイン方針のブレを防ぐことができた。 ■ プロトタイプ検証: デザインレビューにてデザイン案の同意をスムーズに得ること、及びその後の手戻りの懸念を減らすことに役立った。 ■ 簡易ユーザビリティテスト: フロントエンド実装上の要修正点を早めに見つけ出し、実装工程の円滑化につながった。 (ここでユーザビリティテストの計画および実施を行った知見は、その後の別プロジェクトにて近い内容のテストを行う際にも流用できました。別件のユーザビリティテスト実施については、 別のブログ記事 をご参照ください。) このようにそれぞれ意義があることを体感できたわけですが、一方で、これらのリサーチがいまいち有効でない場合もありそうだ、ということも見えてきました。 商品オプション Appプロジェクトは10ヶ月に及ぶ長期プロジェクトであり、長期であるが故にこれらの試行を途中で行いやすかったのですが、通常の(長くとも半年、短ければ1ヶ月で終わるような)スパンのプロジェクトでは、スケジュールとマッチしない場合や、行う意義が薄い場合がありそうです。 開発プロジェクトに際して、都度「今回の開発対象では何を行うべきか・行わないべきか」を見極めるための知見と、いざ「行うべき」と判断したらスムーズに実施できるような実践経験が必要だと感じています。 それらの点を踏まえ、開発プロジェクトとは別の括りでのデザインリサーチの試行を2020年後半に行っているのですが、そちらについてはまた別記事にて述べたいと思います。
アバター
こんにちは。BASE BANK 株式会社 Dev Division にて、 Software Developer をしている東口( @hgsgtk )です。 TL;DR July Tech Festa 2021 winter に「TDD から ATDD へ歩みをすすめる」というタイトルで登壇しました アジャイルテストとその中で有効なプラクティスとされる ATDD (Acceptance test-driven development) ATDD を実践するための E2E テスト基盤を gauge と CircleCI を用いて構築する July Tech Festa 2021 winter July Tech Festa はインフラエンジニアのための祭典と題したイベントです。昨年からオンラインでの開催となっており 500 名超えの参加者が集まった大規模イベントになっています。 jtf2021w.peatix.com ハッシュタグは #jtf2021w でして、ここから当日の盛り上がりを見ることができます。また、登壇者の発表資料一覧は conpass の イベント資料一覧 にまとめられています。 私は、おもに CI/CD や DevOps といったテーマが多い C トラックにて 25 分時間をいただき発表しました。 発表資料 こちらが当日の発表資料になります。「TDD から ATDD へ歩みをすすめる」というタイトルです。アジャイルテストの実践や、シナリオテスト・E2E テストの整備に興味がある方にも参考になると思います。 この資料は、実際に BASE BANK の開発組織内で「イテレーションごとの(外部)品質を高める開発の方法は何か」というテーマについて考えて取り組み始めた内容をまとめました。 具体的には次のような内容をまとめています。 アジャイルテストの考え方 ATDD (Acceptance test–driven development) とはなにか ATDD を実践する開発文化とプロセス 受入テスト自動化フレームワーク gauge を用いたテスト作成環境 エンドツーエンドテストの自動実行基盤 CircleCI を用いた CI/CD パイプラインへの組み込み TDD から ATDD へ歩みをすすめた現場の実例 当イベントは「インフラエンジニアのための祭典」ですので、とくに参加者層にあわせてなるべく自動テストを実行する CI/CD 基盤について具体的に語っています。 以下、発表資料を用いて話した内容を少し省略しつつご紹介します。 アジャイルテスト アジャイルテストという言葉はこの分野において著名な Lisa Crispin 氏と Janet Gregory 氏がこのように定義しています。 agiletester.ca 始まりからデリバリーまで、そしてそれ以降も 継続的に実施される協調的なテスト の実践 により、お客様への 価値の頻繁な提供 をサポートします。テスト活動は、 高速なフィードバックループ を用いて理解を検証しながら、プロダクトの品質を築くことに重点を置いています。このプラクティスは、 品質に対するチーム全体の責任 という考え方を強化し、サポートします。 このアジャイルプラクティスの中で役に立つプラクティスというものを紹介しており、その中のひとつが ATDD です。 アジャイルテストで役立つとされるプラクティスたち 赤枠で示したとおり、ユーザーストーリー(雑にまとめると「顧客視点の機能価値を定義したもの」)の完成条件を、具体的な例を用いて理解することから始める考え方です。 ATDD ATDD の開発ステップを簡易的にまとめるとこのようになります。 ATDD開発ステップ このステップの中で実装作業としてはまずは失敗する受け入れテストから始めます。 入れ子のフィードバックループを作る 失敗するストーリー受け入れテストを最初に書く ストーリー受け入れテストを通す過程でユニットレベルのサイクルを回す n 回のチェックイン後、ストーリー受け入れテストを通す いわば TDD Cycle よりもう一つ大きなフィードバックループをストーリー受け入れテストによって回す入れ子構造を作ることになります。 CircleCIとgaugeを用いたテスト実行基盤 この取組を実践するには、受け入れテストを自動化するというテスト実行基盤がセットになってついてきます。 当資料内では CircleCI と gauge を用いた自動受け入れテスト基盤を紹介しています。 CircleCIでのCI/CDパイプラインに組み込む gauge とは ThoughtWorks 社がメンテしている Go 製の受け入れテスト自動化フレームワークです。 gauge.org 私が検討した際に感じたこととして、次のような特徴を持っています Markdown ベースのシンプルな仕様記述 Syntax ステップ実装のための言語は Java/JS/TS/Python/Ruby/C# をサポートしている Visual Studio Code との統合がよくできている メンテナンスが活発(Issue をあげたら数分後に返事が来た) 実際に gauge で作成したテストを CircleCI で実行するサンプルを作成したので実際に試してみたい方は参考にしてみてください(例では Python を用いています)。 github.com gauge を用いた ATDD 開発ステップ 弊チームで実践し始めた gauge を用いた ATDD 開発ステップは次のようになります。 スプリントプランニング等で、ユーザーストーリーの受入条件をチームで会話し特定する 「ストーリー受入条件」としてユーザーストーリーに記述 自動化可能なテストであれば、失敗する受け入れテストコードを記述(「仕掛中」マーキング) 機能実現に向けて実装する(n回のCycle) 機能完成後、「仕掛中」を外してテストが通ることを確認 「仕掛中」と「完成」を区別するというアイデアは、そもそも受け入れテストのフェーズが 2 つに区分されるため実施しています。 仕掛中 「まず最初に失敗する受け入れテストを書く」 進捗を測るテストであり失敗することがわかっている、ビルドフローには含めない 完成 リグレッションを検出するテスト 完成したストーリーのテストは常に成功することが期待するためビルドに含める 具体的に gauge でこれらを区別するための活用アイデアは、発表資料内にツール仕様とともに解説しているので、実際に gauge を使ってみようと思った方はぜひご覧ください。 謝辞 2020 年に続き 2 年連続スピーカーとしてお邪魔させていただきました。 devblog.thebase.in 発表時間以外もとても楽しく参加させていただきました。基本的にずっと C トラックにいたのですが、直前で発表いただいた Masahiko Funaki さんが「 縁の下をどう支えるか〜CI/CDの伝え方 」にて CircleCI を用いた話をされていて、自分の発表内ととても関連していたので口頭で参照させていただいたりしていました。 14:20~14:45 / C4 -「TDDからATDDへ歩みを進める」 東口和暉 さん CircleCIをご活用いただき、ありがとうございます! #CircleCIJp #JTF2021w #JTF2021w_C — 舟木将彦/Masahiko FUNAKI (@mfunaki) January 24, 2021 たくさんのトラックが同時並行で走る中、運営いただいたスタッフの皆様ありがとうございました!特に、C トラックをご担当いただいた小松様が発表中に聴講者としてうなずく等オフラインのカンファレンスでは見られていた聴講者の反応を zoom 上でしていただいたおかげで、「今ちゃんとインターネットとつながってる」と安心しながら登壇できました。この場を借りて改めて御礼申し上げます。 おわりに 『 実践テスト駆動開発 (Object Oriented SELECTION) 』の「第 1 章テスト駆動開発のポイントとは?」では学習プロセスとしてのソフトウェア開発という捉え方を提示しています。 開発者は自分たちが使う技術はプロジェクト完成の過程で学ぶ 何を実現しようとしているかを理解をしていく 実地からのフィードバックを活用しシステムに活用していくこと 今回の発表で紹介した ATDD についてもフィードバックループをいかに回していくかというアイデアになります。当資料がアジャイルテストのマインドをベースとした開発戦略とそこから必要される基盤構築を試行錯誤する際の助けになれば幸いです。 後記 @CircleCIJapan さんに取り上げていただきました。CircleCIさんいつもありがとうございます。 今日のご紹介は @hgsgtk さんの #ATDD 実践とCircleCI・ #gauge でのE2E自動テスト基盤 です。昨日紹介させていただいた #JTF2021w にて中の人の後の登壇ということもあり聞かせていただいた話です。これぞリアルユースケースという深い内容です!多謝! #CircleCIJp https://t.co/0KbPFDnf4D — CircleCI Japan (@CircleCIJapan) January 26, 2021
アバター
こんにちは。BASE BANK 株式会社 Dev Division にて Manager をしている東口( @hgsgtk )です。 昨年 2020 年は本ブログにて個人の足し算ではなく掛け算で成果が出せるようなチームを目指したアジャイル開発の取り組みを継続して紹介してきました。 チーム開発の潜在的課題が見つかる振り返りワーク「Mad Glad Sad(喜、怒、哀)」 少人数でのアジャイル開発への取り組み実例 (一歩目の踏みだし方) | 詳説 | July Tech Festa 2020 登壇レポート アジャイル開発におけるユーザーストーリー分割実践 〜画面リニューアルの裏側〜 これらの考え方やプラクティスは全体の一部で、開発チームとしての組織ローカルなプラクティスを『BANK DEV 白書』として整理しています。『BANK DEV 白書』では次のような内容を整理しています。 一般的なアジャイル文脈のプラクティスで出てくる概念の実用の仕方 ex.「ストーリーポイント」を用いるシーン・ポイントの付け方 オリジナルな開発プラクティス ex.「井戸端会議」・「TODO コメントのチケット化」 これらは主に振り返り(レトロスペクティブ)にて発見し、その結果をまとめることで積み上げてきました。本記事ではこのプラクティスをブログとして公開します。 知見の積み上げ『BANK DEV白書』 この取組が始まったのは、「なんでこの取組・プラクティスをやっているのか」という認識が人によってブレることを防ごうとしたことがきっかけです。 たとえば、「ペアプログラミング」という 1 つのプラクティスを手にとってもそれぞれ個人が感じる認識・温度感は違います。しかし、「私たちは設計等迷った際のコミュニケーションツールとしての意義に重きを置く」ということが言語化されていれば「ペアプログラミング」という語彙を会話の中で使いやすくなります 1 。 最初は GitHub 内にドキュメントを作成し随時更新し続ける運用としていました。しかし、ドキュメントでの運用は文章を書く心理的ハードルが高いため、基本的に発起人の @hgsgtk が書記となって更新し続けていました。 その後、継続していくうちにチームの文化定着がうまくいき『 アジャイルの「ライトウィング」と「レフトウィング」 』でいうところの 協働でゴールに向かう「チーム環境」 であるレフトウィングがしっかり生えてきたという実感がでてきました。具体的には、レトロスペクティブなど対話の場のファシリテータを持ち回りでやるようになりました。意識の高い個人が引っ張る構図からチーム自身が自身を成長へ引っ張っていく構図へと変わってきました。 そのため、現在は共同編集をより促進できるよう、より更新の負荷が低く各プラクティス間の関係性も明示しやすい Miro に移行しました。 Miroで作成したBANKDEV白書 ロン・ジェフリーズ氏が XP を描いた図である「 Circle of Life 」のように、ビジネス・チーム・技術という 3 つのリングに分類するといったカテゴリライズの必要性は検討しましたが、現場ではビジネスから技術までかなり密接となっており、まだまだ洗練されきっていないため、あえてカテゴライズしていない選択をしました。 一方で Martin Fowler 氏が指摘するような、技術プラクティスが骨抜きにされた「 FlaccidScrum(ヘロヘロスクラム) 」となることを避ける必要があるため、リファクタリングなどといった技術プラクティスも内包されるよう努めています。 前提: BANKチームが目指すあり方・戦略 上の『BANK DEV 白書』と紹介した Miro の画像には中心が定義されていました。この中心が前提となり目指すあり方と戦略です。 『 小さなチームが始めたアジャイル開発 』という資料にて、アジャイル開発を何故始めたかについてまとめています。 BANK チームでの思考プロセスは『 みんなでアジャイル――変化に対応できる顧客中心組織のつくりかた 』という書籍の考え方に最も影響を受けていますが、そもそもの自分たちの課題分析から始まっています。いわゆる「アジャイルをやってみよう」という入り口で考えておらず、課題定義と未来の理想像の定義から逆算した結果「アジャイルだった」という思考をしています。これを明文化したのが下のマップです。 北極星の明文化 これは BANK チーム固有の価値観定義なので、軽くサマリーするのにとどめます。「 生き生きとした開発とはなんだろうか 2 」といった問いについて議論し、 個々人の達成感(個人)と関係各所との約束を守る事(全体)を両方満たすバランス の取れた開発の仕方が必要だろうとなりました 3 。 また、BANK チームでは「技術戦略」と呼称した短期的計画に依存しないエンジニアリング組織としての戦略を定義しています。 技術戦略 ここでは「 柔軟 」という漢字 2 文字を現在定義しています。現在次のような意図を設定しています。 システムの変更可能性を維持・向上すること 運用・監視の効率性を上げることでグロースや新規サービスに割く時間を増やすこと 新規サービス開発の際の作業量を減らす開発効率向上に務めること チーム内での人の動き方を柔軟に対応できるようにすること この「技術戦略」は四半期計画やその場での現場の意思決定に用いる用途で設定されています。 四半期計画における運用方法は以下です。 まず PdM がプロダクトとしてやりたいこと・開発メンバが「プロダクトに対して間接的だが技術的な課題」を用意 それらを INPUT に個々人がやっていきたいことを「野望」として決める 「野望」では四半期レベルに収まらないものも含めて個人目標を設定します 『 駆け出しマネジャーの成長論 7つの挑戦課題を「科学」する 』では、メンバーが同じ船に乗って納得感を持つためには 計画に自分の意思決定が適度に反映される「対話空間」を用意する ことの重要性について語っています。技術戦略は対話空間を設計するための目線合わせのためのツールとなります。 さて、少し前置きが長くなってしまいましたが、これらの前提を踏まえてもう一度プラクティス図の概要を説明します。 Miroで作成したBANKDEV白書 まず中心に添えられているのが、「北極星」と「技術戦略」となります。 中心に添えた北極星と技術戦略 これらを中心に派生していくのがプラクティス図となります。 プラクティスの紹介 さて、それでは具体的な各プラクティスについて特に積み上げが多いプラクティスについて紹介していきます。 ストーリーポイント 「ストーリーポイント」はアジャイル開発において一般的に知られるプラクティスです。ストーリーポイント自体を軽く説明しますと、 相対見積り の考え方に基づいたものです。必要な作業・複雑さ・リスク等を鑑みてポイント設定します。ストーリーポイントの考え方について体系的に知りたい方は、『 アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~ 』がおすすめです。 ストーリーポイントに関するプラクティス BANK チームではストーリーポイントの運用を続けて次の内容について更に言語化しました。 大きさの構成要素 Our 1pt(私達の 1pt) Issue 作成時のポイント付与有無判断基準 ポイントが付けられない時 タスクもストーリーポイントでの見積もりを行う 「見積り」がソフトウェア開発において難しいテーマであるがゆえに、BANKチームでもレトロスペクティブを通じた積み上げが一番多くなっています。 大きさの構成要素 大きさの構成要素として「 複雑性 」・「 量的要素 」・「 検証ループの回しやすさ 」という 3 点があるとしました。 複雑性 (例)複数クラスにまたいで触る (例)リポジトリの数がさまざま分散している 量的要素: 作業量の多さ 同質なものをたくさん並べる (例)テストコードをたくさん書く (例)脳死してコピペするような「量」 検証ループの回しやすさ 通常の作業よりも速度が落ちる ハマったときのリスクがある (例)Terraform だと検証ループが回しにくい Our 1pt ストーリーポイントは相対見積の概念であると前述しましたが、相対の比較基準となるストーリーを用意します。Robert C. Martin 氏の『 Clean Agile 基本に立ち戻れ 』では、これを ゴールデンストーリー という語彙で紹介しています。 BANK チームでは現在 1pt を比較基準としており「だいたい半日程度で終わる程度のユーザーストーリー」をピックアップしています。前提としてユーザーストーリーは時間と直接マッピングされる概念ではないので厳密に半日であることに重点を置いているわけではありません 4 。概ねのチーム内の基準の認識合わせのために「半日程度」というニュアンスを利用しています。 この基準に基づいて使うポイントはフィボナッチ数列内の 1pt・2pt・3pt・5pt・8pt・?pt としています。 Issue作成時のポイント付与有無判断基準 Issue 作成時にポイントを付けるか、Issue を受け取った人がポイントを付けるかの判断基準を整理しました。 Issue作成時の判断基準 課題に対して解決策(HOW)がわかっている場合は Issue 作成者がポイントを付けてしまうことにしています。一方で解決策(HOW)がわかっていない場合は Issue を受け取った人が後述する「スパイク」を利用したりして、ポイントをつけることにしました。 タスクもストーリーポイントでの見積もりを行う BANK チームでは Issue に 3 つのバリエーションを現在設定しています。 ISSUEのバリエーション Epic / ユーザーストーリー / タスクの 3 つです。これについて、特にタスクレベルの見積について組織によって運用が異なるかと思います。たとえば、場合によってはタスクレベルでは理想日(絶対値基準である時間・日にちを用いる見積手法)を使用するパターンも考えられます。現時点ではあえてここを分ける理由と必要性が出ていないため、タスクに対してもストーリーポイントをつけて運用しています。 ここで、そもそもタスクについて見積もりを行うことの意義として BANK チームでは 3 つの理由を言語化しました。 手空きのヘルプ時の状況把握 ブラックボックス化の回避 ゴール設定の意識 見積もりを行う意義 実際この運用によって、個人の作業のブラックボックス化・ゴールの曖昧化が防がれ、協働での開発がスムーズになっていると感じます。 スパイク ポイントが付けられないときに活用する「スパイク」 ユーザーストーリーやタスクに対して、技術的な懸念や不明点によってポイントがつけれない場合があります。これに対して「スパイク」という手法を利用します。『 Ryuzee.com: スクラムにおける技術的スパイクの進め方 』では、次のように説明している概念です。 リリース可能なプロダクトを作るのではなく、質問に答えたり情報を収集したりすることを目的とした作業。 開発チームが技術的な質問や設計上の問題を解決するための実際の作業を行うまで、見積りができないようなプロダクトバックログ項目が出てくることがあります。 解決策は、「スパイク」を行うことです。 目的は、質問に対する回答やソリューションを見つけることです。 いわば、 ストーリーを見積もりためのストーリー と言えます。具体的には次のようなフォーマットで Issue を作成することにして、ISSUE_TEMPLATE にも登録して運用しています。 「Xxx のストーリーの見積もりをするため、Xxx を(検証・調査・把握)する」 ISSUE_TEMPLATEの一覧 このスパイクの使い所として、BANK チームでは 歴史的経緯がある気がしたらすぐにスパイクを打つ という使い方をしています(「スパイクを打つ」というのはスパイク活用の際の言い回しです)。 長年稼働しているソフトウェアに機能追加する場合は歴史的経緯はどうしてもあり、その歴史的経緯の発覚によって大幅な手戻りが発生し見積が大幅に上振れるリスクがあります。実際にプロジェクト運営の中で歴史的経緯にハマってしまった事例がありチームのプラクティス活用術として定義しました。 井戸端会議 これはオリジナルのチームプラクティスです。次のようなシーンで活用します。 何らかの問題が発生しているが次の方向性・やることは決まっていない場合や、 使用可能な道具が目の前にあるがその活用可能性・活躍のさせ方についてアイデアが得られていない際に、 その課題に対して、大まかな見解を得るための対話(Dialogue)を行う。 井戸端会議 井戸端会議で行われるコミュニケーションは「対話(dialogue)」であると定義しています。『 問いのデザイン: 創造的対話のファシリテーション 』では、対話を「自由な雰囲気のなかで行われる新たな意味付けをつくる話し合い」であると定義しています。この定義の通りざっくばらんにトピックについて話し、方針が「なんとなく」決めることを目指します。 これまで次のトピックで開催されました。 OneLogin の活用 OneLogin を活用してできることはなんだろう? Go らしさ・サーバーサイドアーキテクチャ Go を活用する際のチームの認識合わせ OOP を前提とした戦略・戦術とのバランスをどう取るか サーバーサイドアーキテクチャ Logging New Relic の活用を始める中でどのようにロギングを考えるか 5 次の具体的なアクションはどうするか Go に関する井戸端会議の Miro の様子を一部チラ見せするとこのような話題をざっくばらんに取り上げました。 Goらしさ井戸端会議の様子 Go 井戸端会議の中では、自分たちのコードベースではどのように「値オブジェクト」を定義するか、といった具体的な実装指針が決まるところまで至れました。 チームメンバーの感想は次のようなものでした。 雑な情報交換が出来た 事前準備がいらなかったのがよかった、しないくらいのが認識が固定化しなくていい ゴール・やりたいことも見つかった! リモートで欠落しがちなコミュニケーションが補える 実際井戸端会議で行われた内容は、物理的に同じ場所にいれば自然と行われていた会話でもあります。しかし、WFH のなかでお互いリモート同士となるとそういったコミュニケーションが欠落しがちな点を、井戸端会議はうまく補ってくれています。 リファクタリング リファクタリングは技術プラクティスとして重要な活動であることは言及するまでもありません。Martin Fowler 氏は「 FlaccidScrum(ヘロヘロスクラム) 」にて、リファクタリングなどの技術プラクティスを欠いたことで、開発速度が低下していく現象を示しました。 リファクタリングとTODOコメントのチケット化 BANK チームとしてのリファクタリングにおける具体的なプラクティスとして「 TODO コメントのチケット化 」というものがあります。これは BANK チームの @applepine1125 さんが社内ドキュメントに投稿したエッセイから始まったプラクティスです。 社内ドキュメントに投稿したエッセイ 代筆してサマリーすると 人は時間がない・タスク分割の一時的メモとして TODO/FIXME コメントを書く TODO/FIXME は回収する仕組みができないとまずい チケット化することでプランニング等棚卸しで管理しやすくなり、リファクタリングを組み込めるようになる それにより、TODO/FIXME の回収可能性が高まる TODO コメントのチケット化は、いわば意識的にリファクタリングを開発に組み込むための基盤となるものです。その後自然とチケットを作らず無意識にリファクタリングが組み込まれる未来が来ることを見据えた点についても語っています。 開発のスケジュールの中で意識的にリファクタリングを組み込む。というやり方を積み重ねていくことで、最終的にチケットなんて作らなくても日々のフィードバックをもとに自然と正しくあるべき箇所に手が入るようになると嬉しいね。 by @applepine1125 『 エッセンシャル スクラム 』の第 8 章「技術的負債」は技術的負債について非常に有名で示唆に富む内容を語っていますが、その中では技術的負債の発生の管理、可視化、返済の重要性と適用方法について語っています。当プラクティスはその中の「可視化」に一役買うものと言えます。 実際に、実践すると次のような効果が得られました。 特に他の人と協働する時に活用したほうが良いとわかった PR のときに気をつけるようになった TODO の寿命を短くするように意識するように ペアオペ・ペアプロ・ライブコーディング 開発中ペアで作業することについて、これまでのレトロスペクティブで「よかった」という振り返りがあったのがペアオペ・ペアプロ・ライブコーディングでした。 ペアオペ・ペアプロ・ライブコーディング これらは都度都度コミュニケーションツールとして活用する方針としています。組織によっては常時ペアオペ・モブプロするといったところもありますが、BANK チームでは必要に応じての活用にとどめています 6 。 レトロスペクティブ 紹介するプラクティスの最後は「レトロスペクティブ(振り返り)」です。 レトロスペクティブ レトロスペクティブでは 3 つのワークを行っていました。1つ目は「Mad Glad Sad」ですが、こちらは『 チーム開発の潜在的課題が見つかる振り返りワーク「Mad Glad Sad(喜、怒、哀)」 』にて詳説しています。 2つ目は「 見積り振り返り会 」です。この取組は「自分たちの見積もりに対してフィードバックループが回せているだろうか」という課題感から始まったワークです。見積りに課題感を感じた Issue を取り上げて次のような問いを重ね、原因と取りうる ActionItem をチームで見つけ出していきます。 なぜ見積り精度が微妙だったか 何が原因で想定よりも大幅に労力が必要だったか どういう属性のあるものだったか タイムマシーンに乗って昔に戻るなら何をテコ入れするか 今後どうすればよいとおもうか 当ブログで述べている見積もりに関するプラクティスはこの「見積もり振り返り」の結果が得られたものがとても多いです。たとえば、ストーリーポイントの大きさの構成要素は、このワークから発見されたものです。 3つ目は「KPT」で、振り返りワークの中で最も一般的なものと言えるでしょう。 それぞれのワーク内で 「難しいね」で終わらせないファシリテート を心がけようとしています。EVP of Development の @fshin さんの 『部下に対して「難しいね」で終わらせないマネジメント 』で提示したマネジメントの注意事項を参考にしたものです。 仕事に難しいことがあるのは当たり前 当たり前の言葉を言わない 簡単だったら、そもそもあなたに相談しない レトロスペクティブにおける話の進め方についても同様に、「難しい」話題はあがりますが、難しいで終わってしまうとそこでフィードバックループは止まってしまいます。 プラクティスの積み上げ方 組織に限らず「成長」するために必要不可欠なのはただ実践するだけでは足りないと思っています。たとえば、『 エンジニアの知的生産術 』では具体→抽象→応用という学びのサイクルを提示しています。 『エンジニアの知的生産術』における学びのサイクル  レトロスペクティブは上図の「抽象」のフェーズでありスプリント内で経験した「具体」を分析して次の「応用」を発見することに意義があると考えます。BANK チームではレトロスペクティブで「ActionItem」をまとめますが、その ActionItem がプラクティスになるという概念整理によってプラクティスの積み上げを試みています。 レトロスペクティブからプラクティスへの昇華ループ つまり、レトロスペクティブの中で得られたアイデアを ActionItem として実践し、プラクティスとして昇華していくというやり方をしています。 この思考方法が開発チームを洗練させることにつながるかは、2021 年終わりに開発プラクティスの集まりをどのように進化させていったかによって評価されることでしょう。 おわりに BASE BANK の開発チームでは日々「どうすればより良いプロダクトを作れるか」といったことを考え、フィードバックループを自分たち自身に回し進化していけるよう業務に邁進しています。 open.talentio.com 現場の雰囲気に興味を持っていただいた方はお気軽にカジュアルなお話をしましょう。 @hgsgtk 宛に DM 頂いても構いません。 語彙を育てていくという考え方は、クリストファー・アレグザンダー氏の提唱した「パタン・ランゲージ」をプロジェクトで行なう際、個別プロジェクトで調整した「プロジェクト・ランゲージ」を作成するという中埜氏の解説にインスパイアドされたものです。詳しくは『 パターン・ランゲージ: 創造的な未来をつくるための言語 』の「第1章 建築におけるパターン・ランゲージの誕生」をご覧ください。 ↩ 「生き生きとした」という問いは、クリストファー・アレグザンダー氏の建築理論におけるテーマである「生き生きとした場所をもたらすこと」という目的感に影響を受けて設定しました。 https://speakerdeck.com/hgsgtk/design-pattern-usage-inspired-by-pattern-language?slide=11 ↩ 『 エクストリーム・プログラミング 』(俗に言う「XP 白本」)では、『プログラマーの生活を良くし創造的にいきいきと働くために XP を体系化した』と語っています。彼らの考え方にも強く影響を受けて現在の試みを進めています。 https://speakerdeck.com/hgsgtk/xp-is-social-change-in-timeless-programming-way?slide=21 ↩ 『 アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~ 』では、ストーリーポイントの優れた点は 作業量の見積もりと期間の見積もりを分けたこと だとしています。期間の見積もりは規模(ポイント全合計)/ ベロシティ(1 回のイテレーションで完了させたストーリポイントの合計)などの計算式が責務をもっています。 ↩ BASE 社では New Relic プラットフォームを用いた取り組みを開始しています。具体的には CTO @dmnlk さんのこちらの資料をご覧ください。 https://speakerdeck.com/dmnlk/phpcon2020jp-observability ↩ 実際、『 Clean Agile 基本に立ち戻れ 』にて Robert C. Martin 氏は、ペアプログラミングについて、ペアになることは任意であり強制されるものではないし、常にペアになるわけではない点を説明しています。一人でコードを書きたいこともあるので、個人・チームがどのくらいの割合で行なうか決めればいいと。 ↩
アバター
明けましておめでとうございます。 BASE株式会社でUIデザイナーをしている野村( @nomjic )です。 外出自粛ムードに拍車がかかる昨今、週末や連休でもなるべく外に出るのを控えたいところですね。 家に籠もって読書して過ごそう、という人も多いのではないでしょうか。 そんなわけで一つ、読書ネタを書いてみたいと思います。 と言っても「連休に読むオススメ本10選」みたいな話ではなく、読書へのモチベーションの高め方とか、習慣化とかに繋がるような話をさせてもらおうかと。 話す内容をざっくり言うと「 デザインチームで読書会を始めてかれこれ3ヶ月経ったのでその報告 」です。 ちなみに 開発チームでの読書会についての記事 という記事が先月アップされていますが、あちらに比べてこちらの内容はだいぶゆるいです。 知識や技術を身に付けることよりも、会の参加メンバーが読書を習慣化することを支援するとか、チームメンバー間のコミュニケーションを促進する、といった比重が強めです。 巣篭もりタイムを活用して読書したいけどモチベーションが上がらない、または読書を始めたものの習慣化できる自信がない、といった人の参考になれば幸いです。 経緯 昨年より、弊社では新型コロナウィルスの影響を受け在宅勤務が推奨されており、実際多くのメンバーが自宅にて業務を遂行しています。そんな中で、ある時期から以下のような声がチラホラと聞こえていました。 「リモートワークだとやはりコミュニケーションが希薄になる感じあるね」 「今まで通勤時間が読書タイムだったのだけど、その習慣がすっかり消えてしまった」 リモートワークあるあるですね。 あと、リモートワークとは関係なしによく耳にする話で、 デザイナーは活字が苦手 問題があったりします。 私はしばらく前から社外での読書会に参加してまして、読んだ本について話しつつ時には脱線して下らない話で盛り上がりながら楽しく会話する、という状況を経験していたので、「 じゃあデザインチームで読書会やってみるかな 」と思ってチームメンバーに話振ってみたら結構食いつき良かったので企画してみた、というのがデザイナー読書会に至る経緯です。 企画 コミュニケーション活性化とか、読書のモチベーション向上・習慣化とかに重きを置いているので、あまり労力を割かずに 読書が苦手な人でもゆるく楽しく続けられる形 を目指して考えてみました。 zoomを使ったオンライン開催 頻度は週一回、30分〜1時間程度。 一回あたりのボリュームは本の一章分くらい。ページ数で言うと40〜60ページくらい? 各々読んできて、読書会の場では「思ったこと」「わからないこと」等を自由に語る場とする。 どのくらいしっかり読み込んでくるかは自由。斜め読みでもいいし、部分的にしか読んでなくてもいい。何だったら目次しか見てなくたっていい。 その書籍の掲げるテーマをネタにしてコミュニケーションが発生すればそれでよし 。 このくらいのゆるい感じで企画をしました。 2つのメソッド 読書および読書会の品質向上のために、2つ工夫をしてみました。 ①Kindleのマーカー&メモ機能を活用 私は書籍は基本電子版で読んでいるのですが、本を読みつつ「ここ読書会で話そう」と思った箇所にマーカーを引いてコメントを書き残してみました。(音声入力使うと楽です。) 語りたかったことを言いそびれないように始めた工夫だったのですが、やってみると「 同僚たちと語りたくなるような面白い箇所を探す 」という癖がついて、読書のモチベーションが一段階上がったように感じています。 ②Figmaを使って会話のメモ取り もう一つ工夫した点としては、「読書会の記録をどう残すか」です。 楽しく語り合えれば読書会としての目的は達成なのですが、あとから振り返えれたらよりGOODです。とは言えいわゆる「議事録」のようなものを取ると、手間が大きいし場の雰囲気が硬くなってしまいそうです。 ということで、参加者の1人が(というか、仕切り役である私が)読書会中にFigma上にメモを取りました。 書籍内の印象深い箇所とか、面白い発言を書き込んだりします。 一つの画面上にKindle画面で本のページを開きつつ、Figmaでメモを取りつつ、Zoomで皆さんの顔を見つつ... というスタイルをやってみました。 シームレスに 本のページ・メモ・メンバーの顔 が見えて良い具合でした。 詳しい記録を残すことは意図していなくて、「どのへんに興味を持たれていたか、どんな話題が出たか」がざっくり残っていれば良し、くらいに考えました。 詳しく振り返りたいなら書籍そのものを再読すればいいじゃない、というスタンスですね。 1冊目 「オブジェクト指向UIデザイン」 そんなわけで、1冊目の題材を選定して読書会を始めました。 読むのは2020年のUIデザイン界ベストセラーである オブジェクト指向UIデザイン 、通称OOUI本です。 日時は月曜11:00〜11:30に設定しました。 月曜の午前という、「いまいち仕事のエンジンかからない時間帯」にゆるい読書会を差し込むことで、良い具合にウォーミングアップになるのではないか、ということを意図しています。 9月後半から11月初旬までの時間をかけてデザインチーム数名で読みきりました。 読書会中に取ったメモ(の一部)は以下のような感じです。 1冊読み終わってテコ入れ 1冊読み終わった時点で、進め方とか時間帯とか、そもそもこの読書会続けるべきか?という点を再検討するためにKPT会(振り返り会)をしました。 概ね出てきたのは以下のような意見。 記憶の定着に良いし習慣にもなる。続けるべき。 読んで理解できなかった点をちゃんと議論して理解できた。 一回30分は短すぎた。盛り上がってきたあたりで終わる。 進行役は持ち回りにした方が良さそう。(ここまで毎回私がやってました) 進行役がメモ取り同時にやるのつらい。 といった意見を踏まえ、進め方にテコ入れしました。 「午前は苦手だからお昼くらいがいいなぁ」という人もいたので、時間帯も多少調整しています。 テコ入れ1 :モデレータ(進行役)とメモ取りの担当を毎回変える。 テコ入れ2 :時間を12:15 - 13:00の45分間とする。 次何読もうか会 2冊めに入る前に、読書会の時間を一回使って「次何読みたい?」を語る会をしました。 各々興味ある本を提案し合って、最後にみんなで投票して決めています。 書籍だけでなくブログ記事なども候補に挙がりました。 2冊め 「デザインの伝え方」 参加者の興味を一番集めた デザインの伝え方 を11月中旬から読み進めています。12月末時点で半分ほど読み終えたところです。 時間を午前から昼にずらしたのが好ましかったようで、参加者がいくらか増えました。以前の時間帯では4人前後の時が多かったのですが、今はコンスタントに6〜7人参加しています。 また、仕切り役とメモ取り役を分けたことで、メモの精度も向上したように思われます。 以下、メモの一部を抜粋。 まとめ(というか所感) この読書会、割と話題が脱線します。 「 ○章のあたり読んでて、□□の業務の時のこと思い出した 」のような発言から始まり、その業務で何があったか、何に困ったか、何が面白かったか、という話になり、気づけば全然書籍の内容と関係ない話題に及んでいたりします。 本の内容についての知識・理解を深めるという意味では好ましくないかもしれませんが、そもそものこの読書会の意図の一つとして「 コミュニケーションの活性化 」がありますので、我々の読書会においてはこのような脱線をウェルカムとしています。 むしろ、本の内容が呼び水になって各々の業務スタイルやデザインに対する価値観・思い入れといった、 普通の雑談ではなかなか至らない深いところ まで話題が及んでいるので、結構良い具合に成功しているなぁと企画者である私としては思っております。 そんなこんなで、2021年も読書会をゆるく続けていきたいと思います。
アバター
こんにちは!デザイナーの渡邊です。 前回、BDIというデザイナーの勉強会のロゴについての記事を書かせていただきました。 BASEのデザイナー勉強会『BDI NIGHT』のロゴを制作しました - BASEプロダクトチームブログ 今回はBDIでゲームを作るワークショップを企画したので、準備や当日の様子をお伝えできればと思います。 デザイナーもデザイナー以外にも楽しんでもらえる勉強会を BDIは主にデザインチームを対象とした勉強会でしたが、デザイナーが大切にしていることを知ってもらったり、デザインをより身近に感じてもらうために、デザインチーム外の方の参加も歓迎してはどうだろうかという意見が上がりました。 またBDIそもそものコンセプトとして、メンバー間のコミュニケーションを活発にしたり、リラックスして楽しんでインスピレーションを得て欲しいという目的もありました。 ワークショップや情報交換ももちろん大事ですし楽しいのですが、さらにたくさんの人に楽しんでもらうため、年内最後の特別企画を考えることにしました! ゲームを作るぞ! 作る側も見る側も楽しい企画を...ということで、BDI改善チームから「『BASE』のゲーム作ってみる?」というアイデアが上がってきました。年末最後ということで特別感を出したいよね!と即決されました。 前半はBDI改善チームが作ったサンプルゲームを遊んでもらい、後半は実際にfigmaでゲームを作る構成に決定! サンプルゲーム制作開始 プロット・フロー図を書く ゲームの形式はプロトタイプで作ることが容易そうな難易度低めのADV(ノベルゲーみたいなもの)にしました。 ざっくり「BASE」を利用しているオーナーが選択肢を選び、様々な局面を切り抜けながら大成功する、という大筋のストーリーを決め、 BDI改善チームの三人でそれぞれmiroでプロットを書くことにしました。 最初は3つぐらい分岐があればいいかな...と思ったのですがついつい凝ってしまい、最終的には全体でこんな規模に...! Figmaでゲームのパーツのアセットを用意する ストーリーが書き終わったらいよいよFigmaで組み立てていきます! コピペで作業を簡単に進められるように、ゲームっぽいボタンやフレームのコンポーネントを用意しました。 最近のFigmaのアップデートでVariantsが設定できるようになり、バリエーションのあるコンポーネントを制作するのがかなり楽になりました! プロトタイプで遷移とアニメーションを設定する FigmaのPrototypeから遷移先を設定します。 すごい数!!!!遷移先、もうちょっとわかりやすく見られるようになるといいんですけどね...。 スタートとゴールに似たような黒いスライドが並んでいるのは、アニメーションを付けているためです。 キラキラ動くちょっとリッチなOPに BDI当日 当日はサンプルゲームをみんなで試遊して大いに盛り上がりました! プロトタイプ画面を共有しながら、選択肢は参加者全員の反応機能による多数決で決めていきました。 「BASE」の中の人だからわかるあるあるやトンデモエピソードでわいわい楽しむことができました。 後半は実際にストーリーを考えてからプロトタイプを組むまでをチームに分かれて実際に手を動かしてもらいました。プロトタイプ機能はもちろん、figmaのvariants機能などもサッと理解して自由にカスタマイズしてもらっていたようでよかったです。 BDIのTipsは誰でも見られるように 遊ぶだけじゃなく、少しでも参加者が知見を持ち帰れるようにFigmaのTipsは社内用のドキュメントにまとめてみんなが見れるようにしました。 その中の一部をご紹介します! Figmaの装飾ボタンの作り方 左右を装飾できるボタンの作り方がネット上になかったので、メモ書き程度に残しておきます! サイドパーツとセンターパーツを作ります センターパーツ内に最低サイズとして透明のrectangleを仕込んでおくと少ない文字数でもボタンの最小サイズが指定できます サイドパーツでセンターパーツを挟み、ボタンをAuto Layoutを使って作ります ボタン全体のドロップシャドウなどの効果を設定 コンポーネント化 コンポーネントを選択してVariantsの + をクリックしてVariantsを追加 名前やコンポーネントの差分を変更 完成! After DelayとSmart Animateでアニメーションを作る プロトタイプ機能の「After Delay」を活用することで、複数のアートボードを自動で遷移し続ける簡単なアニメを実装することができます。 After Delayに入力した秒数が経過すると、自動で遷移などの行動が発火します。 また「Smart Animate」を指定することで、遷移先と同じパーツがあった場合に透明度や位置、大きさの違いを検知して自動でモーフィングしてくれます。これを使って、タイトルが下からさがったりする動きを実装できます。 オープニングでははじめに再生されるアートボード外にタイトルロゴが配置されています。この状態でプロトタイプを連結すると、上からロゴが降ってくるアニメーションとして再生されます。 うまくSmart Animateが認識されるコツとしては Opt+ドラッグでアートボードを複製をしたあとにパーツを移動する レイヤー名を遷移前と遷移後で合わせる アートボード外に配置するとだいたいはアートボードのグループから外れてしまうので、手動でアートボードの階層にもどしてあげる を意識すると、意図した動きをしてくれることが多いです。 まとめ ゲームをつくるという題材にしたことで、日頃触る機会のなかった機能にもスムーズに馴染めたかな、と思います。デザイナーではない人にもBDIを通してデザインに興味を持っていただける良い機会なので、引き続き楽しくためになる取り組みを続けていきたいと思います!
アバター
「BASE」テキストコミュニケーション・ガイドラインをアップデートする話 この記事はBASE Advent Calendar 2020の25日目の記事です。 https://devblog.thebase.in/advent-calendar-2020 こんにちは。 Product Management GroupでPJのディレクション、Design GroupでUXライティングを担当している藤井です。 ふだんはネットショップ作成サービス「BASE」やショッピングアプリ「BASE」の新機能および改善のディレクション業務をおこないつつ、「BASE」プロダクト全般におけるテキストの品質を向上させるUXライティングに取り組んでいます。 社内で「テキストコミュニケーションをアップデートしていこう!」と声を上げてから約2年。「BASE」のUXライティングは、現在ではこんな視点で、テキストにおけるデザインシステムともいえる「用語リスト」と「運用ガイドライン」を作成、運用しています。 ◯運用ガイドライン・視点の一例 既存の言葉を使う→ユーザーがふだん使っている言葉に合わせるべきであり、すでに理解されている表現があるときに、いたずらに新しい言葉を作り出さないようにする 形を機能に従わせる→美しさ<機能。言葉のほうが伝わるのか、画像のほうが伝わるのか、目的達成のために効果的な方を選ぶ ゴール・オリエンテッドである→その画面において必要な情報のみを、その都度提供する また、タッチポイントごとの特性によって、テキストのコミュニケーションに求められるものも違うため、プロダクトの各タッチポイントで、最終レビュー担当者をそれぞれアサイン。管理画面やメールなどプロダクト全般で1名、ヘルプやFAQなどカスタマーサクセス視点で1名、SNS関連で1名、の体制で最適化を図っています。 あくまで暫定的なものだった、「BASE」のテキストコミュニケーション・ガイドライン と、社内にもプロダクトにもじわじわと根付いてきたUXライティングですが、よくよく考えてみると、あることに気づきました。いまのテキストコミュニケーションの土台になっているガイドラインや視点って、会社のミッションや他社事例を参考にしながら、あくまでもその当時の仮説として設定したものだったなあ、と。 実際のところ、最近では社内でテキストレビューをフィードバックすると、「ここは漢字にしたほうがよくないですか?」「ちょっとしっくりこないです」「ルールとしては合っているかもしれないけれど、読みづらいかも」などの声もちらほら。 そこで、今一度あらためて「BASE」にとってのUXライティングとはなんぞや、を考えてみることに。 UXライティングって? そもそも、UXライティングの定義とはどんなものなのでしょう? ユーザーに愛着を持ってもらえるプロダクトにするために、プロダクト内で使われるテキストを定義して運用すること(出典:効果的なUXライティングのための16のルール) 従来、マニュアルやヘルプ、リリースノートなどで求められてきた、いわゆるテクニカル・ライティングで重視されていたのが<正しさ><簡潔さ><ロジカルさ>だとすれば、UXライティングとは、それに加えて<愛着を持ってもらえる>ことが重要な要素となっているようです。 求められる、<人間らしさ> でも、<愛着を持ってもらえる>こと、つまり<人間らしさ>とユーザビリティのバランスって、とてもむずかしいですよね。多くの情報をすばやく明確に伝えようとすると、どうしてもかしこまってしまい、堅苦しい物言いになってしまうけれど、かといってなれなれしい言い回しも、ちょっと唐突に感じるかもしれない。 そこで、社内のメンバーに集まってもらい、ワークショップスタイルで<ボイス>=ブランドの声と<トーン>を定義してみることにしました。 ボイスとトーン、ボイスチャート <ボイス>とは、ユーザー体験全体を通して感じられる、ブランドの理念を反映した言葉遣いのこと。 <トーン>とは、ユーザー体験の各部分における、言葉遣いの変化のこと。 たとえば、パートナーから電話がかかってくれば、それが自分のパートナーだということがその声自体でわかるし(ボイス)、声の調子でどんな内容なのかがわかる(トーン)、といったところでしょうか。 それを、みんなでキーワードを出し合って5つの要素を定義することで、「BASE」の<ボイス>と<トーン>の拠り所となる「ボイスチャート」ができあがる、という仕組みです。 Product Principle(プロダクトの理念)  →プロダクトがユーザーに提供したい体験を表した言葉。ボイスチャートの土台になる。言葉を通してProduct Principleをユーザーに届けることが、ボイスの目標 Concepts(コンセプト)  →とくに強調したいアイデアやトピック Vocabulary(用語)  →Product Principleを表す象徴的な言葉 Verbosity(言葉数)  →言葉の多さ。情報を正確に、明確に伝えるために言葉を多くすることが適切になる場面もあれば、逆に言葉を少なくすることが適した場面もある Grammar(文法)  →話し言葉を主体とするか、簡潔な表現を主体とするか かくして、こんなワークショップに 今回は、デザインチームをはじめ、ユーザーからのお問い合わせに対応するカスタマーサクセスチーム、ショップオーナーさんが使う管理画面のお知らせやメルマガ、SNS運用を担当するオーナーズサクセスチーム、プロダクトの企画や開発ディレクションを担うプロダクトマネジメント/ディレクターのメンバーをふくむ13名でワークショップを実施。 さまざまな立場から、Product Principle(プロダクトの理念)となるキーワードを軸にして、Concepts(コンセプト)、Vocabulary(用語)、Verbosity(言葉数)、Grammar(文法)がどうあるべきか、じつにバラエティに富んだ数多くの単語が付箋に記され、折り重なっていきました。 そして、1時間半におけるワークショップの参加メンバーのなかで、Principle(プロダクトの理念)として定義づけられたのは、次の3つのキーワードでした。 ◯Principle(プロダクトの理念) 個人をエンパワーメントする 誰でも使える 信頼感 そして、できあがった「BASE」プロダクト・ボイスチャート Principle(プロダクトの理念) 個人をエンパワーメントする 誰でも使える 信頼感 Concept(強調したいアイデア・トピック) プロダクト作りに集中できる 専門知識がいらない みんなが使っている Vovabulary(象徴的なことば) 寄り添う/力を与える/オーナーズファースト かんたん/シンプル/いつでもどこでも やさしい/あんしん Verbosity(ことばの多さ) 「誰でも使える」ようにするために、専門用語や不要な形容詞、副詞を使わず簡潔に伝える 「信頼感」を持ってもらうために、誤解が生まれないように明確に伝える Grammar(文体) 「信頼感」「誰でも使える」ようにするため、シンプルな表現を主体とする 「信頼感」「誰でも使える」ようにするため、シンプルな表現を主体とする ワークショップを通じて見えてきた、「BASE」自身の多様性 興味深かったのは、出てきたキーワードの数々が、かならずしも「BASE」のコーポレート・ミッション「Payment to the People, Power to the People.」に紐づく単語だけではなかったこと。もちろん、<世界のすべての人に、自分の力を自由に価値へと変えて生きていけるチャンスを。あたらしい決済で、あなたらしい経済を。>という宣言から派生するキーワードもたくさんありつつ、たとえば<Webデザインの民主化><コロナ禍の状況におけるセーフティネットになる>など、ミッションのその先をも見据えているような言葉が飛び交うたび、自分の狭い視野だけで「BASE」を見てしまっているのだなあ、と痛感。同じプロダクトと向き合っていても、立場もさることながら、個によってまったく違った風景が見えているのは、とても新鮮な発見でした。 じつは、個人的にはこれが今回のワークショップのなかでの最大の収穫かも、と思っています。というのも、まさに時代と同じように、自分たち自身にとっても「BASE」というプロダクトのとらえ方には多様性があり、だからこそ、時代に合わせてPrinciple(プロダクトの理念)もアップデートしていくだろうし、テキストコミュニケーションもつねに進化を続ける必要がある、ということを、あらためて意識させてもらえたからです。 これから そんなワークショップを経て、今まさに来年からすぐ運用に乗せられるように、2021年版の「用語リスト」と「運用ガイドライン」をアップデート中。もちろん、ここで定義された<個人をエンパワーメントする><誰でも使える><信頼感>という、Principle(プロダクトの理念)を表現するためのテキストの<ボイス>と<トーン>の在り方も、現時点での仮説でしかありません。2021年とはいわず、Day1で検証され更新されていくものとして、「BASE」プロダクト同様、日々進化させ続ける必要があります。 すべては、「BASE」というプラットフォームを使ってくださるショップオーナー様/お客様の体験を、さらに一つ上のステージへと導くために。2021年の「BASE」にも、ぜひご期待ください。
アバター
この記事はBASE Advent Calendar 2020の24日目の記事です。 https://devblog.thebase.in/advent-calendar-2020 先日、BASEのデザインチームでユーザビリティテストを企画し、実施しました。 デザインチーム内でユーザビリティテストを実施したのは今回が初めてで、最初はどんな方法で行うのか検討もつかなかったのですが、みんなで知恵を出し合って、PC1台とスマートフォン2つで本格的なユーザビリティテストを実施してみたので、紹介したいと思います! これからやってみる方へのTipsになれば幸いです。 また、もっと良い方法あるで!!という方はこっそり教えてください。 はじめに 2020年4Qから、デザインチーム内で細かいUIUXの改善を企画から実装まで行うDESIGN PROJECTが始まりました。 サービスが始まってから8年が経過し、今では新しいデザインシステムができつつある中、過去リリースされたページのいくつかには古いデザインが残ってしまっているのが現状。 このプロジェクトは、ユーザーが「BASE」に対して抱くイメージに一貫性を持たせるためにも、旧デザインのページの改善をインパクトの大きいページから順にやっていこうという試みです。 ユーザーへの影響を把握し、今問題となっている箇所をクリティカルに改善するために、 改善するページを決める そのページの離脱箇所や滞在時間などの定量調査 仮説検証のためのユーザビリティテスト デザイン 実装 という順番で1つのページの改善を行いました。 この記事では、上記のステップ3にあたる「ユーザビリティテスト」の方法について説明します。 初めてのテストということもあり、テスト当日はバタバタしてしまったりハプニングもありましたが、今後やっていく上で知見がたまったので皆さんに共有したいなと思います! ※ ユーザビリティテストとは何か?を説明する記事ではないのであしからず! ユーザビリティテストの対象(被験者) 今回は、「BASE」でショップを開設する手順に関する改善だったので、対象は「BASE」初心者。 いきなり実際のユーザーに依頼をするのは少しハードルが高かったので、ショップオーナーではなく、「BASE」をまだ触ったことがない中途入社の方たちに被験者となってもらいました。 また、「BASE」のユーザーはスマートフォンを利用することが多いことや、スマートフォンを使って開設するユーザーの方が、PCユーザーに比べて離脱率が高いことから、ユーザビリティテストは被験者が持っているスマートフォンで行いました。 ユーザーテストで観測したい項目 今回のユーザビリティテストをする上で、観測したかったのは以下の項目です。 初見でどんな内容を入力するのか、どこにどのくらい時間をかけたのかわかるように、被験者のSP画面 どの位置で迷ったか、どこをスルーしたのかなどがわかるように、被験者の手元 疑問点やその時の感情がわかるように、被験者の声 感情と操作を一致させるために、被験者の表情 それぞれを、このようにして観測しました。 最小で被験者・ヒアリングする人の2名で実施することができます。 事前に設定しておいたこと ユーザーの設定 今回の場合、被験者はBASE社員であり実際のショップオーナーと状況が少し異なっていたので、 商材やその他の設定はペルソナ決めの要領で事前に用意して、当日被験者に共有しました。 ユーザビリティテストのセットアップと実施 今回の方法では、ユーザビリティテストじたいのセットアップと、被験者のデバイスへのセットアップがあるところが少し大変なポイントです。 それぞれ工程を分けて説明します。 テストじたいのセットアップ Zoomを立ち上げる→メンバーと被験者にも呼びかけて入室してもらう 被験者以外のZoomの音声をミュートにする 被験者のセットアップを行う 自分のSPでもZoomに入室してビデオをオンにし、被験者の手元を撮影する Zoomの画面収録を開始する ※ 画面収録はホストしかできませんのでご注意を! ステップ6では、このようにSPをハンガーラックに括り付けることで被験者の手元を撮影しました。 Zoomのビデオではズームすることができないので、できるだけ近くにSPをセッティングする必要があります。 (Zoomでズームはできない。というのも今回得た知見です。寒) 被験者のセットアップ 今回は被験者がMacとiOSを使っている場合のテストの方法の紹介です。 PCとSPをLighteningケーブルで接続する QuickTime Playerを立ち上げ、「新規movie作成」 録画ボタンの横の矢印から自分のiPhoneを選択することで、自分のSP画面をPCに写す PCでZoomに入室し、ビデオをオン。QuickTime Playerを画面共有し、PCに共有しているSPの画面をテストメンバー全員に見せる ※ この時声を録音したいので、被験者のPCのZoomはマイクをオンにする 参考: https://minatokobe.com/wp/it-information/tips/post-31188.html 以上のステップを踏むと、このように 被験者のSP画面 被験者の手元 被験者の声 被験者の表情 全てを同時に見ることができます! テストメンバーは遠隔にいてもZoomでテストの様子を見ることができました! tips紹介 この手法でユーザビリティテストをする上で、以下のことを被験者にお願いしました! その時の感じた違和感や疑問を知りたいので、独り言を言いながら行ってもらう SPで確認できるメールアドレスを準備する(メアド認証があるからね!) SPの通知をOFFにする(ZoomでPJメンバーにスマホの画面が共有されるからね!) 今回はBASEメンバーにテストをお願いしたので、比較的スムーズに行えました! 結果と所感 よかったこと ユーザビリティテストの知見が少ない中で、企画から実行までMove Fastに行えました。 また、この記事ではユーザビリティテストの手法に重点を置いてブログを書きましたが、 実際には仮説検証の場としてのテストであることが大切だと改めて感じました。 DESIGN PROJECTではただ単に古いUIを新しくするだけでなく、「なぜ古いUIだと問題が生じているのか」「古いUIの中でもどこを改善するのが効果的なのか」「どのUIや文言がユーザーを混乱させているのか」について、事前に定量データから仮説立ててテストまで実施することで、これまで以上にユーザーファーストな改善を行えるのではないかという期待があります。 そして、大きな録画設備がなくても、身近にあるデバイスで本格的なユーザビリティテストができたことも知見につながりました。 リリースはもう少し先になりそうですが、ショップオーナーの皆様に新しいUIを触っていただけるのが楽しみです! 反省点 メモを取りながら質問するのが難しかったです、、!書記は質問者とは別に担当を決めると良いかもしれません! また、Android端末の場合はiOSとは別の方法で画面共有をするのですが、少し手間取りました。 Andoroid端末でのテストの様子はまた別の記事で書けたらと思います! まとめ いかがだったでしょうか? この記事では、特別な施設や機械がないチームでもユーザビリティテストをする方法を説明しました。 どなたかのお役に立てば嬉しいです! BASEのデザインチームは、今回のように改善企画、リサーチ、ユーザビリティテスト、デザイン、コーディングをチーム内で完結させるプロジェクトもあり、デザイナーの裁量が大きい会社だと感じています! アドベントカレンダーでは、デザインチームからは既に北村さんが「デザイン編集リニューアルまでの長い道のり」の記事を書いているので必読です。 https://devblog.thebase.in/entry/2020/12/14/130000 また、明日はPMDとデザインチームを兼任する藤井さんがテキストライティングの記事を公開予定。 BASEのデザインチームに興味を持ってくれた方はこちらの募集要項からエントリーしていただけると嬉しいです!! https://open.talentio.com/r/1/c/binc/homes/4380 それではみなさん、メリークリスマス🎄👋
アバター
私がGoのソースコードを読むときのTips この記事はBASE Advent Calendar 2020の23日目の記事です。 devblog.thebase.in BASE BANK 株式会社 Dev Division でSoftware Developer をしている清水( @budougumi0617 )です。 freeeさんのAdvent Calendarでも同様の話題がありましたが 1 、私も今回はソースコードリーディング(Go)について書かせていただきます。 なぜ読むのか ライブラリやツールのコードを読む 言語のフォーマルなコーディングを学ぶ コードリーディングをするときのTips IDEを使って読む godocと一緒に読む 関連記事と一緒に読む 動かしながら読む デバッグしながら読む みんなで一緒に読む 終わりに 参考リンク なぜ読むのか まずなぜコードリーディングをするのでしょうか。 Goに限らず業務で多用される言語やフレームワークはさまざまなリッチな機能を提供しています。 それらを利用すればサンプルコードを少し編集してつなぎ合わせるだけでも動くコードを実装できます。 しかし、問題に直面したりあるいはバグなのか自分の使い方が悪いのかわからない場合、コードの中身を理解している必要があります。 ライブラリやツールのコードを読む 我々のGoのプロダクトは標準ライブラリの他にサードパーティのOSSをいくつか組み合わせることでwebサービスを実現しています。 車輪の再発明はしなくてもその車輪がどのような作りになっているのかは把握しておくと不具合発生時に安心です。 同様に普段利用しているツールの挙動を把握するにもコードリーディングは有用です。 弊社では Terraform や ecspresso などのツールを利用しています。 上記ツール以外にもGoで書かれているツールが多いため、すこし不思議な挙動があってもコードで確認できます 2 。 言語のフォーマルなコーディングを学ぶ GoはGoで実装されているため、Goが読めればその内部実装を読めます。 標準パッケージのコードを読むことには次のようなメリットがあります。 Goチームが実装・レビューした命名規則やテストの書き方がわかる その他のコードと比較してあらゆる場面を想定された実装がされている 実装者と見ず知らずの第三者が使っても呼び出し方を間違えないような設計がされている 3 標準パッケージはGopher全員が利用するパッケージです。 多くの呼び出し状況に対応する内部実装とメソッドシグネチャは大いに設計の参考になるでしょう。 また、たとえば変数名などで迷ったときは標準パッケージをgrepすることで「Go way」を類推することもできます。 コードリーディングをするときのTips 「ではGitHubを開いてコードを読みましょう!!」…では難しいですね。コードリーディングを効率的に行なうために私が実践しているTipsをいくつか紹介します。 今回は私が直近で読んだ DNSリゾルバ を例にします。 具体的には「webサーバでリクエストを受け取るたびに http#Client オブジェクトを作って外部APIを叩く実装を書いてるけど、これってDNS Lookupとかどうなるんだっけ?」というようなことを調べていました。 // 該当エンドポイントにリクエストを受け取るたびに外部APIにHTTP通信するハンドラ func indexHandler(w http.ResponseWriter, r *http.Request) { // Prepare request for another service cli := &http.Client{} res, err := cli.Do(req) // Parse response } IDEを使って読む 元も子もないですが、GoLandやLSPを使って読むのが圧倒的に速いです。 特にGoの場合は明示的にインターフェイスを実装しないので、「このインターフェイスを満たす実装は?」ということを調べるときはIDEに頼ったほうがよいでしょう。 godocと一緒に読む まずは何にせよ仕様を確認します。 最近のGoは標準パッケージも含めてpkg.go.devを検索することで仕様を確認できます。 pkg.go.dev DNSリゾルバについて調べたところ、 net パッケージにセクションが設けられ仕様が記載されていました。 普段は net/http パッケージばかりみているのでまったく読んだことがありませんでした。 pkg.go.dev 関連記事と一緒に読む 仕様の他にStack Overflowやブログ記事に情報がないか検索してみます。 Goは「Go」なのですが、検索のときは素直に「golang」で検索します。 今回は「 golang dns resolver 」「 golang dns ttl 」などでググりました。 (これは暗黙知ですが)信頼できるGopherがDNSリゾルバについて記事を書いていたので参考にしてみます。 qiita.com http#Client 構造体に自前のDNSリゾルバを設定する方法がわかりました。デバッグコードを仕込んで自前実装を差し込んで検証してもよいかもしれません。 shogo82148.github.io Go1.7からnet/http/httptraceというパッケージが追加され、 名前解決やコネクション確立etcのタイミングにフックを仕込めるようになりました。 これを利用すれば各段階でどの程度時間がかかっているかが具体的に分かるはずです。 頑張って自前でフックを差し込んでもよいのですが、 deeeetさんのgo-httpstatという便利パッケージがあるので、 これをありがたく利用させていただきます。 go-httpstatを使うと時間計測を行うコードを簡単に差し込むことができます。 こちらを読むと、仕様でデバッグログのようなものを簡単に出力できることがわかりました。 pkg.go.dev github.com これを使ってDNSリゾルバの挙動を検証するコードを書いてみます。 動かしながら読む コードを読むだけより動かしたほうが圧倒的に理解度が上がるので、ミニマムな検証コードを書きます。 今回は「リクエストを受けるたびに http#Client オブジェクトしてリクエストを飛ばすwebサーバ」を実装しました。 // 表示スペースの都合上エラーハンドリングは省略 package main import ( "fmt" "io" "io/ioutil" "log" "net" "net/http" "time" "github.com/tcnksm/go-httpstat" ) func indexHandler(w http.ResponseWriter, r *http.Request) { var result httpstat.Result req, err := http.NewRequestWithContext( r.Context(), http.MethodGet, "https://budougumi0617.github.io" , nil , ) ctx := httpstat.WithHTTPStat(req.Context(), &result) cli := &http.Client{} req = req.WithContext(ctx) res, _ := cli.Do(req) _, _ := io.Copy(ioutil.Discard, res.Body) res.Body.Close() result.End(time.Now()) w.Header().Set( "Content-Type" , "text/plain" ) w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, "response code: %+v" , result) } func main() { srv := &http.Server{ Addr: ":8080" , Handler: http.HandlerFunc(indexHandler), } srv.ListenAndServe() } 普通の動作確認だったら実行がしやすいテストコードの形式でも良いと思います。 今回は「リクエストを受けるたびに(毎回別のgroutine上で)」という状況の挙動が知りたかったのでwebサーバの検証コードを書きました。 また、ネットワークという比較的OS層に近いロジックの動作を確認したかったため運用同様Linux上で動作させたくDockerfileも用意しました。 FROM golang:1.15.6-alpine3.12 as build-env ENV CGO_ENABLED 0 RUN apk add --no-cache git WORKDIR /debuggingTutorial/ ADD . /debuggingTutorial/ RUN go build -o /debuggingTutorial/srv . WORKDIR /go/src/ RUN go get github.com/go-delve/delve/cmd/dlv # 後述するデバッグ用の設定 FROM alpine:3.12 as debugger WORKDIR / COPY --from=build-env /debuggingTutorial/srv / COPY --from=build-env /go/bin/dlv / EXPOSE 8080 40000 CMD [ " /dlv ", " --listen=:40000 ", " --headless=true ", " --api-version=2 ", " exec ", " /srv " ] # デバッガのアタッチなしで起動させる設定 FROM alpine:3.12 as server COPY --from=build-env /debuggingTutorial/srv / EXPOSE 8080 CMD [ " /srv " ] Dockerで立ち上げたwebサーバへ2回リクエストを送ったときのログです。 $ docker build -t til/debug --target server . && docker run -d --rm -p 18080:8080 --name resolver til/debug $ curl localhost:18080/ response code: DNS lookup: 4 ms TCP connection: 21 ms TLS handshake: 89 ms Server processing: 10 ms Content transfer: 1 ms Name Lookup: 4 ms Connect: 25 ms Pre Transfer: 115 ms Start Transfer: 127 ms Total: 128 ms $ curl localhost:18080/ response code: DNS lookup: 0 ms TCP connection: 0 ms TLS handshake: 0 ms Server processing: 8 ms Content transfer: 1 ms Name Lookup: 0 ms Connect: 0 ms Pre Transfer: 0 ms Start Transfer: 8 ms Total: 9 ms 実際に動作させてみると、リクエストを受け取るたびに http.Client 構造体を新規に生成しているのに2回目のhttptraceではTCPコネクションとDNS Lookupが省略されています。 これは http.DefaultTransport 経由でコネクションが使い回されているからなのですが、正直実際に検証するまで自覚せずにつかっていました。 デバッグしながら読む 次のような処理が多いとただコードを読むだけではあまり理解が進みません。 クロージャが多く実動作でどんな関数(ロジック)が呼ばれているのかわからない inteface{} 型の変数やスライスに何が入るのかわからない こんなときはデバッガを利用して実際に動かしたときのメモリの状態を確認します。 Goの場合は delve というOSSを使うことでデバッグできます。 操作感覚はgdbに近いです。VS CodeやJetBrains IDEでデバッグをするときも裏でdelveが動いています。 github.com 詳細な操作方法は割愛しますが、デバッグモードで起動しておけば、Webサーバのプロセスや起動中のDocker上のプロセスのデバッグも可能です。 pleiades.io 前述の httptrace の知識より、DNSリゾルバの処理が走るときは DNSStart メソッドが呼ばれることを知っていたので、その周辺にブレークポイントを設置して動かしてみます。 私はGoLandを使ってデバッグしていますが、gdbなどに慣れていればターミナルからステップ実行など可能です。 実際にブレークポイントを使ってデバッグしているときの状態が次のスクショ画像です。 GoLandでデバッグ中 私はコードを読むだけでは http#Client.Do メソッドからうまく net#Resolver.lookupIPAddr メソッドまでまでたどり着けていませんでした。 しかし、デバッガで止めてスタックトレースを確認することどの関数やメソッドを経由して net#Resolver.lookupIPAddr メソッドが呼ばれているのかわかりました。 上記のTipsをオブジェクトのフィールド値を少し変更してから繰り返すことでコードの挙動を読み解いていきます。 みんなで一緒に読む コツや勘所がわかってくるとコードリーディングのスピードもどんどん速くなっていきます。 しかし、「コツや勘所がわかるためにはコードをたくさん読まないといけない」という鶏卵問題もあります。 それに対して、弊社ではBASEメンバーと合同で定期的にGoコードリーディングパーティを実施しています。 ひとりでは詰まってしまうようなこともみんなで見ていると解決の糸口がみつかったり、他のメンバーのエディタ捌きを盗むことができます。 お題にするコードはその時の参加者の気分で特に決まっていません。 業務でツールを使う前にどんな動きをするのか読みたい この前 terraform apply で失敗したときのエラーメッセージがどう出ているか確認したい Go1.15で追加された機能がどのように実装されているのか読んでみたい terraform-provider-awsにPRを作るのでテストケースを考えたい その時々の課題感でコードを読むので、自分では読もうと思っていなかったコードを読んだり、問題解決のきっかけになったりと毎回勉強になっています。 終わりに 今回の記事ではコードリーディングの重要さと私がコードを読むときによくやる方法をいくつか紹介させていただきました。 何かひとつでも参考になると幸いです。 最後に、今回はコードリーディングにフォーカスしましたが、BASE BANKの開発チームでは自分たちで開発したサービス・機能をグロース・サポートまで担当します。 2021年はコードだけでなくシステム開発ライフサイクル全般に積極的に関わっていきたいぞ!という方は @budougumi0617 までDMください。 open.talentio.com 明日はBASEデザインチームの河越さんです! 参考リンク https://pkg.go.dev https://github.com/go-delve/delve https://pleiades.io/help/go/attach-to-running-go-processes-with-debugger.html https://developers.freee.co.jp/entry/how-to-read-source-code-of-middleware ↩ むしろGoで書かれていることがツール選定理由のひとつになりえます。 ↩ strings.TrimRight 関数と strings.TrimPrefix 関数のような例もありますが。 ↩
アバター
この記事はBASE Advent Calendar 2020の22日目の記事です devblog.thebase.in どうもこんにちは、Web Frontend Groupの青木です 今回は、個人的にWeb開発を補助する目的でPuppeteerを使っていることがあるので、その話をします 前半では、普段どう使っているのか 後半では、ブラウザ操作を記録してコード生成してくれるRecoderについて紹介します そもそも、Puppeteerって? Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium. はい、Chrome、またはChromiumを、DevTools Protocolを使って制御できるNodeライブラリです(※1) どう使っているのか 主に、開発中や問い合わせの動作確認の際に、手動で繰り返し行うことに対して使っています たとえば 開発環境で商品をn件購入する 開発環境で商品をn件登録する(※2) 開発環境でショップを新規に開設する リグレッションが発生していないか変更に伴い繰り返し動作確認する などですね 上記操作を必要とする要件は内容によってさまざまですが、たとえば注文管理の開発をしていた際に、ページャーの動作確認がしたくて商品をn件購入する、みたいな使い方をしていました Chrome CanaryのRecorderを使ってコードを生成してみる 普段はChrome DevToolsから操作する要素のselectorをcopyしてコードを書いて...としていたのですが、Chrome CanaryにPuppeteerで操作するコードを生成するRecoderがついて話題に(※3, ※4)なったりとPuppeteer使う敷居が下がってくれそうだったので、そちらの紹介も兼ねて試します Recoderを有効化し、コード生成 大まかには次の手順でコード生成できます Chrome CanaryのDevTools > Setting Recorder > ExperimentsからReoderを有効化 DevToolsを再読み込み SourcesのタブからRecordingsを選択し、Add Recordingを実行 Recordを実行し、画面操作 画面操作を終えたらRecordを終了し、生成されたコードを取得 画面操作で生成した流れは、商品をカートに入れて、カートから削除したものになります 実行環境を用意し、ひとまず実行、修正 今回向けに実行環境(※5)を用意したので、そちらに生成されたコードの主要部分を貼り付けひとまず実行してみます import { launch } from 'puppeteer' import * as expect from 'expect' ;(async () => { const browser = await launch ( { headless: false , args: [ '--window-size=2000,1000' ] , devtools: true , } ) const page = await browser.newPage () await page.setViewport ( { width: 1440 , height: 900 } ) await page. goto( URL ); await page.click ( "aria/マルチミニポシェットバッグ ¥ 4,800 20%OFF" ); await page.submit ( "div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form" ); await page.click ( "aria/削除" ); await page.click ( "div#orderItems > div.innerContent > div" ); await browser. close (); } )() さっそく、実行以前にTypeScriptで次の指摘が入ったのでその部分を修正 Property 'submit' does not exist on type 'Page'. - await page.submit(selector); + await page.click(selector); 次に実行すると、一番最初のclickで失敗するのでDevToolsからselectorのコピーをして修正 - await page.click("aria/マルチミニポシェットバッグ ¥ 4,800 20%OFF"); + await page.click("#itemList > li:nth-child(1) > a > div > div > div > div.items-grid_infoContainer_c3a2778a"); 次に実行すると、 Error: No node found for selector となったので要素が出るまで待つ処理を追加 + await page.waitForSelector(selector); await page.click(selector); 上記のような修正をして、ひととおり動作を再現 生成したコード修正版 import { launch } from 'puppeteer' import * as expect from 'expect' ;(async () => { const browser = await launch ( { // headless: false, args: [ '--window-size=2000,1000' ] , // devtools: true, } ) const page = await browser.newPage () await page.setViewport ( { width: 1440 , height: 900 } ) await page. goto( URL ) await page.click ( '#itemList > li:nth-child(1) > a > div > div > div > div.items-grid_infoContainer_c3a2778a' ) await page.waitForSelector ( 'div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form' ) await page.click ( 'div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form' ) await page.waitForSelector ( 'aria/削除' ) await page.click ( 'aria/削除' ) await page.waitForSelector ( 'div#orderItems > div.innerContent > div' ) const innerText = await page. evaluate (() => ( document .querySelector ( 'div#orderItems > div.innerContent > div' ) as HTMLDivElement ) .innerText ) expect ( innerText ) .toBe ( 'ショッピングカートに商品は入っていません。' ) await browser. close () } )() expectを追記、headlessで動くようlaunchの引数オプション一部コメントアウト 気に入っているところ 繰り返し画面操作をする手間を少し楽できる 画面操作で作りたいデータを作るハードルを下げてくれる Recorderで生成したコード、下書き程度ではあるが結構嬉しい 課題と感じているところ Recorderで生成したコードは手入れする前提 コードの寿命が短い UIの変更やデプロイによって、要素の特定に使っているclass名のhash部分が変わったりするため 最後に 最近のrecorderを使ってみた結果、興味を持ってもらえそうだと記事にしてみました 課題となる性質を理解した上で、少しでもWebの画面操作の自動化に対する敷居が下がったら嬉しいなと思うところです 明日は、BASE BANK株式会社の清水さんです! お楽しみに! 参考 ※1 puppeteer/puppeteer: Headless Chrome Node.js API ※2 商品をまとめて登録したい場合には、ショップオーナー様は CSV商品管理 から行えます ※3 Automatically record puppeteer tests - Chrome DevTools - Dev Tips ※4 Puppeteer と ARIA Handler | Medium ※5 aokiken/baseinc-advent-calendar-2020
アバター
この記事は BASE Advent Calendar 2020 の21日目の記事です。 はじめに お久しぶりです。BASEビール部部長(兼Data Strategyチーム)のbokenekoです。 今年はほんと辛い1年でしたね。コロナで全くビール部の活動ができませんでした。 その反動で通販でクラフトビール買いまくって冷蔵庫が溢れました。定期便の利用は計画的に。 と、まあそんな私生活はおいておいて、今日はData Strategyチームでのリコメンドにおける取り組みについてお話しします。 BASEでは、ネットショップ作成サービス「BASE」で開設された130万のショップが集まる購入者向けのショッピングアプリ「BASE」を提供しています。アプリでは商品やショップのおすすめを表示していますが、ここに使われているリコメンドのアルゴリズムは実は複数アルゴリズムの組み合わせになっています。例えば協調フィルタリングやFactorization Machinesなどです。今回はそこにさらにGraph Neural Networkによるリコメンドを追加しようとしているというお話をしようと思います。 Graph Neural Networkとは Graph Neural Network(GNN)は2017年頃から話題がでてきた、Deep Learningでグラフ構造を扱う手法のことです。GNNでは大まかに以下のような問題を解くことができます。 Node classification/regression ノードの分類やノードが持つ値の推定 Link prediction 二つのノード間に特定のエッジが存在するかどうかを予測 Graph classification/regression グラフ自体の分類やグラフに関連する数値の推定 GNNのリコメンドへの応用 Link Prediciton Link Predictionはグラフ上のあるノードとノードの間にエッジが存在するか否かを推測する問題です。現在観測されているグラフ構造から、まだ観測されていないエッジが存在しているかどうかを推測します。 今回のリコメンドで言えばユーザーノードと商品ノードを購入エッジで繋いだグラフがあったとして、今はまだないあるユーザーと商品の間の購入エッジが存在しうるかどうか、つまりまだ買ってないけど買ってくれる可能性が高いかどうかを推測します。 ユーザー・商品購買グラフ グラフ構造 購入エッジの存在を予測するにあたって、利用したデータは以下の通りです ユーザーがどの商品を購入したか ユーザーがどの商品をお気に入りしたか ユーザーがどのショップをフォローしたか どのショップがどの商品を販売しているか これらの情報をユーザー・商品・ショップを繋ぐグラフ構造にします。 ユーザー・商品・ショップを繋ぐグラフ このグラフでbuyエッジがあるユーザーと商品の間にあるかどうかを予測するのが目的になります。 (ちなみにこうしたノード・エッジが複数種類あるグラフのことをheterogeneous graphと呼びます。) モデル 今回採用したモデルは R-GCN というモデルです。 R-GCNの構造(論文から借用) R-GCNはあるノードについて、そのノードに出入りしている各エッジ種ごとにGCNを行いそれをまとめるという方法でグラフを畳み込みます。これで計算されたユーザーと商品の特徴量に対して購買エッジが存在するかを分類問題として解きます。 GCNは通常のDNNでいうところのCNNのようなものをイメージしていただければよいです。 実装 処理の手順は以下のようになります。 各ノードに対してノード種毎にIDを振ってグラフを作成 グラフをR-GCNに通して各ノードの特徴量を計算 ユーザーと商品の間にあるリンクが存在するかを二値分類問題として解く 実装にあたっては pytorch と dgl を利用しました。 グラフ作成 グラフは双方向グラフとして作成しています。 import torch import dgl # buy c2i_buy = torch.tensor([(customer_node_id, item_node_id), ...]) # fav c2i_fav = torch.tensor([(customer_node_id, item_node_id), ...]) # follow c2s_follow = torch.tensor([(customer_node_id, shop_node_id), ...]) # sell s2i = torch.tensor([(shop_node_id, item_node_id), ...]) graph_data = { ( "customer" , "buy" , "item" ): (c2i_buy[:, 0 ], c2i_buy[:, 1 ]), ( "item" , "bought-by" , "customer" ): (c2i_buy[:, 1 ], c2i_buy[:, 0 ]), ( "customer" , "fav" , "item" ): (c2i_fav[:, 0 ], c2i_fav[:, 1 ]), ( "item" , "fav-by" , "customer" ): (c2i_fav[:, 1 ], c2i_fav[:, 0 ]), ( "customer" , "follow" , "shop" ): (c2s_follow[:, 0 ], c2s_follow[:, 1 ]), ( "shop" , "follow-by" , "customer" ): (c2s_follow[:, 1 ], c2s_follow[:, 0 ]), ( "customer" , "buy-from" , "shop" ): (c2s_buy[:, 0 ], c2s_buy[:, 1 ]), ( "shop" , "sell-to" , "customer" ): (c2s_buy[:, 1 ], c2s_buy[:, 0 ]), ( "shop" , "sell" , "item" ): (s2i[:, 0 ], s2i[:, 1 ]), ( "item" , "selled-by" , "shop" ): (s2i[:, 1 ], s2i[:, 0 ]), } g = dgl.heterograph(graph_data) training import torch model = RelationPredict(g, 64 , 16 ) opt = torch.optim.Adam(model.parameters(), lr= 0.01 ) model.train() for epoch in range ( 60 ): opt.zero_grad() # グラフから各ノードの特徴量を計算 embed = model(g) labels = torch.zeros( 10000 , dtype=torch.long) # グラフには取り込んでないが存在する購買エッジをpositive sampleとして利用 pos_s = [] pos_d = [] random.shuffle(c2i_buy_train) for c, i in c2i_buy_train[: 5000 ]: pos_s.append(c) pos_d.append(i) pos_s = torch.tensor(pos_s) pos_d = torch.tensor(pos_d) labels[: 5000 ] = 1 # ランダムに取り出したユーザー・商品の組をnegative sampleとして利用 # 全組み合わせのうち本当に存在してるエッジは無視できるほど少ないので問題はず neg_s = torch.randint(g.number_of_nodes( "customer" ), ( 5000 ,)) neg_d = torch.randint(g.number_of_nodes( "item" ), ( 5000 ,)) train_data = { "srcs" : torch.cat([pos_s, neg_s]), "dsts" : torch.cat([pos_d, neg_d]), "labels" : labels } loss = model.get_loss(embed, train_data) loss.backward() opt.step() モデル全体 import torch import torch.nn as nn import torch.nn.functional as F import dgl import dgl.nn as dglnn class RelGraphConvLayer (nn.Module): """ R-GCN layer """ def __init__ (self, in_feat, out_feat, rel_names, bias= True , activation= None , self_loop= False , dropout= 0.0 ): super (RelGraphConvLayer, self).__init__() self.in_feat = in_feat self.out_feat = out_feat self.rel_names = rel_names self.bias = bias self.activation = activation self.self_loop = self_loop self.conv = dglnn.HeteroGraphConv({ rel : dglnn.GraphConv(in_feat, out_feat, norm= 'right' , weight= False , bias= False ) for rel in rel_names }, aggregate= 'sum' ) self.weight = nn.ParameterDict() for rel_name in rel_names: weight = nn.Parameter(torch.Tensor(in_feat, out_feat)) nn.init.xavier_uniform_(weight, gain=nn.init.calculate_gain( 'relu' )) self.weight[rel_name] = weight # bias if bias: self.h_bias = nn.Parameter(torch.Tensor(out_feat)) nn.init.zeros_(self.h_bias) # weight for self loop if self.self_loop: self.loop_weight = nn.Parameter(torch.Tensor(in_feat, out_feat)) nn.init.xavier_uniform_(self.loop_weight, gain=nn.init.calculate_gain( 'relu' )) self.dropout = nn.Dropout(dropout) def forward (self, g, inputs): g = g.local_var() wdict = {} for rel_name in self.rel_names: wdict[rel_name] = { "weight" : self.weight[rel_name] } hs = self.conv(g, inputs, mod_kwargs=wdict) def _apply (ntype, h): if self.self_loop: h = h + torch.matmul(inputs[ntype], self.loop_weight) if self.bias: h = h + self.h_bias if self.activation: h = self.activation(h) return self.dropout(h) return {ntype : _apply(ntype, h) for ntype, h in hs.items()} class RelGraphEmbed (nn.Module): """ node embeding layer node_id -> node embeding """ def __init__ (self, g, embed_size): super (RelGraphEmbed, self).__init__() self.g = g self.embed_size = embed_size # create weight embeddings for each node for each relation self.embeds = nn.ParameterDict() for ntype in g.ntypes: embed = nn.Parameter(torch.Tensor(g.number_of_nodes(ntype), self.embed_size)) nn.init.xavier_uniform_(embed, gain=nn.init.calculate_gain( 'relu' )) self.embeds[ntype] = embed def forward (self): return self.embeds class RelationPredict (nn.Module): def __init__ (self, g, h_dim, out_dim, num_hidden_layers= 1 , dropout= 0.5 , use_self_loop= True ): super (RelationPredict, self).__init__() self.h_dim = h_dim self.out_dim = out_dim self.rel_names = list ( set (g.etypes)) self.rel_names.sort() self.num_hidden_layers = num_hidden_layers self.dropout = dropout self.use_self_loop = use_self_loop self.embed_layer = RelGraphEmbed(g, self.h_dim) self.layers = nn.ModuleList() # i2h self.layers.append(RelGraphConvLayer( self.h_dim, self.h_dim, self.rel_names, activation=F.relu, self_loop=self.use_self_loop, dropout=self.dropout)) # h2h for i in range (self.num_hidden_layers): self.layers.append(RelGraphConvLayer( self.h_dim, self.h_dim, self.rel_names, activation=F.relu, self_loop=self.use_self_loop, dropout=self.dropout)) # h2o self.layers.append(RelGraphConvLayer( self.h_dim, self.out_dim, self.rel_names, activation= None , self_loop=self.use_self_loop)) # score self.fc1 = nn.Linear(self.out_dim* 2 , self.out_dim) self.act1 = nn.ReLU() self.fc2 = nn.Linear(self.out_dim, 1 ) self.act2 = nn.Sigmoid() def calc_score (self, h, srcs, dsts, src_type= "customer" , dst_type= "item" ): s = h[src_type][srcs] d = h[dst_type][dsts] x = torch.cat([s, d], dim= 1 ) x = self.act1(self.fc1(x)) x = self.act2(self.fc2(x)).view(- 1 ) return x def forward (self, g): h = self.embed_layer() for layer in self.layers: h = layer(g, h) return h def get_loss (self, h, train_data): srcs = train_data[ "srcs" ] dsts = train_data[ "dsts" ] labels = train_data[ "labels" ] score = self.calc_score(h, srcs, dsts) predict_loss = F.binary_cross_entropy_with_logits(score, labels) return predict_loss 結果 直近90日分の売り上げ・お気に入り商品・フォローショップのデータを用いて、売り上げの80%をグラフに取り込み、残りの18%を教師データ、2%をテストデータとして試験したところ、テストデータでは以下のような結果になりました。 accuracy: 0.891 recall: 0.858 specificity: 0.925 precision: 0.920 もちろんここでの成績が直ちにユーザーの満足度につながるわけではないのですが、数値的にはかなり優秀なんではないでしょうか。 最後に このモデルは現在開発環境で試験中で、問題なければ来年頭にはアプリのリコメンドに組み込まれる予定です。 GNNはグラフ構造にならば何にでも適用できますので是非皆さんがお持ちのデータでも試していただけると。 明日は、フロントエンドチームの青木さんです。お楽しみに。 もしBASEで働くことに興味を持っていただけた方は、ぜひご連絡ください。 採用情報はこちらから 最後の最後に なんかre:Inventで Neptune ML とかいうの出てましたね。NeptuneでGNNできるんですね。もうちょっとさあ、早くだして欲しかったなぁ。
アバター
TDDで過去と戦った話 この記事はBASE Advent Calendar 2020 20日目の記事です。 devblog.thebase.in こんにちは。BASE BANK 株式会社 Dev Division にて、 Software Developer をしている永野( @glassmonekey )です。 今回は先日リリースした「BASE」上での売上情報をCSVでダウンロードできる売上データダウンロードAppの裏話的な内容となります。 タイトルにTDDとつけたものの、そこまでTDDの話は出てきませんのでご了承ください。 BASE( ᐛ )⛺️ 新しい機能「売上データダウンロード App」が誕生しました〜!👶🎉 売上にまつわるデータをCSVにてダウンロードすることが可能💡 またサービス手数料や、決済手数料についても確認することができます👍 利益の分析にも使うことができるので、ぜひ使ってね💴 https://t.co/WTM4bJlaei — BASE(ベイス) (@BASEec) 2020年11月18日 売上データダウンロードAppについて apps.thebase.in 最初に今回の実装した機能の説明をします。 使い方 のヘルプページに詳細は記載しておりますが、「BASE」上の売上やお金の動きをCSVでダウンロードできる機能です。 この機能の元々は、実験的に弊社のCSEチームが手動で明細を内部向けに出力していた機能でした。それを「BASE」の拡張機能としてリリースすることとなり開発の手はずとなりました。 CSEチームが普段どのような業務をしているかに関しては、弊社の小林( @sharakoba )が書いた 社内業務改善を行うCSEグループのご紹介 を御覧ください。 devblog.thebase.in 「BASE」は今年で8周年 少し話は変わりますが、先日弊社は今年で8周年を迎えました。ネットショップ作成サービス「BASE」は、BASE株式会社設立の1ヵ月ほど前にリリースされたので、サービスもリリースしてから8年の月日が経過しています。 【御礼】 BASE株式会社は創業8周年を迎えました https://t.co/UOuAWrES3G @binc_jp より — BASE, Inc. (@binc_jp) 2020年12月11日 私自身は入社して半年ではありますが、これだけ長く愛されるサービスに関わることができてエンジニアとして感無量だったりします。 ただ、8年も経過するとサービスの内部仕様は当然変化しますし、データもローンチ当初とは色々異なる面がでてきます。 今回作ろうとしていた機能は、データベースの内容を集計して出力する機能なだけではありました。 しかし、過去のデータを加味すると現在のアプリケーションコードでは読み取れないデータの動きなどを想定する必要が出てくることとなり、それなりに難易度の高い機能だったわけです。 過去と戦うアプローチについて では8年の過去と戦うにあたり事前に要件などをそろえることは容易でありませんでした。 今回役に立った観点は以下の3点でした。 生SQLを書く TDDを開発プロセスの軸とする 推測をせず計測をする 生SQLを書く 集計に必要なデータも複数テーブルに散らばっていたり、複雑な条件といった実情がありました。なので、処理的観点と可読性的観点で手続き的に書くことは現実的ではありませんでした。 そこで今回の実装においては、生SQLを書くことで実現させました。 また生SQLを使っていたおかげで、ソースコード上のSQLからGUIツールを使用して、実行計画の検証などが容易に行うことができた点は良かったと考えます。 別途SQLファイルをGit管理するなども検討はしましたがメンテナンス対象を増やしたくなく、SQLをソースコードの中のロジックとして一元管理することを選びました。結果として250行ほどのプロダクションコードで動く生SQLを生むことになってしまいました。 ただ、大規模なSQLのレビューをすることもしてもらうことも現実的ではありません。少なくとも私には難しい… そこで、SQL文自体をレビューすることは半ば諦めて、レビュー時にテストコードや実行計画をつかって共有することでレビューの意図を伝えやすくする工夫をしました。 TDDを開発プロセスの軸とする 今回実装しようとした機能は、基本的なコア機能は集計処理なので、人間が目検で確認することはほぼ意味をなしません。 開発の初期段階では自分たちでデータを作ってチェックしていたのですが、何が正しいか判断がつきずらくあまりワークしませんでした。 そこで、テストデータには過去のデータベースのデータや、CSEチームが作成してくれた先行プロダクトの結果を採用しました。 これにより、我々開発者の手元でフィードバックループを回せるようになったので、より確度の高い開発を効率よく進めることができました。 また、前述の通りプロダクションコードの大部分がSQLを締める実装だったので、レビューも容易ではありませんでした。 テストコードが結果的にどのようなケースを担保するかを明記することになるので、レビュイー側としてはテストコードを見ればよくなります。 どのような意図でSQLを変更したのかのコミュニケーションができたことは良かったです。 ただ、テストのバリエーションに関しては、いたずらに増やしすぎたところもあるので今回の反省として開発に活かしていきたいですね。 推測をせず計測をする また、データ量、バリエーションともに多い機能開発ではあったので事前に、「BASE」上のおおまかなデータ量の分布を算出してはいました。 そのため集計データ量の規模感やワーストケースは事前に把握をできてはいました。 今回の実装処理のボトルネックがデータベースレイヤーだったこともあるので、上記のSQLレイヤーの検証でその要点を押さえることができた点も大変意義のあるものでした。 これにより、我々が作ろうとしている処理の規模感とパフォーマンスを効率よく把握ができました。この点も手戻りの少なく開発できた要因でした。 現在はDBに記録された内容を元に、redashのダッシュボード上で可視化するようにしています。そこから継続的なパフォーマンス確認しています。 Redash上の計測 将来的には弊社で導入をすすめている New Relic に寄せていけたらと思っています。 まとめ 今回は「BASE」上のアプリケーション開発における裏話をさせていただきました。 推測するな計測しろを地でいく開発ができたので、一部予想外な出来事はあったものの大きなハプニングもなく乗り切ることができてよかったという思いです。 今回、TDDというアプローチを取ったことで、エンジニア個人だと観測出来ない問題を手元で観測できるようになりました。その恩恵で、開発期間の中のバグ修正時に、大きな手戻りを引き起こすこともなく効率よく開発を進めることが出来たので実践してみて良かったです。 さらに、今回は大規模SQLをメインに据える選択をしましたが、テストコードのおかげで挙動に対する一定の理解を助けるツールにもなってくれました。レビューなどのチームとのコミュニケーション時に大変助けられました。 リリース後に嬉しい反響もありまして、これを励みに開発者としてこれからも頑張っていきます。 @BASEec の売上データダウンロード機能追加キター!欲しかった機能に歓喜。 https://t.co/RmByzNDcO8 — センベイブラザーズの兄:(有)笠原製菓4代目 (@senbeibrothersA) 2020年11月18日 この機能欲しかったやつ! https://t.co/PXnPn28HJV — 道草 michikusa (@warannahito) 2020年11月19日 昔は「BASEでいい」だったのが最近は「BASEがいい」になってる https://t.co/69azfxhkqi — 陶 (@jamdxsp) 2020年11月19日 そのような開発を一緒にわいわいやっていく仲間を募集中です。 @glassmonekey へのDMなどでも気軽にご相談ください。 open.talentio.com 明日は BASE Data Strategyチームの氏原 ( @beerbierbear )さんによるGNNレコメンドについてです!! こうご期待!!
アバター
この記事はBASE Advent Calendar 2020 19日目の記事です。 devblog.thebase.in BASE株式会社 Product Dev Division エンジニアの田中( @tenkoma )です。 10月から、 ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 の社内読書会をオンラインミーティングでやっています。 開発チーム内で設計や実装パターンについて議論できる知識を増やす目的で始めましたが、オンライン開催で、進行難しそうだな、と思いました。最初の4回くらいは、うまく進められてないと感じていましたが、最近はツールの活用法を理解したり、参加者の皆さんに助けられたりして、進められるようになったと思います。 社内読書会の進行役として何をやっているかや、改善したところを紹介します。 開催の形式 週1回 火曜日16:00〜17:00(現状) Slack に読書会チャンネルを作り、話題に興味がある人に入ってもらう Zoom でオンラインMTG 参加者は10〜12名 読む範囲を予め決め、前半30分で読み、話したいこと・感想をGitHub Issueに書く 後半30分で議論 Zoom スクリーンショットは撮っていませんが、コメントを書き込める場所は、GitHub Project/Issueで管理しています。 GitHub Projectを使うと、今どこを進んでいるかが、参加してなくてもわかること、 GitHub Issueを使うと、後で議論を追いかけられること、実際の開発プロジェクトと相互リンクしやすいため使っています。 実際に使っているページを紹介します。 GitHub Project で、進行がわかるようにする 読書会中のコメントの様子 進行役として何をやっているか 書籍購入の稟議取りまとめ スケジュール調整 読書会をゆるく進行する 進行する時難しいと思った点と改善策 議論をどう始めたらいいかわからない 前半の読書タイムが終わった後、議論をどう始めるかが2,3回は難しかったです。 開催している読書会は複数のチームから参加者が集まっていて、今年2月にリモートワーク体制へ移行後に入社された方も複数いる中で 初対面の場合もありました。 予め議論したい話を僕が決めてみても、うまく話が広がらなかったりして、改善の必要があると感じていました。 改善策としては、読書タイムが終わった時点で、すでに GitHub Issue にコメントがたくさん書かれていたので、 それをZoomで映しながら議論したいところをみて行くようにしました。 そうすると、全員で同じ画面をみているので、他の方も話したいことを見つけられるようになります。 また、議論する内容の出発点が画面に映っているので、話の流れも掴みやすそうです。 話の内容が共有できているか把握しづらい オンラインだと、音質があまりよくなかったりして、話の内容を理解するのに集中しづらいこともあります。 話題の理解度が低いと議論が発展しずらそうだなと思いましたので、なるべく話す内容について聞き返すようにしています。 MTG中に生まれる発言のない間をどうするか オンラインだと、オフラインに比べて遅延があるのと、会話に割り込みしづらさがあり、発言のない間が生まれます。 そこに気まずさを感じて何か話さないとな、と思ってしまうのですが、特にネタを提供できるわけでもなかった場合が辛かったです。 改善策は、無いです!というより、発言がない微妙な間を許容する、というのが改善策と言えると思います。 GitHub Issueに書いていただいたコメントを読み返したり、本を読み返したりする時間にしましょう。 そうしていると、話したいことがある人が出てくることが何度もありました。 おわりに リモートワーク体制で、技術書の読書会をやってみて、難しいと思っている点と改善策を紹介しました。 独特の難しさはあると思いますが、ツールを利用したり、話す内容を丁寧に確認することで、読書会の効果を引き上げられるのではないかと思って取り組んでいます。 明日は永野さん( @glassmonekey )のTDDの話です!
アバター
この記事は BASE Advent Calendar 2020 の 18 日目の記事です。 こんにちは。BASE BANK 株式会社 Dev Division にて、 Software Developer をしている東口( @hgsgtk )です。 先月・先々月と連続で Terraform に関連したブログを投稿しているのですが 2020 年最終月も Terraform 話で締めさせていただきます ^1 。 TL;DR Terraform 0.14 が GA(General Availability)になった dependency lock file .terraform.lock が追加され、VSC 管理化に含めるかについてプロジェクトによって扱いの検討が必要 0.14.0 では、 ignore_changes = all を使用したリソース定義の扱いにバグがあり 0.14.1 で修正された 当該機能を利用している場合は 0.14.1 >= のリリースがおすすめ Terraform 0.14がGAになった 12 月 2 日 0.14.0リリースタグ が切られ、メジャーバージョン 0.14.x が GA(General Availability) となりました。 www.hashicorp.com Highlights となっている機能として紹介されているものは 3 つあります。一番体感としてわかりやすいのは Concise Diff という機能です。文字通り 差分表示が簡潔に なりました。 module.cwl.aws_cloudwatch_log_group.sample will be updated in-place ~ resource "aws_cloudwatch_log_group" "sample" { id = "/ecs/sample/sample" name = "/ecs/sample/sample" ~ retention_in_days = 0 -> 1 tags = {} # (1 unchanged attribute hidden) } Plan: 0 to add, 1 to change, 0 to destroy. # (1 unchanged attribute hidden) と表示されているように関係がなく変更がない記述に関しては差分表示にて省略され、見やすくなりました。 2つ目が、 機密性の高い Variable に対する CLI 表示での扱い についてです。 sensitive=true という設定によって CLI 表示をマスキングすることが出来るようになりました。次の公式の例がわかりやすいですが、 sensitive=true と設定した variable をリソース定義した場合、コンソール表示が (sensitive) と調整されるようになっています。 Terraform will perform the following actions: # some_resource.a will be created + resource "some_resource" "a" { + name = (sensitive) + address = (sensitive) } Plan: 1 to add, 0 to change, 0 to destroy. www.hashicorp.com 上記の説明にありますが、state ファイル内にはそのままの機密情報が記載されるようになっています。 Sensitive values are still recorded in the state, and so will be visible to anyone who is able to access the state data. もう 1 つの新機能が Provider Dependency Lockfile という機能です。こちらについては 0.14.x へアップグレードする際に扱う方法を検討する必要があります。 Provider Dependency Lockfile Terraform 0.14 では Provider の依存関係を terraform init 時点でバージョンロックする機能が導入されました。 www.hashicorp.com BASE BANK の開発では、 AWS Provider を Provider として利用しています。記事執筆時点(2020 年 12 月 9 日)の最新 3.19.0 の状態で terraform init すると次のような内容で .terraform.lock というファイルが作成されます。 # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { version = "3.19.0" constraints = "~> 3.19.0" hashes = [ "h1:FJwsuowaG5CIdZ0WQyFZH9r6kIJeRKts9+GcRsTz1+Y=", "h1:c/ntSXrDYM1mUir2KufijYebPcwKqS9CRGd3duDSGfY=", "h1:yre4Ph76g9H84MbuhZ2z5MuldjSA4FsrX6538O7PCcY=", "zh:04f0a50bb2ba92f3bea6f0a9e549ace5a4c13ef0cbb6975494cac0ef7d4acb43", "zh:2082e12548ebcdd6fd73580e83f626ed4ed13f8cdfd51205d8696ffe54f30734", "zh:246bcc449e9a92679fb30f3c0a77f05513886565e2dcc66b16c4486f51533064", "zh:24de3930625ac9014594d79bfa42d600eca65e9022b9668b54bfd0d924e21d14", "zh:2a22893a576ff6f268d9bf81cf4a56406f7ba79f77826f6df51ee787f6d2840a", "zh:2b27485e19c2aaa9f15f29c4cff46154a9720647610171e30fc6c18ddc42ec28", "zh:435f24ce1fb2b63f7f02aa3c84ac29c5757cd29ec4d297ed0618423387fe7bd4", "zh:7d99725923de5240ff8b34b5510569aa4ebdc0bdb27b7bac2aa911a8037a3893", "zh:7e3b5d0af3b7411dd9dc65ec9ab6caee8c191aee0fa7f20fc4f51716e67f50c0", "zh:da0af4552bef5a29b88f6a0718253f3bf71ce471c959816eb7602b0dadb469ca", ] } このファイルによって Provider のバージョンがロックされ例えば違うバージョンの Provider を利用した際にエラーとすることで、どの環境でも同一バージョンを保証できます。このファイルは基本的に .terraform.lock は手で変更することはせず、 terreform init -upgrade を用いて更新することが期待されています。 同一バージョンを保証するこの機能を有効活用するかは開発対象のリポジトリによりますが、有効活用する場合は Git 等の VCS の管理対象に含めることが推奨されています。 一方で、バージョン固定は不要だと判断した場合は、 .gitignore にファイルを記載することでこの挙動を無効にすることが可能と説明されています。 You can continue with a model similar to the v0.13 behavior after upgrading to v0.14 by placing .terraform.lock.hcl in your version control system's "ignore" file, such as .gitignore for Git. www.terraform.io 筆者の現場では、Git 管理対象に含めることを選択しました。Git 管理対象に含める場合の今後のアップグレード作業は次のようなパターンが考えられます。 バージョンを固定していない場合は、「単に最新に上げておきたい」といったモチベーションになるでしょう。 terraform init -upgrade をさっと実行することで作業完了です。 一方で、必要な機能が含まれるバージョンに固定したいといった理由で tf ファイルで必要なバージョンを定義する場合は、まず tf ファイル内の version を変更します。 required_providers { aws = { source = " hashicorp/aws " version = " 3.19.0 " # ここのバージョンを更新する } } その後、 terraform init -upgrade を実行することで期待した Provider のバージョンに変更できます。なにか最新バージョンでは意図しない差分が発生したといった場合にダウングレードしたいケースがありますが、そういった場合にもこのオペレーションを行ないます。 0.14.0 アップグレード時に発生したError このような目玉機能がある Terraform 0.14 ですが、筆者の現場で最初に 0.14.0 にあげた際に次のようなエラーが発生しました。 Error: Computed attribute cannot be set on ../../modules/sample-api/ecs.tf line 56, in resource "aws_ecs_task_definition" "sample-api": 56: resource "aws_ecs_task_definition" "sample-api" { Error: Computed attribute cannot be set on ../../modules/sample-api/ecs.tf line 56, in resource "aws_ecs_task_definition" "sample-api": 56: resource "aws_ecs_task_definition" "sample-api" { Error: Invalid or unknown key on ../../modules/sample-api/ecs.tf line 56, in resource "aws_ecs_task_definition" "sample-api": 56: resource "aws_ecs_task_definition" "sample-api" { 発生した箇所では次のように変更差分をすべて検知しない設定を入れているものでした。 lifecycle { ignore_changes = all } なにが影響したかを確認していくと changelog 内にあるこちらの機能リリースにおいて問題が発生したようでした。 github.com 当該機能に対する Issue はこちらのものです。具体的には ignore_changes = all と設定した場合に意図しない振る舞いになっていた という Issue です。 github.com それに対して機能を追加したコミッターの方が即座に issue に対して反応、バグ修正が実施されました。 github.com この修正が入っているバージョンが 0.14.1 になります(なお、この 3 時間後に新たに Bug Fixes が含まれた 0.14.2 がリリースされています)。 Terraform 0.14.2 への更新 done (ちょっとした話は社のアドベントカレンダーにでも書こう) pic.twitter.com/Zp8qcjbOFT — Kazuki Higashiguchi (@hgsgtk) December 9, 2020 具体的な 0.14 へのアップグレードについては、 Upgrading to Terraform v0.14 をご参照いただくことになりますが、端的な手順はこちらになります。 0.13.x の最新マイナーバージョンにまず上げておく terraform plan / apply で no changes であることを確認する terraform バージョンを 0.14 に変更する deprecatedとなっているProviderのバージョン指定方法 Provider のバージョンを指定する際に次のように version を指定する方法がありました。 provider " aws " { region = var.region version = " ~> 3.19.0 " } しかし、この記述では terraform plan 等をした場合に Warning が発生します。 Warning: Version constraints inside provider configuration blocks are deprecated on providers.tf line 8, in provider "aws": 8: version = "~> 3.19.0" この非推奨記述については Provider Configuration: version: An Older Way to Manage Provider Versions にて説明があります。 The version argument in provider configurations is deprecated . In Terraform 0.13 and later, version constraints should always be declared in the required_providers block . required_providers の記述を用いて Provider のバージョンを指定することで Warning は解消されます。 terraform { # (省略) required_providers { aws = { source = " hashicorp/aws " version = " ~> 3.19.0 " } } } ちなみに、terraform の内部実装としては次のような処理が加えられて Warning が出るようになっています。 if attr, exists := content.Attributes[ "version" ]; exists { diags = append (diags, &hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "Version constraints inside provider configuration blocks are deprecated" , Detail: "Terraform 0.13 and earlier allowed provider version constraints inside the provider configuration block, but that is now deprecated and will be removed in a future version of Terraform. To silence this warning, move the provider version constraint into the required_providers block." , Subject: attr.Expr.Range().Ptr(), }) var versionDiags hcl.Diagnostics provider.Version, versionDiags = decodeVersionConstraint(attr) diags = append (diags, versionDiags...) } https://github.com/hashicorp/terraform/blob/923e157b5ce4d8650ff0ade755b52aa23423b63c/configs/provider.go#L71-L81 version というアトリビュートがユーザーの tf ファイルに含まれる場合 Warning を出すという内容ですね。 github.com 2020 年 9 月 8 日にマージされ 0.14.0 のリリースの内容に含まれました。 The version argument inside provider configuration blocks has been documented as deprecated since Terraform 0.12. As of 0.14 it will now also generate an explicit deprecation warning. To avoid the warning, use provider requirements declarations instead. (#26135) おわりに Terraform 0.14.x 自体の目玉機能についておさらいし、当該バージョンへのアップグレードにて発生しうる Error/Warning への対応方法について紹介しました。 BASE BANK の開発チームでは特定レイヤーの技術に限定されることなく、自チームで開発したプロダクト・機能をリリースからグロース・サポートまで担う、フルサイクル開発者の考え方で日々業務に邁進しています。興味のある方はお気軽にカジュアルなお話をしましょう。 @hgsgtk 宛に DM 頂いても構いません。 open.talentio.com 明日は BASE 株式会社基盤チームの @tenkoma さんです。お楽しみに!
アバター
この記事はBASE Advent Calendar 2020の17日目の記事です。 devblog.thebase.in はじめまして。 BASEの情シスでマネージャーをやっている猪股です。 ITを活用した社員の利便性の向上とセキュリティを考慮した環境の整備をしています。 2017年1月からひとり情シスで業務を行っていましたが、今年はメンバーも増えチームとしてたくさんの業務を「Move Fast」にこなすことができた一年でした。 新型コロナウイルス感染症の拡大でBASEも一斉に「Work From Home」(以下WFH)へ移行しました。 WFH施策を主に2020年を振り返っていきたいと思います。 2020年 1月 新メンバーが1名入社し、2名体制でスタート 新たな試みとして情シス関連のお知らせを社内報として月1でお届け 2月 「WFH推奨」への移行対応 VPNアカウントの確保 セキュリティインシデント発生時の問い合わせフロー整備 オンライン面接で利用するZoomの使い方マニュアルの作成 通信帯域の監視 2月の社内報のTOPICS VPNとは フリーWiFiや野良WiFiのリスクとは 3月 新メンバーが1名入社し、3名体制に 「WFH推奨」期間延長に伴う対応 オンライン会議ツールをGoogleハングアウトからZoomに変更 全社定例をZoomで開催 社内にメンバーが複数いるときの会議のためにWeb会議用スピーカー内蔵マイク(Jabra SPEAEK 510)を会議室に設置 全社で日報の運用を行うためにSlackのbotサービスのGeekbotを活用 GeekbotからDMでメッセージが届くので、質問に1つ1つ回答 ボットと会話しているかのような感じで回答できます すべての回答が終わると、回答を共有するチャンネルに通知されます GeekbotからのDM 回答を共有するSlackチャンネル 「WFH推奨」から「原則WFH」への移行対応 会社にいなくてもPCで通常の電話のように保留・内線・取り次ぎ可能なCallConnectの導入 3月の社内報のTOPICS ファイアウォールとは WiFiルーターの紹介 4月 「WFH推奨」から「原則WFH」への移行対応 社内で利用しているディスプレイ等の周辺機器を希望者に配送 VPNが接続できない・自宅にネットワーク環境がない人のためにポケットWi-Fiの貸出 4月の社内報のTOPICS インターネット回線の紹介 5月 フルオンライン入社対応 全社定例後の懇親会「締め会」および毎週水曜実施の「みんなの食堂」の代わりに、バーチャルオフィスのように同じテーブルにいる人に話しかけられるツールのRemoをお試し導入 オンラインでセミナーや採用候補者向けのmeetupを開催するためにZoomの ウェビナーを導入 5月の社内報のTOPICS WFHツール(疲労軽減編) 6月 YAMAHA ルーターとMeraki ルーター間をVPNで接続 「原則WFH」から「WFH推奨」への移行対応 「オフィスの人口密度はどれくらいか」「オフィスに誰が行くか」というのをざっくり可視化するために、Pollyを使って翌営業日出社するかのアンケートをSlackに配信 7月 社外とのファイル受け渡し方法の一つとしてDropboxを導入 情報を取得しやすいようにSlackチャンネル名のPrefixを整理 8月~9月 IT全般統制及びIT業務処理統制の整備評価を実施 10月 クラウドサービスの利用フローを作成 オンラインメンバーとの距離感を縮め、Web会議をスマートに行えるよう360°カメラ、マイク、スピーカー一体型を検証 (来年1月から利用開始予定) 12月 ユーザー認証における社内・社外の利便性の向上と安全なクラウドサービス利用に向けたセキュリティレベルの向上を実現させるためOneloginを導入 情報セキュリティ研修実施中 IT全般統制及びIT業務処理統制の運用評価を実施中 全社定例バージョンアップに向けATEM Mini Proと配信ソフトOBSを用いてマルチ映像配信を検証 全社員がWFHに!?その時情シスは… 急なWFH移行によって発生した様々な作業やトラブル、その対処法について振り返ります。 2月17日 新型コロナウイルス感染症拡大の影響で「WFH推奨」を発動する可能性が出てきており、対応の相談がきました。 前提として、BASEではWFHを実施していませんでしたので、WFHを想定したインフラ整備は整っていませんでした。 しかし、BASEの社内システムは完全クラウド化(個人情報を取り扱うなど重要なクラウドサービスへのアクセスはIP制限を設けている)されていて、 また、社員に貸与しているPCはノート型であり、比較的WFHへの移行はしやすい環境でした。 初動として想定される対応の洗い出しを行ったところ YAMAHAルーターのVPN対地数(最大設定可能数)は100のため、60アカウント分足りないことが分かりました。 2月18日 20日からWFHを開始したいと連絡がきました。 不足しているVPNアカウントを用意するため、バックアップ回線とCisco MerakiのWebinar受講でゲットしたルーターをテストし、この環境を利用することに決めました。 (Cisco MerakiのWebinarを受講すると3年間のライセンス付きで機器をもらえるのでお勧めです!) YAMAHAルーターに登録済みのVPNアカウントは60個でしたので、40個新規に登録し、残り60個をMerakiルーターに登録しました。 2月19日 PM16:00 WFHのガイドラインが全社にアナウンスされました。 PCへのVPN設定が完了次第、WFHへの移行が認められたため、VPN設定待ちの行列が・・・ 同じSectionのCSEメンバー2名にもご協力いただき4名でPC側のVPN設定作業をこなし続けました。 VPN設定待ちの列 VPNに同時にアクセス可能な社員数は全社員の1割~2割程度を想定し設計していましたので、VPNはIP制限を設けているサービスを利用する際にのみ接続してもらい、利用が終わったら切断してもらうようアナウンスしました。 2月20日以降 通信帯域の監視とVPN接続できない等の問い合わせ対応が続きました。 みんなの協力もあり、同時接続数は30台程度を推移していたため、帯域を使い切ってしまうこともなく何とかWFHへの移行が出来ました。 VPN接続が出来なかった主な原因は以下でした。 自宅のルーターでVPNパススルー機能が無効になっていた 自宅のネットワークセグメントとVPN接続先のネットワークセグメントが同一だった 我々VPN設定作業者の設定ミス 3月6日 Zoomを利用した最初の全社定例は、ごたつきました。 Proプランを契約していて、最大接続数が100名だったため、ホストを2台用意し、好きな方どちらかに接続してもらうようにしましたが、伝わりづらかったし、安定もしなかったため、30分全社定例開始時間を遅らせてもらい、Businessプランに変更しホスト1台で対応しました。 YAMAHAルーターとMerakiルーター間をVPNで接続 QAなどを担うProcess Engineeringチームにより社内にOpen STF環境が構築されました。 しかし、3月の作業ではMerakiルーターは社内ネットワークに接続させていなかったので、MerakiにVPNアカウントがあるメンバーが、Open STF環境に接続できない問題が発生しました。 YAMAHAルーターとMerakiルーター間をVPNで接続させる設定を紹介します。 ネットワーク概要 YAMAHAルーター側 項目 設定値 接続種別の選択 IPsec ネットワーク環境 自分側と接続先の両方とも固定のグローバルアドレスまたはネットボランチDNSホスト名を持っている 接続先の情報 > 接続先のホスト名またはIPアドレス Merakiルーターの固定IPアドレス 接続先と合わせる設定 > 認証鍵 Merakiルーターとの事前共有鍵 接続先と合わせる設定 > 認証アルゴリズム 特定の認証方式 接続先とわせる設定 > 暗号アルゴリズム 特定の暗号化方式 接続先のLAN側のアドレス MerakiルーターのLAN側ネットワークアドレス Merakiルーター側 項目 設定値 タイプ ハブ(メッシュ) VPN設定 > ローカルネットワーク > サブネット Merakiルーターのネットワークアドレス VPN設定 > ローカルネットワーク > VPNへの参加 VPN ON VPN設定 > NATトラバーサル 自動 オーガナイゼーション全体の設定 > Meraki以外のVPNピア > パブリックIP YAMAHAルーターの固定IPアドレス オーガナイゼーション全体の設定 > Meraki以外のVPNピア > プライベートサブネット YAMAHAルーターのLAN側ネットワークアドレス オーガナイゼーション全体の設定 > Meraki以外のVPNピア > 事前共有シークレット YAMAHAルーターとの事前共有鍵 IPsecポリシー 項目 設定値 フェーズ1 > 暗号 特定の暗号化方式 フェーズ1 > 認証 特定の認証方式 Diffie-Hellmanグループ 特定のグループ Lifetime(seconds) 任意のLifetime期間 フェーズ2 > 暗号 特定の暗号化方式 フェーズ2 > 認証 特定の認証方式 PFSグループ オフ Lifetime(seconds) 任意のLifetime期間 2021年 2021年もITを活用した社員の利便性の向上とセキュリティを考慮した環境の整備をしていく予定です。 新しい働き方をより安全かつ効率的に実現するために、リモートアクセスのスケールアップ、モバイル端末を管理するMDMを導入したいです。 いつでもどこでも信頼できる環境ではないという前提に立ち、徹底したエンドポイントの保護に努めたいと思っています。 また、持ち帰らないことを前提として選定していたコーポレートやCS部門のPC軽量化も行っていきたいです。 総括 BASEの社内インフラ環境は決して十分とは言えません。 バックアップ回線や検証用のルーターがなければどうなっていたか、と考えると本当にぞっとします。 全社員がWFHするための環境整備は、情シスにとってまさに緊急事態で、自分一人だけだったとしたら速やかに進めることができなかったでしょう。 WFH初動段階でチーム化できたこと。実はこれが一番のファインプレーだったのではと個人的には思っています。 明日は BASE BANK 株式会社の東口さん( @hgsgtk )です!お楽しみに!
アバター
こんにちは!2020 年ももう少しで終わりですね。さて、この度は、12/12(土)にオンラインで開催された PHP Conference Japan 2020 にて、4 名のメンバーが登壇しました。また、プラチナスポンサーとして協賛いたしました。今回は、スピーカーとして参加した川口(@dmnlk)、イアン(@brison_ian)、東口(@hgsgtk)、永野(@glassmonekey)の 4 名から参加レポートをお届けします! PHP Conference Japan 2020とは 2020/12/12 (土) に PHP Conference Japan 2020 が開催されました。BASE はこれまでにも開催されている PHP カンファレンスへの登壇並びにスポンサードをコミュニティ貢献活動として行って参りました。当カンファレンスではプラチナスポンサーとして当カンファレンスに協賛しています。 https://phpcon.php.gr.jp/2020/ 当日は、オンライン参加している人たちで専用の slack チャンネルを作成してわいわい盛り上がっておりました。 専用のslackチャンネルでの盛り上がりの様子 一日中カンファレンスに参加しておりましたが、スピーカーの方々の発表は非常に参考になるものが多く、当 slack チャンネルは発表内容についての感想戦が度々起こっていました。 スピーカーより参加レポート スピーカーとして参加した川口(@dmnlk)、イアン(@brison_ian)、東口(@hgsgtk)、永野(@glassmonekey)の 4 名より、登壇内容の資料やカンファレンス参加した際の感想をそれぞれお届けします。 川口(@dmnlk) 今回はスポンサードセッションの場を頂いたので「NewRelic プラットフォームを使ったオブザーバビリティ入門」というセッションをさせていただきました。 PHP らしさのない発表だったのと BASE での取り組みがまだまだということもあり消化不良感のある発表にはなってしまいましたが、これからの可観測性について考えていきたいチームの皆様の一助になれば幸いです。 久々の大きいカンファレンスだったのでとても楽しかったです。 来年はオフラインで出来るといいなと楽しみにしています。 イアン(@brison_ian) こんにちは!11 月に入社したばかりのイアンです! 今年の PHP カンファレンスは、一般参加者としてのみ参加予定だったのですが、ご縁があってパネルセッションの若者役としての参加の打診をいただき、メインの田中ひさてるさんや司会の小山哲志さんと楽しくお話しできました。 当日は自身も田中ひさてるさんの言葉に終始聞き入ってしまい、言葉数はそう多くはなかったのですが、少しでも場を盛り上げることができていたのであれば幸いです。 また、今年のカンファレンスも様々な方の発表から新しい発見が得られて、とっても楽しいカンファレンスでした。来年以降も継続的に参加しつつ、なんらかの形で PHP コミュニティに還元できたらと思っています。 関わった皆さん、今年もありがとうございました! 東口(@hgsgtk) 15:20 から 25 分間 Track5 (PHP8 Special)でお時間をいただき発表いたしました。 当発表では、PHP8 の新機能について PHP 内部実装のテストスクリプトである phpt を読んでいく体験を発表しました。phpt を読むことで、ドキュメントを見るだけでは分からない仕様や内部実装の知識が得られる点を説明しています。 同じ時間帯に魅力的なトークが並ぶ中聴講頂いた方々ありがとうございました。少しマニアックでコアな内容を発表しましたが、聴講いただいた方々の感想を見る限りしっかりマニアックな魅力を伝えることができたようで安心しました。 テストphptから読むやつ聞いてて楽しかったなぁ。 若き頃の好奇心が呼び起こされた気がする。気がする。 — じん (@jin_penguin_616) December 12, 2020 #phpcon 東口さんのセッション良かった。そういえば phpt をメインにしたセッションってあまり無かったですね。phpt は、php-src を見る人は知っていても、まとまった資料も少ないですし、気づいていない人も多いと思うので、PHP の挙動を手軽に知りたい人にはオススメです。 https://t.co/8t3Uq7MtAC — Masashi Shinbara (@shin1x1) December 15, 2020 なお、発表中は YouTube のチャット欄・Discord・Twitter の TL をモニターに表示して、モニター上の反応をちらちら同時に見ておりました。オンライン登壇ではどうしても発表中の反応は見えにくいですが、それらを同時にモニターで開いておくと安心です(コメント等で感想を書いていただいていた方々ありがとうございました!)。 ちなみに他のセッションですが、わたしは基本的にずっと Track5 (PHP8 Special)にいました。 どの発表もコアな内容がたくさん触れられていて非常に興味深かったです。JIT についての仕組みやパフォーマンスを出すための現在の使い所・match 式を活用した JSON パーサー実装や、PHP8 の言語機能からみたフレームワークの未来感の評価、これまでのコミッターたちの歴史的経緯を加味した web に限らないユースケースの未来、その他たくさんの発表がありましたがとても刺激的な内容でした。 永野(@glassmonekey) はじめまして、永野です。 新卒で社会人して以降、PHP を業務で書いてもう約 5 年目です。 今回 PHP コミュニティでの発表を初めてさせていただいて、やっとコミュニティへ参加できた思いがあり感無量です。 PHP カンファレンスには度々参加させていただいておりました。 今回の発表の情報を仕入れた経緯としては、元々は入社前に私が執筆した 2019年のGithubActionのアドベントカレンダー のネタ探しに PHP 版 npm audit コマンドを探していたところ、 issue にて紹介されていたことがきっかけでした。 皆様の Twitter などの感想を拝見していると、このとき知り得た知見を再利用できたのは良かったと実感しております。 改めて、運営の皆様、参加された皆様お疲れ様でした!!大変楽しい時間でした。 謝辞 一日中とても刺激的な時間を過ごさせていただきました。今回は昨今の事情によりカンファレンス運営委員会の方々としても初のオンライン開催となりました。しかし、参加者として不自由なく一日楽しむことができ、スタッフの方々の入念な準備には感謝しきれません。 スピーカー向けのリハーサル期間を数週間用意されていたりと、スタッフの方々には業務でお忙しいにも関わらず、多くの時間をカンファレンス準備に注いでいただいたかと思います。この場を借りて御礼申し上げます。 弊社としても協賛・社員のスピーカー参加を通して PHP コミュニティの盛り上がりの一助になれ大変有意義な時間となりました。運営の皆様・参加者の皆様、誠にありがとうございました。 そしてPHP Conference Japan 2021へ 来年の 2021 年は 10 月 2 日・3 日に開催されます。いまからカレンダーを抑えておきましょう! @phperkaigi @phpcondo @phpcon_sendai @phpcon_nagoya @phpcon_kansai @phpcon_fukuoka @phpcon_okinawa @laraveljpcon はじめまして、PHPカンファレンスと申します。 来年のPHPカンファレンス2021は10月2,3日に開催予定です。 どうかお見知りおきください。 #phpcon #phpcon2021 — PHPカンファレンス2020 (@phpcon) December 15, 2020 直近はPHPerKaigi 2021 PHPer にとって一番近いカンファレンスは PHPerKaigi 2021 になります。3 月 25 日〜27 日の 3 days です。 phperkaigi.jp 現在、スポンサー・プロポーザル絶賛募集中だそうです。ぜひ応募してみてはいかがでしょうか?
アバター