TECH PLAY

キャディ株式会社

キャディ株式会社 の技術ブログ

234

目次 こんにちは、CADDi でフロントエンド エンジニアをやっている桐生です。この記事は CADDi Advent Calendar 2020 の7日目の記事になります。 昨日の記事はモリさんの あえてケイスケ・ホンダのようなデザイナーになるには でした!まだご覧になっていない方はぜひ目を通してみてください! 本日は、最近触っている tailwindcss についてお話できればと思います。 [ toc ] tailwindcss とは tailwindcss は数ある CSS フレームワーク の1つですが、 Utility first というアプローチでスタイルを組み上げていく点がユニークで、OOCSS, BEM, SMACSS など、これまでの CSS 設計アプローチとは考え方が大きく異なります。 tailwindcss では p-6 , max-w-sm などの CSS class を utility class と呼んでおり、これらの utility class は1つ1つの CSS プロパティに対応するほど細かく primitive なものとしてあらかじめ用意されています。 padding のサイズ違いなどにもそれ専用の class が提供されている ので、 CSS そのものを書く代わりに、用意されている utility class をHTMLのclass属性に追加してスタイリングしていく、という開発スタイルになります。tailwindcss が提供しているものはこの utility class のみで、他 CSS フレームワーク がよく提供しているような コンポーネント という大きさのレベルのスタイルは存在しません。これが tailwindcss の Utility-First というアプローチです。以下は tailwindcss の公式ドキュメントからの引用になります。 <!-- 公式ドキュメントから引用 --> <!-- class 属性に付与さているのは全て utility class --> <div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4"> <div class="flex-shrink-0"> <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo"> </div> <div> <div class="text-xl font-medium text-black">ChitChat</div> <p class="text-gray-500">You have a new message!</p> </div> </div> tailwindcss のメリットは? tailwindcss の公式ドキュメントでは、Utility-First アプローチをとることで、3つのメリットがあると言っています。 1. CSS 設計・運用コストの低減 You aren't wasting energy inventing class names. No more adding silly class names like sidebar -inner-wrapper just to be able to style something, and no more agonizing over the perfect abstract name for something that's really just a flex container. class 名の発明に無駄なエネルギーを使わなくて済む。 何かをスタイリングするためだけに sidebar-inner-wrapper のようなばかげたクラス名を追加したり、単なるフレックスコンテナに対して抽象化した名前を悩んだりすることがなくなる。 CSS 設計の経験のある人は誰しも悩んだことがあると思います。 CSS class のネーミングは本当に難しい作業です。UI コンポーネント 構造を抽象化して class 名として表現しなくてはなりません。それに加え、ネーミングルールの設計、ネーミングルールの遵守徹底など、非常にコストのかかる作業でもあります。 Utility-First アプローチをとることによって、そもそも class 名のネーミングという作業自体がなくなるので、ネーミングにかけていた時間をゼロにすることができるということですね。ネーミングという行為自体が不要となるので、それに関わる設計・運用コストもなくなり、一石三鳥というわけです。 2. CSS100%再利用による CSS サイズ肥大化抑止 Your CSS stops growing. Using a traditional approach, your CSS files get bigger every time you add a new feature. With utilities, everything is reusable so you rarely need to write new CSS . CSS の肥大化を止めることができる。 従来のアプローチを使用すると、新しい機能を追加するたびに CSS ファイルが大きくなる。utility を使用すると、すべてが再利用可能になるため、新しい CSS を作成する必要はほとんどない。 tailwindcss が提供する utility class は、 CSS プロパティ1つ1つに対して用意されるほど細かいので(primitive utilities という呼ばれ方もしています)、100%再利用可能です。ほとんどの場面においては用意されている utility だけで十分で、開発者が独自に CSS を実装する必要がないということですね。 3. スタイルの変更をHTMLローカルで安全に行える Making changes feels safer. CSS is global and you never know what you're breaking when you make a change. Classes in your HTML are local, so you can change them without worrying about something else breaking. 変更が安全になる。 CSS はグローバルであり、変更を加えたときに何を壊しているのかが分からない。 HTMLの class はローカルであるため、他の何かが壊れることを心配せずに class を変更できる。 utility class は primitive なので、class 内の CSS を変更するということはありません(もちろん変更することは可能で、その場合は config ファイルを介して変更する ことができます)。もしスタイルを変えたくなった場合は、 CSS を変えるのではなく、HTMLのclass属性に付与している CSS class自体を変えることになり、 <div class="p6"></div> <div class="p6"></div> ↓ 1つだけ太字にしたい <div class="p6"></div> <div class="p6 font-bold"></div> <- 影響範囲はここだけ HTMLの変更は影響範囲がそこのみに限定されるため、非常に安心して変更することができるというわけです。よく CSS は グローバル変数 だと揶揄されますが、それとの対比で 「HTMLの class はローカル」 とはうまい例えだと思います。 tailwindcss のデメリットは? 良いことづくめのような話に聞こえますが、果たしてデメリットはないのでしょうか? インラインスタイルじゃダメなの?? HTML の class 属性に utility class を付与していく様はまるでインラインスタイルを書いていくかのようです。 Why not just use inline styles? ところが、 インラインスタイルで埋め込まれた値は magic number となってしまう インラインスタイルは media query が使えない インラインスタイルは擬似クラスが使えない との理由から、インラインスタイルはむしろ悪手となり、utility class を差し置いて採用する意味がありません。 メンテナンス性は大丈夫?? Maintainability concerns 例えばボタンのような非常に基本的な コンポーネント であっても utiliy first で実装すると相当数の CSS class が class 属性内に展開されます。 <!-- 公式ドキュメントから引用 --> <button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-green-500 hover:bg-green-700"> Click me </button> このボタンスタイル一式を様々なところで使いまわしたい時、毎回コピーしなくてはいけないのだとすると、メンテナンス性を著しく損なうことになりますが、これについてはどう対処すればよいのでしょうか? tailwindcss としての回答は「カスタム CSS class を作らずに template や JavaScript Component を作れ」です。 Extracting template components 一旦カスタム CSS class を作ってしまうと、そこからネーミングコスト、 CSS 肥大化につながってしまうので、template や JS Framework にコンポーズする責務を任せ、 CSS 側はあくまで Utility-First に徹することを推奨しています。 こうすることで、Utility-First のメリットを損なうことなく、メンテナンス性を担保することができるというわけです。 CSS プロパティ <-> utility class の翻訳大変じゃない?? CSS プロパティ1つ1つにあたる utility class が提供されているとなると、その数は膨大で、覚え切ることは難しいです。普段 CSS を書き慣れた開発者にとっては、 CSS プロパティ -> utility class の翻訳がもどかしく感じるかもしれません。 そこで tailwindcss では、DXを向上させるための VS Code extension を提供しています。 Tailwind CSS IntelliSense これにより IntelliSense が効くようになって、効率的にスタイリング作業を進めることができます。実際、私も tailwindcss を触り始めた時にもどかしさを感じていたのですが、この extension を入れてからストレスが減ったと思います。 (閑話)いかにして tailwindcss は生まれたのだろう? tailwindcss のドキュメントの一節にこうあります。 Extracting template components It's very rare that all of the information needed to define a UI component can live entirely in CSS — there's almost always some important corresponding HTML structure you need to use as well. UI コンポーネント を定義するために必要なすべての情報が完全に CSS に存在することは非常にまれ。ほとんどの場合、 CSS と対応するHTML構造が必要になる。 昔から、見た目と構造は分離すべしと言われてきましたが、昨今の CSS 設計手法では、むしろ コンポーネント 構造をいかに抽象化し、適したネーミングを CSS class として表現するか、ということに重きが置かれてきたように思います。よくよく考えるとこれは、見た目と構造が分離されるどころか、むしろ結びつきを強める方向に進んでしまっていたのではないかと思うようになりました。 作者は、これこそが根本的な原因であり、その因果として 構造を抽象化した CSS class名 -> CSS 設計コストの増大 構造に依存した CSS -> CSS 再利用性の低下 という問題が生じているのでは、と考えたのではないでしょうか。そして、その解決策を考え突き詰めた結果として Utility-First というアイディアに至ったのではないか、と推察します。tailwindcss が生まれた背景には 見た目と構造の完全分離 という設計思想があるような気がしました。 そう考えると、tailwindcss の Utility-First アプローチは、単なる奇抜な手法ではなく、現状の問題点への深い洞察から導きだされた論理的な解決方法であり、画期的な手法だ!と感心せずにはいられません。 これはあくまで私の解釈ですが、皆さんはどう思われますか?? まとめ tailwindcss は primitive な utility class を提供することで、 CSS の設計コスト、運用コストを下げる。 CSS の再利用性を高める。 CSS 肥大化を防ぐ。 スタイルの変更容易性を上げる。 をもたらしてくれるものである、と言えるのではないでしょうか。 現在 CADDi では、styled-components を使用したスタイリングを行っているのですが、特に CSS の再利用性に課題ありと個人的に感じています。今回 tailwindcss についてのコンセプトとメリットについて理解を深めたことで、課題解決に期待できそうなので、今後試験的に導入することも考えていきたいなと思っています。 ただ、私自身、tailwindcss を触り始めてからまだ日が浅いこともあり、デメリットらしいデメリットに当たっていないだけの場合もありますので、今後、いろいろと知見を溜めて行ければと思います。 付録 この記事を読んで「tailwindcssなんか良さそうだな、今すぐ試してみたい」と思って下さった方もいるのではないかと思います。いたら嬉しいです。そんな方にぜひ。 tailwindcss をとりあえず触ってみたい人向け 公式の playground があるので遊んでみてください。 https://play.tailwindcss.com/ project に組み込んで使ってみたい人向け tailwind をセットアップしたプロジェクトを作ってみたので、お好きに使ってください。README.md にはセットアップ方法をまとめてありますのでご参考ください。 Next.js プロジェクト create-react-app プロジェクト おわりに CADDi では先日採用サイトをリニューアルしたばかりです!非常にカッコよく仕上がっているので、興味ある方はぜひご覧いただければと思います! https://corp.caddi.jp/recruit/
こんにちは、キャディでUI/UXデザインを担当しているモリです。 この記事は CADDi Advent Calendar 6日目の記事です。昨日はマリアンヌによる キャディのエンジニアの開発環境、ぜんぶ見せます です!とっても面白いのでぜひご覧になってみてください! 僕は本田選手のように?幅広く活躍できるデザイナーになるには、について書いていきたいと思います。 ケイスケ・ホンダ=プロフェッショナル "そもそも"を考え、自分の真ん中を決めることで、迷わず進むことができるんです 本田思考 - 本田圭佑 デザイナーも”そもそも”をよく考える職業だと思います。(え?) 最近のデザイン、特にテック領域では、作家性や審美性に重きをおいたデザインから、 ユーザー体験やシステム設計・開発効率性に重きをおいたデザイン に重心が変わってきました。 またそういった変化に合わせて、 デザイナーであってもビジネスやテクノロジーのリテラシーを持った越境型人材(BTC人材) が求められるようになってきました(う〜ん) ビジネス・テクノロジー・クリエイティブのBTCトライアングル 参考: 田川欣哉. イノベーション・スキルセット~世界が求めるBTC型人材とその手引き いやしかし、そんなどこかの記事で読んだような話をされても、、、と思われるでしょう、僕もそう思います。 そこで今回は僕自身がキャディの開発現場でデザイナーとして感じたB(ビジネス)やT(テクノロジー)の必要性や、デザイナーがBやTを伸ばすには?ということについて僕なりに書いてみます! デザイナーにビジネスやテクノロジーは必要か? 僕たちは、一人の力やひとつの専門的スキルでは太刀打ちできないような複雑で複合化した課題を、ソフトウェアの力で解決しようと試みています。こういったプロダクトは、ビジネス、テクノロジー、クリエイティブがかけ合わさった品質が当たり前のクオリティとして求められます。そして、そのようなプロダクト開発の現場では、高い確立でエンジニアとデザイナーが一緒に仕事をする事になるので、デザイナーはエンジニアの仕事内容と気持ちを理解する事と、いざとなったら自分でも手を動かし、コードが書ける、そういった役割が強く求められます。またプロダクトオーナーや経営メンバーと会話する際も、会社として戦略上どこに注力しているか?また現場でボトルネックになっている箇所はどこか?などのビジネス上の前提を踏まえておかなければ、いくらヒアリングしても結局プロダクトに反映することができません。UIをデザインするにしても、画面上の情報の優先順位付けすらも一人でできないような状態になってしまします。(ふう) ↑こんなイメージで書いています。 参考: レッド-山本直樹 要するにデザイナーであってもビジネスとテクノロジーの観点を持って開発しないと夢のプロダクト・マーケット・フィット〜P・M・F〜に到達することなんてできないぜ!ということを改めて実感したよ、と言いたかったのですが。 いや待て、と。ならばデザイナーはビジネス x テクノロジー x クリエイティブの3領域に知識と経験を持つ究極超人にならねばならないのか、と思われる方もいらっしゃると思います。これって普通に考えてかなり難易度が高いことを言っていますよね、僕が本田圭佑であっても厳しいと思います。 でもよく考えてみてください、本田選手もオフェンス・ディフェンス・経営と全て一人でこなしていたわけではありません。長友選手や香川選手など優秀なチームメイトがいたからこそ結果を出せたはずです。 そう、僕や皆さんの隣にもスーパーエンジニアや強力なプロダクトリーダーがいるでしょう!彼らと共にチームとしてBTCトライアングルが形成されていることを目指せば良いのです。 ただそのためには デザイナーがビジネス・テクノロジーそれぞれの言語を理解する 必要があります。 Learn to code 2010年、今から10年前のことですが、 Adobeフォントライブラリの元クリエイティブディレクターでありデザイナーでもあるElliot Jay Stocks氏がこんなツイートをしたそうな。 Honestly, I'm shocked that in 2010 I'm still coming across 'web designers' who can't code their own designs. No excuse. 「いや〜正直言って、2010年になっても自分のデザインをコーディングできないWebデザイナー?がいてマジビビったわ」 恥ずかしがり屋な御仁なのか言葉はちょっとキツめですが、おそらく彼はこう言いたかったのでしょう 「コーディングの知識がないのにWebプロダクトをデザインすることなんてできないでしょ?」と。 なるほど、と。ごもっともだな、と。 コーディングの知識がないデザイナーが作った、実装面の考慮がない夢のようなデザインを渡されても 、一緒に働くエンジニアは混乱するだけだなと。そんなことで開発効率を上げてPMFを達成できるのか、と。 ではデザイナーとしてどこまでこなせるようになると良いでしょうか? 自分のデザインをコーディングしてプロトタイプができるようになるところまで まずはここを目指すと良いかなと思います。 デザイナーとして表現したいことを実際コーディングして動かせるところまでできれば、エンジニアとのコミュニケーションコストも下がるし、実際の画面で触って使えるプロトタイプは説得力が違います。 今は、ReactやVueなどのフロントエンドのフレームワークが充実していて簡単に実装・アウトプットまで持っていくことができるし、自身でコーディングするからこそ見えてくることも多々あります。 動的コンテンツ(テキスト量や画像サイズ等)の制御や、取り扱うデータが増えた場合はどうするか、バリデーションはどう見せるか、そういった 実装面とUXを考慮したデザイン こそがなによりも求められているのです テクノロジーを学び実装面とUXの両方が考慮されたデザインを作る力を身に付けましょう! A Business Perspective まあテクノロジーはわかった、と。じゃあビジネスはどうなんだ、と。ビジネスといっても。経営戦略・マーケティング・財務管理諸々あるだろ、と。 そうです、デザイナーにとってビジネスは理解し難い領域です、、、ビジネス怖い、、、 そこで僕はデザイナーに求められるビジネス観点とは以下のことなのではないかと考えました。 組織が本当に達成したい事を正確にキャッチアップする力 この観点がずれているといくらクオリティの高いデザインを作っても無駄 になってしまいます。 なのでここを鋭くキャッチアップする力を身につけることでビジネス観点を備えたデザイナーになれるのではないでしょうか! 俺は孤立している。あえてね なるほど、と。キャッチアップ力が大事なのはわかった、と。 それならどうすればどうすればキャッチアップ力を身に着けることができるのでしょうか? 俺は(ミランで)孤立している。あえてね 皆さん、この名言をご存知でしょうか、ACミラン在籍時の本田選手の言葉です。 そしてこう続けました。 昔、日本代表でとっていた行動と一緒ですよね。コイツ、何やろうな? と。自分を持っているな、と。試合へのアプローチも全部、自分のやり方で集中しているな、と。そういう風に(周囲の仲間に)思わせているわけです なるほど、と。一理あるな、と。迎合することなくオーラを放つ作戦ですね! しかしこれは本田選手のカリスマがあるからできる態度であってなかなか真似できるものではありませんね。 なので僕がおすすめする方法としては、"情報よこせオーラ"をバンバン発するのではなく、自ら動きビズサイドの情報との接触機会を増やしていくことだと思います。 Slackなどのメッセージアプリを利用していれば、色々なチャンネルにジョインしてログを収集する・Notionなどの情報共有サービスなどを利用していれば溜まったドキュメントに目を通していきます。 デザイナーであってもこのように情報を収集していくと、今現場では何がボトルネックになっているか、戦略にどのような転換があったか、などが自分の中に情報が蓄積され、自然と課題を適切にキャッチアップする力が養われる気がします。 これ実はキャディでは結構みんなやっていて、しかもかなり効果があります!ぜひデザイナーのみなさんも試してみてください! 役割の変化 プロダクト開発の手法がウォーターフォールからアジャイルに変化しているように、そこで求められるデザイナーも 「綺麗なものを作れるけど受け身な人」ではなく「引き受けて改善できる本田圭佑のような人」 に変わってきていると思います。 ウォーターフォールであればプロダクトオーナーなどから与えられた要件に対して適切にスタイリングしていくことが求められますが、アジャイルな開発であれば経営者やプロダクトオーナー、エンジニアのパートナーとしてプロダクトを前進させる本田圭佑のような態度が求められます。 そして僕自身キャディでのプロダクト開発の経験を経て、デザイナーの役割の変化やBTCの重要性に気付けました。 キャディはデザイナーがビジネスやテクノロジーを伸ばすのに最適な環境が整っていると思います。 ビズ・テック共にメッシやCロナウド、V・ファン・ダイク、デ・ブライネ級のメンバーが揃っているので、少しでもキャディのプロダクト開発に興味をもっていただけたら、ぜひ カジュアル面談 に申し込んでみてください!
こんにちは! @ryokotmng です。本記事は、 キャディ Advent Calendar 2020 – Qiita の4日目の記事です。昨日の記事はagate-prisさんの Orphan Ruleよありがとう ~Rustを採用したおかげでリファクタリングが捗った話~ でした。 キャディのエンジニアがどんな開発環境で仕事をしているのかについて、アンケートをとってみました。その結果を (私の心の声を挟みつつ) まとめてみましたので、本日は弊社エンジニアチームの雰囲気を感じてもらえるとうれしいなと思っています💁 回答してくれたエンジニアの経歴と今のお仕事 合計18人のエンジニアが回答してくれました!まずは、回答してくれたエンジニアたちのバックグラウンドと今のお仕事について、ざっくり見ていきたいと思います。 エンジニア経験は何年くらいですか? ※ 一部未回答の質問もあるため、グラフの数字の合計は必ずしも全回答者の合計人数と一致しません。 ※ 縦軸は人数です。 一番経験が長い言語は? ※ 縦軸は人数です。 経験年数、言語共に、バックグラウンドはかなり多様なようです! 今ざっくり言うとどんな開発をしている? 今一番よく使う言語は? ※ 横軸は人数です。 最もよく使われているのはRustでした。 キャディではほぼ全てのサービスでバックエンドをRustで書いており、フロントエンドやBFFではTypeScriptを使っています。また、フロントエンドやバックエンドの担当と言っても境界ははっきりしているわけではなく、バックエンドエンジニアがBFFやフロントを書いたり、その逆もよくあります。 お待ちかね、開発環境について 使っているPCは? ※ 以下円グラフ内に記載された数は回答者数です。 Macを使っている人が多いですね。 個人的には、自作PCを使っている人が4人もいることに驚きました!Rustを用いた開発では、コンパイルに時間がかかることやメモリが足りなくなりがちなこともあり、高スペックなPCを求めて自作しようと思うようになるのかもしれません。 自作PCの詳細を教えてくれた人たちは、Ryzen 9 3900X + GEFORCE RTX 2080 SUPERを使っているそうです。「コンパイル速度もゲームもいい感じ」とのこと。(Ryzen、私も使ってみたいです!めっちゃ速いんだろうなぁ。) バックエンドエンジニアの @kuwana_kb_ さんより、自慢の自作PCの写真をいただきました🧑🏻💻もはやSFっぽい写真ですね!かっこよすぎて、ウェブから適当にイケてる写真を引っ張ってきたんじゃないかとつい疑ってしまいました (ご本人が作成したものです)w 他にも、「ノートPCながらGTX1070搭載はなかなか熱いと思ってます!(排熱的にも)」という意見もありました。 使っているOSは? 使っているシェルは? zshを使っている人が7割と大多数でした。 fishを使っている人は、「履歴補完が便利で他のshellを使う気にならない」とのこと (そんなにすごいのか、ちょっと使ってみたいな...)。 Nushellは知らなかったのですが、Rust製なのですね!2019年9月頃公開された新しいシェルで、fishのように入力補完機能が強力なようです。 使っているエディタは? ※ 複数回答が含まれます。 VSCodeが最も多く、VimとIntelliJ系がそれに続きました。「VSCodeはデファクトスタンダード」という声や、「IntelliJ系の補完、リファクタリング機能、文字列選択機能が優秀」、「Vimは思考のスピードで編集できる」などの感想もあり、なかなかどれが良いとは一概に言えなそうですね。複数使い分ける派の人も数人いました。 なかには、同じJetBrains製品を複数個使い分けている人も。「Rust は CLion、TypeScript は WebStrom、インフラコードは IntelliJ、という風に使い分けています。どのコードを書く時にも同じ操作感で使える点が気に入っています。CLion は Rust の静的解析がとにかく速いので助かってます」とのことです。 VSCodeにはTabNineというAIによるオートコンプリート機能のプラグインがあり、これをおすすめするエンジニアもいました。 これは開発に必須!と思われる便利なツールは? fzf + ghq、zsh-autosuggestions、fishの履歴補完機能など、コマンドを補完するためのツールが多く挙げられていました。 Vimの場合はLSPのcocを使用している人が多いようです。ほかには、ripgrep、iTerm、tmuxなどを使っている人が多いみたいです。 個人にインタビューする機会があれば、この辺は実際の作業を見ながら細かく突っ込みたいところです!(今回の企画が好評だったらやりたい!) 使っているキーボードは? ※ 複数回答が含まれます。 この結果には含まれていませんが、私も今年のクリスマスにHHKBデビューする予定です。HHKBは大人気な一方で、打鍵音がやや大きいため、「奥さんに不評なので、Apple公式のものと使い分けている」という意見も。 普段使うぶんには気になりませんが、ご家庭がある方は、特に音の問題は難しいですよね。 Nizを使っている人からは、「キーが軽くて深くてしっかり戻ってくるのでかなり打ちやすいです。キーバインドがないので Vim & tmux を使う方でも衝突しなくてよい」という声もありました。 こちらはミートアップ等でおなじみ、エンジニアリング・マネージャー @mura_mi さんのキーボード。Realforceの変荷重だそう。配色が可愛いです✨英字配列であることには拘っているとのことです。 自宅、見せてください!! さて、ここで数人のエンジニアの自宅のデスクを写真でお届けしちゃいます💁🏻 まずはアルゴリズムエンジニア、 @ngtkana さんのデスク。キーボードはNiZですね⌨️左手にペンがありますが、メモを取る時にはiPadのGoodNoteを使っているそうです。左手にある書籍スタンドが便利そうすぎて、これを見てつい私もポチってしまいました❣️ アルゴリズムエンジニアいなむさんのデスク。手元でメモを取れるようにiPadを置いていて、Notabilityというアプリを使っているそうです。奥で虹色に光る自作PCがきれいですね🤤こだわりは、「IDEの背景が可愛いキャラクターであることが重要だと思っています」とのこと。 バックエンドエンジニア、 @gushernobindsme さんのデスク。 スッキリしているなかにもオーディオ機器へのこだわりがみられますね。ディスプレイの左手に立てかけてあるのは、プライベート用のPCでしょうか?「 サウナイキタイ 」のステッカーが貼ってありますね🧖‍♂️ 次にバックエンドエンジニアの木村さんです。 ドラマに出てきそうなかっこいい空間に仕上がっています😍 左側のディスプレイの上にある丸いものは、カメラと女優ライト的なやつでしょうか!?これなら登壇や動画配信もバッチリですね💪🏻 こちらは @mura_mi さんのデスク。 ディスプレイやキーボードスライダーはリモートワークの影響で整備したそう😃 メモ用の紙・ノート類は、「1週間のタスクを書き続けるスケジュール手帳」「1on1記録ノート」「ブレスト用のA4コピー用紙」3種類を使っているそうです。 ついテンションが上がってしまいました💦 私の家の環境はしょぼすぎて残念ながら公開できないのですが、みなさんの写真を見ると環境を整えたくなりますね! 仕事に欠かせないものは? 下記3つは、どれも10人以上から回答されていました。 - ディスプレイ - イヤフォン (含ネックスピーカー) - 良い椅子 他にも、お菓子や音楽、ノートは多くの人が挙げていました。 リモートワークの影響でイヤフォンの長時間利用から耳を痛める人も出てきていて、ネックスピーカーを購入した人も数人いました。私も最近愛用しているのですが、かなり軽くてちょっと席を離れる時もつけてて気にならないので (何より耳が痛くないの最高) 個人的には重宝しています。 一方で、「イヤフォンにノイズキャンセリングは必須」という声や、「FocalのStellia(ヘッドホン)は最高に良い音だぞ...」との意見もありました。こちらの方が没入感があって集中力を上げやすそうなので、使い分けるのも良さそうですね。 @kuwana_kb_ さんより、Focalのヘッドホン。またまた写真がイケてますね!悔しいけどかっこいいです! 椅子については、「思い切ってコンテッサの椅子を買ったら腰痛が一気に楽になった」との意見も。また、電動昇降机を使用している人や、座椅子で仕事をしているという人もいました。キャディも基本的にはリモートワークなので、家にいながら生産性を上げる環境は大切ですね。(ぐぬぬ〜〜、椅子高いけど、いいやつ欲しいな〜〜〜〜。) また、「マイクはAKGのC451という楽器用のものを使用しています~」、「毎シーズン良いダージリンを取り揃えております」というこだわり派も。 木村さんのマイク。本格的すぎる😯 いなむさんこだわりのダージリン。収穫年やグレードまで書いてあります! 他にも、確かに!と思ってしまったものに、このような意見もありました→「最近は、開発する人間の環境(近くの飯屋とか)のほうが重要度が高い気がします」。家の近くのランチにはもう飽きた...なんて方も世の中には多いのかもしれません。 最近気になるものは? 下記のようなものが複数人から挙げられていました。 - M1搭載Mac mini - キーボード - 4Kディスプレイ - 椅子 - 自作PC 自作PCについては、私も年末年始を利用して取り組んでみたいと思っています!社用PCは特に問題ないのですが、Rustを始めてから、自分のMac book pro (ケチって4コア😇) ではなかなか開発に苦労するようになってきてしまいました💦 いかがでしたでしょうか、楽しんでいただけましたか?これまで開発環境について話したりする機会は少なかったので、書いている私としてもとても勉強になりました。 キャディでは、楽しいエンジニアたちが日々業界を変えるために頑張っています!今回は仕事の話は全然していませんが、キャディでの開発や技術に興味を持ってくださった方は、ぜひ カジュアル面談に申し込んでみてください ☺️ お読みいただきありがとうございました!寒い日が続きますが、健康に気をつけてくださいね〜!
おはようございます、CADDiでバックエンドエンジニアをしているagate-prisです。本記事は キャディ Advent Calendar 2020 - Qiita の4日目の記事です。昨日の記事は飯迫の Argo Rollouts で Blue-Green Deployment でした。 本記事は、すでにRustを使っている人向けではありません。まだRustを使っていない人向けの「Rustにはこんな機能があるよ。この機能のおかげでこんないいことがあったよ」とRustを勧める記事です。 Cargoについて CargoはRustのビルドツール兼パッケージマネージャです。Cargoを含むRustの様々なツールチェインは、Rustインストーラおよびバージョン管理ツールであるRustupを使うことでインストールできます。 はじめに - Rustプログラミング言語 cargo コマンドによって、新規パッケージの作成、ビルド、コードフォーマット、テストなどができます。 Cargo.toml でパッケージの情報を管理します。ここにはパッケージの基本的な情報や、依存関係などを記述します。 Cargo.toml は丁度、Rubyなら Gemfile に、Goなら Gomfile に相当します。 Hello, Cargo! - The Rust Programming Language 日本語版 Cargoはその存在だけでもRustの採用を勧めたくなる程に強力なツールです。「Rustは豊かな型システムと所有権モデルによる強力なメモリ安全性を備える」という謳い文句は多くの方が聞いたことがあると思いますが、これに加えてCargo(と crates.io )の存在が、RustにnpmやRubyGemsの様なパッケージマネジメントシステム、エコシステムを標準で付加します。 crates.io: Rust Package Registry さらに、サブコマンド fmt で呼び出すことのできるフォーマッタであるRustfmtの強力さも特筆に値します。Rustはその性質上しばしばC++と比較されますが、C++でコードフォーマッタを利用した結果、悲惨なことになった経験がある方は少なくないと思います。況や最新のC++をや、といったところです。 rust-lang/rustfmt: Format Rust code RustにはC++の様な「バーリトゥード、何でもありの強力さ」は(意図的に制限されていることもあり、基本的には)ありませんが、Mozillaが主導となって旗を振りながら、標準化とツールチェインの開発を、それも非常にオープンな形で進めているおかげで足並みが揃ってる、と言えます。 ワークスペース、クレート、そしてOrphan Rule 閑話休題。 弊社では実際のプロダクト開発でRustを利用しています。その過程で段々とパッケージが肥大化していき、見通しが悪くなってきたため、複数のクレート(Rustにおけるコンパイル単位)に分割することにしました。 Cargoの機能の一つである「ワークスペース」を利用することで、一つのプロジェクトを複数のパッケージ、クレートに分割することができます。 Cargoのワークスペース - The Rust Programming Language 日本語版 その過程で、以下のようなエラーに遭遇しました。 Compiling y v0.1.0 (/home/agate-pris/aaa/y) error[E0117]: only traits defined in the current crate can be implemented for arbitrary types --> y/src/lib.rs:1:1 | 1 | impl From<i32> for x::Foo {} | ^^^^^---------^^^^^------ | | | | | | | `Foo` is not defined in the current crate | | `i32` is not defined in the current crate | impl doesn't use only types from inside the current crate | = note: define and implement a trait or new type instead error: aborting due to previous error For more information about this error, try `rustc --explain E0117`. error: could not compile `y` To learn more, run the command again with --verbose. 実際のコードは以下のようなものです。 impl From<i32> for x::Foo {} エラーメッセージの内容は「任意の型に対するトレイトの実装は、現在のクレート内に定義されたトレイトに対してのみ、これを許可する」というものです。 トレイトは、それをある方に対して実装することで型の振る舞いを定義し、ジェネリックプログラミングの手段を提供します。 トレイトオブジェクトで異なる型の値を許容する - The Rust Programming Language 日本語版 上記のエラーメッセージを素直に解釈すると、 From が y にないためエラーになっている、ということになります。これは間違いではなく、実際に y にユーザがトレイトを定義したのであれば、 x::Foo に対するその実装を y の中に書くことはできます。しかし、 From は標準ライブラリが提供しているトレイトなので、この方法では解決できません(し、解決方法としても誤っています)。ここでは x 側に Foo に対する From の実装を書くのが正解です。 この制限は、ワークスペースではなく、外部クレートの場合でも同じです。ワークスペース内か否かに関わらず、「あるクレートの中で、異なるクレートが提供する型に対する異なるクレートが提供するトレイトの実装を定義することはできない」ということです。 これは言い換えれば「ある型に対する、あるトレイトの実装は、そのトレイトを定義するクレート自身か、その型を定義するクレート自身が定義するべきであり、異なるクレートがそれを定義することは容易にメンテナンス性を破壊してしまうため、これを許可しない」と言えます。 これは rust-lang/rust#23086 でIssue化され、 rust-lang/rfcs#1023 で取り入れられた制限です。 rfcs/1023-rebalancing-coherence.md at master · rust-lang/rfcs この制限は coherence と呼ばれるプログラムの特性の一部で、より具体的には orphan rule と呼ばれます。 トレイト:共通の振る舞いを定義する - The Rust Programming Language 日本語版 今回、社内プロダクトをワークスペース機能によって整理していった結果、上記のエラーが起きたことで「本来トレイトの実装によって解決すべきでない問題を、トレイトの実装によって解決してしまっていた」ことに気づくことができました。実際のプロダクトでは、トレイトを実装する代わりに、単なる関数の呼び出しで置き換えました。必要であれば、新しく型を定義してラップすることで解決しても良いでしょう。 Rustで強めに型をつけるPart 1: New Type Pattern | κeenのHappy Hacκing Blog Rustは豊かな型システム、強力なメモリ安全性、優れたツールチェインを備えるだけでなく、このような「言語の機能と制限によって、プログラマが誤ったコードが書くことを未然に防止し、自然により良いコードを書かせる仕組み」が随所に散りばめられています。 crates.io にも様々なパッケージが揃っており、特定の言語の特定のライブラリやフレームワークを利用する強い動機がなければ、業務用アプリケーションを書くにあたって十分におすすめできるものになっていると思います。
こんにちは。CADDi SWE の飯迫です。この記事は キャディ Advent Calendar 2020 の3日目です。昨日は、村上さんによる 「良いチーム開発」を実現するための「組織的学習」と「自己組織化」の話 でした! 今Q は Rust で Backend を書いているのですが、前Q は主に DevOps 周りの改善をしていました。そのときにやったことのひとつ「Argo Rollouts を使った Blue-Green Deployment 移行」について紹介します。 背景と概要 まず前提として、弊社の生産管理システムは GKE 上で運用しており、大まかな Tech Stack はこのようになっています。 https://caddi.tech/tech_stack 上記の Frontend/BFF/Backend は、今回移行するまでは、Deployment リソースとして管理されていました。これらは RollingUpdate でデプロイしていたため、互換性のない変更をしたとき、新旧 Pod の混在によりそこそこエラーが出てしまう問題がありました。そのため、リリースの作業時は Staging 環境で動作確認後ユーザーへ事前告知し、30 分後に本番デプロイ開始するというフローになっていました。つまり、リリー スリード タイムが 30 分無駄に長くなってしまっており、これを改善するために BlueGreen Deployment ができるようにしました。 Argo Rollouts とは Argo Rollouts is a Kubernetes controller and set of CRDs which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes . https://argoproj.github.io/argo-rollouts/ Argo CD で有名な Argo ファミリーのひとつで、 k8s 標準ではできない Blue-Green や Canary など高度なデプロイが簡単にできるようになります。特に、 Deployment リソースとほぼ使用感が変わらない GitOps フレンドリー というところが、一般的な k8s オペレーションと親和性が高く、導入を容易にしています。また、Blue-Green Deployment は、Service と Deployment を 2 セット用意し、Service から Deployment への向き先を変えることで実現されています。これ自体はシンプルなので自前で用意することも可能ですが、Argo Rollouts では不要になったリソースの破棄や昇格条件などの細かい設定が可能となっています。 v1 でアプリケーションが動いていると仮定して、v2 をデプロイするとまずこうなります。 自動または手動で v2 が昇格する(Active Service からの向き先が変わる)ことで、混在状態がなくデプロイでき、不要になった v1 は自動的に破棄されます。 Argo Rollouts への移行の流れ おおまかな移行の流れは下記の通りです。 k8s に Helm で argo-rollouts を追加 https://argoproj.github.io/argo-helm Service を preview 用 と active 用で2つ追加 preview 用:昇格(入れ替え)前の Rollout(ReplicaSet)を参照するために利用 active 用:実際に各アプリケーションから参照する(既存 Service を置き換えるもの) 既存の Deployment を Rollout に書き換え 詳細は後述 上記の Service と Rollout をデプロイ Service の 参照元 の向き先を新しい方に変更 不要になったリソース(参照されなくなった Service と Deployment)を削除 注意点として、Rollout の初回デプロイ時に 参照元 の向き先も変更していると、Rolloutが生成されるまでの間ダウンタイムが発生してしまいます。向き先はあとで変更しましょう。 Deployment から Rollout への書き換え例 Before apiVersion: apps/v1 kind: Deployment ~~ 中略 ~~ strategy: type: RollingUpdate rollingUpdate: maxSurge: 100% maxUnavailable: 50% After apiVersion: argoproj.io/v1alpha1 kind: Rollout ~~ 中略 ~~ strategy: blueGreen: # Name of the service that the rollout modifies as the active service. activeService: sample-service-active # Name of the service that the rollout modifies as the preview service. +optional previewService: sample-service-preview # The number of replicas to run under the preview service before the switchover. Once the rollout is resumed the new replicaset will be full scaled up before the switch occurs +optional previewReplicaCount: 1 # Indicates if the rollout should automatically promote the new ReplicaSet to the active service or enter a paused state. If not specified, the default value is true. +optional autoPromotionEnabled: true # Adds a delay before scaling down the previous replicaset. If omitted, the Rollout waits 30 seconds before scaling down the previous ReplicaSet. A minimum of 30 seconds is recommended to ensure IP table propagation across the nodes in a cluster. See https://github.com/argoproj/argo-rollouts/issues/19#issuecomment-476329960 for more information scaleDownDelaySeconds: 30 詳しくは 公式資料 に書いてありますが、Blue-Green strategy 設定はこのようになっています。 autoPromotionEnabled 自動昇格フラグ off のときは kubectrl rollouts promote [rollout name] で手動昇格できます previewReplicaCount Preview Rollout(ReplicaSet)のレプリカ数 1 にしておけばリソースの節約になります 昇格の直前に自動的にスケールしてくれます scaleDownDelaySeconds 昇格後、不要になった Rollout(ReplicaSet)を破棄するまでの時間 最小 30sec 推奨 Fast Rollback に関係する設定 https://argoproj.github.io/argo-rollouts/FAQ/#how-does-bluegreen-rollback-work 昇格後、不要になった Rollout(ReplicaSet)が残っている状態で、ひとつ前の Rollout manifest を反映するとすぐに戻すことができます Argo Rollouts コマンド 実際そんなに使う機会はないのですが、Argo Rollouts Plugin のコマンドをいくつか紹介します。 準備 Kubectl Plugin Installation のインストール https://argoproj.github.io/argo-rollouts/installation/#kubectl-plugin-installation 一覧の参照 % kubectl get rollouts NAME DESIRED CURRENT UP-TO-DATE AVAILABLE sample-rollout 1 1 1 1 特定 Rollout の状態取得 % kubectl argo rollouts get ro sample-rollout Name: sample-rollout Namespace: default Status: ✔ Healthy Strategy: BlueGreen Images: gcr.io/sample-dev/sample-image:v1.0.26 (active) Replicas: Desired: 1 Current: 1 Updated: 1 Ready: 1 Available: 1 NAME KIND STATUS AGE INFO ⟳ sample-rollout Rollout ✔ Healthy 26h ├──# revision:14 │ └──⧉ sample-rollout-5bfcfbd578 ReplicaSet ✔ Healthy 130m active │ └──□ sample-rollout-5bfcfbd578-h6dh5 Pod ✔ Running 130m ready:2/2 ├──# revision:13 │ └──⧉ sample-rollout-845f687b6 ReplicaSet • ScaledDown 149m その他 まだ試していないのですが、 kubectl argo rollouts undo が最近追加されたようです https://github.com/argoproj/argo-rollouts/pull/812 運用小ネタ 昇格したかどうかを確認する kubectl apply されてから、実際に 参照元 に反映される(昇格される)まではタイムラグがあります。正確に preview Service や active Service が切り替わったタイミングを外から観測できるようにするため、Datadog logs に SavedView を用意しています。(諸事情あってまだ Argo CD や k8s view tool は入れていません) time="2020-11-27T04:46:53Z" level=info msg="Switched selector for service 'sample-service-active' to value '764cd777bd'" namespace=sample rollout=sample-bff-rollout 特定 Rollout の再起動 Config の修正だけを反映するため、特定の Rollout を再起動したいときがあります。しかし、 kubectl-argo-rollouts restart コマンドは、指定されている strategy (Blue-Green)とは関係なく Pod を再起動するだけです。せっかく Blue-Green Deployment を導入しているので、それを使って再起動相当のこと(つまり、同じ Image Version で新しい Pod 群をつくって入れ替え)をしてほしいのですが、Argo Rollouts コマンドではいまのところできません。ユーザー影響の少ないときは移行前と同様に Pod の再起動をしています。どうしてもダウンタイムが許容できない場面では、 ワークアラウンド として、Rollout の適当な annotations value を書き換えることで通常のフローで Blue-Green Deployment するようにしています。 移行後 移行後は、ユーザーへの価値提供が早くなったと同時に、開発チームが他のことに時間を割きやすくなりました。 リリース回数 * 30min * 人数 なので、なかなか コスパ のよい改善だったと思います。(もちろん移行前は何もせずに待っていたというわけではないですが) 本番リリースの予告はやめ、事後に自動通知 無駄な待ち時間がなくなり、リリー スリード タイムが短縮 わかっている課題 それほど大きなものではありませんが、下記のような課題が残っています。 BFF からの gRPC 接続が Cache されているので、Rollout の昇格と同時には切り替わらない 暫定的に scaleDownDelaySeconds を短くすることで強制的に再接続させている preview Service で 結合テスト することができない 現状、自動昇格を採用しているが、将来的に手動昇格にして preview Service で事前に動作確認できたほうがよい これをやるためには preview Service 同士をつなぎ合わせる変更やルートの設計が必要 自動昇格の優先順が指定できない Service 間には依存関係があるので、Backend -> BFF -> Frontend の順に昇格させたい 参考 https://speakerdeck.com/hi1280/amazon-eks-know-how?slide=32 おわりに あまりにもあっさりできてしまうため、ほとんど書くことがなかったのですが、Argo Rollouts で簡単に Blue-Green Deployment できるということが伝われば幸いです。もし間違いやもっといい方法があれば教えていただけると助かります。また、CADDi では SRE/技術基盤エンジニアも募集しています。まだまだたくさんやるべきことがあるので、少しでも興味がありましたら Twitter からでも採用ページからでも気軽にご連絡ください! https://caddi-careers.studio.site/jobs-tech-sre 明日の CADDi Advent Calendar は、agate-pris さんによる「Rustを採用している社内プロダクトの リファクタリング をしたときの話」です。
こんにちは。CADDi でエンジニアリングマネジメントをしている村上です。 この記事は CADDi Advent Calendar 2日目の記事です。昨日は、いなむさんによる C++20 Approach to Units of Measurement でした! キャディでは、メンバーが持ち回りで発表する勉強会「STUDDi」と、特定領域について講師役を設定してシリーズ物でレクチャーを行う「STUDDi ゼミ」の2種類の勉強会をそれぞれ週1回のペースで行っています。 これまで「インフラ知識」や「ドメイン駆動設計」について扱ってきた「STUDDi ゼミ」ですが、私が9月に4回に渡って紹介したのが 「良いチーム開発を実現するためのアジャイル」 というテーマでした。その内容の一部として、 より良い "チーム開発" を実現するために必要な「組織的な学習」と「自己組織化」 について話したことをダイジェストでまとめようかと思います。 採用サイトや Wantedly の求人としては出していませんが、 アジャイルコーチ的な立場で開発チームを改善するロールの方のジョインも心待ちにしています! チームで学習すること、学習フレーム、心理的安全性 アジャイルソフトウェア開発宣言 の冒頭には 私たちは、ソフトウェア開発の実践あるいは実践を手助けをする活動を通じて、よりよい開発方法を見つけだそうとしている。 という言葉があります。「ソフトウェアのよりよい開発方法」を導くために、自らの実践や他者の実践の手助けの経験を重視するこの態度は、「 経験主義 」と呼ばれます。 「アジャイルなソフトウェア開発プロセスのひとつ」と説明されることの多い スクラム では、「 透明性 」「 検査 」「 適応 」という「3つの柱」を重視していますが、これも「チームの経験から知識を得て、起きたことや経験したことの観察に基づいて意思決定を下す」という経験主義を支えるためのものです。 『 チームが機能するとはどういうことか 』という書籍では、簡単には因果関係を突き止めることの出来ない複雑な問題解決に臨むチームを成功に導く鍵は 「仕事を学習の機会と捉えるか」だと紹介されています 。(このような態度は「学習フレーム」と紹介されており、真反対の態度が「実行フレーム」と呼ばれています。) これも一種の「経験主義」と言えましょう。 このような組織ぐるみでの「経験に基づいた学習」を促進するために重要となるのが「 心理的安全性 」です。 「心理的安全性」とは「 厳しいフィードバックを与えたり、真実を避けずに難しい話し合いをしたりできる 」状況や、「 何かミスをしても、そのために他の人から罰せられたり評価を下げられたりすることはないと思える 」状況を指します。 成長の材料となるはずの「失敗」から目を背けたくなるのは人間の直感的な心理でしょう。その失敗を直視するために必要なのが「心理的安全性」です。 今や広く知られる言葉となったこの言葉ですが、一方で曲解されるケースも目立つと感じています。「心理的安全性」を、単純な「居心地の良さ」や、「何を言っても許される雰囲気」を指すと言われる状況はしばしば見かけます。チームリーダーは、心理的安全性の担保を目的とするのではなく、それによって チームに学習フレームを植え付け、チームがイテレーションごとに強くなってゆくことを目指すという「本来の目的」を見失わないようにしたい ものです。 自己組織化に至るまでの「3つのモード」 チームの文化を実行フレームから学習フレームに移行させる ("リフレーミング" と言います) ことや、それ以外にもチームが「何とか上手く回っている」状況を実現し、維持することがチームリーダーの大きな仕事です。スクラムというフレームワークで言えば、スクラムマスターという役割が主に気を配ることになるでしょう。それでは、 チームはいつまでも チームリーダーやスクラムマスターによるケアやフォローを必要とし続けるのか? そこで重要になるのが、チームの「自己組織化」です。 チームリーダーは、いつまでも自分に依存するチームではなく、自分の元からいつでも巣立ってゆける「自己組織化されたチーム」を作るべきでしょう。『 The Great ScrumMaster 』(邦訳: 『 SCRUMMASTER THE BOOK 』)にも、「自己組織化されたチームの構築に努め、自己組織化を、組織のあらゆる階層において重要な原則にする」ことがスクラムマスターの目標であり責任である、と書かれています。 自己組織化されたチームを目指す上でひとつの指針になるのが『 Elastic Leadership 』という書籍にて紹介されいる、「 チームの3つのモード 」という考え方です。 ※ 書籍『エラスティックリーダーシップ』を参考に筆者が作成 自己組織化されたチームの実現への道は「サバイバルモード」から一足とびには叶わず、まず "火の着いた締切による炎上や難局" を切り抜け、チームを「学習モード」へと遷移させる必要がある、というのが「3つのモード」の特徴です。ここらへんの話は『チームが機能するとはどういうことか』の内容とも通づるところもあり、納得感があります。 GROW モデルと5つのツール スクラムマスターとしてチームの自己組織化を促したり、もしくはメンターとしてジュニアメンバーの成長に貢献したりする際にヒントになるのが 「GROWモデル」です。これは筆者自身が過去に Ebacky による 認定スクラムマスター研修 を受講した際に習った話です。 「誰か」(個人だったりチームだったり) がどこかに向かうとき、まず「 R eality: 現実」を確認した上で「目標: G oal」を定め、そこに至る「選択肢: O ption」を洗い出してから、どの選択肢を「意思: W ill」をもって決める、という枠組みが GROW モデルが示すものです。そんな「誰か」を手助けするときに有効なアプローチが5つ存在し、状況に応じて適切なアプローチを選択する必要があります。 Situationing: 本人の現状を整理し、以下に述べる他の4つのアプローチのどれを適用するべきかを判断する Teaching: 本人に分野や概念の存在をインプットする。知識を一から十まで教えるというより、「より深く調べたい、勉強したい」と思わせるためにインパクトを残すイメージ。 Coaching: 本人の中に潜在しているが自覚していない目標や答えを、問いを通して本人から引き出すことで明らかにする。 Mentoring: 本人にとって明確なゴールへの進み方に迷っている状況で、選択肢を提示して本人に選ばせる。 Facilitating: 「促進する」。脇道に逸れないようにする。現状から目的までの道のりから「脇道」に出た行動を軌道修正させる。 キャディではこの1年間どうだったの? 私自身のキャディでの仕事のひとつは、"klein" と呼ばれるサプライチェーン管理システムの開発・保守・運用を行うチームのコーチングを行うことでした。私たちのチームは「理想的なチーム」と呼ぶには道半ばだと思っていますが、 この記事で言及した概念を STUDDi ゼミにて紹介した9月から現在までの短い期間でも、チームの良い変化をいくつか感じることがあります 。 チームとしての学習 スクラムの定義するイベントの中で、レトロスペクティブ (振り返り) はチームの成長のための戦略を考える重要な場です。 私が klein チームに加入してから、様々な振り返りのフレームワークを紹介したり実践したりしてきましたが、 「振り返りを何故やるか」の理解がチームで共有できたタイミングでクオリティーがぐっと上がった実感 があります。 最近はホワイトボードアプリの miro 上に「klein チームの振り返りボード」を用意して、毎週の振り返りディスカッションログを残していますが、振り返りが「前回の振り返りからチームはどう変わったか」を積み重ねるような進み方をするように、自ずと変化していきました。 スプリント計画における自己組織化 私が加入した頃の klein は、 "誰が" "どの" プロダクトバックログアイテム (PBI) に取り組むかのレベルまで、プロダクトマネージャー (PdM) がケアしている光景がありました。一方で現在では、 チームの開発者たちがスプリントゴールの妥当さや、その達成に向けた作戦を主体的に決めるようになりました 。まさしく「自分たちのするべきことを自分たちで決める」という ”自己組織化された” 姿です。 特に顕著だったのがサブチームの流動化です。klein チームは、ある時期より 3~5人程度の固定化された2つの小規模サブチームで構成されていましたが、振り返りミーティングでの議論を通じ、スプリントごとにスプリントゴールやその達成方法に応じてサブチームをスプリントごとに形成する、という運用になりました。 メンター制度で他者の成長に貢献する風土 キャディのソフトウェアエンジニア職の採用では、経験のない技術要素でもなるべく早く実戦で成果を出すことができるような学習力や自走力を重視しています。しかし、メンバー間でのスキル差は存在するし、他のメンバーと比べて何らかの側面で能力がビハインドしているメンバーには、より一層の成長の機会を提供することが必要です。 このような状況で、キャディのテクノロジーチームでは, 必要な部分から少しずつ メンター・メンティーの関係を構築し始めて おり、またメンター同士の情報交換として隔週30分のフリーディスカッションをする時間を設けています。メンター側も迷いを持つこともある中で、 メンタリング/ティーチング/コーチング/ファシリテーション というアプローチのバリエーションを知っていることで、メンターとしての働きについて客観的な視点を持って振り返ったり議論ができているのではないかと思います。 理解は容易、習得は困難 さて、こういった概念や態度をキャディのエンジニア全員が完全に身につけたのか、と問われれば、答えは「No」だと思います。スクラムについてスクラムガイドが言っていたように、 このような姿勢や考え方は「理解は容易」だったとしても「習得は困難」 なものでしょう (2020年のスクラムガイド改定にて、この文言は削除されたようですが)。私自身だって、知識は頭の中にインデクスとしてはあるけど、それを日々の仕事で体現できているかというと、自信が持てないことも多々あります。 そもそも、「自己組織化」という概念に「完成」は存在しない とも思います。 では、そんな「困難な習得」に攻略法はあるのでしょうか?成功が約束された方程式は無くとも、近道は存在するのではないかと思います。それは 「同じ意識を持ったチームで仕事をする」こと です。 漫然とタスクをこなすチームになるのではなく、常に「自分たちは正しく問題にアプローチしているか」「チームでの学習を促すために自分はどう振る舞うべきか」という視点をチームで共有し、メンバー間の相互作用でチームを強くすることが一番の近道 なのだと、この2ヶ月のチームの成長を目の当たりにして改めて思う次第です。 ぜひ、そんなチームにジョインしませんか?こんな風にチームの成長をサポートするアジャイルコーチを付けたいけど付けられてないチーム、まだまだあります。 ご連絡はこちらからどうぞ 明日の CADDi Advent Calendar は、飯迫さんによる Argo Rollouts の話です。
CADDi で アルゴリズム 系の開発を担当している寺田です。ぜひ一緒に アルゴリズム 開発に携わってくれる仲間を募集したいと思い、筆を執りました。通常の Tech Blog よりPR色の強い内容となりますことをご容赦ください。 そこに課題があるから アルゴリズム が好きな方であれば、 「解くに値する面白い課題」 を欲しているのではないでしょうか。 教科書を読めば、様々な アルゴリズム の技法を学ぶことが出来ます。その技法を使えばどんな問題が解けるかも分かるし、身近なライブラリの背後で使われている理論的背景を知ることが出来ます。 しかし、教科書の例にドンピシャに当てはまる仕事に巡り会う機会はそうあるものではありません。多くの アルゴリズム は既にライブラリとして 実装済 みであり、自分で実装する機会は多くありません。また、現実の問題は教科書のように整理や定式化がされていませんし、場合によっては問題と認識すらされていません。したがって アルゴリズム を活かすには、まず現実を「観察」し、そこから問題を「発見」し、それを アルゴリズム の問題として「定式化」していく必要があります。 こういった機会を得るためには、 問題が顕在もしくは潜在する「現実」 の近くに身を置く必要があります。加えて、 アルゴリズム という選択肢に 戦略的価値 を見出す「組織文化」 が必要です。 この両方が、CADDi にはあります。 調達業務の現実 まず「現実」について。 CADDi は 多品種少量生産 の製造業における受発注プラットフォームです。ただし、受発注を結びつけるだけのマッチングサービスとは違います。 CADDi は仲介業者ではなく、CADDi 自身が受注し、製造を請け負っています。 実際の製造はパートナー企業にご協力を仰いでいますが、品質や納期は CADDi 自身が責任を負って受注するモデルとなっています。 これが意味するところは、問題がゴロゴロしている「現実」と常に隣り合わせであるということです。他品種少量生産の調達業務には、多くの非効率と苦しみが横たわっています。この問題を解くのが CADDi に課せられた使命です。この 調達業務に CADDi が自ら身を置く ことで、突き刺すような現実のペインに晒されることになり、これが問題解決への強烈なモチベーションにつながっています。 困難の一端を紹介します。 まず、ほとんどの案件において、発注部品の仕様は 2次元の図面で届きます 。図面は、DXF のようなCADデータですらありません。画像なのです。それも、紙に印刷されたものをスキャナで取り込んだ画像です。こういった図面が、ひとつのPDFファイルにまとめて送られてきます。大型案件では、PDFは1000ページを超えることもあります。 これを聞くと驚かれるかもしれません。製造業は今や 3D CAD が当たり前であり、3D データを末端まで流通させて効率化していると思われている方も多いしょう。そして実際、自動車・航空・電機、その他多くの業界で 3D CAD は使われています。しかし、「 多品種少量生産 」の「調達」という業務においては、3D CAD のデータが流通することは多くありません。様々な理由が考えられますが、3D CAD には寸法交差や表面粗さといった加工に必要な情報が乗せられない(乗せにくい)ことが理由のひとつかもしれません。 ともかく、これが現実です。 私たちは図面(画像)と向き合わなくてはなりません。ここに多くの「解くべき課題」が横たわっています。 図面には大抵、「図番」という(一意に見えて実は一意ではない)番号が振られており、図面に右下にある表に記載されています。しかし、繰り返しますが、これは画像であってテキスト情報ではありません。従って、1000ページに及ぶPDFから図面を探したくとも、図番で検索することが出来ません。何らかの工夫がなければ、取引先からの問い合わせのたびに目で探すことになってしまいます。(弊社ではかねてより管理シートから該当図面のページ番号を辿れる体制を作っており、最近では図面管理用の社内システムもローンチされました。) 加えて、図面はたびたび「差し替え」が発生します。なんらかのミスの修正や、設計変更が生じうるからです。こういった差し替えが生じると、修正は軽微だとしても、図面の管理コストは跳ね上がります。「xyz.pdf に格納された1000枚の図面のうち、図番 AB-123-345 と図番 CD-987-654 だけは xyz2.pdf の図面を使用する」といったオペレーションは煩雑で、ミスを誘発します。また、差し替え前後の図面はよく似ていますから、その違いを漏れなく読み取るにも注意が必要になります。ミスは不良の発生に直結しますから、影響は甚大です。 問題は複雑であり、複合的であり、単一の施策で全てが解決するわけではありません。ある種の問題は管理システムの構築がソリューションになります。既にテックチームでは図面を管理するためのシステムを社内リリースしており、社内の図面管理体制に変革を起こしつつあります。しかし別種の問題は、Webとデータベースだけでは解決できません。 アルゴリズム が必要だ 、と私たちは考えています。 Orama プロジェクト 弊社では今、図面の画像処理 アルゴリズム を開発するプロジェクトが始動しています。そのプロジェクトは "Orama" と 命名 されました。Orama とは 古代ギリシア 語で「視覚」を意味する語で、panorama の語源となった言葉です。図面管理システムに図面を読み取る「視覚」を授けることが、このプロジェクトの目的です。 課題のひとつは、 図番の自動検出 です。基本的には、 OCR でテキストを検出します。しかし検証の結果、図面をそのまま OCR に渡しても実用に耐えるテキスト検出は望めないことが分かりました。図番は右下の表に書かれていますが、表の罫線が OCR の読み取りを邪魔するようです。従って、まず 外枠や罫線を自動認識 し、これを消去した上で OCR に入力する必要があります。図番が書かれたセルを特定する必要もありますので、その意味でも表の認識は必要です。 下図が、現在得られている成果です。左の入力画像から表の罫線を ベクター データとして検出し、表のセルを認識することに成功しました。(右の画像は SVG 形式でエクスポートされたものです) 他にも 差し替え図面との差分検出 など、さまざまな課題に取り組んでいます。 プログラミング言語 は、主に Python と Rust を使用しています。 Python は OpenCV との親和性がよく知られていますが、Rust は目新しさがあるかもしれません。 Rust は安全性とスピードを兼ね備えているので、パフォーマンスが求められる アルゴリズム の開発には本当に適している と実感しています。 Python + OpenCV だけで賄えない理由は、 ベクター データの処理が必要になるからです。上で紹介した表の罫線認識では、まず画像を ベクター データに変換してから様々な アルゴリズム を組み込んでいます。 ベクター 化 アルゴリズム を含めた各種の アルゴリズム の実装に、Rust が活躍しています。罫線の認識処理は、画像ファイルの読込からセル情報( JSON )のエクスポートまで 200〜400ms ほどで処理が完了します。 アルゴリズム の喜び 上で紹介したテーブル認識処理がうまく動いたときはとても嬉しくて、出力結果を眺めてはニンマリしていました。ちょっと気持ち悪い人ですね。 自分が試行錯誤した アルゴリズム がうまく動いたときの喜び は、格別なものがあります。時には、まるで アルゴリズム が知性を得たかのように思えて、作った本人なのに「どうしてこんなにうまく動くのだろう」と不思議になることがあります。特に、ごくシンプルな アルゴリズム の組み合わせて驚くほど複雑な問題に対処できたとき、それを感じるようです。 再帰 的な アルゴリズム に感じることが多いかもしれません。 こういった喜びは、一部の天才だけに許されたものではありません。私は凡才に過ぎませんが、それでもごく稀にですが、この手の喜びの機会に恵まれています(その瞬間だけ、自分のことが天才だと勘違いできます)。こういった機会に巡り会うためには、自分が天才になる必要はなく、何より 「解かれるのを待っている問題」がある環境に身を置く ことだと思っています。 CADDi が向き合っている問題は、もちろんハードルは高いのですが、しかし天才でないと解けない超難問というわけではないはずです(そういう問題もあるかもしれませんが、それは天才に任せましょう)。何故それが解かれないまま残っているかというと、今まで必要に迫られたプレーヤーがいなかったからではないか、と思います。「 多品種少量生産 」の「調達」という業界で、仲介業ではなく自らが製造責任を負うビジネスモデルで、図面の管理をITの力で解決するという強いモチベーションを持ったプレーヤーが、今まであまりいなかったのではないでしょうか。 あまり前例のないポジションに身を置いていますから、解法が定まっていない問題が多く発生していますし、これからも生じ続けるでしょう。そして解決策はひとつではありません。画像処理も解決策のひとつですが、お客様からCADデータで頂ける機会を増やしていくというビジネス的な解決方法も考えられます。これが可能になれば、そのときはCADデータを扱う アルゴリズム の重要度が増すことになるでしょう。切れば血が出る活きた問題が、次から次へと供給され続ける環境であることは間違いありません。 一翼を担うために CADDi はバリューのひとつとして「一丸」を掲げていますが、半年ほど前までは胸を張って言える状況ではありませんでした。 半年前にあったこと、それは社内業務システムのリリースです。リリースに漕ぎ付ける前は、私たち Tech 組織にとってはやや苦しい期間でした。要件は膨らみがち、リリースは遅れがち、目の前の開発に忙殺される毎日ですが、しかしビジネスに何のバリューも提供できていません。ビジネスサイドから見れば、Tech は「何をしているのかよく分からない人たち」だったことでしょう。 しかし、システムをローンチしてからは状況が一変しました。「効率的になった!」という喜びの声が多くビジネスサイドから届きました。それに伴って、多くの改良要望も届きました。Tech の各チームは今までの鬱憤を晴らすように、それらの要望に迅速に応えています。こうして今、 Biz と Tech が両輪となって業務が回る ようになり、私たちはようやく「一丸」になったと感じています。Biz と Tech は、どちらが上でも下でもなく、対等に互いを尊重し合う良い関係の元で仕事が出来ています。 この流れに、 アルゴリズム も乗せたい…! と私たち Orama チームは考えています。 アルゴリズム が持つ可能性や インパク トの大きさは、社内では十分に理解されています。それと同時に、開発のハードルが高いことも知られています。私たちは アルゴリズム を可能性のまま終わらせず、きちんとビジネスに インパク トを与えるところまで作りきる必要があります。そして Biz 組織と「一丸」になって 継続的に アルゴリズム の開発に取り組んでゆく組織文化 を醸成したい、と思っています。 そのためには、今いるメンバーだけでは力不足です。もっと多くの方の知識や経験、そしてアイディアが必要です。 スキルとマインド では、どんなスキルが求められるのでしょうか? 画像処理? 機械学習 ? 数学? 現時点では、私たちは Orama プロジェクトで画像処理に取り組んでいます。しかし、事業の状況は目まぐるしく変化し、それに応じて求められる開発も変わってゆきます。半年後には全く違うプロジェクトに注力しているかもしれません。従って、画像処理を専門家をお誘いしても、その方が入社される頃にはプロジェクトの内容が変わっているかもしれないのです。入社後に「こんなはずではなかった」というミスマッチが生じるのは本意ではありません。 「画像処理」や「 機械学習 」といった特定の How に軸足を置いて、その技術の専門家として活躍したいという方は、弊社は合わない可能性があります。私たちは「 多品種少量生産 の調達」という What に軸足を置いていて、使う技術は要求に応じて柔軟に切り替えるスタイル だからです。ですから、私たちの事業ミッションに共感し、その問題解決をエキサイティングに感じる マインド をお持ちの方が、より親和性が高いと考えています。 その上で、 数学や アルゴリズム についてある程度の素養がある方、が必要です。 ここの 言語化 が難しいのですが、決して「〇〇の定理について自力で証明できるほど深く理解していること」とか「□□の アルゴリズム について実装および正確な計算量の解析ができること」といったことは必要ありません。もちろんこういった知識は持っていれば何らかの形で活かせるとは思うものの、この知識がなければ仕事が出来ないと断定できるようなものはありません。 ただ、「数式」という論理の表現形式を、(プログラム言語と同様に)言語のひとつとして読み書きする素養は必要だと考えます。例えば、 解析学 の諸定理を即答できる必要はないのですが、$\forall \epsilon>0\ \exists N\ \forall n[n> N \Rightarrow |a_n-a|<\epsilon]$ のような式を見て「ウッ…」と思考停止せずに、「どれどれ…」と読み解いていく地力は欲しいように思います。あとは、初歩的な 線形代数 についてはそれなりに理解していることが望ましいでしょう。 アルゴリズム についても同様で、もちろん初歩的なソート アルゴリズム や二分探索木などは教養として理解している必要がありますが、それ以上は「どの アルゴリズム を知っていれば良い」「これを知らないのはダメ」と断定できるものはありません。ただ、ある程度の複雑な アルゴリズム でも書籍や論文を見ながら自分で実装しきる能力だとか、それを現実の問題に適用して試行錯誤した経験だとかは、あると望ましいものです。 結局のところ、先ほど書いたことと矛盾するようですが、何らかの専門分野(とまでは行かなくとも、得意分野)をお持ちだと良いのかもしれません。その専門がドンピシャに活かせるとは限らないのですが、なんらかの得意分野をお持ちの方であれば上で述べたような素養というのは、既に培われているものと思うからです。 その一例として、手前味噌ながら自己紹介をさせてください。 私は20年近く 3D CAD やその周辺の 3D 技術で仕事をしてきた経歴で、約一年前に CADDi に入社しました。そして見積りシステムのバックエンドの開発を経て、Orama プロジェクトに配属されました。3D 関連の アルゴリズム は経験があるものの 画像処理はほとんど経験がなく 、胸のうちには一抹の不安を抱えていました。しかし始めてみれば、図面の黒い ピクセル を点群として取り出すことで、3D 計測点群を扱ってきた経験を応用できました。図面の ベクター データ変換や、その ベクター データを操作する処理においても、三角形メッシュ等を扱ってきた経験が活きました。 基礎があればなんとかなる ものだと実感しています。 ご連絡をお待ちしています ですので、 今お持ちのスキルと分野が違うからという理由で応募を躊躇う必要はありません し、「まずは軽く話を聞いてみたい」というご要望でも問題ありません。 アルゴリズム 開発の魅力は何と言っても、今までの常識をひっくり返すほどの インパク トを事業や業界に与えるかもしれない、という可能性の大きさにあります。これが達成できたとき、 アルゴリズム は事業の非連続的な成長の支えとなる でしょう。この夢を一緒に叶える仲間を募集しています。皆様からのご連絡をお待ちしております。 弊社のエンジニア採用ページへ (https://corp.caddi.jp/recruit/eng)
Why monorepo ?
TL;DR monorepoは 銀の弾丸 ではない frontend / backend 両方を書く場合、GraphQL / Swagger 等を用いた開発の場合などには有用 monorepoの今 JavaScript / TypeScript におけるmonorepoは babel での利用から有名になったかと思います。 lerna の登場以降ライブラリの更新も落ち着き、導入されている企業も多いのではないでしょうか。 メリット・デメリットに関してはいくつか解説の記事があるかと思いますので、この記事ではmonorepo開発のワークフローとその際の注意点などを記載したいと思います。 monorepo開発のワークフロー Single lint, build, test and release process. Single lint linter , formatter などを共通の設定で扱うことでストレスを減らしたレビューを行うことができます husky + lint-staged の導入により、コミットファイルに統一感のあるフォーマットが適応できます https://github.com/caddijp/frontend/blob/ec53aaf7d751d9bc32c1b2ebb896a502f1a46055/package.json#L41-L52 build workspace 内にある複数のpackageを1つのrepository で操作するのに対してメリットを最大限活かすことになると、package間での依存関係が生まれます。 lerna で提供されている --include-dependencies オプションはpackage間の関係を解決するために便利です。 test and release process テストに関してですが、2種類の方法があります <rootDir>/package.json に共通の設定を定義し、テストを実行する pros 構築が容易 1つのcoverage reportを生成できる cons 複数の前提を持ったアプリのテストに適応が難しい (ex. frontend, backend) workspace ごとにテストの設定を定義し、個別の設定環境(babel ,tsconfig, etc) を前提にテストを実行する pros 個別に設定環境を定義できる cons 複数coverage reportを取り扱う必要がある codecov などの利用 https://www.npmjs.com/package/lcov-result-merger などの利用によりcoverage reportの統合が必要 リリースノートに関しては lerna-changelog を採用しています github を使ったリリース管理にはPRをベースとしたリリース管理としてちょうど良いです CADDi におけるmonorepo導入 一部のプロジェクトにて試験的にmonorepo環境を lerna + yarn workspace 機能を用いて導入しています。 GraphQL / gRPC 定義の共有 マイクロサービス内で利用される共通ライブラリの管理 型定義情報を基準に作成した factory 情報の管理 components / hooks といった汎用性の高い関数の提供 抜粋した中の一部ですが、弊社ではこれらをメンテナンスする担当者が共通であることが多いことなどから、統一した管理が行われていることにもメリットを感じています。 また、node.js では依存する dependency が多いこともあり、ライブラリのバージョン管理を行う上でも役に立つ場合があります。 これからmonorepoを始める方に npm v7よりnpm workspace が利用できるというアナウンスがありましたが、 yarn workspace や nrwl/nx , lerna/lerna を使わない標準の仕組みでmonorepoができるようになるかもしれません。 monorepo化のデメリットであるプロジェクトが肥大化しそうな場合や、複数dockerファイルを管理する方法などまだまだ改善の余地もありますが、 疎結合 な設計を行うことで OSS 公開への敷居が下がることなどメリットもあります。 便利なものを公開して世の中を良くしてゆきましょう。 Refs https://blog.spacemarket.com/code/web-frontend-repository-composition-monorepo-or-manyrepo/ https://blog.cybozu.io/entry/2020/04/21/080000 https://efcl.info/2019/01/26/monorepo-release-flow/ https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b https://medium.com/@adamhjk/monorepo-please-do-3657e08a4b70
こんにちは、キャディ株式会社の朱です! 今回は10月27日に弊社のPdM笹口( @sasaguchisan )が登壇した pmconf2020のイベントレポート をお届けします。 発表内容は「 製造業PFの立ち上げ期にPMが向き合った課題と突破口の話 」と題しまして、弊社の原価計算システム立ち上げ時にぶつかった課題とそれを解決するためにとった突破口についてお話しさせていただきました。 本レポートでは実際に発表で使われたスライドを抜粋しつつ、内容を簡単にサマリーする形でまとめようと思います。 また、大変有り難いことに 発表後の ask the speaker セッションでたくさんの質問をいただけた ので、そちらの回答についてもまとめさせていただいています。 詳細が気になった方は以下にスライドの全文が共有されているので参照いただくか、後述のリンクからぜひ カジュアル面談 を申し込みいただければ幸いです!(発表ではお伝えし切れなかったプロダクト開発・事業に関するより具体的な内容もお話しできるかと思います!) https://speakerdeck.com/sasaguchi/pmconf2020-zhi-zao-ye-pffalseli-tishang-geqi-nipmgaxiang-kihe-tutake-ti-totu-po-kou-falsehua それでは前置きが長くなりましたが、レポートの本文に参りましょう! [toc] お話しした内容 キャディが取り組んでいる課題のマクロな話 PMとして取り組んでいる課題のミクロな話 どんなPMか 慶應義塾大学法学部を卒業 新卒SIerで常駐派遣エンジニアを経験 ミスミで情シス+事業開発 Yahoo!でPMチームの立ち上げ 2019年6月にキャディへ what's CADDi? CADDiに関するよくある間違い マッチングだけではないよ → 実態は商流に深く入り込んでファブレスメーカー的な動きをしている why CADDi? 多品種少量加工品の調達難易度の高さ 発注者・加工会社双方にペインが存在 → 「 取引コストが高い 」状態 キャディの解 標準インターフェースの実装と自社見積 → 今回のテーマ データに基づいた最適な選定と確定発注 複雑な受発注及び工程管理の効率化 向き合った課題 原価計算は創業以来プロトタイプで開発が行われていた 事業成長に伴いハイペースで拡張を続けた結果、課題が顕在化 原価計算システムの役割 いつまでたっても固まらない業務要件 課題 戦略・組織が3ヶ月ごとに劇的に変化し、要件も変わる トラフィックが増え続けるのでオペレーションがボトルネックにならないことへの要請が強い 突破口 拡張性の高いドメインモデル、アーキテクチャを常に志向 → 多いときは1日に4時間以上エンジニアと議論していた 詳細なケースまで含めて徹底的に思考しきる 開発よりも早く変わっていくプロトタイプ 課題 リソース不足 チカラワザで頑張ったが追いつけず… 事業成長が止まらない プロトタイプの開発も止まらない 突破口 〇月末までプロトタイプの開発を止める、と社長と握る → 失敗 真の課題 スピード不足 とにかく早く作るという意識が先行し、保守性を犠牲に デリバリー速度がローンチ後にボトルネックになりかねなかった たまたま参加していた t_wada さんの講演のおかげで吹っ切れた 真の突破口 保守性を捨てない覚悟・定期的なリファクタリング "Shift Left"の実現と本質的なスピードの追求 ここのお話については先日投稿させていただいた Tech Blogの記事 に詳しく書かれているので、ぜひご覧ください! まとめ ask the speaker(いただいたご質問 & 回答のまとめ) Q) 要件がどんどん増えていったと話にあったが、ビジネスは上手くいっていたのか? A) 万事上手くいっていた訳ではない。もともとWebプラットフォームLikeにやっていた → 現在ではセグメントを絞って提供している、というピボットはあったりする。 Q) (↑の質問の続き) もともとは多品種少量ですらなかった? A) そこは最初から変わっていない。もともとは顧客セグメントを絞っていなかったのを絞るようになった。 Q) スピード不足について。それに気づいたときにプロダクトチーム対してどのようなアクションを取ったか A) もともとは期限意識が強かったのでウォーターフォールLikeになりがちだったが、まずはそこについてエンジニアに謝ってアジャイルにやっていこうと伝えた。社内のPJ調整は裏で握れるように動き、必要な品質面はチーム内で議論を重ねた。 Q) (↑の質問の続き) プロダクトチームからの反応は賛成だった? A) プラスに受け止められた。こういうスタンスのPMは出会ったことがないと驚かれた。 Q) PMとして発注者・メーカー双方のユーザーとどのように関わっているのか? A) 製造業という業態もあって基本的には顧客・パートナーともに営業が回るのを大切にしている。担当者の声の方が解像度が高いのでそれを参考にすることが多い。より深いヒアリングしたいときは営業に同行したりして聞いている。 Q) 過去を振り返って、事業がスケールしたきっかけ(Bizでもプロダクトでも)になった出来事などありましたら教えてください。 A) 事業的には難しい案件をやったとき。これまでの基盤ではこなせないので新規に開拓したりして一度に伸びることが多い。 Q) 規模感を知るために現在のプロダクト開発(PM、デザイナー、エンジニア、QAなどなど、、)の体制についても可能であれば教えてください。 A) Tech本部が30名くらい。PM3名、デザイナー2名、残りがエンジニア。QA専任は置いていない。 Q) 開発のリソース不足、開発止めるのも失敗した中で、どうやってリファクタリングのリソースを確保されたのか教えてもらえますでしょうか A) リファクタリング用のリソースを純増した訳ではない。sprint内の20-30%を投資すると決めた。最初はベロシティが落ちたが、1-2ヶ月経った後は明確にデリバリー速度が高まった。(例を挙げると、1ヶ月かかると言われていた規模の開発が5日で終わるようになった) Q) リソースないのになんでRustを選定したんだろう? A) もともとC++でアルゴリズム開発をしていたメンバーがRustを使って開発したいとなって選定した。アルゴリズムの開発はパフォーマンスが重要なので。Rustは経験者が少ないので最初はみんな結構苦戦している。 Q) リファクタリングする際に何か新しく計測した指標はあった? A) 特にはなかった。もともとSP(ストーリーポイント)を使った見積を行っていたのでそれは継続していた。(もともとスクラムでやっていた) Q) 今後目指されているマイルストーンやプロダクト体制など、どのように目標設定されていますか? A) 3年後にいくらという売上目標は設定している。このあと採用資料を展開するがそこに詳しく書かれているのでぜひ!今後の展望でいうとサービスラインナップの拡充は検討しており、例えばファクタリング事業などはロードマップに入っている。 Q) 最終的にはテスト作成やリファクタリングに踏み切ったとのことですが、なにかのタイミングを見計ったのでしょうか?あるいは、もうムリ!となった時点で舵取りをしたのでしょうか。 A) 明確にタイミングを見計らった訳では正直ない。たまたま参加したイベントでt_wadaさんの「質とスピード」に関する講演を聞き、もともと悩んでいたポイントだったので吹っ切ることができた。また、原価計算のプロダクトは長期的に見るとキャディにとってもコアなプロダクトになるので、デリバリー速度がボトルネックになるのは避けるべきと考えた。 おわりに 駆け足になりましたがイベントレポートは以上になります。ここまでご覧になられた皆さま、ありがとうございました! 発表でもあったようにキャディではまだまだ仲間を募集しています。 https://speakerdeck.com/caddi_eng/caddi-recruit-202008 最新の採用資料 ここに当てはまる方もそうでない方も、 幅広い職種の方々からのご応募をお待ちしております! また、 登壇者の笹口とのカジュアル面談 も可能です。発表で興味を持ってくださった方は、 下記のリンクからまずはお気軽に申し込みください! カジュアル面談リンク
キャディのテクノロジー本部でエンジニアリングマネジメントをしている むらみん ( @mura_mi ) です。 Tech Blog ではありますが、技術者をマネジメントする立場として、 最近取り組んだ 評価制度の改善 についてご紹介します。 大事な話を先に! キャディでは、各領域のソフトウェアエンジニアのみならず、 エンジニアリング・マネージャーも絶賛採用中です!! この記事で紹介しているような、エンジニアリング職メンバーの生産性やキャリア開発に直接関わるような仕事を日々頑張っています。少しでも興味をお持ちいただけたら、当ブログ右上のリンクや、Wantedly の求人記事からカジュアル面談申込みいただけると幸いです。 状況 キャディには "HELIX" という人事評価制度と, ミッショングレード制度があります。 https://twitter.com/yushirodesu1/status/1266729912192270338?s=20 これは社員数がかなり少ない時代から整備され運用されてきたものであり、この制度に基づいた四半期ごとの人事評価 (正式評価は半期ごと) はすでに1年以上運用されていました。私自身、前職のスタートアップ企業にて評価制度やグレード制度が無いまま組織が拡大したことの負の側面も体験していたので、これらの制度の存在を知った際には良いことだなと素直に感じました。 一方で、HELIX 自体は制定当時のキャディの人数構成比もあり、ビジネスメンバーの評価に重きが置かれていた節がありました。具体的なポイントは後述しますが、ソフトウェアエンジニア (以下、単純にエンジニアと呼びます) のメンバーが増える中でそのギャップを早めに埋める必要がありました。 また、エンジニアメンバーが増えていったことで、それまでの 「CTO ひとり による全エンジニアの評価」に様々な課題が生じていました 。 CTOも人間なので与えられた時間は有限であり、エンジニアのヘッドカウントが20人を越えたあたりで、「CTO ひとりが全員の自己評価に目を通し、全員のアウトプットを直接確認して評価を下す」という運用には限界が来ていました。 単純に評価対象者が増えたことだけでなく、人数が増えることで、CTOが普段の仕事を直接見る機会が少なくなったことも難しさの背景でした 。そこで、自然と、テクニカルリード的な役割を果たしているメンバーを評価者に任命し、評価という行為を組織の機能に埋め込もう、となりました。 こういった課題について CTO の小橋と私が話し始めたのが2020年6月頃で、 2020年9月の評価期に間に合うように評価制度運用の改善に着手しました。 やったこと まず、評価制度運用の改善に取り組むことをエンジニアのメンバーに伝えつつ、CTO判断で評価者となるメンバーを決めました。このときのアナウンスは社内Wiki (Docbase) に内容をまとめつつ、定例ミーティングにてスライドモードで表示しながら説明しました (以下一部抜粋)。 その後、実際に行った取り組みは、細かいものを挙げるとキリがありませんが、大きなアクションは以下の2点でした。 評価水準の適度な具体化 セルフレビューシートの改善 それぞれ詳説します。 評価水準の適度な具体化 キャディのエンジニア向けのミッショングレード (以下, MG) は、 「技術力(専門性)」「価値提供」「組織貢献」の3つの評価軸 から構成されています。(以下は社内向け資料より抜粋) しかし、 MGの評価軸と、各軸の水準に関する記述は以前より設けられていましたが、その記載内容は比較的ファジー でした。その記載を元にこれまでどのように評価を下していたかを素直に述べると、それは CTO のさじ加減でした。実際、取り組む問題が移ろうスピードも早く、エンジニアリング組織としての熟達度も高まりきっていない中、業務の中で重視されるスキルや働き方を敢えて予め定義せずに、評価を CTO の判断に任せるというのは一定の合理性がありました。 また、 「CTO が評価を一人で回すこと」の大きなメリットのひとつは、評価基準が一貫し、被評価者間での評価結果のズレが起きづらかった点 です。複数人で評価を回すことで、評価結果が評価者によって高く or 低く付くという傾向を後から調整する必要が出てきますが、予めそのブレが生じないような工夫ができるならしたいものです。 そこで、ミッショングレード水準の記述について 「評価要素の分解および具体化」と「記述の適切な詳細化」をする ことにより、評価者間での目線を揃えやすくするようにしました。 具体的には、以下のステップで評価要素の分解と詳細化を行いました。 直近の評価 (CTO一人で行ったもの) の評価結果の理由についてヒアリングする 評価要素の候補となるものを洗い出して、仮で評価要素と水準のマトリクスを作成 仮で作ったマトリクスに従って、2020年6月時点のメンバーの評価を改めて行ってもらう。 (これを "バックテスト" と呼びました) ・・・① バックテストの内容を鑑み、「実は評価には大きな意味をもたらしていない」要素のあぶり出しや、評価水準の記述の改善を行う ・・・② 上記の①→②を2回ほど繰り返して一旦確定 この MG水準 の文書化にあたっては、CircleCI の Competency Matrix が大いに参考になりました。 また、このMG水準の具体化に取り組むにあたり、以下の点に注意しました。 適度な抽象化/具体化 具体的すぎると、スキルや実績の星取表となってしまうが、ミッショングレードは従業員に期待する職責の表現であるべきであり、ある程度、マネジメントや評価者による主観やメッセージを入れられる余地を設けたい。一方で、抽象的すぎて解釈の幅が拡がりすぎてしまうことは避けたい。 過度に厳密性や客観性にこだわらない 人事評価は「最強の評価関数づくり」ではない。メンバーが評価内容や報酬に納得感を持ち、成長支援につながることがメインミッションであることを常に念頭に置く。 評価基準の「刷新」ではなく「明文化・形式知化」 これまでの評価要素を明文化して、これまで行っていた評価を CTO に過度に依存せずに行えるようにすることが目的。今回のプロセス見直しが理由で過度に「評価されるようになった」「評価されなくなった」と思うメンバーが極力出ないようにする。 セルフレビューシートの改善 HELIX では、評価期間の終わりに「セルフレビューシート」に自己評価を記入して評価者に提出し、評価者はこれをベースに評価を行います。 このレビューシートは、個人レベルまで OKR や実績数字を事細かにトラックするビジネス職メンバーの評価を主眼としたものでした。そのため、「期初に建てた評価軸ごとの目標」と「目標達成のために行ったこと」を記入するフォーマットとなっていました。 これが、エンジニアリング職のメンバーが記入するには少々不便なものでした。キャディのエンジニアの仕事には、 数字には表れない部分も含めた技術的投資の判断をすることが求められたり、計画に追従することより変化に対応したり、複雑な業務をチーム全体で理解・モデリングしながらの価値提供といった定性的な側面 が重要であると考えています。実際、期初に立てた目標の通りに行動できたかを検証するフォーマットであったが故に、 期中に生じた突発的な対応についてセルフレビューにて全く言及されない、という現象も過去の評価ではしばしば発生 していました。 そこで、エンジニア向けのセルフレビューシートのフォーマットを少々改良し、「期初に建てた評価軸ごとの目標」と「各MG評価軸についての自己評価」の間に、 「期中の主な仕事について振り返る」セクションを設けました。 これは、 いわゆる "アジャイル開発" における振り返りにおいて、アイディアを出す前にデータ (事実) を収集する ステップに似ています。 3ヶ月に渡って濃い時間過ごしていると、評価対象期間の序盤については記憶が定かではないこともよくあるので、GitHub の評価期間中の Pull Request 一覧や、Docbase (社内用wiki) に期中に書いた記事一覧を確認できるようなリンクを設けるなどの配慮をしました。 実際、メンバーの Slack times チャネルを見ていると、この変更はかなり好評だったようで、企画冥利に尽きました。実際、評価する側にとっても実際に書いたコードや Pull Request などのアウトプットを確認するポイントを絞って重点的に見ることが出来るようになり、印象での評価や「振る舞いが目立った者の勝ち」な評価を防ぐ上でも大きな助けになりました。 やってみてどうだったか これらの取り組みを経て、2020年9月末のエンジニア評価はなんとか無事完了しました。次回以降、しばらくは今の仕組みの微修正で評価プロセスを回していくことは出来そうです。 (初回特有のモタツキも次回以降改善できるはず!) また、ちょうど現在、キャディのテクノロジー本部では評価結果の被評価者へのフィードバックの真っ最中であり、実際にメンバーがこのプロセスを経験してどう感じたかの意見はヒアリングしなければなりません。 そして当然ながら、 人事評価制度の良し悪しは、短期的な視点よりも、その制度の上でメンバーが実際に成長していったかどうかが重要です 。評価自体は目的ではないことを忘れず、今後も、組織の状況を踏まえて柔軟にアクションを取ってゆく必要があるんだろうなと考えています。特に、ミッショングレード水準の記述は継続的に見直していく必要があるでしょう。 加えて、 思わぬ副作用 として現れたのは、 MG の要素分解や詳細化を行ったことで、採用面接がスムーズになったこと でした。採用面接の質問項目や、どのような側面をどの程度深堀りするかはいつも悩む難しい問題なのですが、MG表として明文化した評価要素を念頭に入れることで、社内での具体的な活躍イメージや期待を持ったり、その期待が妥当なものかを確認する面接が出来るようになってきました。これは、スクラムに於いて完了の定義 (Definition of Done) に基づいてスプリントバックログを書き出す作業に似ているな、と感じました。 というわけで 自分自身、前職にてピープルマネジメントに従事していた時期はあったものの、ここまで人事企画的な仕事に携わったのは初めての経験だったので、非常に刺激的でした。 キャディのエンジニアリング・マネジメントは、今回紹介したメンバーマネジメントのみならず、チームビルディング、プロジェクトマネジメント、開発プロセスマネジメントといった領域でその時々の課題を解決しています。 未だエンジニアリングマネージャーがいない (けど、課題が山積みな) チームもあります。興味を持った方は、ぜひカジュアルにお話できれば幸いです。 それでは。
はじめに TL;DR どんなプロダクトなの? 高度な「見積」業務を民主化する 加工をモデル化して誰でも扱えるように そして本格システム化へ… デリバリー速度改善の3STEP チカラワザ期 もともとあった仕組み プロジェクトの前提にあった不確実性 突破口となった施策 整備期 課題の分解 解消に向けた取り組み Shift Left 期 残った課題 やったこと 余談 学びの抽象化 これから 事業成長に負けない「本質的な速さ」を追求する というわけで… We are hiring ! 参考文献 はじめに はじめまして、キャディ株式会社で原価計算システム(詳細は後述)の QA 兼 PM的ロールを担当している朱( @shugenshugen )と申します。 キャディには2019年の1月にジョインし、約1年間事業開発やデータ分析基盤の整備に携わった後、プロダクトチームに転属されました。 今回お話しするのは、自分が所属しているチームにて直近半年ほどで見られた「プロダクトのデリバリー速度向上のための取り組み」についてです。 TL;DR はじめに、どんな課題があってどのように解決したのかについて簡単にまとめます。 まず、課題については明確に デリバリー速度の遅さ がありました。 (開発当初のリリース目標を複数回に渡って超過しており、チームに求められる開発スピードに追いつけていない状態でした…) これを分解すると、 テスト工程のボトルネック化 開発プロセスの非効率性 が原因として存在しました。 対してチームが採ったアプローチは大きく以下の2つです。 テスト自動化 によるボトルネックの解消 開発の 前工程への染み出し による開発プロセス効率化 取り組みの結果として、当初は 約1ヶ月かかっていた原価計算ロジックのメジャーアップデートが、約1週間で実現できるように なりました。 以降の項では、プロダクト・チームが置かれていた状況をもう少し詳細に解説しつつ、各開発フェーズでどのような取り組みを行ってデリバリー速度を改善していったかを深掘っていこうと思います。 どんなプロダクトなの? 具体的な取り組みの話に入る前に、キャディで開発している「原価計算システム」について少し解説します。 現在キャディでは製造業の受発注領域に特化して、取引を効率化するために複数のプロダクトの開発を進めています。 その内の1つが「原価計算システム」です。 (キャディの開発体制や他のプロダクトについては こちらの記事 も参考に!) 高度な「見積」業務を民主化する キャディでは「お客さまから図面をいただき、製品を製造して納品するまでの全責任を負う」というビジネスモデルを採用していますが、その取引プロセスの中で製品1つ1つに適切な価格を算出する必要があります。 そして、キャディが主に取引の対象としているのは製造業の中でも「特注品」と呼ばれるカテゴリの製品のため、製品の価格といっても一品一様であり 価格算出の難易度が非常に高い ことが特徴として挙げられます。 実際に、この「価格を算出する」業務(= 見積)は、 一般的な町工場だと社長やベテラン社員の方しか担当できない非常に専門性の高い業務 として知られています。 そこで、キャディでは ソフトウェアの力を活用して「見積」業務を民主化する ことに取り組んでいます。 加工をモデル化して誰でも扱えるように 「見積業務の民主化」をするための中心的なアプローチとして取り組んでいるのが、 加工のモデル化 です。 これだけだといまいちピンと来ない方がほとんどだと思うので、具体例を使って説明をします。 例えば、キャディで扱っている製品領域の1つに「板金加工品」というものがあります。ここで言う板金加工とは、金属の板を曲げたり溶接したりして、目的の製品の形に仕上げていく加工の種類です。 (画像は弊社 サービスページ より) さて、このようなボックスの価格はどのように計算されるのでしょうか? 加工の大まかな流れは以下の通りです。 平らな金属の板に穴を開ける(穴あけ) 板を曲げてボックス状にする(曲げ) 曲げた板同士を溶接して隙間を無くす(溶接) 製品の見栄えを良くするために色をつける(塗装) かっこ内はそれぞれ加工の 工程 と呼ばれ、各工程についていくらかかるのかを表すのが 製造原価 という概念です。 基本的にはこの原価の積み重ねで製品の価格は計算されます。 かなり単純化されたチャートですが、この製品の価格は上記のようなモデルで表現できそうです。 実際には、用いる材質や製品の形状によって工程ごとに多種多様なパラメータが存在したり、原価も段取り費用と実加工費用に分解できたりなど、より複雑な要素が価格算出には関わってきます。 加えて更に複雑性を増しているのが、 こうした加工の捉え方が製造業というドメイン内で決して統一されている訳ではない という事実です。 実際に、同じ図面について異なる町工場さんに作り方や製作にかかる費用をヒアリングしても、全く違う製作方法を前提とした金額回答が得られることが良くあります。(そしてこの捉え方の違いこそが各町工場の持つ強み弱みであり、競争力の源泉だったりします。) キャディは町工場のアグリゲーター的な存在であるという立場上、様々な強み弱みを持つ町工場に対して的外れではない製品価格の見積を行い、最適な発注するという責務を負っています。 そのためには、上記のように 業界内でも統一が図れていない幅広い加工に関する知識について、本質的な部分を抽出して繰り返しドメインモデリングを行う ことで、誰にでも使えるシステムに落とし込んでいく必要があります。 そして(まだ道半ばではありますが)キャディは、この取り組みを業界内でもほぼ唯一と言って良いほどの精度で成し遂げている存在であると考えています。 「見積」が一般的には長年の経験を必要とする非常に高度な業務であることは先述しましたが、 キャディでは入社してまず最初に受ける研修が原価計算システムを活用して「見積」が出来るようになること であり、実際に社内(ビジネス本部)のほとんどの人材が業務をこなせるようになっているほど、共通言語として浸透しています。 そして本格システム化へ… 原価計算システムはキャディのビジネスにおけるコアとも呼べる存在のため、その 構築には創業当初から長い時間をかけて投資がなされてきました 。 しかし、どんなシステムでも共通することだと思いますが、長期に渡って開発が続けられた場合には得てして負債が溜まります。 立ち上げ当初はPDCAの回しやすさを重視してなるべくライトな構成で作られていた原価計算システムのプロトタイプも、類に漏れずその道を辿ることになりました。 事業の拡大に伴って取り扱える加工の幅が広がるにつれて、初期に構築したモデルの保守性・拡張性の低さに起因する 品質保証の難しさ、保守運用の職人芸化 が顕在化してきました。 また パフォーマンスやUX面でも課題 を抱えており、システムの利用が社内のオペレーションのボトルネックとなりつつありました。 こうした課題を踏まえて、 保守性・拡張性を考慮したモデルの再設計 実行速度の速いRustによるバックエンドの書き換え UI/UXの全面的見直し などからなる 本格システム化計画 が始動し、現在の「原価計算システム」が形作られることになります。 デリバリー速度改善の3STEP 前置きが長くなってしまいましたが、ここから本題の「デリバリー速度の改善」について書いていきます。 その際に、主にテスト周りの仕組みや開発プロセスのあり方に焦点を当てて、開発期間を以下の3つに分割しようと思います。 チカラワザ期 整備期 Shift Left 期 チカラワザ期 まず最初の期間が、プロダクトの1stリリースまでの「チカラワザ期」です。 ここで生じた課題が、開発の終盤に差し掛かって テスト工程のボトルネック化 が顕在化してきたというものです。 もともとチームで設定していたリリース目標に対して、原価計算ロジックのテストにかかる工数を少なく見積もり過ぎていたことが露見し、数回に渡って目標を後倒しにせざるを得ない状況に追い込まれてしまいました。 もともとあった仕組み この時点でチームに存在していたテストの仕組みは、以下の通りです。 原価計算を担当するコンポーネント内で実装されていた単体テスト Pythonで実装された手動テストツール 両者の住み分けについて補足しておくと、前者の単体テストは工程ごとに最低限のカバレッジしか担保していなかったため、リリースに際して網羅的な品質保証の仕組みとして手動テストツールが誕生した、という経緯があります。 プロジェクトの前提にあった不確実性 これを聞くと「最初からテストの仕組みを担保した状態で開発を進めることは出来なかったの?」という至極真っ当な疑問を持たれる方がいらっしゃるかと思いますが、ここに本プロジェクトにおける最大の不確実性が潜んでいました。 それは、 新規開発を進めている最中にも、プロトタイプ側では事業領域の拡大に合わせてモデルの開発が活発に進む という無慈悲な事実です。 (実際に、開発に着手してからプロダクトの1stリリースまでに、プロトタイプ側のメジャーバージョンは20回以上アップデートされていました。) この前提は当時の事業運営上回避できないポイントだったため、新規開発側では常に要件が不安定な状態での開発を余儀なくされました。 そのため、開発初期には原価計算ロジックについてはテスト駆動開発で開発を進めようと検討がされていましたが、そもそも正となるテスト結果が変動する以上は自動テストのメンテナンスコストの方が上回ってしまうという結論に至り、テストの工程自体も開発終盤に押し込められる結果となりました。 突破口となった施策 最終的に「テスト工程のボトルネック化」という課題自体の解消は次の「整備期」を待つことになりますが、手動テストを用いたチカラワザに頼るしかなかった開発終盤を突破することになった施策があるため紹介しようと思います。 それは、 プロトタイプとの差分の許容値を定量的に定め、リリースの基準とした ことです。 具体的には、プロトタイプを用いて価格算出した過去案件の実績データを用いて、その製品価格との差分が一定の割合以内に収まっていることとしました。 これによって、それまでは工程レベルでわずかにでも価格差分が生じていればバグとして対応しなければならなかったのに対し、金額インパクトが小さいものについてはリリース後に後追いで修正する、という意思決定が容易にできるようになりました。 これが開発リソースのフォーカスに繋がり、数ヶ月遅れではありますが無事にプロダクトのリリースに至りました。 整備期 次に、先述した「テスト工程のボトルネック化」を仕組みの整備で解決しようとしたのが「整備期」です。 課題の分解 ボトルネック化において実際に生じていた課題を分解すると、以下のようになります。 単体テストの負債化 高負荷な手動テスト テストデータのアップデート工数 まず、単体テストの負債化については先にも述べたようにテスト結果そのものが不安定な状態で開発を続けていたため、プロトタイプ側で原価計算ロジックのアップデートがある度に、関連する単体テストの結果部分も書き換える必要がありました。 この書き換えの工数が地味に重く、 実装の約半分近くを単体テストのアップデートに費やしていた という声もあったほどです。 次に、手動テストの負荷については新規開発の過程でモデルの再設計を行っていたため、プロトタイプと新規開発後のモデルで工程ごとの計算結果の粒度が異なるという事象が生じました。 そのため、異なる粒度間での比較を実現するために手動テストを実行する度に手元で追加の計算をするという対応を取ることになり、 実行の負荷が非常に高い 状態でした。(関連して、新規開発側でモデルのアップデートを行った際に リグレッションテストを実行するのもほぼ不可能 になってしまい、デグレの検知が遅れて終盤の開発を圧迫する遠因にもなりました。) 最後にテストデータのアップデートについてですが、当時は手動テストの実行時にプロトタイプから製品ごとの価格の算出結果をCSVファイルとして出力して、手動テストツールに与えるような仕組みになっていました。 その際にプロトタイプのアップデートがあると膨大な数のCSVファイルも更新することになり、この テストデータの更新作業に毎回丸1日かけて人力で取り組む 必要がありました。 解消に向けた取り組み 以上のような課題の解決に向けて、チームでは大きく以下の2つのアプローチで取り組みを行いました。 保守性が低い単体テストは勇気を持って捨てる 代わりとなる自動テストの整備 1点目の単体テストの切り捨ての是非については、チーム内でも繰り返し議論があったポイントです。 実装工数を圧迫しているのは誰の目で見ても明らかだったのですが、複雑な原価計算ロジックの開発を進めるエンジニアの視点に立つと、ビルド時に合わせて実行できて結果もすぐに分かる単体テストは、やはり心強い味方でした。 それでも、今後もロジックのアップデートは定常的に行われていくという前提に立つと、これまでのように カバレッジの低い単体テストを高いメンテナンスコストを払って維持し続けることはROIが低い よねという結論に至り、勇気を持ってこれを捨てる意思決定をしました。 代わりに注力したのが、2点目で挙げている 自動テストの整備 です。 まず実行負荷が高い手動テストツールに代わる仕組みとして、工程間の価格を自動で比較できるテストツールを新規で開発しました。 その際に意識したのが、 必要であればプロトタイプ側にも開発の手を入れる という点です。それまでは目の前のユーザーに直接価値を提供するような機能にリソースをフォーカスさせる形で開発を進めていましたが、以降は たとえ直接的にはユーザー価値に寄与しない部分だとしても(そして長期的には無くなる仕組みだとしても)、デリバリー速度向上に繋がるのであれば時間をかけてでも仕組みを作った方がトータルの工数は下がるはず 、と方針を転換できたという意味で転機だったように思います。 テスト自動化の文脈でもう1つ実施したのが、 テストデータのアップデートの自動化 です。 改善としては一見些細なもので自動化自体も2日ほどで実現できたのですが、これにより作業時間を確保できるようになり、他の改善活動に時間が回せるようになったという点ではインパクトの大きい施策でした。 Shift Left 期 最後の期間が、直近まで続く「Shift Left 期」です。 「整備期」の取り組みが功を奏し、 テスト工程が開発全体のボトルネックになることはなくなりました が、デリバリー速度は依然として求められる水準に達していた訳ではありませんでした。 前提として、新規開発直後のプロダクトには原価計算ロジックをアップデートするためのインターフェースが存在していなかったため、リリース後のロジック更新はプロトタイプで一度実装したものを再度新規開発先に移植する、というフローをとる必要がありました。 結果的に、リリース後最初のメジャーアップデートに要した時間は1ヶ月を超えました。 これは以前の開発スピードからするとかなりの進歩を見せてはいたものの、ビジネス上の要請からはまだまだ遅いと言わざるを得ず、以降はデリバリー速度を更に短縮していくために 開発プロセスそのものの改善を進めていく ことになります。 残った課題 この時点で残っていた課題は ウォーターフォール的な開発プロセスの非効率性 モデルの初期設計起因の保守性の低さ の2つです。 1つ目について、原価計算ロジックのアップデートでは 要件となるプロトタイプの加工モデルの更新を行う → 新規開発先で実装が必要になる差分を洗い出しチケット化する → 実装 → テストを実施しバグを書き出す → バグ修正 & 再度のテスト実施 という一連の流れを経る必要があり、 開発プロセスの長大化によって発生するコンテキストスイッチの積み重ね や 開発アイテムの管理コスト が大きな非効率性を生んでいました。 2つ目の保守性の低さについては、新規開発のタイミングで解消し切れなかった ロジックの複雑な依存関係 や 仕様バグ が、繰り返しバグを生んだり、仕様確認の工数を肥大化させる要因になっており、ボディブローのように効いてきたタイミングでした。 やったこと 対して、行った取り組みは以下の3つです。 テスト駆動開発による開発プロセスの圧縮 モデルの設計段階からレビューに加わる 計画的なリファクタリング まず、コンテキストスイッチや開発アイテムの管理コストを最小化するために、実装の段階からあらかじめ用意されたテストケースを元に自動テストをこまめに実行して、バグが発生していた場合は合わせて修正するようにプロセスを変更しました。 このようなテスト駆動開発的な手法は、 以前に自動テストの仕組みを整備したからこそ採用できた アプローチでした。 次が、プロトタイプから新規開発先へ加工モデルを移植するプロセスにおける改善です。 ここで1点補足をすると、 プロトタイプにおける加工モデルの実装と新規開発先での実装は担当するチームが分かれている という前提があります。これは、前者のチームにはより深いドメイン知識やビジネスサイドとの折衝といった機能が求められるのに対して、後者にはRustを始めとするエンジニアリングの知識が求められるため、分業による効率性を優先した結果このような開発体制に落ち着いたという経緯があります。 この分業体制は本格システム化が実現する前は、キャディのビジネスモデルにおける要でもある加工のモデルの成長速度を落とすこと無く新規開発側の開発も並行して進めることができるというメリットがありましたが、いざプロダクトがリリースされて以降は単純に 両チーム間のコミュニケーションコスト が足かせになったり、 それぞれのチームに部分最適になった開発が進められてしまう といったデメリットの方が勝るようになってきました。 そこで、徐々に両者の組織的な融合を図っていくための第一歩として、 プロトタイプ側で加工モデルを設計するタイミングで新規開発側の開発視点から設計にフィードバックをする機会 を設けるようにしました。 これによって、仕様バグになりそうなポイントを未然に潰すことができたり、実装を同時並行で進めることで新規開発側の開発プロセスそのものを前倒したりすることも可能になってきています。 最後の改善事項として、直近では開発プロセスの改善で生まれた余剰時間を 計画的なリファクタリング に充てるようにしています。 リファクタリング自体は新規開発を進める途上でも必要に応じて行っていましたが、あくまでも個々のエンジニアが実装の片手間で行うに留まっており、 チームとして負債を計画立てて返済していくという形では実施できていませんでした 。 対して、現在では理想の加工モデルの形をチームで議論し、そこに向かってやるべきリファクタリングの内容を列挙したり、 週次のプランニングでタスクの一部として明確に組み込む など、意識的にリファクタリングを進めることができています。 以上のような改善の甲斐もあってか、直近あった原価計算ロジックのメジャーアップデートでは 実装に着手してから約1週間でアップデートを完了させることができました 。これは諸々の改善を行う前からは想像のつかないペースであり、デリバリー速度の向上として一定の成果を残せたのではないかと考えています。 余談 章題にもなっている「Shift Left(Shift left testing)」とはソフトウェア開発の文脈で、 開発ライフサイクルの終盤で実施されることが多いテスト工程をより上流の要件定義や設計の段階で行うことで、手戻りによるコストを削減したり、設計へのフィードバックを通じてソフトウェアの品質を向上させたりする活動 のことを指します。 本章で挙げたテスト駆動開発によるテスト実施タイミングの前倒しや、モデルの設計へのレビュー(静的テスト)は、この Shift Left の活動の一環と呼ぶことができるでしょう。 似たような概念として、製造業には「 フロントローディング 」という用語があります。 フロントローディングとは、製品開発のプロセスにおいて上流工程である設計の初期段階で最も作業負荷をかけることで、最終的な品質を高めたりトータルのコストを削減する取り組みです。 MONOist 「設計者がフロントローディングという怪物に立ち向かうための“3つの武器”」より これは正に Shift Left に通ずる考え方ですが、ソフトウェア開発というデジタルの世界だけでなく、実物を作るモノづくりの世界でも成立する概念なのです。 実を言うと自分は、最初は Shift Left という概念は知らずにフロントローディングという言葉を使ってこの発表をしていました。そこで、「これはソフトウェアでは Shift Left だよね」という指摘をいただいて知識が広がったという経験があったため、この項を書き加えました。 このように、 ソフトウェア開発とモノづくりという似て非なる領域の知見が Connecting the dots 的に繋がる瞬間がある のが、キャディで開発をしていて面白いところでもあります。 (ちなみに更なる余談ですが、モノづくりの世界でソフトウェア開発における「テスト」に相当するのが、CAD(Computer-Aided Design)や CAE(Computer-Aided Engineering)といった技術たちです。これらについては同じチームに専門家がいるので、そのうち解説記事を書いてくれることを密かに期待しています。) 学びの抽象化 今回の開発から得られた学びを抽象化すると、以下のようになります。 プロダクトに求められる品質を定量化し、指標とすべし 自動テストへの投資を怠るな、小さな改善を侮るな 前工程への染み出しで品質をコントロールせよ チームで改善のムーブメントを維持し続けよう 第一に、バグが一切存在しないプロダクトというものは存在しません。 片っ端からバグを潰していくのはアンチパターン です。 大切なのは、許容できるバグとそうでないバグを切り分けるための定量的な基準を定めることです。これができれば開発リソースをフォーカスさせて一時的な突破力を手に入れることができます。(あくまでも一時的なものであり、本質的な問題の解決には以降の取り組みが欠かせないという点がミソです) 第二に、自動テストへの投資は早すぎると思われるくらいのタイミングから始めるべきです。開発を振り返ると、テスト結果が常に変動するという制約があった以上、初期に自動テストの仕組みを構築しても無駄になってしまった可能性が高いとは言えそうです。しかし、少なくとも開発終盤で手動テストを実施するタイミングでは、 愚直に手を動かすよりは仕組みの構築に投資すべきだった と考えています。このような仕組みの類は 時間が経つにつれて複利で効果を発揮する ものだし、開発が長期化した際には避けられない リファクタリングにも自動テストは不可欠 です。 どうせ後でやることになる仕組みの構築は、多少は全体の進捗を止めてでも先に持ってくるべき 、というのが現時点での自分の意見です。 また、同じく仕組みの構築という文脈で、小さな効率化・自動化の力を見くびってはいけないという学びもありました。開発プロセス改善において「これをやっておけば一度に効率化が進む」といった銀の弾丸は存在せず、小さな改善の積み重ねが最終的なデリバリー速度向上に寄与するのだという肌感が得られました。ゆえに、 日々の開発の中でどこがボトルネックになっているのだろう?と考え続け、効率化・自動化できそうな部分があれば積極的に取り組んでいくことが重要なのだ と思います。 第三に、既存の枠組みに閉じた改善では早晩効率化の限界が訪れるため、プロセスの外に踏み込んだ動きを取ることも重要だということです。 実装やテストをいくら効率化しても、そもそもの要件や設計がイケていなければ理想的なデリバリーはできません 。そんなときは与えられた前提を疑い、視野を広く保って開発プロセス全体を Re-Engineering すべきです。 最後に、以上で挙げたような取り組みはプロダクト・チームが置かれている状況によって着手すべきか否かや優先順が大きく変わると思います。その見極めを行いつつも、 改善に向けたムーブメントをチームで絶えず維持し続けること が最終的な成果に繋げる上での最も重要な要素であると自分は考えています。 これから 終わりに、今後の開発で目指したいことをまとめます。 事業成長に負けない「本質的な速さ」を追求する これまでは「プロトタイプで実装された原価計算ロジックを新規開発先のプロダクトに移植するまでのリードタイム」をデリバリー速度の指標として扱っていました。 しかし、今後は新しい「原価計算システム」が正となる時代がやってきます。 目標としていたデリバリー速度の向上は一定の水準で達成できたと言えそうですが、有り難いことに事業はそれ以上のスピードで成長し続けており、 現状の原価計算システムでは価格算出できない領域が日々増えていっています 。 これに応えるためには、より本質的なデリバリー速度の改善に向けて、既存の 2つの開発チーム間の組織的な融合を進めていく ことや 開発体制のスケーラビリティを確保する ことが必須です。 というわけで… We are hiring ! カジュアル面談のお申し込みは こちら から! Twitter( @shugenshugen )のDMからもお気軽にご連絡ください! 参考文献 Shift-left testing | Wikipedia https://en.wikipedia.org/wiki/Shift-left_testing 設計者がフロントローディングという怪物に立ち向かうための“3つの武器” | MONOist https://monoist.atmarkit.co.jp/mn/articles/2003/11/news002.html
いきなり結論 仕事で docker-compose を使いたく、せっかくなのでWSL上で docker-compose しようとしたら、まったく上手くいかず大苦戦した。本記事はその格闘の記録である。 結論から述べると、WSL上でDockerを取り扱いたい場合、WSL 2、Docker for Desktop、そして Visual Studio Code の組み合わせがオススメだ。 各 コンポーネント のインストールと利用方法は以下のオフィシャルのドキュメントに任せるとして、本記事ではどのような問題が、何故発生したかについて述べる。 Using Docker in Windows for Linux Subsystem (WSL) 2 Docker Desktop WSL 2 backend Windows Subsystem for Linux (WSL) を Windows 10 にインストールする どこで躓いたのか? 問題の要旨は以下の通りだ。 Dockerは docker-ce 17.09.1 以降、 MS_SLAVE を用いる。ところがWSLは MS_SLAVE をサポートしていない。この問題を回避するためには 17.09.0 以前の docker-ce を使う必要がある。 しかしながら、新しいCompose file formatを使うためにはDocker Engine releaseも新しい必要がある。古い docker-ce をインストールし、新しいCompose file formatと組み合わせて使用した場合、クライアントのバージョンが古すぎる、と怒られる。 docker-ce をアップデートすると動かなくなる。 以下無限ループ。 というわけで、WSL上で新しいCompose file formatを使おうとすると デッドロック が発生してしまう。尚、ここで言う docker-ce とは、WSL上で apt でインストールできるパッケージを指す。 この問題を回避するために、WSL上でDockerを使う場合は Windows 上にインストールされた Docker Desktop を使用する。 この際、WSL 2を利用することは必ずしも必須ではないが、前述のDockerのオフィシャルドキュメントでも以下の様に「WSL 2、Docker Desktop、 Visual Studio Code の組み合わせを推奨する」と述べられている。 We recommend that you have your code in your default Linux distribution for the best development experience using Docker and WSL 2. After you have enabled WSL 2 on Docker Desktop, you can start working with your code inside the Linux distro and ideally with your IDE still in Windows . This workflow can be pretty straightforward if you are using VSCode . その他、参考文献 Compose file formatのバージョンとDocker Engine releaseのバージョンの対応は以下のオフィシャルドキュメントを参照。 Compose file version 3 reference MS_SLAVE は以下の記事に詳しい。 jygoro's comment from discussion "docker is running natively on wsl" WSL (Ubuntu 18. 04) にDockerを入れる - Qiita 実際に MS_SLAVE が使用されるようになった変更は以下のコミットを参照。 Use rslave instead of rprivate in chrootarchive · docker/docker-ce@6c6a182
※ STUDDi とは 表題の STUDDi とは、弊社 CADDi の Tech 本部で行われている社内勉強会の名称です。週に一回開催されており、メンバー全員の持ち回り制で何らかの技術トピックについて発表します。 去る7月7日、私が担当した回で 3D グラフィックスについて初歩的な説明をしましたので、その時の資料を公開します。 発表スライド 講師役を務めたものの、私は 3D グラフィックスの専門家というわけでは決してなく、高度なグラフィックス技術については理解できておりませんし、このスライドにも何らかの誤りが含まれている可能性は十分あります。 デモページ デモページ スライドの P21, 22, 23 あたりのシェーダーのデモです。 全く高度なデモではありませんので期待しないでください。 '1' のキーを押すとシェーダーが切り替わるようになっています。適当に作ったので雑仕様ですみません。(ブラウザに Vimium 等の プラグイン を入れているとそちらにキーイベントが取られてしまって動作しないようです。その場合は プラグイン を切ってください。) 勉強会用に強引にデモ機能をねじ込んだので、このデモ機能は将来削除してしまうかもしれません。動作しなくてもご容赦いただきたく…。
業務でRustのコードを書いていて、 rustfmt が失敗する事象に遭遇した。 少し調べたところ、 MatchArms の後にカンマを含むコメントがあると、うまく動かないことが分かった。 以下は2つの連続した改行が1つの改行に詰められることを期待したコードである。 rustfmt はマッチ式全体のフォーマットを諦めてしまう。 fn f() { let x = 0; match x { 0 => {} 1 => {} _ => {} // foo // bar, } } 尚、マッチ式の外のフォーマットは継続される。ファイル全体がフォーマットされなくなったりはしない。 おそらく、コメント中のカンマと MatchArms 中の Expression に対応するカンマの区別が出来ず、混乱していると思われる。 いくらか恐ろしいのは、 rustfmt は上記のコードについてフォーマットが失敗したことを一切エラーとして報告しないことだ。フォーマットに失敗するコードがCIの際に検知されず、デプロイまで素通りしてしまう恐れがある。 マッチ式の途中に意図的に空白だけの行を配置する等の手段でエラーとして報告させることはできる。その場合は error[internal]: left behind trailing whitespace として報告される。フォーマット後のバリデーションチェックで( rustfmt 自身の)エラーチェックを行っているものと思われる。 この事象は GitHub でIssueとして報告した。 rustfmt fail to format if there is a multi-line comment at the end of the match expression and it is terminated by a comma · Issue #4037 · rust-lang/rustfmt
平方分割の練習をしようにも難しい問題ばかり、そんなお悩みに狙いを決めて手取り足取りのレクチャーです! ちなみになのですが、「気になる💓あの子を平方分割!列の区間和を求める恋の特効薬アルゴリズムを、幸運なあなただけにご紹介です。」というタイトルは没になりました。(← これはタイトルでふざけすぎて怒られたのに懲りずにこっそり本文でサルベージする人です。) 対象の読者さん紹介です。 当記事は、平方分割にあまりなれていない方が、簡単なものであれば息を吸うようにスラスラかけるようになるのが目的です。したがってですね…… カモンです。 平方分割の実装は大変そうです。 練習のやり方がわかりません。 ノー・カモンです。 遅延評価のない平方分割なんて…… 親に重めなデータ構造を持たせるのが醍醐味ですよね〜〜 クエリ平方分割で常勝!w どのように練習するのが良いでしょう。 おすすめな練習方法です まず、だめなパターンです。 平方分割が大活躍する問題で練習します。 これはだめです。正しくはこうです。 どうにでも解けそうなものを、平方分割で解きます。 いいですか? 欲張ってはいけません。 まずは使わなくても良いところでもどんどん使って練習していきましょう。 平方根は定数です。適切な場面でだけ使うのは、なれてきてからで良いでしょう。ひたすら素振りです! おすすめの問題です。 入門におすすめなのは Range Sum Query です。実装に挑戦してみたい方は、Aizu Online Judge に練習問題( 🔗aoj-dsl-2-b ) がありますから、こちらに挑戦してみると良いです。 どうして Range Sum Query で練習するのがよいでしょう。 理由は 2 つあります。まず 1 つ目に、簡単だからです。平方分割は難しくありません。難しい問題でよく使うので、難しく感じるだけなのです。まずは簡単な問題で使ってみて、「平方分割自体は難しくない」ということを覚え、本質部分をサッとかけるようにしましょう。 2 つ目に、重なるのですが、デバッグがしやすいからです。難易度だけで言えば Range Minimum Query などでもよさそうなのですが、こちらは答えの情報量が少ないですから、デバッグがしづらいがちです。最適化よりも数え上げのほうが、嬉しいですよね。 Range Sum Query さんを平方分割です! 先程もリンクをいたしましたが、再びです。( 🔗aoj-dsl-2-b ) 概要です。長さ $N$ の列があります。リンク先の問題では、これははじめは $0$ で初期化されているのですが、そうではなく任意の初期値で考えましょう。。ここに 2 種類の クエリです。 クエリ 1: $a _ i$ を $x$ 増加させます。 クエリ 2: $a _ l + \dots + a _ { r - 1 }$ を計算です。 愚直だいすきクラブのコーナーです まずは愚直を書きましょう。無駄にはなりません。各クエリは次のように処理です。 クエリ 1 です。これは、$a _ i \leftarrow a _ i + x$ のように代入をすると良いです。 クエリ 2 です。普通は C / C++ でいう for (int i = l; i < r; i++) のようなことをするでしょうが、今回は i を使わずに書いてみましょう。 まずは新しい変数 $\mathtt{ ans }$ に $0$ を格納します。 $l$ が $r$ と異なる限り次のことを続けましょう。 俺のターン、ドローです! $\mathtt{ ans }$ に $a _ l$ を足します。そのあと $l$ を $1$ だけ増加させて、ターン終了です。(どやぁ) 図解をしましょう。これは $l = 1, r = 4$ のときの図です。それでは皆さんの両手を拝借です。いいですか? 左手を [ に、右手を [ に添えましょう。(はい?) $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ \big[ 30, \ 20, \ 50, \big[ \ 40 & ) \end{array} $$ 繰り返しの $1$ 回目が終わると、$l$ が一つ増えて、もともと $a _ l$ だったものが答えに足されることになります。C / C++ でいうところの、 ans += a.at(++l) です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, \ \big[ 20, \ 50, \big[ \ 40 & ) \end{array} $$ もちろん逆からでもよいです。では先程の疑似コードとは異なりますが、ここから今度は $r$ を減少させましょう。すると、今度は新しく $a _ r$ になったものが答えにたされることになります。C / C++ でいうところの、 ans += a.at(--r) です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, \ \big[ 20, \big[ \ 50, \ 40 & ) \end{array} $$ 愚直イヤイヤ期のみなさんにお送りする、スンマートなアーウゴリーズンです。 数列 $a$ を、$k$ 個ごとに分割です。部分和 $b$ を前計算しておくと、このようになります。しかしただ前計算するだけではいけません。クエリ 1 が来るたびに、配列 $b$ も更新していきましょう。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 10, \ 30, & |& 20, \ 50, & |& 40 & ) \\ b = & (& 40, & |& 70, & |& 40 & ) \end{array} $$ 難しいのはクエリ 2 です。しかしみなさんには手が 2 本ありますね。これは我々人類がうなぎよりもアルゴリズムが上手であることを示唆しています。存分に活用しましょう! 右手左手の準備はよろしいですか? まずは手の置き場所を決めましょう。数列の隙間に失礼して、求めたい区間を両手ではさみましょう。小さすぎると難しいですから、少し大きめな数列にしましょう。また、添字に言及するのも億劫ですし、値も $0, 1, 2, \dots$ にしてしまいましょう! これは、$l = 1, r = 10$ のときの図です。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ \big[ 1,\ 2, & |& 3, \ 4, \ 5, & |& 6, \ 7, \ 8, & | & 9, \big[ \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ 大筋は先ほどと同じです! 左手と右手を近づけていって、ぺったんこをしたら、ゲーム終了です。みなさんの両手は、両思いです。 ただ近づけるだけではいけません。ここが平方分割のスマート・ポイントなのですが、 一気にできるところは一気に足しましょう! アルゴリズムは 3 段階に分かれます。 おそるおそる左手を近づけてポジショニングです。 おそるおそる右手を近づけてポジショニングです。 おりゃーーです。 まずは 1, 2 です。やり方は先ほどと同じなのですが、どれくらい近づけると良いのでしょう。おりゃーーーができるまで近づけましょう。これで $l = 3, r = 9, \mathtt{ ans } = 11$ です。ぐっと睨むと、$l$ と $r$ が $k = 3$ の倍数になっていることがわかります。プログラムを書くときには、これを目印にしましょう! $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& \big[ 3, \ 4, \ 5 & |& 6, \ 7, \ 8, & | & \big[ 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ おたのしみ、おりゃーーのコーナーです。 疑似コードはこうです。 $l$ が $r$ と異なる限り次のことを続けましょう。 $\mathtt{ ans }$ に $b _ { l / k }$ を足します。(この添字は整数になります。) 図解です。$l$ を $3$ 増やすとこうです。$\mathtt{ ans }$ には、$3 + 4 + 5$ が足されるのですが、$b _ 1 = 12$ を見るのがスマートです。次の一手でトドメがさせるのですが、デキるアルゴリズマーは背中で語ります。最後はみなさんでどうぞです。 $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& 3, \ 4, \ 5, & |& \big[ 6, \ 7, \ 8, & | & \big[ 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ ごめんな、父さんな、コーナーケースなんだ…… 今まで黙っていたのですが、先程のアルゴリズムをそのまま実装するのではだめな場合がります。第 1 段階と第 2 段階に注目です。左手と右手は、$k$ で割ったあまりだけを頼りに突き進んでしまいます。たとえばこちらの場合、$l = 7$ は $9$ まで進み、$r = 8$ は $6$ まで進み、二人の思いはすれ違いです。最悪の場合、$l$ は地平線の果てまで負い続けて、ついには……です。こういった悲しい事故をなくすためにも、$k$ で割り切れるかどうかだけでなく、$l = r$ かどうかを必ずチェックです。(画像: Flat Earth - Wikipedia ) $$ \begin{array}{rrrrrrrrrrrrrrrrrrrrrrrrr} a = & (& 0, \ 1,\ 2, & |& 3, \ 4, \ 5, & |& 6, \ \big[7, \big[ \ 8, & | & 9, \ 10 & ) \\ b = & (& 3, & |& 12, & |& 21, & | & 19 & ) \end{array} $$ 実装です。 C++ ならば、こうです。まずはブロックサイズと、ブロックの数です。 int k = std::sqrt(n), q = (n + k - 1) / k; クエリ 2 だけで良いでしょうか。1 のほうは、背中で語ることにしようと思います。 int ans = 0; for (; l < r && l % k; ans += a.at(l++)); for (; l < r && r % k; ans += a.at(--r)); for (; l < r; ans += a.at((r-=k) / k); ご挨拶 いかがでしたでしょうか。 みなさまの平方分割ライフが、より豊かで快適なものになるよう、願っております。ツイッター・インスタグラムはこちらです。それではまたのご視聴をお待ちしております。ばいばーい👋(← これは言ってみたかっただけの人です。)
キャディのバックエンドエンジニアをして働いている高藤です。 キャディではRustを使った API サーバを開発しています。今回はその開発の過程で導入した cargo workspace を使ったプロジェクト構成についてまとめました。 今回のアプリケーションについて Rustで記述 ドメイン 駆動設計を用いて設計をしており、 ドメイン 層を明確に分離している アプリケーションの役割はgRPCで API を提供したり、MessageQueueからくるメッセージの処理を行う 実装しているアプリケーションで使っている技術や設計手法などは弊社エンジニアが書いた別の記事もご参照下さい。 DDDのパターンをRustで表現する ~ Value Object編 ~ TypeScriptにおけるgRPC関連ライブラリの比較とプロダクト開発で採用した方法の紹介 workspaceを使うようになるまでの経緯 開発初期、 cargo new コマンドで生成されたプロジェクトを以下のような構造にして実装していました。 application_name ├─ app │ └─ main.rs ├─ src │ ├─ domain/ │ │ ├─ aaa.rs │ │ └─ ...etc │ ├─ usecase/ │ │ ├─ bbb.rs │ │ └─ ...etc │ └─ infrastructure/ │ ├─ grpc/ │ │ └─ ...etc │ └─ mq/ │ │ └─ ...etc ├─ Cargo.toml ドメイン 層などを ディレクト リを使い階層構造でmoduleを配置しています。処理をどこに記述すべきかを理解しやすくするためこのような構成にしていました。この構造でプロジェクトが進むにつれ、各 ディレクト リ内のmoduleは増え続けると共にビルド時間が増大し、開発の効率を悪化させる事象が発生しました。 cargo workspace の利用 上記の問題を解決するため、 cargo workspace という機能でプロジェクトを複数のcrateに分離しました。 workspaceを使うメリット crateを分割するメリットとしては保守性や再利用性の向上ももちろんありますが、今回のケースとしてはビルド時間を少しでも短縮することが当初の目的でした。 なぜならRustのビルドツール cargo では依存関係のない crate は並列に コンパイル する事が出来ます。 上記のケースでは infrastructure の中にあるコードは domain , usecase に依存しています。他方で infrastructure 内部の grpc , mq などの処理はお互いに依存はないため、分割することで コンパイル 速度を向上させることが可能です。 workspaceを使ったプロジェクト構成 application_name ├─ app │ ├─ src/main.rs │ └─ Cargo.toml ├─ domain │ ├─ src/...etc │ └─ Cargo.toml ├─ usecase │ ├─ src/...etc │ └─ Cargo.toml ├─ grpc │ ├─ src/...etc │ └─ Cargo.toml ├─ mq │ ├─ src/...etc │ └─ Cargo.toml ├─ Cargo.toml 上記の構成では5つの crate に分割しています。 workspaceの作り方 application_name/Cargo.toml を以下のように定義します。 [workspace] members = [ "app", "domain", "usecase", "grpc", "mq", ] workspace 配下に配置するcrateを上記の様に members として記述をします。 それぞれの crate の中には Cargo.toml を用意する必要があります。 なお、 members に記述のは path になるため、必ずしも同一階層に全ての crate を配置しなくても定義可能です。 例: ./infrastructure/grpc , ./infrastructure/mq のように定義することも可能。 workspace適用後の効果 今回のケースの場合、下記グラフの通り最終的に10分前後かかっていたビルド時間が、2分弱の時間で実行できるようになりました。 workspaceの使い方メモ workspace に関する詳細は各ドキュメント等を参考にしてください。簡単な説明となってしまいますが箇条書きでいくつか利用方法等をご紹介させてもらいます。 workspace の中では コンパイル 成果物が格納される target ディレクト リは workspace 直下に配置されます。(上記例だと application_name/target ) Cargo.lock も同様に workspace 直下に配置されます。これにより workspace 配下の crate が依存する crate のバージョンを保証しています。 workspace を利用しているときも通常のプロジェクトと同様に cargo コマンドでビルドを行うことが出来ます( cargo check , cargo build , cargo run ...etc) workspace 配下の crate にカレント ディレクト リを変更してビルドを行った場合その crate を対象にビルドができます。 カレント ディレクト リを変更したくない場合は --package オプションを使ってビルドも可能です( cargo check --package domain ) The Rust Programming Language ch14-03 The Cargo Book 最後に 私達が開発するアプリケーションは現在16 crateまで分割しています。正直まだ分離させられる余地もあり、成長と共にビルド時間が増えたり、保守観点から分離すべきタイミングで分けるべきだと考えています。 また、参考までにRust製のservice meshである linkerd2-proxy を確認すると55のcrateから構成されています。 このようにアプリケーションが成長し規模や複雑さに応じて簡単に workspace を使って分離できるのはかなり有用かと思っています。 ある程度の成長が予測されるアプリケーションなどは最初から workspace の構成を考えておくなどしておくと良いと思います。 参考: linkerd2-proxy
こんにちは、CADDi でフロントエンドエンジニアをしている桐生です。 少し前になりますが Deno 1.0 がリリース され話題になったかと思います。まだ記憶に新しい方も多いのではないでしょうか。タイムリーにも 隅田川.js #1(オンライン) にてLTをさせて頂く機会があったので、話題の Deno について発表してきました。 本ブログはこの発表内容をブログ化したものになります。(一部、可読性向上のため内容を改変しています) 目次 [toc] Deno とは Deno ざっくり Deno は Node.jsの作者である Ryan Dahl 氏が開発している TypeScript/JavaScript ランタイムです。 https://deno.land/ のトップによると V8, Rust, Tokio 上に構築された、TypeScript/JavaScript ランタイム デフォルトではファイルやネットワークへのアクセスが禁止されていてセキュア デフォルトでTypeScriptをサポート ビルトインでコードフォーマッタや各種ユーティリティを備えている といった特徴があります。 Node.js との違い V8 上の JavaScript ランタイムといえば Node.js です。Node.js があるにもかかわらず、なぜ新しい JavaScript ランタイムが必要なのでしょうか?背景には、Ryan Dahl氏の Node.js における設計ミスへの後悔 があります。興味のある方は見てみてください。 Comparison to Node.js によると npm を使わない。module への URL か file path を使う。 package.json を使わない。 require() を使わない。Es module を使う。 import * as log from " https://deno.land/std/log/mod.ts "; 全ての非同期アクションは Promise を返す。 file, network, environment への明示的な許可をが必要 Uncaught error で停止する といった違いがあります。 Deno で試したこと そんな Deno について、習うより慣れろということでいろいろと試してみました。 Getting Started をなぞる まずは https://deno.land/manual/getting_started に倣って、インストールと実行をやってみました。 $ brew install deno $ deno run https://deno.land/std/examples/welcome.ts Welcome to Deno 🦕 やったことはこれだけで非常に簡単でしたが、見事 Deno の第一歩を踏み出すことができました。 続いて HTTP Request を伴うモジュールの実行を試してみました。 $ deno run https://deno.land/std/examples/curl.ts https://example.com error: Uncaught PermissionDenied: network access to "https://example.com/", run again with the --allow-net flag at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11) at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10) at async fetch ($deno$/web/fetch.ts:591:27) at async https://deno.land/std/examples/curl.ts:3:13 残念ながらエラーとなってしまいました。なぜでしょうか?冒頭でお伝えしたとおり、Deno はデフォルトで、ファイルやネットワークへのアクセスを禁止しているからです。この場合は --allow-net オプションを追加することで実行することができるようになりました。 $ deno run --allow-net https://deno.land/std/examples/curl.ts 他にもたくさんの --allow-xxx オプションがあり、用途に応じて追加することができます。 $ deno run -h OPTIONS: -A, --allow-all Allow all permissions --allow-env Allow environment access --allow-hrtime Allow high resolution time measurement --allow-net=<allow-net> Allow network access --allow-plugin Allow loading plugins --allow-read=<allow-read> Allow file system read access --allow-run Allow running subprocesses --allow-write=<allow-write> Allow file system write access ... 例えば複数追加する場合は、このような指定になります。 $ deno install \ --unstable \ --allow-net \ --allow-read \ --allow-write \ --allow-run \ -f -n das \ https://deno.land/x/deno_app_setuper/cli.ts 環境構築をする Getting Started のあと、環境構築を行うことにしました。私は普段 VSCode を使っているので、Deno 用の VSCode Extension をインストールすることにしました。(2020/5/19時点) そして、ワークスペースに .vscode/.settings.json を用意して Deno Extenstion を有効化しました。 { "deno.enable": true } 私が行った環境構築は以上で終わりです。 注意: 2020/6月現在、上記の extenstion は deprecated になっていました。代わりに以下が公式で出ているので、こちらをインストールしましょう。 これ以外にも shell autocomplete の設定や、Jetbrains や Vim など向けのプラグインも提供されているので、お好みでセットアップしましょう。 https://deno.land/manual/getting_started/setup_your_environment Style Guide を読む 基本的な実行と環境構築が終わり、コードを書ける状態になりました。しかし、どう書くのがベターか知っておきたいと思い、Style Guide を読むことにしました。 https://deno.land/manual/contributing/style_guide 一般 TypeScript を使う。 module という言葉を使う。library や package という言葉は使わない。 メタプログラミングは推奨しない。 依存は最小に。循環参照を避ける。 呼称のブレをなくし module で統一する、というのはいいなと思いました。 ファイル命名規則まわり ファイル名の区切りには underscore _ を使う。 ex) foo-bar.ts ではなく foo_bar.ts _foo.ts など underscore から始まるファイル名は、internal module なので import してはいけない index.ts / index.js を使わない。 もしエントリーポイントが必要なら mod.ts を使う。 ファイル名の単語の区切りにはハイフンではなくアンダースコアを使いましょうということで、私自身、普段ハイフンで区切ることが多いので、これは注意しておかなくてはと思いました。 またエントリーポイントの名前には index ではなく Rust に倣い mod を使おうというのも新鮮でした。 テストまわり Module が公開している機能にはテストを書く。foo.ts -> foo_test.ts Unit Test は明示的であるべき。 コードスタイルまわり module には copyright header を入れる。 TODO コメントには Github の Issue や author name を入れる。 公開機能には JSDoc を書く。 関数の必須引数は最大で2つまで。オプション引数はオブジェクトとして渡す。 オプション引数だけが唯一、”プレーン”オブジェクトである。必須引数はプレーンオブジェクトとは区別ができる必要がある。(Array, Map, Date, class MyThing) トップレベル関数には function を使う。Arrow function はクロージャーに限定する。 個人的に気になったのは下3つで、module を設計・実装するにあたって具体的な指針になると思いました。 Deno では関数のインターフェースを厳格に定めており、関数は必須パラメーターを最大2つまで指定できるとしています。さらに、必須でないその他の引数は全てオブジェクトに入れることとしています。このようにデザインすることで、オプションの位置が変更された場合にも、下位互換性を維持しつつ進化することができることのことです。 具体例を出しつつ見ていきましょう。 関数の必須引数は最大で2つまで。オプション引数はオブジェクトとして渡す。 // BAD: optional parameters not part of options object. export function resolve( hostname: string, family?: "ipv4" | "ipv6", timeout?: number ): IPAddress[] {} 上記のコードはどこが悪いのでしょうか? 引数 family , timeout はオプション引数ですが、オブジェクトとして提供されていません。 // GOOD. export interface ResolveOptions { family?: "ipv4" | "ipv6"; timeout?: number; } export function resolve( hostname: string, options: ResolveOptions = {} ): IPAddress[] {} 引数 family , timeout を含む ResolveOptions として提供するのがよいとされます。 オプション引数だけが唯一、”プレーン”オブジェクトである。必須引数はプレーンオブジェクトとは区別ができる必要がある。(Array, Map, Date, class MyThing) // BAD: `env` could be a regular Object export interface Environment { [key: string]: string; } export function runShellWithEnv( cmdline: string, env: Environment ): string {} 一見問題なさそうですが、 Enveronment は単なる key-value オブジェクトなので、実質的にオプションオブジェクトと同意ですが、必須オプションのように見えてしまいます。 // GOOD. export interface RunShellOptions { env: Environment; } export function runShellWithEnv( cmdline: string, options: RunShellOptions ): string {} RunShellOptions というオプションオブジェクトを定義し、その中に Enveronment を入れることで、オプションであることが自明となります。 これは陥りがちなポイントかもしれません。注意深く設計していく必要がありそうだと感じました。 トップレベル関数には function を使う。Arrow function はクロージャーに限定する。 // BAD export const foo = (): string => { return "bar"; }; // GOOD. export function foo(): string { return "bar"; } 確かに function を使ったほうが、関数であることが自明であったり、 function.name で名前を取得できたりといった利点があったりするので、よいルールだと感じました。 私は普段 React で functional component を Arrow function で書きまくっているので、気をつけたいポイントです。 Deno API を眺める 続いて、Deno のAPI を見てみようと思い眺めてみると、基本的に Web compatible に設計されているおかげで、おなじみのAPIがたくさんありました。API名を見ただけでどんな処理ができるのか大体のあたりがついたので、一旦おいておくことにしました。普段 Web をやっている者にとっては、学習コストがかなり抑えられるので、非常に大きなメリットではないかと思いました。 https://doc.deno.land/https/github.com/denoland/deno/releases/latest/download/lib.deno.d.ts Third Party Modules を使う そして最後に、いざプログラミングをしようと思ったときに、サードパーティモジュールは欠かせないということで、サードパーティモジュールはどう扱えば良いかを確認してみました。 https://deno.land/#third-party-modules Web 上のどこからでも import できる Github 独自の Web Server CDN pika.dev jspm.io etc... 冒頭で説明した通り、module の import には URL かファイルパスを指定可能なので、論理的には Web上のどこからでも import が可能です。 Deno 公式サービス https://deno.land/x これは Deno で動く ES Module をホストしているサービスです。試しに、ここにホストされている moment.js を使ってみることにしました。 // moment_sample.ts import { moment } from 'https://deno.land/x/moment/moment.ts'; console.log(moment.now()); このように moment への URL を指定して import し、現在時間を出力するサンプルプログラムを作成し、実行してみると・・・ $ deno run --reload moment_sample.ts Compile file:///Users/xxx/yyy/zzz/moment_sample.ts Download https://deno.land/x/moment/moment.ts Download https://deno.land/x/moment/vendor/moment.js 1589849822499 無事実行することができました。 確かに、URL をしっかりと指定すればサードパーティモジュールを使うことができるようです。 ただし、上記のプログラムには1点問題がありました。moment の型がなぜか any となっており、せっかく TypeScript で書いているのにコード補完が効かなかったのです。 module の型の指定 この問題を解消するには、型定義ファイルを使用すればよいと考え、Deno での型定義ファイルの適用方法を調べてみました。すると、 // @deno-types="" という pragma を指定すればよいことがわかりました。 https://deno.land/manual/getting_started/typescript#compiler-hint moment 用の型定義ファイルは deno.land にはホストされていなかったため、GitHub から直接 import することにしました。さらに、型定義があったとしても、deno.land の moment を使っている限りはなぜかコード補完が効かなかったため、こちらも GitHub のものを使うように変更しました。すると、ようやくコード補完が効くようになりました。 // @deno-types="https://raw.githubusercontent.com/moment/moment/develop/ts3.1-typings/moment.d.ts" import moment from "https://raw.githubusercontent.com/moment/moment/develop/dist/moment.js"; console.log(moment.now()); 単に module への URL を指定するだけで簡単に使えると思っていたのですが、実際に期待する動作を得るまでにかなり苦労しました。 Third Party Modules まとめ その後 moment 以外にも様々な module の import を試し、いろいろと試行錯誤した結果わかったことをまとめます。 Third Party Module を使う場合 CDN や GitHub などに公開されている module の URLを指定する。 ただし、import できる module には制限あり。 Node の Module Resolution に依存していないもの(外部 module を import していないもの、あるいは、import に 明示的なパスやURLが記述されているもの)は import 可能。そうでないものは Deno がパス解決できないため無理。 TypScriptの型のサポートを得たい場合 // deno-types="" で CDN や GitHub などにホストされている型定義ファイル の URLを指定する。 ただし、サポートする型定義ファイルには制限あり Node の Module Resolution に依存していないもの(外部の型定義 を import していないもの、あるいは、import に 明示的なパスやURLが記述されているもの)は import 可能。そうでないものは Deno がパス解決できないため無理。 ex) React の 型定義ファイル 内部では以下の参照が含まれているので Deno では扱えない。 import * as CSS from 'csstype'; // パス解決できない import * as PropTypes from 'prop-types'; // パス解決できない Deno は import に 明示的なパス指定を要求するため、npm package は(Node Module Resolution に依存しているので)使えない可能性が高い。 npm の @types に相当するものはないのか? あった! https://deno.land/x/types しかし現状は React の型定義ファイルのみ。。 deno.land に置いてある module は TypeScript としてホストされているので、型定義なしでも使えるはずでは? そんなこともなかった。型がついていなかったりする。少なくとも moment に型はついていなかった。 Deno が扱える module の正しい URL を見つけ出して指定するのが辛い。 pika.dev は Deno フレンドリー。検索性が高く探しやすい。 module を import すると一緒に型定義ファイルもダウンロードできる仕組みを提供している。 余談: TypeScript の Top level "for-await" が実装されていない // server.ts import { serve } from "https://deno.land/std@0.50.0/http/server.ts"; const s = serve({ port: 8000 }); for await (const req of s) { req.respond({ body: "Hello World\n" }); } 上記は 公式サイトに載っているサンプルコード ですが、このサンプルコードの bundle は成功するものの、bundle 後の run が失敗してしまいます。 $ deno bundle server.ts server.bundle.js $ deno run --allow-net server.bundle.js error: Uncaught SyntaxError: Unexpected reserved word for await (const req of s) { なぜだろうと思い調べてみると、Issue が挙がっていました。 Top-level for-await not working in bundles #4207 この Issue のコメントを読んでいくと原因が判明しました。なんと、そもそも TypeScript で Top Level "for-await" が実装されていない、というオチでした。。 Top Level "for await" not supported, but should be #37402 ということで、しばらくは Top Level "for-await" は使わないようにしましょう。 Deno 所感 Getting Started から Third Party Module を使ってみるところまでをやってみて感じたことは、 Install からサンプルを動かすまではあっという間。 API は Web フレンドリーでとっつきやすい。 Style Guide は目を通しておいたほうがいい。 Module の import はちょっとツライと思ったが pika.dev のおかげでだいぶ和らぎそう。 という感じでした。現時点では Node.js を置き換えるほどではありませんが、将来がとても楽しみです。今後の動向に注目していきたいと思います。 というわけで、Deno 知らなかった、興味はあるけど触ったことなかった、という方はこれを期にぜひトライしてみてください。 Let’S Try Deno!
前回の記事 でデザインの基本原則をご紹介しましたが、そちらで抜けていたもう1つ重要な概念をご紹介したいと思います。 SaaSなどの多機能で複雑な機能要件を抱えるサービスを、「わかりやすく作り上げる」にはデザイナーは何を基準にUI/UXをデザインしていけば良いでしょか。 今回は人が感じる「わかりやすさ」の指標となる<概念モデル>について自分なりにまとめてみたいと思います。デジタルプロダクトのUI/UXをデザインする上で意識しておくとユーザーにとってわかりやすく、また言語化しやすいデザインができると思います。 概念モデルとは 概念モデルとは人間が対象を知覚したときに無意識的にイメージするモデルです。 例えば私たちは蛇口を見たときハンドルを捻ると水が出ることを知っています。 なぜ私たちはハンドルを捻ると水が出るとわかるのでしょう? これは私たちが「ハンドルを捻ると水が出る仕組み」を無意識にイメージしているからです。 そして「掴んで回せそうなハンドル」や「水が出てきそうな蛇口の先端」はユーザーのイメージ化を助ける手がかりとなります。 また<概念モデル>はユーザーのイメージなので、正確である必要はありません。 水道から水を飲むのに川やダムから水がどのように家庭に届けられているかまで理解する必要はないのです。 イメージ化 蛇口のように機能が単純であり、物理的に存在する製品であれば、機器が示唆する構造をイメージ化し、<概念モデル>を頭の中に形成することは比較的かんたんに行えます。 しかし、デジタルプロダクトのように複雑で多機能な機器をイメージ化するのは一苦労です。 そこでデザイナーはイメージ化を容易にする手がかりを提供しなければなりません。 例えばGUI(グラフィックユーザーインタフェース)などはその手がかりの1つと言えるでしょう。 コンピュータの画面上にフォルダのアイコンがあります。 私たちはこのフォルダアイコンをクリックすると中身のファイルが表示されることを知っています。 これはデザイナーが「現実のフォルダと、その中に収納されている書類」というアナロジーをイメージ化の手がかりとして提供し、それによってフォルダの<概念モデル>が形成され私達はフォルダシステムを利用します。 では実際のフォルダシステムはどのような構造を持っているのでしょうか。 実際のフォルダシステムは階層的に情報を保持しているわけではなく、ファイルが記憶ディスク(HDやSSD)上のどの場所に保存されているかという位置情報を保持しているというような構造になっています。 参照:教養としてのコンピュータサイエンス講義 しかしユーザーはコンピュータの論理構造を理解しなくても<概念モデル>を自分の中に形成するのとでフォルダシステムを利用することが出来ます。 このように複雑な機器とユーザーがコミュニケーションできるよう<概念モデル>の形成を手助けするのがデザイナーとしての重要な仕事です。 概念モデルを形成するのに役立つのはGUIだけではありません。 遷移の設計やマイクロアニメーション・パターン化・普遍化など多くの要素があります。 デザイナーはプロダクトを通してユーザーが良い概念モデルを形成できるように適切な情報を提供しなければなりません。 ユーザーが概念モデルを形成していく過程を素早く、楽しく行えるようにUI/UXを設計することが大切です。
はじめに こんにちは、キャディでバックエンドエンジニアとして働いている kuwana-kb です。 キャディではバックエンドで Rust を採用しています。また、設計手法としてドメイン駆動設計(以下 DDD)を取り入れています。Rust と DDD 、それぞれの解説記事は今では珍しくありませんが、 Rust と DDD をかけ合わせた記事はまだあまり目にしません。 今回は、 Rust で DDD の実装パターンをどう表現するかをまとめたいと思います。DDD に登場する概念は色々とありますが、今回はそのうちの1つである Value Object に焦点をあてたいと思います。 ※ 本記事は、2020/04/28 に開催された「 下町.rs 」にて kuwana-kb が発表した内容を記事にしたものです。 目次 はじめに 目次 DDD とは DDD の実装パターンを Rust で書いてみた 実装パターンの紹介 DDD における Value Object とは まずはシンプルに実装してみる derive を使って実装を省略する 型による制約を与える Value Object にふるまいをもたせる 幽霊型を用いて Value Object の型を区別する まとめ 参考文献 DDD とは 前提知識として、軽くDDDについておさらいしましょう。DDDとは、 Domain-Driven Design(ドメイン駆動設計) の略です。アプリケーションの扱う業務領域に焦点をあてた設計手法であり、エリック・エヴァンスが提唱しました。 DDDといえば、「エリック・エヴァンスのドメイン駆動設計」と「実践ドメイン駆動設計」という2つの書籍が有名です。これらの書籍ですが、内容はDDDの原典としてよく言及される一方で、初心者には理解するのが少しむずかしいという側面もあります。私もその難しさにやられた一人でした。 そんな中で出会ったのが 「ドメイン駆動設計入門」(著: 成瀬 允宣氏) という書籍です。 この本は、DDD に登場するモデリングとパターンの用語うち、パターンを集中的に解説した入門書です。これ 1 冊で DDD の内容を網羅しているわけではありませんが、抽象的な DDD のパターンを具体的なコードとして学ぶことができます。サンプルコードも豊富です。C# で書かれていますが、 C# を知らなくても雰囲気で読めました。 DDD の実装パターンを Rust で書いてみた DDD の理解を深めるためには自分で書いてみるのが早い、ということ Rust で書いてみました。 コードは「ドメイン駆動設計入門」のサンプルコードをベースに Rust で書き直したものです。 kuwana-kb/ddd-in-rust 実装パターンの紹介 DDD における Value Object とは 実装パターンの紹介に入る前に、Value Object について整理します。 Value Objectには、以下のような特徴があります。 システム固有の値をオブジェクトとして定義したもの ex. 金銭や製品番号など プログラミング言語にはプリミティブな値が用意されているが、業務領域で使う値をオブジェクトとして定義することで、業務ルールに反した値を混入させない また、Value Object の性質として、以下が挙げられます。 値が等しいかどうか、他と比較できる(値の等価性) 状態が不変である http://bliki-ja.github.io/ValueObject/ この性質をコードにも反映したいと思います。 まずはシンプルに実装してみる まずは、シンプルに実装してみましょう。 今回は例として、 氏名 をコードで表現してみます。 氏名 は以下のような特徴を持っているとします。 氏名 は 姓 と 名 で構成される 姓 と 名 は個別に出力できるようにする 姓 と 名 はプリミティブな型とする 以下が実際のコードです。 // サンプルコード // https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a1_simple_vo.rs #[derive(Clone, Debug)] pub struct FullName { first_name: String, last_name: String, } impl FullName { pub fn new(first_name: &str, last_name: &str) -> Self { Self { first_name: first_name.to_string(), last_name: last_name.to_string(), } } pub fn first_name(&self) -> String { self.first_name.clone() } pub fn last_name(&self) -> String { self.last_name.clone() } } // trait PartialEqは半同値関係の性質を表す // PartialEqを実装することで「==」演算子による比較が可能になる impl PartialEq for FullName { fn eq(&self, other: &Self) -> bool { self.first_name() == other.first_name() && self.last_name() == other.last_name() } } impl Eq for FullName {} #[test] fn test_equality_of_vo() { let taro_tanaka_1 = FullName::new("taro", "tanaka"); let taro_tanaka_2 = FullName::new("taro", "tanaka"); let jiro_suzuki = FullName::new("jiro", "suzuki"); // 値が同じVOの比較。一致する assert_eq!(taro_tanaka_1, taro_tanaka_2); // equal // 値が異なるVOの比較。一致しない assert_ne!(taro_tanaka_1, jiro_suzuki); // not equal } 今回は、必要と思われるメソッドや trait を愚直に書きました。値の等価性は、 PartialEq と Eq を実装することで表現しました。また、値の不変性は、 値を immutable にする(= mut な処理を実装しない)ことで表現しました。ひとまずこれで Value Object の性質をコードで表現することができましたね。 ※ 今回は FullName の各フィールドに対して getter を実装していますが、Value Object が getter を持つべきかしっかりと検討した方がよいと考えます。無闇に getter をはやすことは、意図しない値の使われ方を招く可能性があるためです。 derive を使って実装を省略する 先程の例で Value Object を表現できたわけですが、ひとつの Value Object を定義するのにいちいち実装を書くのは手間ですね。ということで、次は derive を用いて実装を省略してみます。 // サンプルコード // https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a2_derive_vo.rs #[derive(Clone, Debug, Getters, PartialEq, Eq)] pub struct FullName { first_name: String, last_name: String, } impl FullName { fn new(first_name: &str, last_name: &str) -> Self { Self { first_name: first_name.to_string(), last_name: last_name.to_string(), } } } derive マクロによって、記述量を削減することができました。 具体的には、 PartialEq , Eq を derive で自動実装し、 getter を Getters という derive マクロで自動実装しました。 derive マクロは、 std crate 以外にも外部 crate で定義された便利なものがたくさんあります。以下に一部をご紹介します。 derive-getters フィールドのgetterを自動実装する derive マクロ 公開したくないフィールドは skip attribute で飛ばせる derive-new コンストラクタを自動実装する derive マクロ strum enum に対する Display , FromStr を自動実装する derive マクロ derive マクロは便利ですが、時と場合によって使い分ける必要があります。 例えば、先程紹介した derive-new 。このマクロは、コンストラクタを自動生成してくれる点で便利ですが、コンストラクタ内にvalidationをはさみたい時は自分で実装する必要があります。 型による制約を与える さて、これまで登場した例は Value Object の性質を満たしているものの、業務領域特有の値は特に持っていませんでした。今回は、業務領域特有の値を型として表現し、より実践で役立つ形にしたいと思います。 今回の例では、以下のような Value Object を表現してみます。 ビジネスの要求として、 姓 と 名 はアルファベットだけにしたい 具体的には、 FullName.first_name , FullName.last_name がアルファベットしか受け付けないようにする それでは、実際のコードをみてみましょう。 // サンプルコード // https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a3_all_vo.rs /// 氏名 // このケースでは、プリミティブだったフィールドに対して、独自型(Name)を定義している // 独自型に対して制約を与えることで、「その型である = 制約を満たした値である」ことが保証される #[derive(Clone, Debug, Getters, PartialEq, Eq)] pub struct FullName { first_name: Name, last_name: Name, } impl FullName { pub fn new(first_name: Name, last_name: Name) -> Self { Self { first_name, last_name, } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Name(String); impl FromStr for Name { type Err = String; fn from_str(s: &str) -> Result<Self, Self::Err> { let re = Regex::new(r#"^[a-zA-Z]+$"#).unwrap(); if re.is_match(s) { Ok(Name(s.to_string())) } else { bail!(MyError::type_error("許可されていない文字が使われています")) } } } #[test] fn show_full_name() { let first_name = "taro".parse().unwrap(); let last_name = "tanaka".parse().unwrap(); // この時点でfirst_name, last_nameは型のコンストラクタがもつ制約によりアルファベットであることが保証されている let full_name = FullName::new(first_name, last_name); println!("{:?}", full_name); // FullName { first_name: Name("taro"), last_name: Name("tanaka") } } #[test] fn test_parse_name() { let valid_name = "taro".parse::<Name>(); let invalid_name_with_num = "taro123".parse::<Name>(); let invalid_name_with_jpn = "太郎".parse::<Name>(); assert!(valid_name.is_ok()); // Ok() assert!(invalid_name_with_num.is_err()); // Err() assert!(invalid_name_with_jpn.is_err()); // Err() } 今回は、 氏名 を構成する 姓 と 名 に制約を設けました。具体的には、 姓 と 名 をプリミティブな型 String から、新しく独自に定義した型 Name に置き換えています。 この Name 型は、 Name::from_str() でregexによる値のチェックをし、不正な値を弾くような実装になっています。したがって、 Name インスタンスを生成できるということは、今回のビジネス要求である「アルファベットのみの値で構成された文字列である」を満たした値になっていることが保証されます。 Value Object にふるまいをもたせる Value Object は値としての意味だけでなく、ふるまいを持つことができます。 例えば、お金に対して加算のふるまいをもたせてみましょう。 今回実装するのは以下のような Value Object とします。 Money というValue Objectを定義する このValue Objectに、加算のふるまいを実装する 今回は、同じ通貨である場合のみ加算ができるとする それでは実際のコードをみてみましょう。 // サンプルコード // https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a4_vo_with_behavior.rs // 振る舞いを持つVO // 具体的には通貨単位が一致した場合に限り加算が可能 // // このケースでは通貨単位をフィールドの一部として定義している // 通貨単位をフィールドではなく、型として表現するケースは`a5_vo_with_phantom`参照 #[derive(Clone, Debug, new, Eq, PartialEq)] struct Money { amount: Decimal, currency: String, } // Add traitは「+」演算子による加算を表現する impl Add for Money { type Output = Money; fn add(self, other: Money) -> Self::Output { // 通貨単位のチェック // 通貨単位が一致しない場合はpanicを起こす // traitのシグネチャ上、Result型として返せないのでこれは仕方ないはず... // その意味で、通貨単位を型として表現することでコンパイル時に検査できる方が嬉しいと思われる if self.currency != other.currency { panic!("Invalid currency") } let new_amount = self.amount + other.amount; Money::new(new_amount, self.currency) } } 今回は、お金の持つ性質として、加算のふるまいを Add trait で実装しました。 また、「同じ通貨である場合のみ加算ができる」という要求を満たすため、 Money.currency というフィールドを定義し、このフィールドを加算時にチェックするようにしています。 しかし、この実装には問題があります。 それは Money.currency のチェックを add() メソッド上でしている点です。 add() メソッドは返り値がtrait上で Self::Output と指定されており、 Result 型にできません。したがって、異なる通貨で加算した時のエラーハンドリングは panic!() せざるを得ないです。panic はアプリケーションを強制終了させてしまうため、本番運用のプロダクトではなるべく利用を避けたいです。 幽霊型を用いて Value Object の型を区別する さて、先程の実装だと異なる通貨単位同士を加算すると panic してしまうという問題がありました。そこで、通貨を値ではなく型として表現することで、この問題を解決したいと思います。 ここで登場するのが幽霊型(Phantom Type)です。 幽霊型について、Rustの公式のドキュメントをみてみましょう。 幽霊型(Phantom Type)とは実行時には存在しないけれども、コンパイル時に静的に型チェックされるような型のことです。 構造体などのデータ型は、ジェネリック型パラメータを一つ余分に持ち、それをマーカーとして使ったりコンパイル時の型検査に使ったりすることができます。このマーカーは実際の値を何も持たず、したがって実行時の挙動そのものにはいかなる影響ももたらしません。 https://doc.rust-jp.rs/rust-by-example-ja/generics/phantom.html この幽霊型をラベルのような形で型のパラメータとして用いることで、同じ型も別の型としてコンパイル時に区別することができるようになります。今回の例の Money<T> 型がその一例です。 // サンプルコード // https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a5_vo_with_phantom.rs // 振る舞いを持つVO // 具体的には通貨単位が一致した場合に限り加算が可能 // // このケースでは通貨単位を型として表現している // Money<T>のTで通貨単位を表すようにする // ここで嬉しいのは、誤った通貨単位同士の加算をコンパイル時に検査できること // Tはただのラベルとして扱いたいだけだが消費しないと怒られるので、std::marker::PhantomDataを用いる // 参考: https://keens.github.io/blog/2018/12/15/rustdetsuyomenikatawotsukerupart_1__new_type_pattern/ #[derive(Clone, Debug, PartialEq, Eq)] pub struct Money<T> { amount: Decimal, currency: PhantomData<T>, } impl<T> Money<T> { fn new(amount: Decimal) -> Self { Self { amount, currency: PhantomData::<T>, } } } impl<T> Add for Money<T> { type Output = Money<T>; fn add(self, other: Money<T>) -> Self::Output { Self::new(self.amount + other.amount) } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum JPY {} #[derive(Clone, Debug, PartialEq, Eq)] pub enum USD {} #[test] fn test_phantom_money() { let jpy_1 = Money::<JPY>::new(Decimal::new(1, 0)); let jpy_2 = Money::<JPY>::new(Decimal::new(2, 0)); let _usd = Money::<USD>::new(Decimal::new(3, 0)); let result = jpy_1 + jpy_2; // コンパイルOk assert_eq!(result, Money::<JPY>::new(Decimal::new(3, 0))); // let cannot_compile = jpy_1 + usd; //コンパイルエラー } 今回は、 PhantomData で通貨単位を型として表現しました。この実装によって、異なる通貨 = 異なる型となるため、異なる通貨の加算をコンパイル時にエラー検出できるようになりました。テストを見ていただくと、 jpy 同士の加算はコンパイルが通りますが、 jpy と usd の加算はコンパイルエラーになることがわかると思います。 幽霊型を用いることのメリットは、似たような型を複数作らなくても良くなる点にあります。 例えば、通貨に関する実装として、 MoneyUsd , MoneyJpy といった形で通貨ごとに Money の型を分けることもできます。しかし、この実装方法は同じような実装が通貨分できてしまうため、あまりうれしくありません。今後扱う通貨の単位が増えた場合を考えるとなおさらです。 幽霊型であれば、通貨の単位を enum で追加するだけです。 まとめ 今回は、Rust で Value Object をどう表現できるか、についてご紹介しました。 Rust の機能によって、Value Object を豊かに表現することができました。 derive マクロ、 trait によるふるまいの実装、型システムによるコンパイル時チェックなどなど... Rustの魅力が少しでも伝われば幸いです。 また、今回のサンプルコードの元となった「ドメイン駆動設計入門」は良書です。ぜひ一緒に DDD に入門しましょう! 参考文献 ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 | Naruse Masanobu https://www.amazon.co.jp/dp/B082WXZVPC/ バリューオブジェクト | Martin Fowler's Bliki (ja) http://bliki-ja.github.io/Value Object/ DDD Propaganda | Naruse Masanobu https://speakerdeck.com/nrslib/ddd-propaganda Rust で強めに型をつける Part 1: New Type Pattern | κeen の Happy Hacκing Blog https://keens.github.io/blog/2018/12/15/rustdetsuyomenikatawotsukerupart_1__new_type_pattern/
キャディでは エンジニア積極採用中! キャディでは エンジニア積極採用中!!!!! ということで、 CTO 小橋の思いが詰まった The letter from CTO を公開しました! [siteorigin_widget class="WP_Widget_Custom_HTML"]<input type="hidden" value="{"instance":{"title":"","content":"<div style=\"margin: 30px 0;\">\n\t<script async class=\"speakerdeck-embed\" data-id=\"8db0fb0f70c440d39942dd37f1a9047f\" data-ratio=\"1.77777777777778\" src=\"\/\/speakerdeck.com\/assets\/embed.js\"><\/script>\n<\/div>\n\n<p>\n\t<a href=\"https:\/\/speakerdeck.com\/caddi_eng\/caddi-the-letter-from-cto-to-all-the-engineer-applicants\"\u3000target=\"_blank\">https:\/\/speakerdeck.com\/caddi_eng\/caddi-the-letter-from-cto-to-all-the-engineer-applicants<\/a>\n<\/p>\n","so_sidebar_emulator_id":"custom_html-132510001","option_name":"widget_custom_html"},"args":{"before_widget":"<div id=\"panel-1325-0-0-1\" class=\"so-panel widget widget_custom_html\" data-index=\"1\" data-style=\"{"background_image_attachment":false,"background_display":"tile"}\" >","after_widget":"<\/div>","before_title":"<h3 class=\"widget-title\">","after_title":"<\/h3>","widget_id":"widget-0-0-1"}}" />[/siteorigin_widget] 内容は、今まで候補者の方々とお話する中で頂いた疑問を解消するようなものにしました! 例えば・・・ ・ キャディってマッチングサービス? ・ 製造業系出身のエンジニアが多いんじゃないの? ・ Technology を何に使っているの? などなど。 キャディのTech組織の現状から今後の展望までを理解して頂ける内容になっていると思います!ぜひご覧ください!