TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

579

こんにちは。BASE BANK 株式会社 Dev Division にて、Engineering Manager をしている東口( @hgsgtk )です。 BASE では @budougumi0617 さんが主催となって Go のコードリーディング会を行っています。昨年は『 私がGoのソースコードを読むときのTips 』にてその様子を少し紹介いただいていました。 その後も隔週の毎月 2 回ペースでコードリーディング会を継続しています。2020 年 12 月以降は前述したブログをきっかけに興味を持っていただいた @dice_zu さんや po3rin さんにもゲスト参加いただき、ゆるゆると継続しています。 当エントリではその中で話題に上がった Go 1.16 の機能についてご紹介いたします。 TL;DR 構造体フィールドタグ json オブジェクト名の中でセミコロン( ; )が許可されました たとえば json:"hoge;hoge" のように ; が JSON オブジェクト名 に含まれる場合も json.Marshal / json.Unmarshal 可能となりました JSONの仕様を定義した RFC7159 によるとセミコロンは文字種として valid であるため 社内で開催中のコードリーディング会はゲスト大歓迎です。興味がある方は身近な社員の方か @hgsgtk にご相談ください Go 1.16 の Minor changes Go 1.16 の Release Notes にて 1.15 からの変更点について見ることが出来ます。 golang.org 私は毎回 Core library の Minor changes to the library を眺めて気になる PR のコードを読んでみるのが好きです。 その中で、コードリーディング会までネタにあげさせていただいたのが encoding/json パッケージの変更点です。 Go 1.16 における Minor changes https://golang.org/doc/go1.16#encoding/json The json struct field tags understood by Marshal, Unmarshal, and related functionality now permit semicolon characters within a JSON object name for a Go struct field. どういった内容なのか、当日参加者で 15 分程度読んだ内容を皆様にご共有いたします。 encoding/json パッケージの Minor changes 構造体フィールドタグ json オブジェクト名の中でセミコロン( ; )が許可されました。具体的には次のようなサンプルコードです。 package main import ( "encoding/json" "fmt" ) func main() { encoded := [] byte ( `{";": "World!"}` ) // セミコロンがKeyのJSON Object type MyObject struct { Hello string `json:";"` } var decoded MyObject if err := json.Unmarshal(encoded, &decoded); err != nil { fmt.Println(err) return } fmt.Printf( "%+v" , decoded) // Output: {Hello:World!} } https://play.golang.org/p/3HUq4N66AK5 変更の背景 「セミコロンが JSON オブジェクト の Key にはいっているものを扱うユースケースがあまり想像つかないね」という話をコードリーディング会ではしていました。どういった背景でこの変更が行われたのでしょうか。起点となった Issue を見てみましょう。 issue: encoding/json: does not recognise semicolon as a valid field name https://github.com/golang/go/issues/39189 この変更の背景根拠となったのは JSON 仕様を規定している RFC7159 のようです。 So pretty much we check JSON spec (RFC-7159) for validity on our "bug" and it seems to us that the spec would treat a semicolon as a normal character. Go内部実装における変更点 ここで行われた内部実装の変更は次の CL から確認できます。 https://go-review.googlesource.com/c/go/+/234818 src/encoding/json/encode.go に入った修正  isValidTag というメソッドにて tag の正当性を検証するのですが、その内部での文字種ルールに ; を追加しています。 strings.ContainsRune( "!#$%&()*+-./:;<=>?@[]^_{|}~ " , c): 余談 Go 2structured tag という提案 Go コードリーディング会の雑談のなかで Go2 のプロポーザルで structured tags というものが提案されていることを知りました。 github.com 現在の構造タグのフォーマットは文字列リテラルですが、この Issue の提案およびディスカッションの結果、具体例として次のようなコードを記述するような機能提案となっています。 package mypackage import json import sqlx type MyStruct struct { // [] 内にstruct定義する Value string [json.Rules{Name: "value" }, sqlx.Name( "value" )] PrivateKey [] byte [json.Rules{Ignore: true }] } 個人的には、文字列リテラルで記述する方法と比較した際に、記述ミス時に気が付きやすい点が嬉しいと思いました。実行時エラーや無視されてしまうのではなくユーザーがスペルミスした際にコンパイルエラーになります。 Issue 内での会話は 2020 年 1 月ごろに落ち着いていますが、「そういうアイデアがあるんだな」と頭の片隅に入れておくといいかもしれません。 おわりに かんたんにですが、encoding/json の minor changes について紹介しました。Go の内部コードリーディングのはじめの一歩として身近な標準ライブラリのちょっとした変更を追ってみると、その周辺コードもちょっとしれたりしておすすめです。 社内で開催中のコードリーディング会はゲスト大歓迎です。興味がある方は身近な社員か @hgsgtk にご相談ください。 こちらも余談ですが、BASE BANK 株式会社は絶賛採用募集中です!もし、「ちょっと興味あるな〜」とおもったら、Go 1.16 の話とか現場での Go 開発についてワイワイはなしましょう! open.talentio.com
アバター
はじめに CTOの川口 ( id:dmnlk ) です。 BASEは現在140万ショップを超え、サービスの安定性・信頼性を維持することは非常に重要になっています。 その中で去年、New Relicを本格的に導入しました。 https://newrelic.com/jp/press-release/20201217 しかしNew Relicを導入しただけで安定性が獲得できるわけではありません。得られるのは可観測性のための武器であり、その使い方を適切に学ばなければ無駄になってしまいます。 今回、New Relic社のご提案もあり社内メンバー向けにオンボーディング会を開いていただけることになりました。 オンボーディングを行うことでまずNew Relic Oneのオブザーバビリティプラットフォーム で何が出来るのか、どのように使うことでBASEシステムの可観測性を得ていくことが出来るのかを開発チームが体系的に学べることを狙うこととしました。 オンボーディングに参加して SREチームのngswです。 当日の参加者は最終的には50名近くとなりました。プロダクトに直接関わるフロントエンド、サーバサイド開発陣のほかにCSE( Corporate Solutions Engineering )チームも参加する形で、大変にぎやかな会になりました。 14:00〜17:00という長丁場の中で、個人的に特に盛り上がったなと感じたところである以下を中心に当日の雰囲気をお伝えできればと思います。 「New Relicに期待することはなにか」 New Relic でできることについて NRQL / Query builder について 「New Relicに期待することはなんですか?」 期待に胸を躍らせる面々 オンボーディング開始の直前まで、Slack上ではNew Relic社のSenior Solutions Consultant 1 の清水毅様と調整を行っておりました。 清水さんにはBASEを強くサポートいただいており、 その中でご提案いただいたのが「各開発チームの代表者を1名ずつ決めて、それぞれに『New Relicに期待すること』をそれぞれ教えていただきたい」というものでした。 当日発表内容は以下になりました。 所属チーム New Relicまたは今日のオンボーディングで期待すること ペイメント New Relic Browserを組込中。 ユーザがどのような形で <BASE> を利用しているか、解明していきたいと考えていて、 他の機能もさわっていきたい CSE 内部統制を効率的かつ有効に進めていけそうな機能を、このオンボーディングの中で見つけていきたい ショップ1 (New Relicを用いることで)開発側でも監視/モニタリングを積極的に行える土壌ができることを期待している フロントエンド1 パフォーマンス改善の足がかりとしてNew Relicを活用していきたい 安定運用を自チームを含めた全開発陣でやっていくことを ショップ2 リリースした機能/APIが期待通りのパフォーマンスがでているか、ある時点から劣化していないかということを追跡したい フロントエンド2 JavaScriptのエラートラッキングや、追いきれていないパフォーマンスのボトルネックの発見を行うためのツールとして期待しており、このオンボーディングで学んでいきたい 基盤 今、アプリから呼び出されるAPIのパフォーマンス改善を行っているところで、ログ追跡がより便利に、かつ簡便になることを期待している SRE インフラ側(たとえばSRE)と開発陣での共通の話題が、たとえばNew Relicのダッシュボードを中心になっていくようなことを期待している ネイティブアプリケーション モバイルアプリから呼んでいるAPIの負荷/ボトルネックなどの、問題の発見と追跡を行いたい BASE BANK社開発陣 マイクロサービス化していく中で、3〜4サービスまたいでログを確認しなければいけないなど、運用に苦しんでいるところがある SLOなどの設定を考えていく中で、そもそものメトリクスをどうとるか(そして投げ入れるか)などのヒントをこのオンボーディングで学んでいきたい DS New Relicをチーム内でどの様に活用できていけるのか、その下地になる知識を仕入れていきたい よかったなと感じたのは、CSEチームの「内部統制に活用したい」というものでした。 この発想を自身は持ち得なかったので、共有いただけたのは個人的な収穫のひとつでありました。 もうひとつ感じたことは「開発陣はパフォーマンスに関心がないわけではなくて、関心はあるのだけれど、追跡に必要な情報へアクセスしづらいがために、結果的に後回しになってしまっている現状がありそう」というものです。これはわれわれSREチームの至っていない点であるなと反省した次第です。 このあと清水さんより New Relic の概要説明がありときには補足も入りながら、New Relicの目指すところはなにかという内容へと進んでいきました。 紙幅の都合もありますし、より正確な内容をご自身の目と耳でご確認いただきたいと考えますので、興味を持たれた方には以下のウェビナーへの参加や過去動画の閲覧をおすすめいたします。 セミナー・ウェブセミナー | New Relic(ニューレリック) Q 内部統制でどういったところにつかえるでしょうか、たとえばDB操作の監視であるとか…… A ログベースの検知であったり、そのキーワード監視ができます。加えていうとリリースプロセスが内部統制の制約をうけるので、モニタリングをQAと連携させることもひとつの重要な観点になります Q バックエンドで稼働するバッチ処理の成功可否など、きちんと検知する仕組みはできるでしょうか A Webフレームワークの機能を利用しているものであれば、組み込むことは難しくないです 補足をするとバッチ処理などは実行と終了だけでなく、処理内容とその出力成果物の整合性など、バッチ処理の価値そのものを追っていくことも大事なことなので、いくつかの観点で複合的に考える必要がでてくるかもしれません New Relic 入門(機能紹介) 「ビジュアル化の重要性を体現的に理解している」と話題になるメンバーのプライベート機 ここからは開発メンバーから代表した数人が、一人ずつZoom画面共有機能を用いてNew Relicに触れていく形になります。「モブプログラミング」でいうドライバー役のようなものを想像いただければ間違い無いかと思います。代表者を含む参加したほとんどのメンバーはNew Relicを今日触るのが初めてと言っていい状態ですから、清水さんがナビゲータ役として位置する形になります。 統合ダッシュボードとしてのNew Relic One Add More Data New Relicにデータを投入するための言語/ツールごとのセットアップ手順がチュートリアルのような形でわかる親切設計です アカウント BASEではサブアカウントを環境ごと(Prd/STG/Dev/……)に対応する形にしました Services BASEではAppNameをリポジトリ名から引用して対応させるようにしました リポジトリAを実行しているEC2は複数台構成で稼働していますが、リポジトリ=AppNameとしているためNew Relicのデータ上ではまとめて表示されるようになっています APM導入後に活用できる各機能 Summary サンプルにしたAppNameでは、MySQLのレスポンス時間が高くなっているグラフが目に付きました Databases SummaryでみつけたMySQLのレスポンス時間が高くなっていた部分を調査していきます QueryTimeが長い部分をグラフで視覚的に確認することができます Slowest query time で sort することにより Slow queries を参照することができます MySQL の Queryそのものと、PHPのStack Traceを同時に確認することができます 「EXPLAINだ」「slowlog を grep して explain して…みたいなことをここでシュッとできる」というようなコメントがSlack上で見受けられました Workloads アカウント(BASEではサブアカウント)ごとのリソース群の健全性状態を一覧できます 自動的に組み込まれていくので手間いらずで便利そうです NRQL / Query builder がやってきた 『ある期間内にどのショップがイベントを行ってリクエストが増大したかがひと目でわかる』ことに感心する一同 個人的にもっともエキサイティングな時間がやってきました。 APMで投入されたデータはNew Relic上でNRDBとして格納され、NRQL(New Relic Query Language)を利用してさまざまな解析が可能となります。 今回のオンボーディングでは以下のNRQLを用いた可視化が披露されました。 FROM Transaction SELECT avaerage(duration) ここでの duration は属性[応答時間]です このNRQLで本番環境すべてのトランザクション平均時間が得られました FROM Transaction SELECT avaerage(duration) TIMESERIES TIMESERIES のおかげで指定期間単位ごとの集計結果を時系列化したグラフが表示されました FROM Transaction SELECT percentile(duration, 99) TIMESERIES percentile(duration, 99) は duration の99パーセンタイルを意味しています TIMESERIES により同様に時系列化したグラフを返してくれました FROM Transaction SELECT percentile(duration, 99) TIMESERIES FACET name name は属性です ここでは実行されたPHPのファイル名でした FACET はグループ化を行ってくれます 個人的には FACET がとても好きです FROM Transaction SELECT percentile(duration, 99) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago Base.shop_id はBASEが埋め込んだ Custom Attribute になります。 Custom Attribute について詳しくは以下をご参照ください アプリケーション固有の属性を活用した性能分析 | Observability Platform - New Relic公式ブログ 意訳すると「直近1週間でBASEショップごとの応答時間99パーセンタイルで(処理時間がよりかかっているものの)上位30件の時系列グラフ」となるでしょうか FROM Transaction SELECT average(duration) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago こちらは先のものを平均時間で置き換えたものです FROM Transaction SELECT average(duration) * count(*) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago 先の平均時間では、各ショップのリクエスト数の多少は考慮されていませんでしたので、 average(duration) * count(*) として考慮する形に変わりました この形にかえることで、より戦略的かつ効果的な、費用対効果の向上に直結するパフォーマンス改善のために必要な、実践的なデータが得られるようになりました ここではNRQLのパワフルさを見せつけられました。 興味を持たれた方は以下をご覧ください。 Get started | New Relic Documentation NRQL Lessonsアプリケーションを使ってNRQLをマスターしよう - New Relic公式ブログ 結びに New Relicの基本的な機能について学べたオンボーディングでした。 まだ導入当初であって即効的なものを期待したわけではなかったのですが、この記事を書いている2021年03末ごろまでで以下のような成果がありました。 特定ショップのページ表示が遅くなった原因の特定 お問い合わせの原因となった不具合がどのリリースと関連するものかの特定 今後は、SRE的観点からダッシュボードを充実させていき、様々な意思決定にNew Relicを利用していきたいと考えています。 サービスの信頼性を獲得することはSREチームだけのミッションではなくバックエンド・フロントエンドエンジニア問わず期待しています。 New Relicを始めとしたツールの支援を受けながら、積極的に改善していくエンジニアを募集しています。 open.talentio.com open.talentio.com SREチームは先日求人を見直しました。 devblog.thebase.in 清水さんのことを心のなかでは 「New Relic アニキ」と呼んでいます ↩
アバター
こんにちは!! BASE株式会社 SREチーム エンジニアリングマネージャの富塚( @tomy103rider )です。 2021年3月現在、SREチームは私含め3名で、最近私は成長するサービスを一緒に支えていって頂けるSREの仲間を求めて採用などをメインに業務を行っています。 はじめに 突然ですが、皆さんのチームの求人票は誰が作っていますか?そしてそれを読んだことはありますか? 思い出してみると元々のSREチームの求人票は私と前のマネージャが考えて作ったものでした。 その内容を改めて確認すると、 見ていただいている方に現在のSREチームの思いが伝わる内容だろうか? チームのメンバー自身も迎えたい仲間のイメージができる内容だろうか? 採用に関わるCTOにもそのイメージは共有できているだろうか? などと感じる部分が出てきたため、このタイミングで見直してアップデートすることにしました。 今回はこのときの話を紹介しようと思います。 どうやって見直す? まずは今までのようにマネージャが考えて作るのではなく、SREチームとして今どういう仲間を求めているのかを認識を合わせながら自分たちで考えて見直し、その内容を最終的にCTOとすり合わせることを今回のゴールにしました。 また、話し合いを始めるにあたって、 過去や今の求人票を検討したときの経緯を見れると良さそう 過去の求人票の歴史を見れると良さそう そもそも社内にオープンに見れる状態になっていても良さそう という意見が出て、過去の歴史を残すとなるとこれはGitHub上でやってみては?ということになり、試しに見直しの過程やアップデートをGitHub上に残してみることにしました。 見直しの過程をIssueに残す! 新たに作成したRepositoryのIssueに「やっていくぞ!」とCTOがビシッとコメントを残してスタートです。 スタート直後の様子 数回の見直しMTGをやりながら、このような雰囲気で議事録的なコメントやカジュアルなコメントを割と何でもこのIssueに残していきました。 求人票のアップデートはPull Requestベースで! 最終的に1つの求人票を作るために、見直しMTGで話し合った結果の大枠を反映した内容をまず私が作成し、 Pull Requestの様子1 これをみんなでレビューしていき、途中CTOのアドバイスを貰ったりして、まずはSREチームの中で認識があった状態にしました。 Pull Requestの様子2 そして最後にCTOにレビューしてもらい・・・ Pull Requestの様子3 無事 approvedとなったことで、今回のゴールであるCTOとの認識合わせもできました。 やってみてどうだった? 折角なので最後にチームのメンバーに今回の感想をIssueのコメントに残してもらいました。 N氏コメント A氏コメント 抜粋すると 前の内容に比べてより今求めているイメージが表現できたかも! 採用についての当事者意識を持つキッカケになった! 履歴を残したことが将来の財産になりそうな期待が! というような良い感想をもってくれたようです。 まとめ チームの規模感や状況によって今回のようなやり方が適しているかというのはあるかと思いますが、私としては見直しの過程を財産として残せたこと、みんなで新たな仲間のイメージを持てたこと、とても有意義な時間にできたのではと思いました。 そして・・・ 今回のアップデート後の求人票が open.talentio.com こちらです!!! ここまでの内容すべてが前フリだったかのようになりますが・・・ 現在SREチームでは成長するサービスを一緒に支えていって頂ける仲間を募集していますので、求人票をご覧いただいて少しでも興味を持っていただけましたら、まずはカジュアルにでもお話できればと思いますので是非ご連絡ください!!!!!
アバター
こんにちは、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の話です!
アバター