キャディ株匏䌚瀟のブログ - TECH PLAY

TECH PLAY

キャディ株匏䌚瀟

キャディ株匏䌚瀟 の技術ブログ

å…š239ä»¶

Tech Blog 初投皿で緊匵しおいる、岡野です。(キャディで゚ンゞニア採甚を担圓しおいたす) 2021幎11月11日に開催されたFIndyさん䞻催のむベント 泚目の蚀語Rust、開発珟堎でどう䜿われる掻甚䌁業の珟堎に聞くVol.2 こちらにキャディのバック゚ンド゚ンゞニア長田歌菜さん( @ngtkana )が登壇したした。今回は、その内容をたずめたしたので圓日参加できなかった方にも目を通しお頂けるず嬉しいです 泚目されおいる蚀語の1぀ずしお取り䞊げられるこずも倚いRust。実際に開発の珟堎で䜿う機䌚は限定的な面もあり、 実際に䜿っおいる゚ンゞニア目線でどうなのか  Rustを業務に取り入れおみたいず考えおいらっしゃる方、技術遞定の候補ずしお怜蚎されおいる方の参考になればずずおも嬉しいです。 [toc] Rustをキャディで採甚した背景は 長田私自身は、蚀語遞定そのものに関わっおいないので、聞いた話や残っおいる資料ベヌスでの説明ずなりたす。 キャディでは生産管理や原䟡蚈算などのプロダクトを内補しおいくプロゞェクトがあり、倧きなサヌビスを展開するのが初めおずいうフェヌズがありたした。 それたでは、C++ ずか Python など様々な蚀語を䜿っおいたのですが、堅牢なものを䜜るにはどのような蚀語・フレヌムワヌク・技術を䜿うべきか 瀟内でディスカッションを重ねおいたした。 最終的にはRustを遞定したポむントは、 コンパむル時の型や怜査に匷い こず。 その理由は、数倀぀取っおも、 ・長さなのか重さなのか ・金額であっおも「売り」なのか、「買い」なのか など数倀の意味を定矩しないず間違ったこずが曞けおしたうため、蚈算・怜査がしっかりできるこずが必芁でした。 そんな背景から、Rust , Go, Kotlin などが候補に䞊がるも、 ・キャディのプロダクトによっおはスピヌドが芁求されるこず ・元々キャディに数名Rust䜿える人がいた ずいうこずからRustに決定した背景があるようですあくたで䌝聞です 䟋えば、グラム重さずメヌトル長さが勝手に足し算されおるこずがあれば、圧倒的に信頌性を倱いたすからね。 実際にRustを䜿っおみお特城を挙げるずするず 3点あるかなず思っおいお、それは 「速い」「安党」「゚ンゞニアに優しい」 です。 「速い」ずいうのは、Rustは 実行時間も速いし、実装時間も速い ず思いたす。 ゚ンゞニアによっおは、たどろっこしいずいう先入芳もあるかもしれたせんが、実装がしやすくなるよう考えられお䜜られおいる蚀語なので、「早い」ずいう特城がたずありたすね。 次に「安党」ずいうのは、 メモリリ゜ヌスの管理で悩むこずがない 点です。 Rustには所有暩ずいう抂念があり、1぀のメモリリ゜ヌスは1぀の倉数が持぀ずいうものです。 最初は぀たづくかもしれたせんが、慣れおくるずこの所有暩のおかげで実装がしやすくなりたす。 最埌の「゚ンゞニアに優しい」ずいうのは、 コンパむル時に実装の誀りを指摘しおくれるこずが倚いから です。 私が Rust を勉匷し始めたバヌゞョン1.30くらいからを比范しおも、曞きやすくバヌゞョンアップしおいたす。 詳しくは Rust公匏 や、初心者向けの 入門のたえがき にもありたすが、たどろっこしい曞き方をしなくお良くなるのがいいです。 フレヌムワヌクに぀いおはどうか キャディが䜜っおいるプロダクトはWebサヌビスがメむンです。むンフラは共通しおいるもののチヌムによっおアヌキテクチャや䜿甚技術はバラバラです。 私のチヌムは warp を䜿っおいたすが、これの良さはサヌバを立おるずころがそんなに長くならない点ですね。 蚭蚈者は偉いなあず感じるのですが、サヌバやFPIやフロント゚ンドなどがきっちり分かれおいお、ずおも読みやすいず感じたした。 どのくらい型の恩恵をうけおいるか 盎接的なメリットずしおは2点。 1぀目は、 ドキュメントが小さくなるずころ です。 これは、型が芁件を衚しおいるので、関数にわたすものも现かく指定しなければいけたせん。 ずころが Rust だず、 ”こういう意味で実装したした” ずいうのが分かりやすく䌝えられるので、コミュニケヌションコストが䞋がる点はメリットですね。 2぀目は、 テストが枛るこず です。 C++はテンプレヌトが実䜓化しおいないずコンパむルが通る保蚌がないため、党おのコヌドポむントを通るように気を぀ける必芁がありたす。 そのような点で、テスト時に泚意する芁玠が枛るのは嬉しいポむントになりたすね。 私が以前担圓しおいたプロダクトで原䟡蚈算をするずきは、ずおも现かく型を぀けおいたした。 uom ずいうラむブラリがありたしお、これは物理的なもの長さ・重さなどを扱うずきに、「長さ」「重さ」を区別できるようになり、掛け算しお面接を算出できたりするものです。私たちは、さらにお金の単䜍も远加しお利甚しおいたした。 Rust を実際に䜿っおみおのメリットは メリットは3぀あるず思っおいたす。 ぀目は、 環境構築が楜でビルドの再珟性が高い 点ですね。 比范察象ずする蚀語にもよりたすが、䟋えば ・コンパむルのプロバヌゞョンが入っおいる・入っおいない ・ラむブラリがどのパスに入っおいる・入っおいない などビルドは難しい感じがしたす。 それず比范するず Rust の難易床は䜎い方かなず思いたすね。 2぀目は、 型が匷い ずいうこず。 そしお3぀目は、 実行速床が速い こずですね。 オブゞェクトを䜜るずどうしおも遅くなったり、抜象化するず抜象化コストがかかるので、コヌドの読みやすさず実行速床はトレヌドオフの関係になるのが普通かなず思いたす。 だけど Rust は”曞きやすくお速い”ずいうのを実珟できるのがいいですね。 逆にデメリットは こちらは2点ありたす。 たず1぀目が、 ビルドに時間がかかる こず。 キャッシュが倧きく、ビルドでの埅ち時間が長いのはネックですね。 キャディのバック゚ンドのプロゞェクトは、だいたいが Rust を利甚しおいるのですが、様々なプロゞェクトを経おビルドを行うず、すぐデヌタがいっぱいになっおしたいたす。 なので、泣く泣くプロゞェクトを消したり、ロヌカルから萜ずすなどで察応しおいるのですが、もう少しキャッシュが小さくなるず嬉しいずいうのはありたす。 2぀目は、ただ 発展途䞊の蚀語 ずいうこずです。 以前ず比范するず曞きやすく、機胜も増えおいる事実はありたす。 ただ、䟋えば Rust の゚ラヌ凊理たわりに関しお䜕のラむブラリを䜿うかでいうず、 私のチヌムは anyhow を䜿っおいたすが、郚分的にちょっずこの蟺はただただだな・・ずいう点は正盎ありたすので、そこは我慢しお䜿っおいるのが実情です。 たた乱数を扱うずきに、rand ずいうものを䜿うのですが、数ヶ月前にアップデヌトがあり、その巻き添えになっお倧倉だったこずもありたした。 たたただ、発展する䜙地がある印象です。 人によっおデメリットず感じるか、メリットず感じるか個人差があるず思いたすが、 倧きなフレヌムワヌクがないのも Rust の特城 です。 “ずりあえずこのフレヌムワヌクを䜿えば䜕でもできる” ずいうものがないのです。 よっお、Rust を䜿うずいうこずはアヌキテクチャを自分たちでむチから考えるこずになるので、これずこれを組み合わせおこうしたプロダクトを䜜ろうなど、自分たちで構成を考えなければいけたせん。 これは楜しくもあり、倧倉なこずでもありたすよね。 Rust を孊習するずきの難易床はどうか 元々 どういう蚀語ができる゚ンゞニアなのかにもよる ず思いたす。 䟋えば C++ ができるのであれば、それほど難しくないのではないでしょうか。 逆に Python や JavaScript をメむンに扱っおいる方だず難しいかもしれたせん。 Rust そのものが特別に難しい蚀語ずは思っおいたせんが、あらためお所有暩ずいう抂念は難しいかもしれたせんので、その点くらいでしょうか。 ラむブラリに぀いおは、公匏が管理しおいるものが広範囲で、 crates.io でみるず䞀芧で出おきたす。 基本的には ラむブラリは1箇所に集たっおいたす。 ドキュメントの生成も、コンパむラず䞀緒になっおいるので同じフォヌマットでたずたるようになっおいたすし、ドキュメントを集積する堎所はdoc.rsにあり、 crates.io から簡単にゞャンプできたす。 なので、情報がどこにあるのかずいうこずで迷わないですし、それは知らなかったずいうこずもなく、独孊で孊習するには向いおいる蚀語だず私は思いたす。 たた、 コミュニティの皆さんの面倒芋がよかったり もしたす。 Twitter で質問するずいろいろな人が教えおくれたりしたすし、rust-jp のslackにも初心者向けチャンネルがありたすので、ぜひずも孊習しおみおください。 採甚・育成の芳点ではどうか コンパむルが通りやすいのは、初心者にやさしい点ですよね。レビュヌする偎ずしおも安心できるのかなず。 ただ、Rust はただマむナヌ蚀語なので、元々 Rust がかける人は集たっおこないず思いたす。 なので、これは Rust に限らずですが、曞けない蚀語を曞けるようにするために、勉匷しおいく必芁があるのはデメリットかもしれたせん。 Rust そのものが普及すれば、そうしたデメリットも枛るでしょう。 日本で Rust を䜿っおいる䌚瀟䞀芧 があったりするので、私はそういうのをみお仲間意識を育んだりもしおいたす 笑 Rust 習埗の具䜓的な手順は Rust-by-Example を䞀通り読んでいれば、あずはOJTで倧䞈倫かなず思っおいたす。 そこから興味を持おたら、公匏のリファレンスやチュヌトリアルを芋たり、ラむブラリは暙準のドキュメントを芋たり。 いずれにしろ 公匏が提䟛しおいるコンテンツを読むず勉匷が進むし、情報がたずたっおいるのでチェックしやすい です。 開発に参加するだけであれば、そんなに知識は必芁ないかなず思いたす。 あずは、プログラミングで䜕をやりたいのか Tech Blog をご芧になっおいる皆さんによっお、曞きたいものや解決したいこずは違うず思いたすが、それを Rust を䜿っおやっおみるずいいのかなず思っおいたす。 新しいメンバヌがチヌムに参加するずきの GAP は 私自身が Rust を䜿うチヌムに2週間埌に移動ずなったずき、そこからがんばっお勉匷したした。 新しいプロゞェクトに぀くず、このコマンドにはこうしなければいけない、これをむンストヌルしなければ、など環境を敎えるのが倧倉ですよね。 そういう意味で、 Rust のプロゞェクトはビルドが簡単なので、オンボヌディングは楜 かなず思っおいたすので、あたり GAP は感じたせん。 C++ で開発されおいるずころから Rust ぞの切り替えは C++ でやっおいおよかったのは既存のラむブラリがあったこずですね。 Open CASCADE ずいうラむブラリですが、C特有のメモリヌ管理が難しかったりしたした。 Rust に切り替えるず、 既存の倧きなラむブラリを䜿えなくなるので、自分で䜜っおいくしかない ずころは私的にはよかったです。 最埌に 今回は、FIndyさん䞻催のむベントに登壇させおいただいた長田歌菜さんの発衚内容を蚘事にしたした。 着食らない等身倧のお話ですので、Rust に觊れる機䌚のある゚ンゞニアにずっお有益な情報になっおいればずおも嬉しいです。 キャディでは、バック゚ンド、フロント゚ンドなどのWebアプリを埗意ずする゚ンゞニアやアルゎリズム、ML、デヌタサむ゚ンスなどの数孊の知識を掻甚した゚ンゞニアリングを埗意ずされる方にも興味を持っおいただきたいなず思っおいたす。 ご興味をお持ちくださった方は、ぜひ こちら から゚ントリヌくださいカゞュアルにお話できるず嬉しいです。 最埌たでお読みくださり、ありがずうございたした。
こんにちは、キャディで゜フトりェア゚ンゞニアをしおいる小倉です。今はフロント゚ンドを䞻に觊っおいたす。 いきなりたずめ(TL;DR) 本蚘事は、AgGridのセルの倀のReadずWriteに぀いおの機構をたずめた蚘事になりたす。 蚘事が長くなっおしたったので、觊れる内容をたずめた図を先に持っおきたした。 やり方が耇数あるものに぀いおは以䞋のように考えおおくず良いです。 valueSetter,valueGetterは䜿わなくお良いなら䜿わないようにしお、なるべくfield指定だけですむようにrowDataを蚭蚈する。 文字列凊理だけで䜏むずきはvalueFormatter, それ以䞊の凊理( CSS で食り付ける、リッチな機胜を぀ける、etc...)が必芁ならcellRenderer プロロヌグ キャディで䜜っおいるTechプロダクトの半数は、人力で回しおいたオペレヌションを代替する立ち䜍眮のものです。どうすればうたくいくのかわからない補造業の課題に察しおたずは人力で詊しお、手応えがあったら、効率化や芏暡拡倧のためにTechプロダクト化する、ずいうよくある流れですね。 さお、このようなプロダクト開発に携わっおいる方が必ずずいっお良いほどナヌザヌから聞くフィヌドバックがありたす。 「 Excel ・ スプレッドシヌト の方が䟿利 」 人力で回すオペレヌションはUIずDBの郚分を 衚蚈算 ゜フトで間に合わせおいるこずが倚いです。プロダクト化しおも良さそうだぞずなるオペレヌションはだいたい匷力な Excel ・スプシが出来䞊がっおいるこずでしょう。それず比范されお出おくるフィヌドバックです。 衚蚈算 ゜フトはUIずデザむンを䞀緒くたにしお䜿っおしたうず甚倧な アンチパタヌン を螏んでしたいがちですが、そこをちゃんずすれば、掗緎された迅速なオペヌレヌションを回す手助けをしおくれたす。ずくに 衚蚈算 UIは倧量のデヌタを扱うのに匷かったりしたす。 衚蚈算 ゜フトの アンチパタヌン を螏たれないようにFE゚ンゞニアがうたくUIを蚭蚈すれば匷力なツヌルずなるでしょう。 そういうわけで、私達のチヌムでは 衚蚈算 コンポヌネント をプロダクト内に導入するこずにしたした。 衚蚈算 っぜい コンポヌネント を提䟛するラむブラリには WijmoのFlexGrid , Handsortable , cheetahGrid など様々ありたすが、私達は AgGrid を䜿うこずにしたした。 この蚘事はなに AgGridは機胜が倚く、同じこずを実珟するにしおも様々な曞き方ができおしたいたす。ずくにセルぞの入出力たわりはその傟向が顕著です。 公匏ペヌゞをよく読めばどういうずきにどの機胜を䜿うのかのポリシヌが曞いおあるのですが、開発のたびに参照しに行くのも面倒なので、䞀芧性を重芖したたずめドキュメントを䜜るこずにしたした。この蚘事はそのドキュメントをブログ向けにアレンゞしたものです。 甚語 Grid衚党䜓 Row行 Cellマス䞀぀ 行は「どのオブゞェクトか」、列は「オブゞェクトをどう料理するか」 AgGirdでは各行Rowは1぀のオブゞェクトず察応したす。぀たりGrid党䜓ずしおはオブゞェクトの配列になりたす。その配列がしばしばrowDataず呌ばれるものです。 各列(Column)は、察応するオブゞェクトをどう料理するかに察応したす。ここでいう「料理」は倧抵の堎合は「オブゞェクトのこのフィヌルドの倀をtoStringした倀を衚瀺」になりたす。それ以倖にも、耇数フィヌルドの合蚈倀を蚈算しお黄色い背景色で衚瀺、などの耇雑なパタヌンにも察応可胜です。 ぀たり、$i$行目$j$列目の堎所にあるCellの倀は rowData[i] をj列目共通の料理の仕方で埗られた倀になりたす。UI䞊で列や行の䞊びを倉えられるので厳密な説明ではありたせんが、、、 コンポヌネント の䟋 基本的には AgGridReact コンポヌネント に行デヌタのArray(rowData)をPropsで䞎えるず、Gridが出来䞊がりたす。 AgGridReact のchildrenずしお AgGridColumn を远加するず列に関する情報を入れるこずができたす。 AgGridColumn の field 属性にフィヌルド名を入れおおくず i 行目の圓該列は rowData[i] のそのフィヌルドの倀を toString した倀が衚瀺されたす。 ▌玠朎なAgGrid コンポヌネント import { AgGridColumn, AgGridReact } from "ag-grid-react"; import "ag-grid-community/dist/styles/ag-grid.css"; import "ag-grid-community/dist/styles/ag-theme-alpine.css"; const rowData = [ { firstName: "Taro", lastName: "Tanaka", age: 10 }, { firstName: "Hanako", lastName: "Yamada", age: 25 }, { firstName: "Jiro", lastName: "Suzuki", age: 32 }, ]; export const MyGrid: React.VFC = () => { return ( <div className="ag-theme-alpine" > <AgGridReact rowData={rowData}> <AgGridColumn field="firstName" editable={true} /> <AgGridColumn field="lastName" editable={true} /> <AgGridColumn field="age" /> </AgGridReact> </div> ); }; ▌芋え方 衚瀺 䞊で説明した「料理」は、おおたかに以䞋の2パヌトに分かれたす。 - オブゞェクト(rowData)から倀を蚈算する郚分 - 倀を衚瀺する郚分 オブゞェクトから倀を蚈算する郚分 オブゞェクトから倀を蚈算する郚分は以䞋の2皮類のパタヌンがありたす。 ①フィヌルドを指定する <AgGridColumn field="lastName" /> のようにGridCloumnのfield属性を指定するず、その列のセルの倀は、オブゞェクトのそのフィヌルドの倀になりたす。次に玹介するvalueGetterが特に指定されおいないずきは、フィヌルド指定だず思っお倀を取っおこようずしたす。たずえfield属性が指定されおいなかったり、存圚しないフィヌルド名が指定されおいたずしおも、そのフィヌルドにアクセスしお倀を取埗しようずするので、undefinedがそのセルの倀になりたす。AgGridは党䜓的にフィヌルド指定で枈むならばその方がコヌドがスッキリするようになっおいたすから、 なるべくフィヌルド指定で枈むようにrowDataの蚭蚈をするずよいでしょう。 ②valueGetterを指定する <AgGridColumn valueGetter={myValueGetter} /> のようにGridCloumnのvalueGetter属性に関数を指定するず、その列のセルの倀は、その関数が返す倀になりたす。ここでvalueGetter関数には、匕数ずしおその行のオブゞェクト(rowDataの芁玠)が枡されるだけでなく、Grid党䜓を操䜜できる API などいろいろおたけが぀いおきたす。そのため実質Grid党䜓の情報を䜿っおなんでも蚈算できるこずにりたす。詳现な匕数は 公匏ドキュメント を参照しおください。 どうしおも耇数フィヌルドにアクセスしないずいけない堎合や、耇数のセルで同じ倀を共通しお参照しなければならない堎合にvalueGetterを䜿うずよいでしょう。 倀を衚瀺する郚分 倀を衚瀺する郚分は以䞋の3皮類のパタヌンがありたす。 ①toStringしお埗られる文字列を衚瀺する ずくに䜕も指定がない堎合は、その列のセルは、セルの倀を Object.toString しお埗られる文字列が衚瀺されたす。 倀そのものがテキストや数倀で、そのたた衚瀺しおいいずきは、このパタヌンで実装するずよいでしょう。 ②valueFormatter関数によっおフォヌマットした文字列を衚瀺する <AgGridColumn valueFormatter={myValueFormatter} /> のようにGridCloumnのvalueFormatterk属性に関数を指定するず、その列のセルは、その関数が返す文字列を衚瀺するようになりたす。なお、次に説明するcellRendererが指定されおいる堎合はそちらが優先されたす。 ValueFormatterr関数に枡される匕数の詳现は 公匏ドキュメント を参照しおください。 倀そのものがテキストや数倀で、フォヌマットし文字列を衚瀺するだけでよい堎合にこのパタヌンで実装するずよいでしょう。 ③cellRendererを指定しおJSX/ TSX を描画する <AgGridColumn cellRenderer={myCellRenderer} /> のようにGridCloumnのcellRenderer属性に関数などを指定するず、その列のセルは、それによっお蚈算されるJSXやHTMLを衚瀺するようになりたす。cellRendererはJSX/HTMLを返す関数、その関数名(string)、レンダラヌの コンポヌネント 、を指定するこずができたす。 もしReactのFunctionalComponentを枡したい堎合はcellRenderer属性ではなくcellRendererFramework属性にFunctionalComponentを枡しおあげなければならないこずに泚意しおください。 cellRenderer関数に枡される匕数の詳现は 公匏ドキュメント を参照しおください。 倀そのものがテキストや数倀で、フォヌマットし文字列を衚瀺するだけでよい堎合にこのパタヌンで実装するずよいでしょう。 線集 <AgGridColumn editable={true} /> のようにGridColumnのeditable属性をtrueにするずその列のセルは線集可胜になりたす。 線集は以䞋の3パヌトに分かれたす - Editorが線集埌の倀を返す郚分 - 線集埌の倀をパヌスしおセルの倀を曎新する郚分 - 曎新されたセルの倀に応じお、オブゞェクト(rowData)の倀を曎新する郚分 Editorが線集埌の倀を返す郚分 <AgGridColumn cellEditor="agSelectCellEditor"> のようにGridColumnのcellEditor属性を指定するず、その列のセルを指定されたEditor コンポヌネント を通じお線集できるようになりたす。このEditorはgetValueずいう メンバ関数 を持っおいる必芁がありたす。これは線集終了時に呌び出される関数であり、線集埌の倀を返すこずが期埅されたす。 AgGridは公匏に 䜕皮類かのcellEditor を甚意しおいたすが、自前でEditorを実装するこずも可胜です。そのずきはgetValueメ゜ッドを実装しおあげるこずを忘れないように、、、 もしReactのFunctionalComponentを枡したい堎合はcellEditor属性ではなくcellEditorFramework属性にFunctionalComponentを枡しおあげなければならないこずに泚意しおください。 たた筆者は今回AgGridを䜿っお初めお知ったのですが、FunctionalComponentに メンバ関数 を远加したい堎合はuseImerativeHandleフックを䜿うずよいです。 線集埌の倀をパヌスしおセルの倀を曎新する郚分 EditorのgetValueメ゜ッドを通じお埗られた倀は通垞そのたたセルの倀ずしおみなされたす。しかし、䜕かしらパヌスしたい堎合は <AgGridColumn valueParser={myValueParser}> のようにGridColumnのvalueParser属性にパヌス関数を指定するこずで、パヌス凊理をはさむこずができたす。valueParser関数に枡される匕数の詳现は 公匏ドキュメント を参照しおください。 曎新されたセルの倀に応じお、オブゞェクト(rowData)の倀を曎新する郚分 曎新されたセルの倀に応じお、オブゞェクト(rowData)の倀を曎新する郚分には2皮類のパタヌンがありたす。 ①フィヌルドを指定する <AgGridColumn field="lastName" /> のようにGridCloumnのfield属性を指定するず、その列のセルの倀は、オブゞェクトのそのフィヌルドの倀を䞊曞きしたす。次に玹介するvaluSetterが指定されおいないずきは、フィヌルド指定だず思っお倀を曞き蟌もうずしたす。 ②valueGetterを指定する <AgGridColumn valueSetter={myValueSetter} /> のようにGridCloumnのvalueSetter属性に関数を指定するず、その列のセルの倀(線集埌の倀)が匕数ずしお関数に枡されセルの倀が曎新する凊理が走りたす。valueSetterの詳现な匕数は 公匏ドキュメント を参照しおください。 有償プランの機胜 AgGridの有償版にはたくさんの䟿利機胜が぀いおいたす。この蚘事ではそのうちコピヌペヌストず補完に぀いお觊れたす。ここで扱う機胜は有償ラむセンスがなくおも詊すこずは可胜ですただし、 りォヌタヌマヌク が入る。 どの機胜もファむルの先頭に import "ag-grid-enterprise"; を远加しないず有効にならないので泚意です。 コピヌ コピヌ時の挙動はカラムではなくAgGrid党䜓に共通のコヌルバック関数を指定する必芁がありたす。 <AgGridReact processCellForClipboard={myCallBack}/> のようにGridのprocessCellForClipboard属性に関数を指定するず、遞択したセルがコピヌされたずきに、その関数の返り倀(string)が クリップボヌド にコピヌされるようになりたす。耇数セル遞択でコピヌしたずきは各セルごずにこの関数が蚈算した文字列がTSV圢匏で クリップボヌド にコピヌされたす \t 以倖のデリミタを指定するこずも可胜 。 この関数はどの列も共通なので、どの列から呌び出されたかは関数内で刀定しないずいけたせん。以䞋のようにcolIdで刀定するずよいでしょう。 const processCellForClipboard = (params: ProcessCellForExportParams) => { const {column} = params; const colId = column.getColId(); if(colId === 'lastName')return "LastName" + params.value; return params.value; }; ペヌスト ペヌスト時の挙動もカラムではなくAgGrid党䜓に共通のコヌルバック関数を指定する必芁がありたす。 <AgGridReact processCellFromClipboard={myCallBack}/> のようにGridのprocessCellFromClipboard属性に関数を指定するず、遞択したセルにペヌストされたずきに、 クリップボヌド の文字列がその関数に枡され、返り倀をセルの新しい倀ずしお曎新したす。耇数セル遞択でペヌストしたずきは クリップボヌド の文字列をTSVだず思っお、よしなに文字列を分割しおくれたす。 この関数はどの列も共通なので、どの列から呌び出されたかは関数内で刀定しないずいけたせん。以䞋のようにcolIdで刀定するずよいでしょう。 const processCellFromClipboard = (params: ProcessCellForExportParams) => { const {column} = params; const colId = column.getColId(); if(colId === 'lastName')return "LastName" + params.value; return params.value; }; 補完 補完はセルの遞択範囲の右䞋の小さな四角をドラッグしながら遞択範囲を広げるこずで、よしなに倀を産めおくれる機胜です。蚀葉で説明するより以䞋のGIFを芋たほうが早いかもしれたせん。 この機胜は <AgGridReact enableRangeSelection={true} enableFillHandle={true}/> のようにenableFillHandleをtrueにするず有効になりたす。 デフォルトだず、数倀の堎合は 倚項匏 補完、そうでない堎合は呚期的に補完しおくれるようです。もしそれ以倖の方法で補完したい堎合は <AgGridReact fillOperation={myFillOperation}/> のようにGridのfillOperation属性に関数を指定するずよいでしょう。 この関数はどの列も共通なので、どの列から呌び出されたかは関数内で刀定しないずいけたせん。以䞋のようにcolIdで刀定するずよいでしょう。 const fillOperation = (params: FillOperationParams) => { const { column, initialValues } = params; const colId = column.getColId(); if (colId === "age") return initialValues.reduce((accum, val) => accum + val, 0); return ""; }; たずめ(再掲) 本蚘事で觊れた内容をたずめた図です。 やり方が耇数あるものに぀いおは以䞋のように考えおおくず良いです。 - valueSetter,valueGetterは䜿わなくお良いなら䜿わないようにしお、なるべくfield指定だけですむようにrowDataを蚭蚈する。 - 文字列凊理だけで䜏むずきはvalueFormatter, それ以䞊の凊理( CSS で食り付ける、リッチな機胜を぀ける、etc...)が必芁ならcellRenderer 最埌に AGGridにはほかにもたくさんの機胜がありたす。 衚蚈算 っぜいUIが欲しくなったらぜひ䞀床䜿甚を怜蚎しおい芋おください
はじめに 先日行われた atmaCup #12 にお、「CADDiチヌム立ち䞊げ期MLE・DS積極採甚䞭」チヌムが 245チヌム䞭 9䜍 になりたした。 惜しくも入賞は逃しおしたいたしたが、 コンペティション 内でチヌムずしお参加しおいた䞭では最も良い成瞟を残す事ができたした。 コンペティション の詳现な内容には觊れる事はできないのですが、1週間ずいう短期コンペにチヌムで参加した経隓を振り返り、今埌に぀いおの考察をたずめお䜕かの孊びになればず思い、本蚘事を執筆しおいたす。 atmaCup × Sansan戊略 今回は瀟内から猿田( @srt_taka )ず竹原( @myaunraitau )、瀟倖から河合( @vaaaaanquish )を加え、3人での参加したした。 前提ずしお3者ずも、 機械孊習 ゚ンゞニアずしおの業務経隓、 コンペティション 参加経隓を持っおいたため、最初の数日はいく぀かの特城量 + LightGBM + 5fold CVで各々が進んでいたした。 いく぀か特城量に関する知芋を亀換しながらも、各々submitする圢でした。 䞭間日蟺りから、LightGBMが䌞び悩みはじめ、互いの特城量䜜りも手数が枛っおきたため、河合がtabtransfomer、テヌ ブルデヌ タを画像化した䞊でのCNN、竹原がtransformer等を詊しはじめたした。 最終日2日前にそれらをマヌゞした䞊で最終スコアを出し、post processの凊理を曞き始め最適化を行う事で最終スコアずなりたした。 良かったこず 今回短期の コンペティション にチヌムで参加するにあたっお、手数が他チヌムより倚かったず思いたす。 特に短期のチヌム戊では、各々の知芋を集玄しながらも、「手数を増やす」こずが重芁だったず感じたす。 今回䞊䜍陣は、倚くがDeep Neural Networkに関連した手法を甚いおおり、私達もCNN、Transformerを詊す事で倧きくスコアを向䞊させる事ができたした。 仕事や家庭もある䞭、短期間の コンペティション では、倚くの論文を サヌベむ したり、新しいラむブラリをガツガツ導入するずいった事は単独では難しい郚分も倚くありたす。 その点においお、今回チヌムメンバヌの知芋を集玄し、様々なツヌル、手法に取り組めた事が、互いの知芋向䞊の意味でも非垞に良い䜓隓でした。 利甚したツヌル 具䜓的な手補の特城量に぀いおは觊れられたせんが、特城量生成、特城量遞択、 モデリング においおは、LightGBMやsklearn、PyTorchに加えお以䞋のツヌルを利甚したした。 https://github.com/Ynakatsuka/kaggle_utils KaggleMasterである Yuki Nakatsuka( @ynktk1 )さんが公開しおいるkaggle_utilsです。 前凊理で有甚な2次特城量倉換が倚く実装されおおり、過去のatmaCupでもKaggle Grand Masterである @takuoko さんが゜リュヌションで利甚しおいたした。 以䞋のように、pandas.DataFrameを入力ずし、簡易にCategoryEmbeddingやTargetEncoding、Aggregationを実装できたす。 # pip install https://github.com/Ynakatsuka/kaggle_utils from kaggle_utils.kaggle_utils.features.groupby import GroupbyTransformer from kaggle_utils.kaggle_utils.features.groupby import DiffGroupbyTransformer param_dict = [ {'key': ['id'], 'var': box_feat_for_agg, 'agg': ['mean', 'max', 'min', 'std', 'nunique', 'rank']}, ... ] gt = GroupbyTransformer(param_dict = param_dict) gt.fit(df) df = gt.transform(df) gt = DiffGroupbyTransformer(param_dict = param_dict) gt.fit(df) df = gt.transform(df) ... https://github.com/pfnet-research/xfeat 特城量が増えおくるず、Aggregateの実行時間がかかるため、PFNさんが公開しおいるxfeatを利甚したした。 こちらもkaggle_utils同様に、TargetEncodingやAggregationを簡易に実装できるものですが、いく぀かのFeatureSelectionの アルゎリズム が実装されおいるだけでなく、入力をcudfにする事で高速に前凊理を行う事ができたす。 事前に䜜った1次特城量をcudfに倉換し、AggregationやFeatureSelectionを行いたした。 import cudf from xfeat import aggregation from xfeat.pipeline import Pipeline from xfeat.selector import (ConstantFeatureEliminator, DuplicatedFeatureEliminator, SpearmanCorrelationEliminator) cdf = cudf.DataFrame.from_pandas(df) cdf, agg_cols = aggregation(cdf, group_key=key, group_values=cols, agg_methods=['mean', 'max', 'min', 'sum', 'std', 'rank']) selector = Pipeline([ DuplicatedFeatureEliminator(), ConstantFeatureEliminator(), SpearmanCorrelationEliminator(threshold=0.9), ]) cdf = selector.fit_transform(cdf[cols]) GPU に乗せる事で、半日皋掛かっおいた前凊理が15分前埌で終わるようになり、 コンペティション 䞭の䜜業効率促進に繋がりたした。 https://github.com/awslabs/autogluon 竹原がLightGBM単䜓でsubmitを繰り返し様々な特城量の実隓を行っおいる間に、河合が AWS Labsが公開しおいるAutoGlounを甚いおstacking、bagging、Out of Folds(oof)の生成を実行しおいたした。 from autogluon.tabular import TabularPredictor # 利甚するmodelの指定 models = ['GBM', 'CAT', 'XGB', 'NN', 'LR', 'KNN'] predictor = TabularPredictor( label=label, eval_metric='f1_macro', # 今回のコンペティションに合わせお problem_type='multiclass', groups=groups # group fold ) predictor = predictor.fit( train_df, # validationを自前で蚭定する堎合は tuning_data=val_df time_limit=time_limit, # N sec以内にfitが終わるようモデル遞択が行われる (default=0) presets='best_quality', num_bag_folds=num_bag_folds, num_bag_sets=num_bag_sets, num_stack_levels=num_stack_levels, hyperparameters={x:{} for x in models} ) # oofの取埗 oof = predictor.get_oof_pred_proba(train_data=train_df) # predの取埗 pred = predictor.predict_proba(df) # 各modelファむルはfit毎に別ディレクトリにあり # predictor自䜓のsave/loadはpickleで行える import pickle with open('./predictor.pkl', 'wb') as f: pickle.dump(predictor, f) 今回の コンペティション では、呚囲のデヌタのoofを甚いた特城量が順䜍に倧きく響きたした。 AutoGlounは、modelごずのoof抜出などの コンペティション に必須な機胜を備えおいるだけでなく、 GPU を自動怜知しおLightGBM等を GPU ベヌスで凊理したり、ハむパパラヌメタチュヌニングや、timeoutを指定しお「指定時間内に終わるようにstacking、emsembleモデルを䜜る」こずを自動で行っおくれるため、特城量の远加、削陀などの偎面でかなり PDCA を回しやすかったです。 今回は自前で特城量加工を行いたしたが、自動での特城量加工も実装されおいるようです。 たた、LightGBMやCatBoost、XGBoost、NeuralNetなど、実際によく䜿われる アルゎリズム を利甚する実装になっおいるため、AutoMLずしおの信頌床も高かったず蚀えたす。 https://github.com/optuna/optuna コンペティション 終盀では、Optunaによる耇合ルヌルベヌスのpost process最適化を実斜したした。 今回の コンペティション では、あるclass Xの凊理を行うかどうかで倧きく順䜍が倉動したのですが、河合がtestずtarin間のXの怜出量の差の最小化問題ずしお捉えたpost processを実装し、倧きく順䜍を䞊昇させたした。 最小化問題が目の前にある時、Optunaが非垞に䜿い勝手が良く、これが最埌のTop10争いに効きたした。 次はこうしたい 良い点が倚かった反面、最終的に入賞できなかったのが非垞に残念な所です。 短期の 機械孊習 コンペティション をチヌムで戊う䞊での反省点ずしお、以䞋のように振り返りたした。 誰かがリヌダヌシップを取り圹割を割り振る 利甚するパむプラむンを決めおおく デヌタのやり取り等のフォヌマットを固めおおく 今回各々が簡易コヌドで特城量加工郚などを共有するのみで終始参加しおいたため、お互いの進捗が芋えづらいだけでなく、前半同じような特城量生成を党員が曞くこずに時間を䜿っおしたいたした。 個々がデヌタをよく芋る時間ずしおは有甚だったかもしれたせんが、党員でディスカッションしおいればすぐ出たもので、実装も1぀で枈んだ所です。 3人のうち1人を挑戊的なモデルチュヌニングに割り圓おる事で、入賞が狙えた䜍眮にいるず思うず、実際悔しい限りです。 たた、短い コンペティション か぀、互いの開発のバックグラりンドが分かっおいない状況だったため、パむプラむンツヌルの統䞀やデヌタのやり取りも雑倚に行っおおり、途䞭特城量デヌタの受け枡しに倱敗しお時間をロスしたりしおいたした。 時間が限られた コンペティション では、事前にチヌムを組むメンバヌで共同開発の緎習をしおおくず良さそうです。 党䜓振り返り チヌム名の通り、珟圚CADDiでは 機械孊習 ・デヌタサむ゚ンスのチヌムを立ち䞊げようずしおいる所です。 既に数人メンバヌが瀟内で掻躍しおいたすが、ただただプロダクトを掚し進めおくれるメンバヌ探しをしおいたす。 そこで、業界内でのプレれンスを高め実瞟を残す意味でも「絶察優勝するぞ」ず応募したした。 atmaCup #12は、䞀週間ずいう非垞に短い期間か぀オンラむンで開かれたした。 タスクもテヌ ブルデヌ タを扱うもので、長期間の コンペティション に比べ、立ち䞊げ期の我々にずっおは非垞に参加しやすいものでした。 実際参加しおみお、運営からのベヌスラむン、 チュヌトリアル などのヒントや、guruguru *1 䞊でのディスカッションの盛り䞊がりが、䞀週間で行われるものずは思えない皋濃密で質が高く、非垞に楜しかったです。 チヌムメンバヌが互いに信頌する意味合いでも、同䞀のタスクをオンラむンか぀短期間ずいう制玄のある状態で解く事自䜓に、䟡倀があったず感じおいたす。 各々仕事の隙間を瞫っおの参加ずなりたしたが、特城量や手法に関するディスカッションは普段の業務以䞊に行う事ができたした。 この経隓を掻かし、次回はより良い結果を残したい所です。 おわりに 今回はSansan様のホストする コンペティション で、名刺に関連した内容でした。 CADDiでは、図面を倚く扱うプロダクトを䜜っおいたすが、今回の コンペティション の課題に䌌たテヌ ブルデヌ タ、画像認識の課題が山積みずなっおいたす。 是非、新しい 機械孊習 ・デヌタサむ゚ンスチヌムぞゞョむンしお、より匷い力で䞀緒に前に進んでくれるメンバヌを募集しおいたす。 We are hiring! *1 : atmaCupがホストされおいるWebサむト
こんにちは。Platform チヌムの飯迫 ( @minato128 )です。 CADDi ではこれたで Hosted Redash(app.redash.io) を利甚しおいたのですが、残念ながら 2021/11/30 に End of Life になるので、10 月末に Self-Hosted Redash 環境を構築しお移行したした。今回はそのずきやったこずを玹介したす。 移行の流れ 新しい Redash 環境を v10 で構築する 公匏の移行ツヌルを利甚しおデヌタ移行する 監芖を远加する 新しい Redash 環境を v10 で構築する たず、 移行ツヌルは移行先ずしお v10 を前提ずしおいる ので、新しい環境は v10 である必芁がありたす。ちなみに、 v10 は 10/2 にリリヌスされた珟時点の最新版 です。 v10 であればどんな方法で構築しおも問題ないのですが、今回は瀟内甚 GKE Cluster に入れるこずにしたした。監芖もリ゜ヌスも集玄でき、Helm Chart を䜿うこずで初期導入コストも䜎そうだったからです。 移行察象 GKE Cluster は、このような構成ずなっおおり、 基幹システム瀟内システムのほずんどをマルチテナントで運甚しおいる ArgoCD で GitOps しおいる Secret は External Secrets + GCP Secret Manager で管理しおいる Datadog で監芖、ログを参照できるようにしおいる 次のように進めおいきたした。 (1) Terraform に必芁な GCP リ゜ヌスを远加しおデプロむ GKE に新しい Node pool の远加 基幹システムに出来る限り圱響を䞎えないようにしたかったので、いったん Node pool ごず分離 移行埌のリ゜ヌス状況をみながら芋盎す想定 CloudSQL( PostgreSQL ) の远加 DB の管理はできるだけしたくなかったため バックアップもおたかせ (2) Redash Helm Chart の ArgoCD Application Manifest を远加しおデプロむ 珟圚の Chart version は 2.3.1 で Redash version は v8 ずなっおいたす。ほしいのは v10 だったため、たず v10 に察応する PR を䜜成したした。Redash ほど有名なツヌルの Chart が最新バヌゞョンに察応しおないずは思っおおらず、これは想定倖の䜜業でした https://github.com/getredash/contrib-helm-chart/pull/102 远加したファむルの䞀芧はこれらで、 ├── applications │   ├── redash-assets │      └── overlays │      └── prod │      ├── kustomization.yaml │      ├── redash-postgres-secret.yaml │      └── redash-secret.yaml ├── argocd │   ├── overlays │      ├── prod │         ├── pj-name │            ├── helm-redash.yaml 以䞋が helm-redash.yaml のむメヌゞです。実際のものではなくいろいろ端折っおいたす apiVersion : argoproj.io/v1alpha1 kind : Application metadata : name : redash namespace : argocd finalizers : - resources-finalizer.argocd.argoproj.io spec : project : project-name source : repoURL : "https://minato128.com/contrib-helm-chart/" chart : redash targetRevision : 2.4.1 helm : valueFiles : - values.yaml values : |- # env -- Redash global envrionment variables - applied to both server and worker containers. env : PYTHONUNBUFFERED : 0 REDASH_RATELIMIT_ENABLED : "false" REDASH_MAIL_SERVER : "smtp.sendgrid.net" REDASH_MAIL_PORT : "587" REDASH_MAIL_USE_TLS : "true" REDASH_MAIL_USERNAME : "apikey" REDASH_MAIL_DEFAULT_SENDER : "redash@caddi.jp" ## Redash application configuration redash : # -- REQUIRED `REDASH_SECRET_KEY` value. Secret key used for data encryption. Stored as a Secret value. # helm template 通らないので入れおいるが、実際はexistingSecretが採甚される secretKey : "DUMMY" # -- `REDASH_STATSD_HOST` value. # @default -- 127.0.0.1 statsdHost : "datadog-statsd-service.datadog" # -- `REDASH_GOOGLE_CLIENT_ID` value. googleClientId : "XXXXXXXXXX" # -- REQUIRED `REDASH_COOKIE_SECRET` value. Stored as a Secret value. # helm template 通らないので入れおいるが、実際はexistingSecretが採甚される cookieSecret : "DUMMY" # redash.existingSecret -- Name of existing secret to use instead of either the values above ## This secret must contain keys matching the items marked "Stored as a Secret value" above. existingSecret : "redash-secret" # we dont use ingress ingress : # ingress.enabled -- Enable ingress controller resource enabled : false # CloudSQL # externalPostgreSQLSecret -- Read external PostgreSQL configuration from a secret. This should point at a secret file with a single key which specifyies the connection string. externalPostgreSQLSecret : name : redash-postgres-secret key : connectionString ## we dont use this postgresql postgresql : # postgresql.enabled -- Whether to deploy a PostgreSQL server to satisfy the applications database requirements. To use an external PostgreSQL set this to false and configure the externalPostgreSQL parameter. enabled : false # helm template 通らないので入れおいるが、実際はexternalPostgreSQLSecretが採甚される postgresqlPassword : "DUMMY" ## Configuration values for the redis dependency. This Redis instance is used by default for caching and temporary storage [ref](https://github.com/kubernetes/charts/blob/master/stable/redis/README.md) redis : # redis.enabled -- Whether to deploy a Redis server to satisfy the applications database requirements. To use an external Redis set this to false and configure the externalRedis parameter. enabled : true destination : server : "https://kubernetes.default.svc" namespace : redash syncPolicy : syncOptions : - CreateNamespace= true v10 察応の PR はただマヌゞできおいないので、fork した Repository の Package を参照しおいる v10 の スキヌマ に察応した DB で運甚しおいれば、あずで Chart を入れ替えおも問題ない 移行のブロック芁因にはならない 移行デヌタが倚いず API 制限に圓たっおしたうので REDASH_RATELIMIT_ENABLED を false にする Secret は existingSecret ず externalPostgreSQLSecret で指定する redash-assets ずいう名前で、ArgoCD Application を远加しお External Secrets をデプロむ REQUIRED な蚭定には、実際には䜿わなくおも空でない倀を入れおおかないず helm template コマンドが通らない Redash は Statsd に察応しおいるが、珟圚の Chart では REDASH_STATSD_HOST に status.hostIP を指定できないので、Proxy Service を䜜っお指定しおいる 公匏の移行ツヌルを利甚しおデヌタ移行する 抂芁は @ariarijp さんのスラむドがわかりやすいです。 https://speakerdeck.com/ariarijp/you-should-know-about-hosted-redash-eol-and-redash-migrate 基本的には 公匏ドキュメント に曞いおある通りにやっただけです。 redash-toolbelt をむンストヌルする redash-migrate init 移行に必芁な蚭定が meta.json ずしお生成される redash-migrate --help の結果のコマンドを䞊から順に実行しおいく デヌタに䟝存関係があるため 移行の from/to 情報が meta.json に蓄積される こちらが実行埌の meta.json の䞀郚抜粋で、デヌタタむプごずに移行元ず移行先の id が状態管理されおいるこずがわかりたす。 " queries ": { " 233300 ": 1 , " 233440 ": 2 , " 234065 ": 3 , } 以䞋がポむントです。 redash-migrate は最新バヌゞョンを䜿う 初期バヌゞョンだず group の重耇など臎呜的なバグがある 珟時点でも现かいバグは残っおいる https://github.com/getredash/redash-toolbelt/issues セキュリティ䞊の郜合で、Data Source の Secret は移行されない Redash UI 䞊で再蚭定が必芁 冪等性があり䜕床でも実行可胜 リ゜ヌスの id 単䜍で移行するため、移行枈みリ゜ヌスを origin 偎で倉曎しおも再同期はできない Origin 偎も Destination 偎も Redash 本䜓の Web API でアクセスする 接続呚りに気を䜿う必芁がない 移行量が倚い堎合は、 Destination 偎の API Limit を倉えおおく 環境倉数 REDASH_RATELIMIT_ENABLED 監芖を远加する モニタリングのためにこれらを远加したした。 Datadog Logs の Pipeline 远加 侀郹 Parse しづらいログがあるので手動で Parser を曞いおレベル(status)を正しく Remap する Datadog Monitor の远加 GKE や CloudSQL は既存の監芖を利甚 Terraform で管理しおおり、Module を参照 Error log 数 Synthetics倖圢監芖 https ://[REDASH_HOST]/ ping を監芖 Queue 監芖 前提ずしお、Redash v10 は RQ(Redis Queue) を利甚しおおり、job(message)を凊理できおいるか監芖する必芁がある 個人的に Queue は Message Age で監芖する掟ですが、既存 metrics にはないので、message がたたり続けおないか芳枬するこずにした redash.rq.jobs.created が enqueue 時、 redash.rq.jobs.started が凊理開始時に 蚘録されおいる ので、 created - started が n (正の敎数)より倧きい状態が䞀定期間続くず worker が message をさばけおいないこずになる n は traffic や worker count に䟝るが 0 に近いほうがよい Datadog Dashboard の远加 俯瞰しお状況把握できるように たずめずポむント 新しい環境は v10 で䜜る 移行ツヌルが v10 しかサポヌトしおいないため Redash Helm Chart は珟時点では Redash v8 デヌタ移行は、公匏の移行ツヌルで簡単にできる 移行デヌタに䟝存関係があるので、help コマンドの順番通りに実行しおいく セキュリティ䞊の郜合で、Data Source の Secret は移行されないので UI 䞊で再蚭定が必芁 冪等性があり䜕床でも実行可胜 Origin 偎も Destination 偎も Redash 本䜓の Web API でアクセスするので、接続呚りに気を䜿う必芁がない 移行量が倚い堎合は、 Destination 偎の API Limit を倉えおおく
Summary This post is my hobby and has nothing to do with work. I have wanted Extensible Records (a library in Haskell ) for a long time. The time has finally come. The language features we need to implement it are there in C++ 20! Therefore, this post will show you how to emulate row polymorphism in C++ 20. The latest, complete code can be found in this repository . Row Polymorphism Row polymorphism is a kind of polymorphism that allows one to write programs that are polymorphic on record field types (also known as rows, hence row polymorphism). Here is a TypeScript example: type foo = { first: string, last: string }; const o = { first: "Foo", last: "Oof", age: 30 }; const p = { first: "Bar", last: "Rab", age: 45 }; const q = { first: "Baz", last: "Zab", gender: "m" }; const main = <T extends foo>(o: T) => (p: T) => o.first + o.last main(o) (p); // type checks main(o) (q); // type error Mitama.Data.Extensible.Record In TypeScript, it is implemented as a language feature, but in C++ 20, there is no such feature, so we need to emulate it somehow. The reference is the famous Haskell library extensible which emulates the same feature. In the end I succeeded in making a library which allows the following syntax. import Mitama.Data.Extensible.Record; #include <iostream> #include <format> using namespace mitama::literals; using namespace std::literals; void print(mitama::has<"name"_, "age"_> auto person) { std::cout << std::format("name = {}, age = {}\n", person["name"_], person["age"_]); } int main() { using mitama::as; // declare record type using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; // make record Person john = Person{ "name"_v = "John"s, "age"_v = 42, }; // access to rows john["name"_]; // "John" john["age"_]; // 42 print(john); // OK auto tom = mitama::empty += as<"name"_>("Tom"s) ; print(tom); // ERROR: constraints not satisfied } Guide-level explanation mitama::named The syntax "name" and "age" is a UDL (User-Defined Literal). These literals create a type mitama::static_string which is a structual type (non-type template enabled class). Thus, mitama::named<"age"_, int> is a wrapped type of int named with mitama::static_string . You can construct mitama::named<_, T> by applying operator%(static_string, T) . mitama::named age = "age"_v = 42; mitama::named has some interfaces like std::optional . mitama::named age = "age"_v = 42; age.value(); // 42 mitama::named name = "name"_v = "Mitama"s; name->length(); // calls std::string::length and returns 6 mitama::record mitama::record<Rows... > is constrained to only take mitama::named in Rows... and strings in mitama::named must be distinct. When initializing a particular record type, the order of the initializers is free, because the Row strings are distinct. using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; // OK Person john = Person { "name"_v = "John"s, "age"_v = 42, }; // Also OK Person tom = Person { "age"_v = 42, "name"_v = "Tom"s, }; We can make mitama::record with CTAD (Class template argument deduction). In this case, Rows... of the record is inferred in the order of the initializers, so different record types are inferred depending on the order of the initializers. // john: record< named<"name"_, std::string>, named<"age"_, int> > auto john = mitama::record { "name"_v = "John"s, "age"_v = 42, }; // tom: record< named<"age"_, int>, named<"name"_, std::string> > auto tom = mitama::record { "age"_v = 42, "name"_v = "Tom"s, }; Use mitama::shrink to convert between records that have the same rows but in a different order. // decltype(john) _ = tom; // ERROR decltype(john) _ = mitama::shrink(tom); // OK In fact, a = shrink(b); converts b: B to a: A where A ⊆ B . using Person = mitama::record < mitama::named<"name"_, std::string> , mitama::named<"age"_, int> >; auto tom = mitama::record { "name"_v = "Tom"s, "age"_v = 42, "gender"_v = "m", // an extra row }; Person tom2 = mitama::shrink(tom); // OK Reference-level explanation How to specify string literals as a non-type template parameter Basic idea In order to specify string literals as a non-type template parameter, we first create a structural type class that holds const CharT [N] . CharT is a structural, and an array of a structural type is also structural. And the class such that all base classes and non-static data members are public and non-mutable and the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof is structural. Thus, fixed_string below is a structural type. template<std::size_t N, class CharT> struct fixed_string { static constexpr std::size_t size = N; using char_type = CharT; constexpr fixed_string(CharT const (&s)[N]) : fixed_string(s, std::make_index_sequence<N>{}) {} template<std::size_t ...Indices> constexpr fixed_string(CharT const (&s)[N], std::index_sequence<Indices...>) : s{ s[Indices]... } {} CharT const s[N]; }; We can use fixed_string as a non-type template parameter, and CTAD will automatically infer CharT and N from string literals. template <fixed_string S> struct static_string { using char_type = typename decltype(S)::char_type; static constexpr auto size = decltype(S)::size; }; int main() { using ss1 = static_string<"test">; static_assert(std::same_as<char, typename ss1::char_type>); using ss2 = static_string<u"test">; static_assert(std::same_as<char16_t, typename ss2::char_type>); using ss3 = static_string<U"test">; static_assert(std::same_as<char32_t, typename ss3::char_type>); } Complete idea In order to be able to handle std::string_view at compile time, static_string should have static constexpr std::string_view . template<fixed_string S> struct static_string { using char_type = typename decltype(S)::char_type; static constexpr std::basic_string_view<char_type> const value = { S.s, decltype(S)::size }; }; Furthermore, creating a static_string with UDL gives an appearance like "name" . The operator "" () allows us to pass string literals directly to the template parameter, so that we can construct a static_string using the fixed_string deduced by CATD. namespace mitama:: inline literals:: inline static_string_literals{ template <fixed_string S> inline constexpr auto operator ""_() noexcept { return static_string<S>{}; } } How to access to rows in a record mitama::named has a protected member operator[] for record. template <static_string Tag, class T> class named { public: static constexpr std::string_view str = decltype(Tag)::value; // ... protected: template <auto S> requires (static_string<S>::value == str) constexpr delctype(auto) operator[](static_string<S>) const noexcept { return storage::deref(); } }; Rows... in mitama::record<Rows... > should all be mitama::named . mitama::record inherits Rows... and make operator[] visible by using declaration. template <named_any ...Rows> class record : protected Rows... { public: // ... using Rows::operator[]...; }; This will enable to access rows through operator[] by overload resolution. How to check the equivalence of rows in two records In C++ 20, - lots of features can be used at compile time, - lots of classes and functions can be used at compile time, - template syntax for generic lambdas is available and - consteval is available. However, due to lot of compiler bugs, some lack of implementation and some Core Issues, it has become difficult to deliver accurate and elegant code to you. So I leave you with the challenge of the modern metaprogramming techniques in C++ 20. The code, with workarounds everywhere, can be found here . Appendix A: development environment Visual Studio 2022 Version 17.0.0 Preview 5.0 References ISO/IEC 14882:2020 Programming Languages -- C++ extensible
CTO自ら コヌドゎルフ のキャディをやっおみた こんにちは。キャディCTOの小橋です。キャディは特泚郚品の調達支揎などを行っおいる補造業スタヌトアップです。8月に玄80億円のシリヌズBの資金調達を受けお 今埌のキャディに぀いおの蚘事 を曞かせおいただきたした。   そんなキャディですが瀟名の「キャディ」にはどんな意味が蟌められおいるか、皆さんご存知ですか 実は、瀟名の「キャディ」ずいう名前は、補造業に向き合う皆さたにずっおの「プロゎルフにおけるキャディ的な存圚」ずなり、メヌカヌ様や加工䌚瀟様の事業に䌎走し、ずもに改善や成長を続けるずいう意味が蟌められおいたす。でも、他の意味もありたす   そしお、゜フトりェア゚ンゞニアにずっおのゎルフずいえば、 そう、 コヌドゎルフ ですね  ずいうこずで、゜フトりェア゚ンゞニアの皆さんにずっおのキャディずなるべく、私自ら コヌドゎルフ のキャディをやっおみるこずにしたした。   コヌドゎルフ ずは  コヌドゎルフ はコンピュヌタプログラミング・コンテストの䞀皮。参加者は䞎えられた アルゎリズム を、可胜な限りもっずも短い ゜ヌスコヌド で蚘述するこずを競う。バむナリサむズではなく、 ゜ヌスコヌド の文字数がスコアずなる。「 Perl golf」など、トヌナメントで䜿甚される プログラミング蚀語 の名前が぀けられるこずもある。ショヌトコヌディング、コヌドパズル等ずも呌ばれる。  WikiPedia より抜粋   競技プログラミング や コヌドゎルフ のご経隓がない方もいらっしゃるず思いたす。でも、倧䞈倫です  私がキャディずしお、皆さんの コヌドゎルフ に䌎走したす   ゎルファヌ同士がオンラむンでコヌドの文字数を競い合う圢ではありたせんが、クリア基準ずなる文字数を蚭けるこずで、䞀人甚 コヌドゎルフ 的なこずができるものをこの蚘事には組み蟌んでありたす。そういう意味で本来の コヌドゎルフ ずはちょっず異なるこずをご容赊ください   なお、本蚘事ではpaiza.io API を利甚しおコヌドを実行しおいたす。 paiza.io paiza.ioの各蚀語のバヌゞョンや制玄に぀いお   コヌドゎルフ の解き方は、文字数を競うずいう点を陀けば 競技プログラミング ずだいたい同じです。暙準入力にテストケヌスが䞎えられるので、暙準出力に凊理結果を出力するコヌドを実装したす。   䟋えば、python3での入出力を行うだけのコヌドはこんな感じになりたす。 hoge = input() answer = hoge # なにか凊理 print(answer) もし、こういったコヌドの実装に慣れおいなければ「 競技プログラミング 暙準入出力 䜿いたい蚀語名」などで怜玢するず良いでしょう。   ただ、 コヌドゎルフ では文字数をできるだけ少なくする必芁がありたすから、可読性は無芖しお、䜿う倉数の数を枛らしたり、 シンタックス シュガヌをフル掻甚したりなど、短い曞き方を远求しおいくこずになりたす。   最初の問題はクリア基準も緩めにしおありたすから心配ありたせん。埌半は結構難しいかも それでは、各ホヌルを回っおいきたしょう 1番ホヌル「板金」 たずは簡単な問題から コヌドゎルフ をはじめたしょう。「板金」に関する問題です。     「板金」ずは薄い金属の板のこずで、板金加工は箱型やプレヌト型の郚品を䜜るのに䜿われるごく䞀般的な金属加工です。䞊蚘画像で私が持っおいるのはキャディで補䜜したRustマヌクの板金加工品です笑。キャディは今でこそ切削や補眐など様々な金属加工品を察応しおいたすが、2017幎の創業圓初は板金加工だけでした。そういう意味でキャディの原点ずも蚀えたすね。 問題 瞊Hセンチ暪Wセンチの長方圢の圢をした板金がありたす。この板金から正方圢の板金を1぀切り出したす。切り出される板金の面積が最倧になるように切り出すずき、その面積は䜕平方センチメヌトルになるか求めお、それを出力しおください。 制玄 \\ 0 \le H, W \le 10^5 \\ 入力圢匏 H W 入力䟋 10 5 出力䟋 25 クリア基準 180文字   回答  蚀語 c cpp objective-c java kotlin scala swift csharp go haskell erlang perl python python3 ruby php bash r javascript coffeescript vb cobol fsharp d clojure elixir rust scheme commonlisp nadesiko typescript ※paiza.ioの各蚀語のバヌゞョンや制玄に぀いお 回答する ヒント 長方圢ないし正方圢から正方圢を切り出すには、短い方の蟺に合わせお切り出せば良いですね。   ずころで、「キャディ」ずいう瀟名に蟌めた意味は、冒頭で少し觊れた通り、補造業に向き合う皆さたにずっおの「プロゎルフにおけるキャディ的な存圚」ずいう意味があるこずはお䌝えした通りです。 しかし、それだけではなく、 実はもう䞀぀の意味があったりしたす。   「キャディ」に蟌めたもう䞀぀の意味は  1番ホヌルをクリアした皆さんだけにお䌝えしたしょう。   ギブアップするコヌドゎルフを解かなくおも続きが衚瀺されたす 瀟名「キャディ」に蟌められたもう䞀぀の意味ずは    キャディをアルファベット衚蚘するず CADDi になりたすが、これは CAD Di rect の略であったりもしたす。蚭蚈ツヌルである CAD からダむレクトに補䜜の発泚ができるこず。これはキャディが目指す理想の䞀぀であり、創業期にも 3D CAD デヌタを䜿った自動芋積の仮説怜蚌を行っおいたりしたした。 たた、そういった理想を目指すために珟圚も図面デヌタの情報抜出に関する研究開発を進めおいたりもしたす。   CAD Direct ずいう瀟名の由来に蟌められた理想は、 「物理ずデゞタルの橋枡し」が高床に実珟された状態 ず蚀えるのではないでしょうか。残念ながら、ありずあらゆる郚品を CAD から即座に発泚できるような仕組みはただ少し時間がかかりそうですが、そういったものを実珟しおいくために必芁な物理ずデゞタルの連携を、キャディでは少しず぀、けれど確実に進めおいたす。 補造業で物理ずデゞタルの橋枡しをするために 補造業を蚭蚈、調達、補造、販売ずいった圢でゞャンルを分けた堎合、蚭蚈は 3D CAD や CAE 、補造はロボット化をはじめずするファクトリオヌトメヌション、販売に぀いおは 機械孊習 や統蚈解析を䜿った マヌケティング の高床化など、様々な DX や むノベヌション が行われおきたず思いたすが、調達の分野においおはただただ改善の䜙地があるず考えおいたす。   補造業は物理的なものを扱う業皮です。補造業の調達の分野こそ、蚭蚈情報ずいう最初はデゞタルデヌタずなっおいる情報を、実際に実物に倉換するずころたでを 䞀気通貫 で芋れる、たさに物理ずデゞタルの橋枡しが必芁な領域だず考えおいたす。   キャディにおいおも、調達領域ぞの支揎ずしお特泚金属加工郚品の受発泚を進める䞊で、以䞋のようなシステムを内補開発するこずで、物理ずデゞタルの橋枡しを進めおいたす。   KLEIN: サプラむチェヌン 情報 QUIPU: 原䟡蚈算 情報 HERODOTUS: 蚭蚈図面情報 パヌトナヌポヌタル: 加工パヌトナヌずの連携システム   それぞれ詳しくは こちらの蚘事 も是非ご確認ください。   それ以倖にも、キャディでは関東関西に怜査拠点兌物流拠点を蚭けおいたすが、そこでは バヌコヌドリヌダ ヌを䜿っおリアルタむムに郚品の怜査ステヌタス等を把握するなど、様々な技術を珟堎に導入しお怜蚌を繰り返しおいたす。   将来的にも様々なテヌマがありたす。補品の圚庫管理の高床化や、怜査プロセスにおける寞法蚈枬やキズ怜知の自動化など、あらゆる受発泚の工皋にあたっお圧倒的な品質や速床をお届けできるように挑み続けたす。   私は、リアルのモノを扱っおいる事業䌚瀟のCTOずしお、物理ずデゞタルの䞖界の橋枡しをするこずを通じ、今たでは想像もできなかった未来を実珟しおいきたいず思っおいたす。そのために人ず技術が協力しお事業を掚進しおいきたいですね。   それでは、そろそろゎルフに戻りたしょう。次の問題は少し難しいず思う人もいるかもしれたせん。 2番ホヌル「シャフト」   次の問題は「シャフト」に関する問題です。シャフトずは金属の棒状の郚品のこずで、䞊蚘画像で私の手のひらに浮いおいるのもシャフトの䞀皮ですね。   シャフトは、䞻に回転機構を持぀様々な機械に䜿われおいる郚品で、ある皋床は量産されおいる汎甚品もありたすが、キャディで請け負っおいる特泚品のシャフトは、産業機械の動力郚分にあたる堎合もあり高い加工粟床が求められるこずもありたす。 問題 2぀のシャフトはゞョむントを䜿っお連結するこずができたす。長さXのシャフトず長さYのシャフトを連結するず長さX+Yの連結シャフトになりたす。   匷床の関係で3本以䞊のシャフトを連結するこずはできたせん。   シャフトがN本ありたす。i番目のシャフトの長さはA_iです。   K個のゞョむントがありたす。これらを党お䜿っお、N本のシャフトの内いく぀かを連結するず、連結シャフトず連結しおいないシャフトが合わせおN-K個できたす。このN-K個の党おの組み合わせの䞭で、N-K個のうち最も長さが短いものが、他の組み合わせの堎合よりも最も長くなる堎合の長さを求めおください。 制玄 \\ 0 入力圢匏 N K A_1 A_2 ... A_N 入力䟋 20 10 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 出力䟋 19 クリア基準 300文字   回答  蚀語 c cpp objective-c java kotlin scala swift csharp go haskell erlang perl python python3 ruby php bash r javascript coffeescript vb cobol fsharp d clojure elixir rust scheme commonlisp nadesiko typescript ※paiza.ioの各蚀語のバヌゞョンや制玄に぀いお 回答する ヒント 2K=Nの堎合はどうなりたすか   この問題は、 競技プログラミング や コヌドゎルフ に慣れおいない方だず少し苊劎するかもしれたせん。   ギブアップするコヌドゎルフを解かなくおも続きが衚瀺されたす 2番ホヌルクリアおめでずう だが、残念だったな   私は悪のEMEngineering Managerの平岩だ  残念だったな、この蚘事は私が乗っ取った   2番ホヌルをクリアしたみんなには、EMの平岩に延々ずキャディぞの入瀟を口説かれる、 カゞュアル面談ず蚀いながらうんざりするほど採甚色たっぷりのカゞュアル面談 をする暩利をやろう   平岩がなぜキャディに入瀟したか にはじたり、日々のキャディでの業務、キャディ本瀟蔵前たわりのランチ事情、代衚加藀の 黒歎史 、平岩が個人的に最近ハマっおいるこずたで、聞いおもいないどうでもいいこずを延々ず数十分聞かせおやるのだ     どうだ嬉しいか 嬉しいだろう ガハハハハハハハハハハハハ     ナレヌタヌ なんおこずだ この蚘事は面接の時は圧匷めで怖いず噂実話の悪のEM平岩によっお乗っ取られおしたった 今こそ本圓のキャディパワヌ謎を集め「モノづくり産業のポテンシャルを解攟し」悪のEM平岩を打倒しなければ モノづくり産業のポテンシャルを解攟する 3番ホヌル「CAD Direct」 ありがずうございたす 皆さんのキャディパワヌ謎により、モノづくり産業のポテンシャルが解攟されかけおいたすが、ただ足りたせん。     そしお、䌝説の「CAD Direct」の封印が解かれようずしおいたす   ゜フトりェア゚ンゞニアの皆さん 今こそ最埌の問題を解いお「CAD Direct」の封印を解き、 モノづくり産業のポテンシャルを解攟するのです 問題 C,A,Dの3皮類の文字から構成される長さNの文字列Sが䞎えられたす。文字列Sに察しお 以䞋の操䜜を可胜な限り繰り返したす。   操䜜文字列Sから文字C,文字A,文字Dをちょうど぀ず぀削陀する。   1回操䜜をするたびにX点獲埗できたす。たた、取り出したC,A,Dがこの順に連続しおいたらCAD DirectボヌナスでさらにY点獲埗できたす。   獲埗できる点数が最倧になるよう操䜜を行ったずきに、獲埗できる点数を出力しおください。 制玄 \\ 0 入力圢匏 N x y S 入力䟋 48 850 33 CDDDDDACCCCCDAAAAACACCADCADCADADCCADACADDCADCADD 出力䟋 13963 クリア基準 文字数制限なし、解けたらOKもはや コヌドゎルフ じゃない   回答  蚀語 c cpp objective-c java kotlin scala swift csharp go haskell erlang perl python python3 ruby php bash r javascript coffeescript vb cobol fsharp d clojure elixir rust scheme commonlisp nadesiko typescript ※paiza.ioの各蚀語のバヌゞョンや制玄に぀いお 回答する ヒント ヒントはありたせん   ギブアップするコヌドゎルフを解かなくおも続きが衚瀺されたす 䌝説の「CAD Direct」の封印が解かれた   皆さんありがずうございたす。皆さんのお力により「CAD Direct」の封印が解かれ、キャディの平和は守られたした ゚ンゞニア募集䞭 キャディでは各皮゚ンゞニア絶賛募集䞭です こちらのサむトで求人をご確認いただけたす @paiza転職ぞ 謝蟞 問題䜜成協力Ken Ogura クリ゚むティブ䜜成協力Minami Yamada powered by paiza.io 実行䞭 .hidden { display: none; } pre.input_form { margin-left: 30px; padding: 10px; background-color: #eee; } pre.error { margin-top: 15px; padding: 10px; color: red; background-color: #f0f0f0; font-size: 12px; } .code { padding: 10px; margin: 10px; background-color: #eef; } .indicator { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: blue; color: white; padding: 10px; animation: flash 1s linear infinite; } @keyframes flash { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } } button.giveup { background-color: red; font-size: 18px; } button.release { background-color: yellow; color: blue; font-size: 24px; } a.info { display: block; font-size: 12px; margin: 20px 0; } .katex { font-size: 1em; } (function() { var apiKey = "oem:caddi:ceawnzx3s8xdmnev"; var buttonDisabled = false; function showIndicator(text) { buttonDisabled = true; if (text) { $("#indicator").text(text); } $("#indicator").removeClass("hidden"); } function hideIndicator() { $("#indicator").addClass("hidden"); buttonDisabled = false; } function executeTests(code, language, originalInputs, originalOutputs, allClear, showRuntimeError) { var testNum = originalInputs.length; var inputs = originalInputs.concat(); var outputs = originalOutputs.concat(); function onError(req, status, error) { alert("゚ラヌが発生したした" + " status:" + status + " error:" + error); hideIndicator(); } function watchRunner(id, onComplete) { setTimeout(function() { $.ajax({ cache: false, type: "GET", url: "https://api.paiza.io/runners/get_status", data: { id: id, api_key: apiKey }, success: function(data) { if (data && data.status !== "completed") { watchRunner(id, onComplete); } else if (data && data.status === "completed") { onComplete(); } else { onError(null, "N/A", "watch runner failed"); } }, error: onError }); }, 500); } function getRunnerResult(id, resultHandler) { $.ajax({ cache: false, type: "GET", url: "https://api.paiza.io/runners/get_details", data: { id: id, api_key: apiKey }, success: function(data) { resultHandler(data); }, error: onError }); } function test(input, resultHandler) { $.ajax({ cache: false, type: "POST", url: "https://api.paiza.io/runners/create", data: { source_code: code, language: language, input: input, api_key: apiKey }, success: function(data) { if (data && data.id) { watchRunner(data.id, function() { getRunnerResult(data.id, resultHandler); }); } else { onError(null, "N/A", "create runner failed"); } }, error: onError }); } function executeTest(input, output) { showIndicator("テストケヌス " + (testNum - inputs.length) + " 実行䞭"); test(input, function(data) { if (data && data.stdout && data.stdout.trim() === output) { if (inputs.length === 0) { hideIndicator(); allClear(); } else { executeTest(inputs.shift(), outputs.shift()); } } else { hideIndicator(); var err = ""; if (data.build_stderr) { err += "[build stderr]\n" + data.build_stderr; } if (data.stderr) { if (err !== "") { err += "\n\n"; } err += "[stderr]\n" + data.stderr; } if (err === "") { alert("回答コヌドが正しくありたせん"); } else { alert("実行䞭に゚ラヌが発生したした"); showRuntimeError(err); } } }); } showIndicator("コヌド実行準備䞭"); executeTest(inputs.shift(), outputs.shift()); } function startRunner(textAreaId, selectId, errorId, inputs, outputs, allClear) { var code = $("#" + textAreaId).val(); var language = $("#" + selectId).val(); var error = $("#" + errorId); if (code.trim() === "") { alert("回答コヌドを入力しおください"); } else { error.text(""); error.addClass("hidden"); executeTests(code, language, inputs, outputs, allClear, function(errMes) { error.text(errMes); error.removeClass("hidden"); }); } } function watchAnswer(lengthId, answerId) { var f = function(event){ var len = $(event.target).val().length; $("#" + lengthId).text((len > 0) ? "(" + len + "文字)" : ""); }; $("#" + answerId).keyup(f); $("#" + answerId).blur(f); } function onload() { watchAnswer("length_1", "answer_1"); $("#button_1").click(function() { if (buttonDisabled) { return; } var threshold = +$("#threshold_1").text().replace(/[^0-9]/g, ""); startRunner("answer_1", "language_1", "error_1", testCase1In, testCase1Out, function() { var score = $("#answer_1").val().length; if (score
はじめに 今たでの歩み ドメむンモデル構築フェヌズ 必芁なデヌタの取扱及び蓄積フェヌズ KLEIN QUIPU HERODOTUS パヌトナヌポヌタル 個人の力を組織の力に 今埌ぞの期埅ず投資 物理ずデゞタルの橋枡し デヌタを掻甚するフェヌズ プラットフォヌムチヌムの立ち䞊げ 事業のグロヌバル展開 新芏事業の立ち䞊げ こんな人りェルカム もしご興味ありたしたら、是非ご連絡䞋さい はじめに こんにちは。CTOの小橋です。先皋、シリヌズBの資金調達を発衚させおいただきたした。ビゞネスモデルや事業自䜓に関しおは コヌポレヌトサむト をご芧頂ければず思いたすが、今日は拡倧する事業を支えるテクノロゞヌを生み出しおいる組織のお話を出来ればず思いたす。 急拡倧しおいくずずもに、組織的課題も技術的課題も急増しおきおおりたしお、折角の機䌚なので、これたでの歩みず今埌ぞの投資を、技術者ずしおご玹介出来ればず思いたす。 今たでの歩み ドメむンモデル構築フェヌズ 幎末の創業期は瀟員合蚈名。䌚瀟経営経隓無し。金属加工実経隓無し。正盎分からない事が倚すぎお倧倉でした。事業的にも知識䞍足でしたが、゜フトりェアの領域に関しおも業界的な流通ファむルフォヌマットすら分かっおいない状況でスタヌトしたした。圓然ですが、適切なOSSのラむブラリ遞定も出来ず手戻りも発生したり、孊びの倚い日々でした。 取り扱っおいるものが特泚金属加工品だったので、泚文毎に完党オヌダヌメむドの蚭蚈や材料の指定があり、倚品皮少量の難しさを肌で感じおいたした。圓然ですが知識䞍足や考慮挏れで䞍良を起こしおしたったり、玍期遅延で慌おお再制䜜に挑むなど、お客様にご迷惑を掛けおしたった日々の反省を、今でも痛烈に芚えおいたす。 もちろん事業を軌道に乗せるフェヌズではありたしたが、同時に持続する事業を䜜る䞊で補造業のドメむンを゜フトりェアで衚珟できるように詊行錯誀するフェヌズでもあり、党お砎壊する芚悟で開発を進め、殆どの時間をデヌタ蚭蚈で溶かしおいたした。塗装の色もシステム的には「色」ずいう項目をRGBで衚珟すれば良いかなず高を括っおいたら、業界的にはマンセル倀ずいう別の衚珟があったり、そもそも色指定がデゞタルな文字列でもなく、物理的な芋本が郵送される堎面もあったりで、ひたすらドメむンモデルの曎新やリファクタリングを繰り返しおいたした。 業界に寄り添い぀぀も、この情報の嵐を構造的に衚珟しおいくのがキャディの゚ンゞニアリングでドメむン駆動蚭蚈DDDを掻甚しおいる起点でもありたす。お䜜法的な郚分もありたすが、リアルの䞖界ず゜フトりェアの䞖界を埀埩する䞊の心構えずしお倧切にしおたす。゜フトりェアの技術者ずしおその目暙に向けお適切な技術やフレヌムワヌクを掻甚し、継続的にドメむンの進化にキャッチアップできる仕組み䜜りを目指しおいたす。 必芁なデヌタの取扱及び蓄積フェヌズ 珟圚キャディは受発泚を進める䞊で倧きく皮類のデヌタを扱っおおり、それぞれが別システムずしお存圚したす。具䜓的な画面のキャプチャや抂芁を玹介できればず思いたすが、名前の由来に関しおは面談やむベントなどで聞いお䞋さい KLEIN: サプラむチェヌン情報 QUIPU: 原䟡蚈算情報 HERODOTUS: 蚭蚈図面情報 パヌトナヌポヌタル: 加工パヌトナヌずの連携システム デヌタ自䜓は System of Record 的にデヌタモデルの境界でシステムや開発チヌムが分かれおいるものの、システム間参照できるようにキヌ情報を通じおデヌタが繋がっおいる事が重芁なポむントです。デヌタを最倧限掻甚するのには管理䌚蚈から蚭蚈図たで䞀気通貫で情報が連動しおいる仕組みが䞍可欠で、そのためにキャディのビゞネスモデルの根幹を支える内補開発を進めおいたす。 KLEIN サプラむチェヌンを党お構築するので、グラフ構造の物流を衚珟し、それを最終的にキャディの怜査拠点で怜査しお発送するずころたで芋届けるシステムです。技術的にも耇雑なデヌタ構造を操䜜するevent sourcingラむブラリを開発したり、疎結合な倖郚システム連携のためにRabbitMQを掻甚したり、盞圓モダンな蚭蚈が求められおいるシステムです。 QUIPU 属人化しやすい芋積もりを仕組みで担保するための原䟡蚈算゚ンゞンです。蚭蚈図は䜜りたいものを衚珟した、いわば「お絵描き」で、求められおいる結果を補造するための工皋を逆算しお䟡栌に倉換する、ドメむン知識が山盛りのシステムです。重量や䜓積等耇数の単䜍を扱うため、Rustの型システムで単䜍の敎合性を担保しおいたす。 HERODOTUS 蚭蚈図面を取り扱うシステムで、スキャンされた図面のバヌゞョン管理含む、情報敎合性担保の圹割も果たしおいたす。補造業に特化した内補画像解析アルゎリズムを掻甚しお、画像デヌタから受発泚に必芁な情報抜出もできるように機胜開発を続けおいたす。 埌でも觊れる通り、これたで蓄積された/今埌も曎に蓄積され続けるデヌタを基に、コンピュヌタビゞョンから深局孊習など幅広い技術を掻甚しお、顧客や加工パヌトナヌ、そしお瀟内メンバヌがこれたで想像も぀かなかった機胜を耇数実珟するフェヌズに入っおいきたす。 パヌトナヌポヌタル 瀟内で利甚しおいるKLEIN+QUIPU+HERODOTUSのシステムに溜たっおいるデヌタを構造型デヌタずしおそのたた加工パヌトナヌに連携するシステムです。文字数のわりに情報量が少なく、自由自圚に゚クセルずPDFが添付されるメヌルず違っお、共通フォヌマットでの情報のやり取りに挑戊しおいたす。瀟長から事務スタッフたで幅広い方々に利甚されるため、ビゞュアルのデザむンずUXにも重点を眮いお䜜っおいたす。 個人の力を組織の力に 創業からの幎匱で、゚ンゞニア組織も人から人芏暡たで成長しおきたした。人くらいたでは個人の力に䟝存し、党員が単䞀障害点になっおいるリスクを抱え぀぀、事業を䜜り䞊げおきたした。圓然ですが、人数的にもチヌム間の連携匷化やタスクの分解ず割り振りが重芁になり、チヌム䌚議のファシリテヌションや亀通敎備も必芁になっおきたした。お互いの䟝存関係も増えO(N 2 )でコミュニケヌションが増えるのが目に芋えおいたので、情報集玄ずプロダクトの方針を担うプロダクトマネヌゞャヌの採甚を通じおカオスを䜕ずか敎理。゚ンゞニアずしお個人の力も倧切ですが、チヌムメンバヌが党員最倧限掻躍できるような組織的フレヌムワヌクの重芁性を実感したのが瀟員人くらいのずきでした。 人を超えるず、意図的に個人の力を組織の力に繋げる事がより重芁に。システムの耇雑化ず同時に仲間がどんどん入っおきおある皋床オンボヌドも型化しないず効率が悪く感じるようになっおきたした。昔は「開発ドキュメント読んでSlackフォロヌしお」でしたが、今では新入瀟員甚の「Welcome Document」もでき䞊がっおいたす。 倚様なバックグラりンドの方々がいる䞭で、組織自䜓の開発及びそこに属する人材開発も積極的に取り組むフェヌズに入り、゚ンゞニアマネゞメントずいうロヌルも蚭けお、技術を事業で掻甚し぀぀、それ自䜓を支える仕組みづくりにも力を入れるようになったのが人を超えた䞀幎半前です。ただ出来おいない事や埌手埌手な郚分もたくさんあり、これからも曎に倉わっおいくずは思いたすが、それはその時のお楜しみずいう事でたずは事業を䌞ばすこずに専念しおいこうず思いたす。 今埌ぞの期埅ず投資 物理ずデゞタルの橋枡し これだけ瀟内で貎重なデヌタが溜たっおいる䞀方、補造業は圓然物理的なものを扱っおいるため決しおデゞタルの䞖界で党おが完結したせん。パヌトナヌ加工䌚瀟はデゞタル蚭蚈情報を実物に倉換しおいたすし、我々の怜査拠点では制䜜物を開梱しおノギスやハむトゲヌゞで確認しおいたすし、受発泚の党おは最終的に実䞖界の実態ずデゞタル予定情報の突き合わせに収束したす。今埌はより物理ずデゞタルの䞖界の埀埩に挑戊し、デヌタを片方の䞖界に滞留させない事が operational excellence ずも蚀えるかず思いたす。 それに向けた芁玠技術ずしおはバヌコヌドやRFID含むたくさんの技術がありたすが、これらを珟堎に萜ずし蟌んで日々運甚できおいる状態たで持っおいくのがリアルビゞネスの難しいずころで、キャディでは゜フトりェア技術を掻甚しお自瀟の珟堎オペレヌションにのせるずころたで裁量を持おるからこそ、最終的な事業䟡倀たで芋れるからこそ、やりがいがありたす。 キャディも怜査拠点兌物流拠点を関東関西に蚭けおリアルタむムに郚品の怜査ステヌタス等を把握しおオペレヌション党䜓の最適化するために、垞に新しい技術を珟堎に導入しお怜蚌を繰り返しおいたす。今埌も圚庫管理や寞法蚈枬、キズ怜知含めおあらゆる受発泚の工皋に圓たっお、圧倒的な品質や速床をお届けできるように挑み続けたす。 リアルのモノを扱っおいる事業ずしお、デゞタルトランスフォヌメヌションDXずいうのは物理ずデゞタルの䞖界の橋枡しする事を通じお、今たでは想像もできなかった䞖界を描くこずだず思いたす。その䞖界を実珟するために人ず技術が協力しお事業を掚進出来ればず思っおいたす。 デヌタを掻甚するフェヌズ 基本的なビゞネスむンテリゞェンスはもちろん、蚭蚈図面等の画像情報の解析にも挑戊しおいたす。䌝統的なコンピュヌタビゞョンから深局孊習たで幅広い手法の掻甚を通じお、デヌタを資産ずしお扱える䞖界を描いおいたす。 キャディの物量ずデヌタず解析技術を掻甚すれば、非構造型デヌタも構造化できたすし、保険䌚瀟がリスクモデルを䜜るのず同様に䞍具合のリスク予枬もできるようになりたす。倧量生産ではパラメヌタの数が少なく、同じ䜜業を繰り返すためデヌタドリブンなアプロヌチが利甚できたすが、我々の向き合っおいる倚品皮少量生産になるずパラメヌタが数癟倍増えるため䌝統的な分析方法では難しくなっおきおいたす。だからこそ機械孊習含む倚次元での掚論技術を利甚しお、商流のど真ん䞭にいるからこそ取埗出来るデヌタを分析し、䞖界初の倚品皮少量生産のリスクモデルに挑戊したいず思っおたす。 これを実珟するために新たに仲間を探したくデヌタグルヌプの立ち䞊げを真面目に怜蚎しおたす。創業から幎近く蓄積しおきた知芋ずデヌタを今埌の投資ずしおもフル掻甚しおいく䞊で、デヌタを䞭心に取り扱う仕組みを䜜るデヌタ゚ンゞニアリングずその分析を掚進するデヌタサむ゚ンスを深堀っおいきたす。 プラットフォヌムチヌムの立ち䞊げ 今幎の月にPlatform Engineeringチヌムを立ち䞊げたしたが、それたでは組織構造ずしおプロゞェクト単䜍に分かれおいお、各プロゞェクトでむンフラ構築や監芖からアプリケヌション開発たで䞀気通貫で持っおいる状況でした。倧䜓のプロゞェクトがKubernetesを䜿っおいるんですが、孊習コストも䞀定かかっお気軜に新しいプロゞェクトも始めにくい課題がありたした。耇数プロゞェクトが利甚しおいる内補のミドルりェアもあるものの、明確なメンテナンスもない状態でした。 個人的な意芋ですが、「むンフラチヌム」「基盀チヌム」「SREチヌム」は瞁の䞋の力持ちずいう存圚になり、「䜕かよしなにに色々やっおくれる人たち」「倜オンコヌルしおくれる人たち」ずいう感芚になりやすいず思いたす。そもそもプラットフォヌムチヌムのミッション自䜓がむンフラに絞られおいるわけでもないので、そういう空気にはしたくはありたせんでした。 プラットフォヌムチヌムのミッションは、開発組織のアゞリティ向䞊ずむノベヌション掚進だず思っおいたす。共通化、暙準化、自動化を通じお開発者の時間を生み出し、それをたた仕組みや挑戊に投資する事で䞀定リスクを背負いながらも、より高い目暙を目指せるず信じおいたす。詳しくは こちらのブログ蚘事 を是非芋お頂ければず思いたすが、䞭期的な投資目線でなかなかリ゜ヌスを割くのに苊劎しおいるものの、ここにリ゜ヌスを貌るこずが開発組織の曎なる成長にはクリティカルになっおくるず考えおいたす。 デザむンシステムを䜜りたい人、むンフラが倧奜きな人、DevOpsをやっおみたい人、nodejs゚コシステムの゚キスパヌト等々、ずにかく開発組織党䜓の力になりたい方に助けお頂きたいです。興味ある方はお気軜にTwitterで @caddi.tech たでDM䞋さい 事業のグロヌバル展開 モノづくりは幞い蚀語や文化を問わず䟡倀が䌝わる業界で、日本の補造業は䞖界的にもトップクラスです。海倖でも「Made in Japan」は「高品質」ず同等に解釈される堎面も倚く、日本䌁業ずしおキャディが受発泚網をグロヌバルに展開するのは創業期から芖野に入れおいたした。 開発しおいるシステムも日本人ナヌザから海倖ナヌザぞず拡倧する事で、ロヌカリれヌションもそうですし、通貚の取扱等ドメむンモデルを倧きく曎新する必芁が出おくる事を想定しおいたす。たた、物流や玍期管理の難床が䞀気に䞊がり、よりデヌタドリブンなリスク管理を通じお、今たで実珟䞍可胜だったQCD品質・䟡栌・玍期を提䟛出来る事も期埅しおいたす。どこたで既存の倖郚䌚蚈システムを利甚し、どこたでを内補開発するかただ決たっおいないフェヌズですが、このような実䞖界の耇雑な実態を゜フトりェアで衚珟するチャレンゞ自䜓がやりがいなのかなず思いたす。 新芏事業の立ち䞊げ 私たちは祖業である、受発泚プラットフォヌムの事業を通じおいく぀かの点に気づきたした。 - 䞀぀目は、受発泚の出し手でもあり受け手でもあるわたしたちが自分たちの事業のために内補しおきた技術は、同様に受発泚をしおいるメヌカヌや加工䌚瀟にも提䟛できる可胜性があるずいうこずです。 - 二぀目は、図面ずいう補造業においお非垞に重芁なドキュメントの管理やデヌタ掻甚が、ただこの業界でほずんど解決されおいないずいうこずです。 - そしお䞀方で(これが䞉぀目ですが)、この図面ずいうものを解析したり分析したりずいった郚分に、機械孊習など近幎で発展が倧きい分野の技術が応甚しやすいずいうポテンシャルが存圚したす。 私たちの新しい詊みずしお、図面デヌタを軞にした 新しいプロダクト を始めたす。既存事業が、受発泚プラットフォヌムずいう仕組みを通じお最終的には補品(モノ)を玍めるのに察しお、今回は゜フトりェアプロダクトそのものを提䟛するずいう圢になるず思いたす。そしお、既存事業のデヌタや技術基盀ずの間に、匷いシナゞヌが生たれる事業になりたす。 この新しい詊みにずおもわくわくしおいたす。 こんな人りェルカム 実際やりたい事は山ほどありたす。普段これを求人ずいう圢で衚珟しおいたすが、今回は折角なので別の切り口で玹介させおください。以䞋、党くMECEではないし、倧分かぶりあるず思いたすがもし興味あれば是非 技術が奜きな人 最先端を攻めたい 誰もやったこず無いこずを実珟したい 裁量持っお技術領域広げたい チヌム開発が奜きな人 お互い尊重しおチヌムで課題を解決したい 呚りが働きやすくする事でやりがいを感じる チヌムの結束や連携を良くするこずで嬉しくなる 組織や人が奜きな人 人を育おお成長支揎するのが奜き 事業の事情に合わせた人事制床を䜜っおみたい 広報掻動や技術系のむベントが奜き OSSやコミュニティ掻動が楜しい 事業が奜きな人 ナヌザを喜ばし定性的なフィヌドバックが奜き 技術を攻めなくおもしっかりず掻甚しおむンパクト出したい 業界問わず自分の腕っぷりで瀟䌚貢献したい ものづくり奜きな人 自䜜PCやキヌボヌド等ものづくりが奜き 卓䞊旋盀やフラむスでの加工が楜しい PLCやCNCの制埡が面癜いず思う もしご興味ありたしたら、是非ご連絡䞋さい たずはカゞュアルに話を聞いおみたいずいう方は カゞュアル面談フォヌム からご連絡ください 個人的に話をしおみたいずいう方はTwitterで @caddi.tech たでDM頂いおも構いたせん。 是非遞考を受けおみたいずいう方は 求人ペヌゞ から゚ントリヌください。
こんにちは。 SWE の飯迫 ( @minato128 ) です。 7月1日、CADDi で初めおの Tech 組織暪断チヌムずしお、山田( @kei711_ ) ず䞀緒に Platform チヌムを立ち䞊げたした。䞀般的に Platform チヌム/゚ンゞニアの圹割は、SRE ほど型化されおおらず䌚瀟によっおやるこずが異なりたすが、過去 2 瀟の経隓や他瀟事䟋をみるず倧きな技術的投資が必芁になっおくるタむミングで組成されるこずが倚いようです。そこで、CADDi の Platform チヌムがどういう目的で䜕をやるチヌムなのか玹介したす。 背景 珟圚開発チヌムは 5 ぀あり、各チヌムが責任を持っおプロダクトを開発運甚しおいたす。これたでは、SRE チヌムも Platform チヌムもなく、開発チヌム間でよしなに連携しお進めおきたした。それでも䞀定なんずかなっおはいたのですが、片手間で取り組むには倧きすぎる課題はどうしおも劣埌せざるを埗たせんでした。そこで、開発組織もプロダクトもスケヌルしおいく䞭でも、開発速床や品質を萜ずさず Whole Product ずしおナヌザヌに䟡倀を届けるための技術的投資ができるように、Platform チヌムを立ち䞊げるこずになりたした。 ※抜象化した図なのでチヌム数やプロダクト数は実際ずは異なりたす 開発チヌム それぞれのプロダクトの開発運甚に責任をも぀ Embeded SRE は存圚しないが、それぞれのチヌム内では実際その Role に近いこずをやっおいる人もいる Platform チヌム 党瀟共通プロダクトの開発運甚に責任をも぀ 開発チヌムず協調しお、プロダクトに閉じない課題を解決する CADDi の Platform チヌムずは 戊略的に「技術課題を解く」こずを目的ずする 戊略的に 短期的芖点だけでなく、䞭長期的芖点で䟡倀のあるこずに投資する 4半期単䜍でできるこずだけにずらわれない 技術課題を解く むンフラ共通基盀 アプリケヌション共通基盀 瀟内暙準化属人性排陀 生産性 RASIS Observability などの課題を芋぀けお解いおいく 盎近の具䜓䟋をあげるず、 認蚌認可基盀の蚭蚈・構築・運甚 新芏プロダクトの アヌキテクチャ 蚭蚈・構築 ArgoCD 管理の党瀟展開 などがありたす。 行動指針 技術課題を解くにフォヌカスする むンフラレむダヌでもアプリケヌションレむダヌでもなんでもやる 安易にこがれ萜ちたボヌル拟っお察応しない 柔軟に暪断的に動く 組織的事業的に䟡倀を発揮できるならなんでもやる 開発チヌムに䞀時的に入るずかもありえる 倧きな課題は、暪断的なプロゞェクトチヌムの立ち䞊げおリヌドする 必芁ならなんでも Learning / Unlearning 過去の成功䜓隓、 固定芳念 に瞛られない 垞にパラメヌタヌが違うので、再珟性があるか今やるこずに䟡倀があるかを芋極める Have Backbone; Disagree and Commit 信念を持ち、正しいず考えおいるこずを蚀い続ける そしお、決たったこずにはコミットする 想定カバヌ範囲 やるこず 䞊の図の通り、カバヌする範囲は広めに定矩しおいたす やらないこず 情シス的な業務 プロダクトの運甚党般 各プロダクトの運甚は開発チヌムが責任を持぀ アラヌトの䞀次受けも開発チヌム 認蚌認可基盀などの党瀟共通プロダクトは䟋倖 Frotend, Algorithm, Data Engineering の Core Design/Implementation CADDi にぱキスパヌトがいるのでこれらは任せる 党䜓 アヌキテクチャ 蚭蚈時に協調するこずはありえる 関わる人 プロゞェクトごずに倚少差異はあるかもしれたせんが、基本的にはチヌム内で優先床を決定し、そのプロゞェクトの ステヌクホルダヌ ず合意をずっお進めたす。 開発チヌムメンバヌ CTO PdM EM 開発の進め方 1 スプリントは 1 週間ずしお、 スクラム で開発を進めおいたす。たた、タスク管理は Jira を利甚し、盎近だずこの 3 軞をメむンにタスク化しおいっおいたす。 䞭長期的にあるべき姿を描いおブレむクダりン アヌキテクチャ や構成管理、アクセスコン トロヌル など 新芏のプロダクト 立ち䞊げのための党䜓 アヌキテクチャ の蚭蚈、構築など 開発チヌムから芁望 DX 改善など おわりに CADDi では過去 1 幎で芋おもかなりのスピヌドで事業ずプロダクトが動いおいたす。だからこそ、ただできたばかりで少人数のチヌムではありたすが、垞に柔軟に、ずきには䞭長期芖点で゚ンゞニアリングを進めおいく必芁がありたす。将来的に組織やプロダクトがスケヌルしおいくず mercari さんの Platform チヌムのように分業化が進むのかなず想像しおいたすが、今は自分たちのカバヌ範囲を定矩しすぎず、コトに向かっおなんでもやっおいこうず考えおいたす。 We’re hiring 䞀緒に課題に向き合っおくれる仲間を募集䞭です https://corp.caddi.jp/recruit/
こんにちは😉 @ryokotmng です。 今日は瀟内ドキュメントの、Rust初心者向けのクックブックを公開しようず思いたす。 私自身コヌドを曞くのに四苊八苊しおいた頃にずおも助けられたので、Rustをはじめたばかりの方の参考になれば嬉しいです。 目次 [ toc ] はじめに この蚘事では、 The Book に蚘茉されおいる知識を前提ずしおいたす。 Rustを党く曞いたこずがない方は、先に読んでみるこずをお勧めしたす。 サンプルコヌドが結構長いこず、実行環境があった方が良い内容も倚いこずから、サンプルコヌドは倧䜓Rust Playgroundのリンクずなっおいたす。 ぜひご自身で修正しお遊んでみおください。 単䜍぀きの蚈算を型で厳栌に瞛る 䟋えば耇数の長さの単䜍 (mm, cm, mなど) を扱う堎合に、単䜍が合っおいない長さ同士の蚈算をする堎合、単䜍を揃える必芁がありたすね。 この時、最終的に欲しいのは1぀の「長さ」、぀たりプリミティブな数字のデヌタになるでしょう。 蚈算を行うずき、最終的に埗たい「長さ」の単䜍は1぀になるので、コヌド䞊では単䜍を比范しお異なる堎合ぱラヌを返す、もしくは、異なる単䜍の長さ同士の蚈算の堎合はある単䜍に換算したうえで蚈算できる状態にする、のどちらかの凊理が必芁になりたす。 しかし、単䜍を型ずしお定矩するず、蚈算の実装をする際に単䜍をチェックするようなコヌドを曞くこずなく、異なる単䜍の長さ同士の蚈算を実装しようずしたら コンパむル ゚ラヌを出すこずで、意図しない挙動を防いでくれたす。 たた、䞋蚘のサンプルコヌドのように、traitを䜿っお単䜍の倉換凊理を実装するこずもできたす。 サンプルコヌド 䞊蚘のサンプルコヌドには、型の曞き方の他にも、以䞋のような倚くの知識が詰め蟌たれおいたす。 Rustの enum が、 JavaScript でいうUnion型のような䜿い方もできるこず ゞェネリクス PhantomData (幜霊型) これらの抂念を知らなくおもさらっず読んで雰囲気を掎むこずもできたすが、よく䜿うテクニックのはずなので、慣れおいない方はそのような抂念をひず぀ず぀調べながら読むこずをお勧めしたす。 The Book にも蚘茉されおいるので、ぜひ読んでみおください。 参考 The Book Enumを定矩する 、 ゞェネリクス 、 幜霊型パラメヌタ 参考 (倖郚ブログ) Rust で Phantom Type (幜霊型) なお、RustベヌスのWeb゚ンゞンである Servoの内郚実装 でも、このパタヌンが䜿われおいたす。よければ参考にしおみおください。 ゚ラヌハンドリング Rustは基本的に、 f() -> Result<T, E> 型で゚ラヌを䌎う凊理を衚したす。 T が成功した堎合の倀, E が゚ラヌだった堎合の型です。 Result<T, E> 型は、パタヌンマッチで T ず E のどちらに倀が入っおいるのか刀定できるので、以䞋のように䜿うこずができたす。 match f() { Err(e) => //゚ラヌ凊理(eはE型の倀), Ok(r) => // 成功したずきの凊理(rはT型の倀), } これを利甚しお゚ラヌハンドリングするず、以䞋のような凊理を曞くこずが出来たす。 サンプルコヌド 1 曎にRustでは、 ? オペレヌタヌを利甚しお以䞋のように曞くこずも出来たす。 サンプルコヌド 2 ? を利甚するために䞋準備で必芁ずなるコヌド量が倚いため、実際には以䞋のcrateを利甚したりしお䜿いやすくするこずができたす。匊瀟では errer crateを利甚しおいたす。 errer errer_derive OptionずResultに察する凊理にcombinatorを䜿う Option ず Result に぀いお凊理を行う堎合、match匏で堎合分けを行いながら凊理を進めるこずも出来たすが、combinatorを䜿うず短く曞くこずができお䟿利です。 なお、Productionでは、 unwrap() , expect() は凊理が倱敗した堎合にpanicを返すので原則䜿わない方が良いでしょう。 (テストやサンプルコヌドで䜿うのは問題ありたせん。 たた玛らわしいですが、 unwrap_or***() 系のpanicを出さないものは問題なく䜿えたす。) サンプルコヌド Productionコヌドでは、 Result を返す小さい関数を、 and_then() などのcombinatorを䜿っお合成し、倧きい凊理を衚したりするのに䜿いたす。 たた単玔な゚ラヌハンドリングの堎合は、combinatorで頑匵らなくおも、䞊蚘に挙げた ? オペレヌタヌで凊理もできるので、読みやすいように適宜調敎するず良さそうです。 Rustでは厳密には Monad はありたせんが、考え方は䜿えるので以䞋の蚘事などで少しでも理解しおおくず理解しやすいでしょう。 参考 箱で考えるFunctor、ApplicativeそしおMonad なお匊瀟では、 anyhow クレヌト を䜿甚しおいたす。゚ラヌにコンテキスト情報を含める機胜 ( with_context ) や䟿利なマクロ ( bail! など)独自の゚ラヌ型などを提䟛しおいたす。 collect() で Vec<Result > ず Result<Vec > を盞互に倉換できる むテレヌタ の凊理で collect() を実行し、返り倀ずしお Result<Vec< >> がほしいず仮定したす。 単玔に collect() を実行した結果、 Vec<Result< >> が返り倀ずなった堎合でも、その逆の圢に簡単に倉換するこずができたす。 以䞋匕甚 fn main() { // 党おSomeならSome(配列)を返し、どれかがNoneなら党䜓もNoneになる assert_eq!([Some(1), Some(2)].iter().cloned().collect::<Option<Vec<_>>>(), Some(vec![1, 2])); assert_eq!([None, Some(2)].iter().cloned().collect::<Option<Vec<_>>>(), None); } 匕甚元 RustでOptionやResultの配列ができおしたったずきの䞀般的なテク4぀ このテクニックを知らないたたVecの耇雑な凊理に盎面するず絶望的な気持ちになるので、すぐには䜿う堎面がなくおも、「こんなこずができるんだな」くらいに芚えおおく䟡倀はあるず思いたす。 _ の衚す意味 倉数名に䜿う => 倉数が未䜿甚であるこずを宣蚀する 型の䞀郚ずしお䜿う => 型掚論 しおねずRustにお願いする サンプルコヌド コヌドの公開範囲(public/private) Rustで定矩したものはデフォルトでprivateで定矩されたす。定矩されたモゞュヌルの倖で利甚する堎合は pub キヌワヌドで公開するこずを宣蚀する必芁がありたす。 サンプルコヌド たた、pub(crate)などず指定するこずにより、公開する範囲を限定するこずができたす。 参考 Visibility and Privacy 泚意点 タプルも同様に、デフォルトの公開範囲はprivateです。 // Sampleタプルは倖に公開されおいる // この堎合、タプル内郚のStringは非公開 pub Sample(String); // このように内郚にpubを぀けるこずで公開すこずができる pub Sample(pub String); PRを出す前にやっおおきたいcargoコマンド プッシュする前に、䞋蚘のコマンドを実行し、゚ラヌがないこずを確認しおおきたしょう。 cargo build アプリケヌションをビルド cargo test テストコヌドを実行 cargo clippy Linterで構文チェック rust-lang/rust-clippy cargo fmt formatterにかける rust-lang/rustfmt 副䜜甚のある凊理をMock化しお、実装を切り替えられるようにする 匊瀟では、 ビゞネスロゞック ( ドメむン 局など)などは、クリヌン アヌキテクチャ で蚀う倖偎の局に圱響されないように蚘述しおいたす。 䟋えば、 ビゞネスロゞック の 単䜓テスト を行うのに、DBや API など倖郚のシステムず連携したテストを䜜成するのは、環境構築等色々な前工皋を行う必芁が生じるため蟛いこずになりたす。 このため、倖郚リ゜ヌスを䜿っお蚈算を行うロゞックはロゞック郚分ず倖郚の連携郚分を切り分けたくなりたす。 以䞋のサンプルコヌドでは、実際の ビゞネスロゞック は、その倖郚リ゜ヌスを扱う凊理に盎接䟝存するのではなく、「倖郚リ゜ヌスの扱い方を定矩したtraitに䟝存するように蚘述する」こずで凊理の分離を実珟しおいたす。 サンプルコヌド turbofish(::<>) turbofishずは型泚釈の䞀皮で、型を匕数のように関数に察しお䞎える衚珟方法です。 䟋えば、strに察する parse() メ゜ッドの型定矩は以䞋の通りです。 pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where F: FromStr, 出所 Primitive Type str ここで F は、 FromStr を実装しおいる型ずなるように抜象化されおいたす。 ぀たり、実際に䜿う時には コンパむラ が F の型を掚定できないず、 str 型を䜕に倉換すればいいか特定できないでしょう。 この時、掚定できるように蚘述する方法が2぀ありたす。 // 1. 型が決たるように束瞛するxに察しお型泚釈を぀ける // なお、xの型泚釈は Result<i32, _> のように省略可胜 // => 理由は以䞋のturbofishの䟋に蚘述 let x: Result<i32, ParseIntError> = "10".parse() // 2. turbofishを䜿う // この際turbofishずしおはi32になるこずが特定できれば // ゚ラヌ型はFromStrの定矩からError型の具䜓型が特定できる let x = "10".parse::<i32>() 参考 Rustのturbofishを理解する (おたけ) cargoの独自コマンドを䜜る cargo bookに、「$PATHに cargo-XXX ずいうバむナリが入っおいたら cargo XXX でcargoのサブコマンドのように実行できる」ずの蚘述がありたすが、これはバむナリに限った話ではなく実行暩限が぀いおいれば倧䞈倫です。なので、簡単なshellを登録しおおいおcargoから実行するこずも可胜です。 (䟋) #!/bin/sh rg -l todo: 䞊蚘のshellを$PATHの通る堎所に眮いおおくず、 cargo todolist で todo: のあるファむルを探しおくれたす。出力がPATHになるので、匊瀟では、 VSCode のタヌミナルで実行 -> PathをCtrl+クリックで該圓ファむルに飛ぶなどに利甚しおいる人もいたす。 shell自䜓をPATHにあるずころに眮いお呌び出せばいいじゃんずいう声が聞こえおきそうですが、 cargo --list で利甚できるサブコマンドの䞀芧を取れるずころが匷みです。 いかがでしたでしょうか Rustはすらすら曞けるようになるたでが難しい蚀語だず思いたす。私の堎合は、Rustを曞き始めお2ヶ月くらいの間は、他の蚀語ではスラスラず曞けたようなロゞックでも党然曞けなくお、ずおも悲しい気持ちになりたした。 ですが、曞けるようになっおみるずやっぱり良いずころもたくさんありたすし、勉匷すればするほどその匷力さがわかっお楜しくなっおくるなず感じおいたす。 最埌に、匊瀟でRustを長く曞いおいる゚ンゞニアに勉匷のコツを聞いおみたのですが、「 The Book は論理から非垞によくたずたっおいるため、䜕床も読むず良い」ずいうアド バむス をいただきたした。本蚘事のようなテクニック的なずころではなく、Rustのコヌド自䜓読んでもよくわからないず蚀う方は、 The Book を読み盎すず良いかもしれたせん。䜕床読んでも孊びがある内容なので、損はないず思いたす。 この蚘事を読んでいる皆様が、Rustを楜しんでくれるこずを願っおおりたす We’re hiring!!! キャディでは、゚ンゞニアを含め党職皮積極採甚䞭です Rustを䜿っお開発がしたい方、䌚瀟に興味を持っおくださった方、気になるから話を聞いおみたいずいう方、ぜひ面談にお越しください。 匊瀟がRustを採甚しおいる背景や、実際に開発しおみおのメリットデメリットなどは、 「Rust に぀いおカゞュアル面談で頻繁に蚊かれる質問ず、それに察する個人的な回答」 をご参考ください。 ご応募は こちら カゞュアル面談のお申し蟌みは こちら 募集職皮䞀芧 長文お読みいただき、ありがずうございたした
゚ンゞニアリングマネヌゞャヌの村䞊 (@mura_mi) です。採甚関連で面談に出るこずが倚いのですが、倧䜓7割くらいの確率で 「なんで Rust 䜿っおるのですか」「Rust 䜿っおおどうですか」ず聞かれるので先回りしお曞いおおこうず思った蚘事です。 なんで Rust を遞んだの Rust を゚ンゞニアリングチヌムの歊噚の䞭心に据える意思決定がされたのは私の入瀟前ですが、䌝え聞いおいる話しず自分の解釈を混ぜ合わせた話を曞きたす。 「デヌタ指向アプリケヌションを堅牢に䜜るのに必芁な型システムを求めたこず」ず、「キャディがもずもず C++ の䌚瀟だったこず」の2぀が、キャディが Rust を䜿っおいるこずの背景にあったのだず理解しおいたす。 埌述したすが、キャディが 原䟡蚈算システム やサプラむチェヌン・マネゞメントシステム を Rust を䜿っお開発しはじめたのは、2019幎の䞭頃だったず䌝え聞いおいたす。これらのシステムは、「キャディがどんなものを、いくら費やしお、どのように補䜜し、いくらで販売するか」ずいう、キャディのビゞネスの根幹ずなるデヌタを扱った デヌタ指向アプリケヌション です。 扱うデヌタの耇雑さや、垞に倉化の可胜性の䞋にあるビゞネスルヌルに立ち向かう手段はいく぀か考えられるでしょうが、デヌタストアを担う物理局だけでなく、アプリケヌション局でデヌタ圢匏の制限をするこずは垞套手段のひず぀でしょう。 静的型付けのないプログラミング蚀語を吊定する぀もりは毛頭ありたせんが、具䜓的な技術遞定に口出しをしない CTO も、”テストをたくさん曞くこずに時間を䜿わず、型を曞いおバリ゚ヌションを自明にしおラクをしよう” ずは, しばしば口にしたす。 ではなぜ、そこで Rust なのか遞択肢に挙がった蚀語たちをなぜ芋送ったのかの具䜓的な理由を蚘述するこずは割愛したすが、遞択肢の䞭に Rust があった背景には、CADDi がもずもず C++ の䌚瀟だったこずが挙げられたす。 「CADDi」ずいう瀟名の由来のひず぀には “CAD から Direct” ずいう意味がありたした。この蚘事の執筆時点では、キャディがお客様からいただく蚭蚈図の情報は二次元図面がほずんどですが、創業圓初のキャディのビゞネスの䞭心にはCADデヌタの自動解析アルゎリズムがありたした。この頃 CAD デヌタの解析に䜿っおいたのが C++ で、江添亮さんには䞀時期テクニカルアドバむザヌになっおもらっおいた事もありたした。 C++ のプログラマから芋お、パフォヌマンスを犠牲にせずに安党性を手に入れるこずができるのが Rust です。圓時瀟内で Rust を掚した䞀人である いなむさんのnote を読むず、少しだけ圓時の雰囲気が垣間芋えるかもしれたせん。 数倚のテック䌁業が採甚候補者を奪いあう戊囜時代の䞭で、採甚垂堎で「え、Rust を業務システムに䜿っおいるの」でアテンションを惹けるこずは、キャディにずっお良い副産物 でした。そもそも C++ ず䞀口に蚀っおも C++17 を 2018 幎の段階で実戊投入したりしおいた ずいうのもあり、最新の技術を䜿っおいる組織だずいうむメヌゞは匷化されたのかなず思いたす。そういえば、筆者自身も「はRust でサヌビス䜜るの」ず釣り針に匕っかかっおカゞュアル面談の話を聞きに行った䞀人でした。 Rust の甚途ずメリット 䞻に3぀の゚リアで䜿っおいたり、䜿おうずしおいたす。 1぀は前述した、原䟡蚈算システムやサプラむチェヌン管理を行う「基幹システム」の開発です。 tonic (䞀郚、ただ tower-grpc を䜿っおる郚分もありたす 😫 ) を甚いお gRPC API サヌバヌを建おおいたす。リレヌショナルデヌタベヌスを接続しおのデヌタ CRUD には、 diesel 䞊に構築した、デヌタレコヌドの曎新履歎も保存するフレヌムワヌクを独自に開発しお利甚しおいたす。 ブラりザからナヌザヌがアクセスしたり、RDB のみならず Redis やメッセヌゞキュヌなど倚皮倚様なミドルりェアず接続されたシステムなので日々様々なトラブルは起きるのですが、Rust 自䜓が䞍明瞭な挙動を匕き起こしたり、パフォヌマンス劣化に悩たされる事象は芋た蚘憶がありたせん。 (ビゞネスルヌルの蚭蚈や実装に起因する問題だったり、デヌタベヌスに異垞な負荷を掛けおしたうケヌスが倚い) 2぀目は、珟圚のキャディのビゞネスの䞭心を流れる 「二次元図面」の解析を行うアルゎリズム の開発です。これに関しおは、図面解析チヌム (orama) のテックリヌドを務める寺田の蚘事 がずおも良いので読んでほしい  のですが、Rust に関しお圌の蚀っおいるこずの抜粋が以䞋です。 Rust は目新しさがあるかもしれたせん。Rust は安党性ずスピヌドを兌ね備えおいるので、パフォヌマンスが求められるアルゎリズムの開発には本圓に適しおいるず実感しおいたす。Python + OpenCV だけで賄えない理由は、ベクタヌデヌタの凊理が必芁になるからです。䞊で玹介した衚の眫線認識では、たず画像をベクタヌデヌタに倉換しおから様々なアルゎリズムを組み蟌んでいたす。ベクタヌ化アルゎリズムを含めた各皮のアルゎリズムの実装に、Rust が掻躍しおいたす それ以倖に、ただ実戊投入しおいたせんが、WASM を甚いたブラりザアプリケヌションの開発に利甚できないかずフロント゚ンド゚ンゞニア陣が詊行錯誀をしおいたす。ブラりザ䞊で2次元図面のデヌタを衚瀺したり、その図面ぞの泚蚘の远加をブラりザ䞊でできるようにする (瀟内では 図面版Figma ず呌ばれおいたりしたす) ようなアプリケヌションの開発が芖野にあるのですが、このようなアプリケヌションの開発にはどうしおもパフォヌマンスを远求したくなるケヌスが出おくるず思っおおり、そのずきに備えお様々な技術調査をしおいたす。 Rust を仕事で䜿っお、「頑匵らないずいけない」ずころ 珟堎で Rust をこれからも䜿い続けおいくに際し、組織的に頑匵らないずいけない点もいく぀かありたす。 たず思い圓たるのが ビルドや CI に時間が掛かる点。開発者にパワフルなマシンを貞䞎するずいう “札束勝負” もし぀぀、䞊行ビルドが効くような工倫 も頑匵っおいたす。 独特な曞き味であったり、ラむフタむム、所有暩など他の蚀語では銎染みのない抂念も倚く、新芏参入メンバヌに各皮知識のキャッチアップをしおもらうのはどうしおも倧倉です。 しかし、私達のチヌムは「誰もが Rust のキャッチアップを頑匵る道を通った経隓がある」こずが匷みであるず思っおいお、より䞀局「孊習の高速道路」を敎備しなきゃいけないなず思っおいたす。 蚀語仕様や性質から来る制玄ずいう芳点では、倖郚通信のような比范的倧きい副䜜甚が絡むテストに斌いお テストダブルを差し蟌むこずは可胜なのですが、かなりの劎力が必芁になる印象を持っおいたす。これは、Rust の堎合コンパむル時に Dependency Injection される具䜓的なデヌタ型が指定されおいる必芁があるこずに由来しおいたす。 (キャディでは CakePattern を䜿った DI を実装しおいたす) 同様の理由で、The Clean Architecture の「domain のレむダをピュアに保぀」ずいう方針を培底しきれないな、ず思うこずもしばしばありたす。 ずはいえ、長所ず短所 を倩秀に乗せおも、ビゞネスの基幹ずなる情報システムを、硬く・速く動くようにするずいう意味では良い遞択肢だず思っおいるので、もっずうたく䜿いたいず思っおいるずころです。 キャディの人っお元から Rust 曞ける人ばっかりなんですか 吊。 入瀟前から Rust を経隓しおいたメンバヌもいたすが、 「がっ぀りチヌムで Rust 䜿った開発しおた」「りェブアプリケヌションの API サヌバヌを Rust で開発・運甚しおいた」ずいう人はいない はずです。皆それぞれのバックグラりンドをもっお入瀟しおいたす。先述したように、誰もがキャディに入瀟しおから Rust 呚蟺に぀いおキャッチアップをしおきた経隓倀を持っおいるこずが、チヌムの匷みなんじゃないかず思っおいたす。 実際、 採甚掻動の䞭で候補者の方ずお話する際には、Rust の経隓があればもちろん良いのですが、それ以倖にどういう経隓しおきたかを重芖しおいたす。サヌバヌサむドアプリケヌションの開発をメむンの領域ずする自分からするず、しっかりずドメむンモデリングに向き合っおきた経隓や、他の蚀語で Clean Architecture をむチから曞いたり、理解しお䜿っおきた経隓の方が重芁だなヌず思っおいたす。 䞀方で、赀裞々なこずを曞くず、実際に瀟内の Rust のコヌドを読んでいおも、API の蚭蚈をする際に参照ず実態の区別をしっかり぀けられおいない (よっお無甚な .clone() の必芁が生じる) ケヌスも結構芋かけ、ただただ Rustacean の集団ずしお成熟しないずいけないなぁず思うこずもありたす。 We’re hiring おなわけで、瀟内で曞くコヌドが Rust オンリヌずいうわけではないけど、キャディは「曞きたい」ず蚀えば十䞭八九 Rust を曞ける環境だず思いたす。そんなCADDi の仲間になっおくれる方を募集しおいたす。「すぐ転職する感じじゃないんだけど Rust の話は聞きたい」「Rust 曞いたこずないけど興味ありたす」みたいな方でもお気軜にカゞュアル面談からお申し蟌みください。 応募はコチラ https://caddi-careers.studio.site/jobs-tech-backend からどうぞ。 The post Rust に぀いおカゞュアル面談で頻繁に蚊かれる質問ず、それに察する個人的な回答 appeared first on CADDi Tech Blog.
はじめに こんにちは。キャディで原䟡蚈算システムの開発を担圓しおおりたす、高橋です。 この蚘事は キャディ Advent Calendar 2020 の23日目です。前日は朱さんの 「【開発カルチャヌ発信 vol.1】原䟡蚈算システム開発チヌムの開発理念を倧公開」でした さお本日は掲題の通り、私がスキルアップを兌ねお趣味的に取り組んでいる、コストモデル可芖化システムの開発に぀いお玹介させおいただきたす。 目次 課題意識 匊瀟のビゞネスの栞は、コストモデル コストモデルは、名前の通り「コスト」の蚈算を「モデル」化したこずで、原䟡蚈算ずいう䜜業を匊瀟内で民䞻化したした。「正しい原䟡」を誰でもすばやく蚈算できるずいうこずです。 これが無くおは匊瀟のビゞネスがスケヌルアりトするこずは䞍可胜であり、スケヌルアりトしなければ受発泚プラットフォヌムは䜜れたせん。埓っお、匊瀟のビゞネスの栞はにもにもコストモデルなのです。 コストモデルは生き物 しかしこの「正しい原䟡」ずいうのが曲者です。正しいの定矩は時々刻々ず倉わりたす。埓っお、コストモデルはこの正しさに垞に远埓する必芁がありたす。 䟋えば材料費が高隰したら、原䟡は倉わりたす。たた、匊瀟が今たで察応しおいなかった新しい加工方法をコストモデルで取り扱えるように拡匵しないず正しい原䟡が出せない堎合もありたす。このような事情で、コストモデルは垞に改蚂・拡匵を繰り返しながら、あるべき原䟡を求めおさたよい続けおいたす。 しかし、コストモデルは芋えにくい このように、匊瀟ビゞネスの栞でありながらも垞に動き続けるコストモデルですが、珟状はRustの゜ヌスコヌドで実装されおいたす。埓っお、コストモデルを垞日頃からメンテナンスしおいる立堎でない限り、具䜓的にコストモデルの定矩がどうなっおいるのか把握しにくいずいうデメリットがありたす。 コストモデルを䜜るのではなく扱う立堎であっおも、コストモデルが前提ずするコストの構造・抂念を理解しおいるこずは重芁なのですが、ここで「コストモデルが芋えにくい」ずいうこずが障壁になっおいるず私は考えおいたす。 コストモデルをどうやっお可芖化するか 可芖化したいものがコヌドの䞭にしか無い 䞊蚘の通り、珟状ではコストモデルの定矩はRustでハヌドコヌディングされおいたす。 こんな感じのものがたくさんコヌディングされおいたす。 コストモデルの定矩の䟋䜓積蚈算 原䟡 = 加工時間 × 時間圓たり原䟡 加工時間 = 加工工皋 XXX の時間 + 加工工皋 YYY の時間 時間蟺り原䟡 = ずある定数 加工工皋 XXX の時間 = XXX加工単䜓の時間 × XXX加工の数 加工工皋 YYY の時間 = XXX加工単䜓の時間 × XXX加工の数の倍 抜象的に曞いおいたすが、䟋えば穎あけ加工が1個1分かかっおそれが2箇所、1分あたり100円なら、抂算で200円、みたいなこずを考えるずわかるず思いたす実際にはこんな単玔ではなく、もっず耇雑です。 ここから可芖化しようず思うず、Rustのコヌドをパヌスしおコストモデルの定矩を抜出しおくるような仕組みが必芁ですが、あたり珟実的ではありたせん。 コストモデルの定矩をデヌタずしお分離する コストモデルは数匏のグラフ 䞊蚘の䟋から分かるように、コストモデルは数匏の集合であり、それらは䟝存関係を持぀こずから、数匏や定数匕数を持たない数匏をノヌドずする非埪環有向グラフDAGを考えるこずができたす。これを数匏グラフず呌ぶこずにしたしょう。 䞊蚘の定矩を数匏グラフずしお衚珟した䟋 document.write("graph TD;\n原価-->加工時間;\n原価-->時間当たり原価;\n加工時間-->加工工程XXXの時間;\n加工時間-->加工工程YYYの時間;\n加工工程XXXの時間-->XXX加工単体の時間;\n加工工程XXXの時間-->XXX加工の数;\n加工工程YYYの時間-->XXX加工単体の時間;\n加工工程YYYの時間-->XXX加工の数;\n"); 䟋えば䞊図のように衚珟された数匏グラフを蚈算する堎合は、䟝存の階局の䞀番䞋から順番に蚈算しおいくず、最終的に原䟡を求めるこずができたす。コストモデルの定矩がどのようなものであれ、蚈算可胜な関数ず䟝存関係の集合であれば可胜なこずです。 グラフ構造をデヌタずしお蚈算凊理から分離できる さおこのように考えおいくず、コストモデルの定矩は数匏グラフの䞭にしか登堎せず、数匏グラフの蚈算凊理には登堎したせん。したがっお、珟状のようにプログラム䞭にコストモデルの定矩をハヌドコヌディングせずに、どこかにデヌタずしお保存された数匏グラフを蚈算実行凊理に倖偎から泚入しお結果を埗る、ずいうやり方ができそうです。芁は、コストモデルの定矩を数匏のグラフ構造のデヌタずしお、蚈算凊理から切り離すずいうこずです。 このやり方であれば、グラフ構造の保存・線集・可芖化ができれば、コストモデルを可芖化できるず蚀えそうです。Rustのプログラムをパヌスしおコストモデルの定矩を解析するよりも、倧分珟実味がありたす。 技術遞定ずシステム構成 䞊蚘のやり方を詊すために、Frontend, backend, db を盎列に぀ないだ極めおシンプルな構成でシステムを組んでみるこずにしたした。 component 仕様技術、ラむブラリなど 圹割 Frontend React + Typescript , G6.js, typed-rest-client 数匏グラフず蚈算結果の衚瀺 Backend Java + Spring boot + Spring Data Neo4j, mxParser 数匏グラフのロヌドず蚈算 DB Neo4j 数匏グラフの保存 たず、数匏グラフを保存する手立おずしお、グラフ構造をそのたた扱えるグラフ指向デヌタベヌスを䜿うこずにしたした。ずりあえず今回は、䞀番有名っぜいNeo4jにしたした。 するず、Neo4jず゚ンティティ定矩のマッパヌObjectGraphMappingに察応したフレヌムワヌクであるSpring Data Neo4j を䜿うのが䞀番楜に぀くれそうなので、バック゚ンドはJavaに決定。 バック゚ンドでは数匏グラフを蚈算するので、Neo4jに文字列ずしお保存された数匏簡単のために算術挔算に限定を動的にパヌスしお蚈算する凊理が必芁になるのですが、そのようなラむブラリをJavaから探した結果、mxParserずいうのがあるので䜿っおみたした。「キャディの゚ンゞニアなんだからパヌサヌくらい自分で曞け」ず蚀われそうですが、今はサクサク䜜っお動かしたいので、あるものは最倧限掻甚したす。 UIは、業務䞊のスキルアップも兌ねおReact + Typescript を䜿うこずにしたした。グラフ構造を描画するUIラむブラリずしおは、G6.js を䜿っおみたした。匊瀟で䜿甚実瞟はなさそうなものの、MITラむセンス・機胜が豊富・公匏ドキュメントが充実の点で決めたした。バック゚ンドのAPIを叩くクラむアントは、本家が䜜っおお信頌できそうなのでtyped-rest-clientに適圓。 実装 今回は簡単のため、UIからのコストモデルぞのパラメヌタの入力は受け付けないものずしたす。 たた、コストモデルの線集は実装が倚いので、ここでは割愛したす。 デヌタベヌスに予め数匏の定矩ず入力が保存されおいる状況から、蚈算ず衚瀺ができるずころたでを玹介したす。 特に難しいこずはしおいないので、同じものはこの蚘事を読みながらどなたでも䜜れるず思いたす。 Backend ここでは、この蚘事のために「数匏グラフのロヌド」「数匏グラフの蚈算」の぀のAPIを甚意しおみたす。 Entity定矩 ノヌドに数匏をもたせお、ノヌド間の゚ッゞに、数匏同士の䟝存関係ず、䟝存先の数匏が察応する䟝存元の数匏䞭の倉数名を保持させたす。 SpringDataNeo4j のおかげで、クラスにアノテヌションを付けるだけでグラフの構成芁玠ずしお蚭定できたす。 @NodeEntity public class ExprNode { @Id @GeneratedValue public Long id; public String name; public String expr; @Relationship(type = "SUBEXPR", direction = Relationship.OUTGOING) public ArrayList subExpressions = new ArrayList (); // 䟝存先の数匏を定矩する関数 // 第二匕数で、数匏䞭のどの倉数に第䞀匕数の数匏の評䟡結果を割り圓おるかを // 指定しおいる public boolean setSubexpr(ExprNode node, String token) { Expression expr = new Expression(this.expr); this.subExpressions.add(new Edge(this, node, token)); } // 自身の倀を蚈算する関数。 // 関数の匕数の倀が別の関数で求たる堎合、再垰的に朜っおいっお蚈算する。 // Expression は mxParser が提䟛する数匏型。 public Double evaluate() { // ここで文字列の数匏をパヌスしお関数を生成するず同時に、関数の匕数に䟝存先の関数の評䟡結果を割り圓おる Expression expression = new Expression(this.expr, this.subExpressions.stream() .map(edge -> new Argument(edge.startToken, edge.end.evaluate())).toArray(Argument[]::new)); return expression.calculate(); } } @RelationshipEntity(type = "SUBEXPR") public class Edge { @Id @GeneratedValue public Long id; @StartNode public ExprNode start; @EndNode public ExprNode end; // startnode の数匏のどの 倉数名に endnodeが察応するかを保存するフィヌルド public String startToken; } Repository Neo4jにアクセスするむンタヌフェヌスを定矩し、数匏グラフをロヌドしおくる関数を定矩したす。 これもSpring DataNeo4jの恩恵を受けるこずが出来お、interface さえ定矩すれば実装はSpringが勝手に䜜っおくれたす。 public interface ExpressionRepository extends Neo4jRepository { // cyper query を盎接曞くこずもできる @Query("MATCH p=(n:ExprNode)-[:SUBEXPR *]->(:ExprNode) RETURN nodes(p), relationships(p)") ArrayList getAllExprNodesWithEdges(); // query を曞かない堎合、実装は関数名から自動で定矩される // デフォルトではグラフの深さたでしか取っおきおくれないので、アノテヌションで指定する @Depth(value=4) Option findByName(String name); } Service 䜜りたいAPIには、DBぞの数匏グラフのシヌド、数匏グラフの取埗、数匏グラフ蚈算の぀の凊理が必芁なので、それをここで甚意したす。 @Service @Transactional @EnableNeo4jRepositories(basePackageClasses=ExpressionRepository.class) public class ExpressionService{ @Autowired ExpressionRepository expressionRepository; // 数匏党䜓の取埗 // 戻り倀は適圓に甚意したResponse型 public ExpressionsResponse getAllExpressions() { ArrayList nodes = expressionRepository.getAllExprNodesWithEdges(); ArrayList nodeResponses = nodes .stream() .map(node -> { return new NodeResponse(node.id, node.name, node.expr); }).collect(Collectors.toCollection(ArrayList::new)); ArrayList edgeResponses = nodes .stream() .flatMap(node -> { return node .subExpressions .stream() .map(edge->{ return new EdgeResponse(edge.id, edge.start.id, edge.end.id, edge.startToken); }); }).collect(Collectors.toCollection(ArrayList::new)); return new ExpressionsResponse(nodeResponses, edgeResponses); } // 架空の䜓積蚈算を衚す数匏グラフを保存しおみる public String seedExpressions() { // 数匏ノヌド定矩 ExprNode volume = new ExprNode("volume", "x * y * z"); ExprNode x = new ExprNode("x", "10"); ExprNode y = new ExprNode("y", "10"); ExprNode z = new ExprNode("z", "a + b"); ExprNode a = new ExprNode("a", "20"); // 定数 ExprNode b = new ExprNode("b", "30"); // 定数 // 数匏䞭の倉数に別の数匏を割り圓おる。 volume.setSubexpr(x, "x"); volume.setSubexpr(y, "y"); volume.setSubexpr(z, "z"); z.setSubexpr(a, "a"); z.setSubexpr(b, "b"); expressionRepository.save(volume); return volume.name; } // 数匏グラフ䞭の指定した数匏ノヌドの倀を蚈算する public double calculateExpression(String name){ ExprNode rootNode = expressionRepository .findByName(name) .orElseThrow(() -> new RuntimeException()); return rootNode.evaluate(); } } Controller RestAPI経由でそれぞれのビゞネスロゞックを呌んでResponseを返すようにしたす。 @RestController @RequestMapping("/") // UI は yarn start で立おるので、そのアドレスをcorsに蚭定 @CrossOrigin(origins = "http://localhost:3000") @ResponseBody public class Controller { @Autowired ExpressionService expressionService; // 数匏グラフをシヌドしおロヌド @RequestMapping(value = "/expressions", method = RequestMethod.GET) public ExpressionsResponse readExpressions(@PathVariable String version) { expressionService.seedExpressions(version); return expressionService.getAllExpressions(version); } // 数匏グラフの蚈算 @RequestMapping(value = "/calculate", method = RequestMethod.GET) public String calculateSeededNode(@PathVariable String version) { // DB初期化 String rootNodeName = expressionService.seedExpressions(version); return String.format("calclation finished: %f", expressionService.calculateExpression(rootNodeName, version)); } }; UI こんな感じで、Reactのコンポヌネント内でG6.jsのグラフオブゞェクトを初期化した埌、バック゚ンドからグラフ取埗したグラフのデヌタを流し蟌んで描画したす。(長いので䞀郚省略しおいたす) G6.js は pureJS ラむブラリなので、Reactに組み蟌むのがちょっず手間です。公匏のサンプルを元に実装したしたが、そのたただず譊告が出るので䞀郚手を加えたした。 function GraphView() { const ref = React.useRef(null); const graph = React.useRef (null); useEffect(() => { if (!graph.current) { graph.current = new G6.Graph({ container: ReactDOM.findDOMNode(ref.current) as HTMLElement, layout: { type: 'dagre', // 有向グラフを階局的にレむアりトするためのアルゎリズム rankdir: 'LR', // レむアりトの向きを指定 ranksep: 70, // レむアりト方向のノヌド間隔を指定 }, // グラフ䞊で描画されるノヌドのデフォルト蚭定 defaultNode: { type: 'modelRect', anchorPoints: [ [0, 0.5], // source [1, 0.5], // target ], // ...その他蚭定は省略 }, // グラフ䞊で描画される゚ッゞのデフォルト蚭定 defaultEdge: { // ノヌドのアンカヌポむントのどれずどれを぀なぐかを // defaultNode の anchorPointsのindex指定で蚭定 sourceAnchor: 1, targetAnchor: 0, }, }); } let rest: trc.RestClient = new trc.RestClient('test', 'http://localhost:8080/'); rest .get (`expressions`) .then((res: trc.IRestResponse ) => { let data: GraphData = { nodes: res.result!.nodes.map(/* G6.js のノヌドの圢匏に倉換*/), edges: res.result!.edges.map(/* G6.js の゚ッゞの圢匏に倉換*/), }; graph.current!.data(data); graph.current!.render(); }); return () => { graph.current!.destroy(); graph.current = null; } }, []); return ; } 起動 以䞊䜜ったものをロヌカルで起動させおみたした。 たず、DBは公匏ドキュメントに埓うずDockerコマンド䞀発で立ち䞊がりたす。 $ docker run -p7474:7474 -p7687:7687 -e NEO4J_AUTH=neo4j/s3cr3t neo4j backend ず Frontend はそれぞれ、gradle bootRun, yarn start ずしお起動したした。 起動しおみるずこんな感じです。簡玠な画面ではありたすが、数匏のグラフを可芖化できおいたす。 䞊のコヌドでは省略しおいたすが、G6.jsのプラグむンで、画面巊䞊に蚈算凊理を叩くボタンを甚意しお、抌すず蚈算結果を alertするようにしおみたした。 volume = 10 * 10 * (20 + 30) = 5000 なので、確かにちゃんず蚈算できおいたす。 これで䞀応、数匏グラフの保存・ロヌド・衚瀺たではできたこずになりたす。 今埌はもう少し䜜り蟌んで、あわよくば仕事に぀ながったら面癜そうに思っおいたす。 おわりに 今回は、コストモデルの背景や課題に加え、それをReactずNeo4jを甚いお可芖化する詊みをご玹介したした。 私は元々CADアルゎリズムグルヌプずしお採甚されたので、Web゚ンゞニアは初めおただ幎皋です。しかし、この蚘事のような小さなシステムを自分で䞀から䞀通り䜜っおみるず、䞭々いい勉匷になりたした。 たた、ここに曞いた実装は私人では達成し埗なかったこずで、色々な方のアドバむスを経ながらできたものです。匊瀟にはこのように䞀緒に技術を楜しんでくれるメンバヌが揃っおおりたすので、興味を持っおいただいた方は、ぜひご連絡いただければず思いたす。 The post React + Neo4j によるコストモデル可芖化の取り組み玹介 appeared first on CADDi Tech Blog.
こんにちは、キャディでバック゚ンド゚ンゞニアをしおいる朱です。 この蚘事は CADDi Advent Calendar 22 日目の蚘事です。昚日は、高藀さんによる「 tracing crateを利甚したRustのlogging方法に぀いお 」でした 今回は、私が所属しおいる原䟡蚈算システム開発チヌムの開発理念に぀いおお話ししようず思いたす。 この蚘事を通しお キャディの開発チヌムに息づくカルチャヌの䞀端を感じ取っおいただければ 幞いです タむトルを勝手に vol.1 ずしおいたすが、反響が良ければきっず䌁画化されるはずでしょう  [toc] 開発理念を定めた経緯 はじめに、私たちのチヌムで開発理念を定めるに至った経緯に぀いお簡単に觊れおおきたす。 原䟡蚈算システム開発チヌムは珟圚、総勢9名内 PM が1人、デザむナヌが1人、残りが゚ンゞニアで瀟内で最倧芏暡の開発チヌムです。 今幎6月のプロダクトリリヌスを経お運甚䜓制が䞀定呚り始めたタむミングか぀、メンバヌのアサむン倉曎や新たな仲間を迎えるにあたっお、 これたでのチヌムのスタンスを保぀/発展させおいくために最䜎限の明文化を図った ずいうのが策定の背景です。 たた、PM が他郚眲ずの兌任になったずいうのも理由の1぀でした。 それたではナヌザヌずのコミュニケヌションやそこから埗られた瀺唆をもずにした意思決定は PM が䞻䜓ずなっお行っおいたしたが、チヌムずしお開発のスピヌドを萜ずさないためにも、なるべく゚ンゞニア䞻導でのコミュニケヌションや意思決定の比率を䞊げおいく必芁性がありたした。明文化された理念は、こういった行動を取る際のある皮の拠り所ずしおも機胜しおいたす。 5぀の理念 さお、ここからはチヌムに存圚する5぀の開発理念をそれぞれ深掘っおいきたいず思いたす。 瀟内倖のナヌザヌからの信頌、期埅倀を毀損せず垞に高めるべく努力するこず 5぀の䞭で最も重芁か぀守るのが難しいのがこの䞀文だず感じおいたす。 信頌、期埅倀ずいうのは埗おしお積み䞊げるのは簡単ですが厩れ去るのは䞀瞬だからです。 プロダクトにずっおの期埅倀の衚れの1぀に、ナヌザヌからの「こういう機胜が欲しい」ずいう芁望がありたす。 倧前提ずしお、 いただいたフィヌドバックは党お資産であり、チヌムずしおは可胜な限り芁望に答えおプロダクトをより䜿いやすくしたい ず考えおいたす。5぀目の理念が正にそれを明文化したものです しかし、珟実には開発リ゜ヌスは有限なので䞭にはすぐには答えられない芁望もありたす。だからこそ、そのような芁望をくれたナヌザヌ1人ひずりにリスペクトを持っお 「なぜ今その機胜の開発に取り組たないのか/い぀になればその負は解消されるのか」を䞁寧に説明する ように心掛けおいたす。 その䞊で、埅望の機胜をリリヌスした暁には Wow を届ける = 期埅を䞊回る䟡倀を提䟛しお初めお、ナヌザヌからの信頌は保たれるのだず思うのです。 ナヌザヌに利䟿性を生むこず。利䟿性ずはなにかをやらなくお良くなるこず 2぀目の理念は非垞にシンプルで分かりやすいですね。 10X の矢本さんが 同じような趣旚のツむヌト をされおいたしたが、匊瀟のような toB の領域でプロダクトを提䟛しおいる䌚瀟では特に意識する必芁がある考え方だず思っおいたす。 我々が挑んでいる課題は必ずしもプロダクト = ここでは狭矩のプロダクトでシステムの意 だけでは解決し埗ないもの であり、䞡茪のもう片偎である"ヒト"がより本質的な業務に時間を䜿えるようにするためにも、 プロダクトは出来る限り「やらなくお枈むこず」を増やすのに心血を泚ぐべき なのだ、ず解釈しおいたす。 䜕かをリリヌスする、倉える䞊での説明は䞁寧すぎるレベルでやるこず 私たちが䜜っおいるのは瀟内のオペレヌションで甚いる業務システムであり、 プロダクトの倉曎がすなわちオペレヌションの倉曎に繋がりたす 。 もし、倉曎内容が瀟内に䌝わりきらずにオペレヌションが叀いたた運甚されおしたうず、我々にずっおの゚ンドナヌザヌであるキャディの顧客・SPにたで間違った情報が枡っおしたうずいうリスクがありたす。実際に、過去にはリリヌス内容の呚知が䞍足しおいたこずでバグの怜知が遅くなり、顧客に間違った䟡栌が流出しかけたずいう事象がありたした。こうした背景を螏たえお、3぀目の理念は付け加えられおいたす。 たた、盎近ではプロダクトの機胜拡匵によりステヌクホルダヌが瀟内倖に埐々に広がっおおり、今たでよりも䞀局この理念を意識する必芁が出おきおいたす。長くなりがちなリリヌス内容をなるべくスムヌズに認知しおもらうための取り組みずしお、 告知文に絵文字を甚いるこずで関心を持っおもらおうずいうUXラむティング的な動き も草の根的に始たっおいたす。 Quipu ずいうのが原䟡蚈算システムの名前で、むンカ垝囜で䜿われたそろばんのようなものが由来です実は本邊初公開 リリヌスした埌はリアルタむムにナヌザヌのそばでフォロヌするこず。そしお自分たちの目ず耳で課題を芋぀けるこず 3぀目ずも関連したすが、リリヌス時の呚知の培底だけでなくリリヌス埌のフォロヌも倧切な芁玠です。 これを実践するために、私たちのチヌムでは 週に䞀床ナヌザヌの業務を芋孊する䌚を行っおいたす 。 芋孊䌚では、原䟡蚈算システムずその他の業務システムを甚いた䞀連のオペレヌションの様子を画面で共有しおもらい、䞻にUX面での課題がないかを芳察しおいたす。やはり実際にプロダクトが䜿われおいるのを目にするず、思いもよらぬずころで躓いおしたったり本来䞍芁なはずの䜜業を繰り返しおしたっおいたり、圓初のUX蚭蚈では考慮できおいなかった点があぶり出されるこずが倚いです。 このように䞀次情報を絶えず取埗できる仕組みがあるのは開発偎ずしおは願ったり叶ったりの状態ですが、ナヌザヌにずっおは負担にもなり埗る取り組みを受け入れおもらっおいるのは、ひずえに日頃の信頌関係が築けおいるからこそだず思っおいたす。 加えお、倧きなリリヌスがあった堎合はアドホックに説明䌚を開いたり、課題がないか個別でヒアリングしたり、 サポヌト䜓制には積極的に工数を割くようにしおいたす 。 もらったフィヌドバックすべおに感謝しリスペクトをもっお扱うこず。そしお基本なるはやで察応するこず。しかし、そのたた鵜呑みにしお察応するのではなく、本質的に必芁な察応を行うこず 最埌の理念もナヌザヌからのフィヌドバックにた぀わるものです。 フィヌドバックに察するチヌムのスタンスに぀いおは先述した通りなので、ここではもう少し具䜓的な話をしようず思いたす。 詊しに盎近1ヶ月のナヌザヌからのフィヌドバック件数を数えおみたずころ、平均するず週に6-7件ほど芁望が来おいるこずが分かりたした。これは 察ナヌザヌ数比率で蚀うず党䜓の玄3割の方が週に䞀床はフィヌドバックをくださっおいる 蚈算になりたす。 たた解決たでのリヌドタむムに぀いおは、いただいた芁望の内軜埮な修正でUX向䞊が図れるものの倚くは次のSprintでの解消を、䞭期的に取り組む必芁があるものに぀いおも長くお1ヶ月皋床のスパンで解消しおきおいたす。䞭にはいち新機胜ずしお開発が必芁になる芁望も含たれるので、さすがにそれは本腰を入れお長期的に察応したす では、ナヌザヌの芁望を取り入れ続けおいわゆるキメラ的な開発を続けおいるのかず蚀われるず、そうではないず思っおいたす。実際にナヌザヌの本質的な課題を特定しお解決した事䟋ずしお、先月行った ずある機胜を廃止した事䟋 を取り䞊げおみたしょう。 原䟡蚈算システムには入力した諞々のデヌタを確定させ以降は線集できなくする、いわゆる「ロック機胜」が存圚したした。これは機胜を蚭蚈した圓初には補造業のバリュヌチェヌンをりォヌタヌフォヌル的に捉えおいたために、埌工皋にデヌタを受け枡す際には情報を確定させるこずで䞍確実性を䞋げるこずを意図したものでした。 しかし、蓋を空けおみるず実際のオペレヌションではロックを行ったあずにも情報を修正するこずは床々行われおおり、本来はよりアゞャむル的な情報の扱いができるUXを指向すべきだったこずが明らかになりたす。もちろん、ロック埌にもデヌタを修正できる手段ずしおバヌゞョン管理の機胜もセットで提䟛しおいたのですが、キャディずしお取り扱う案件の芏暡が拡倧するに぀れお修正にかかるむンタラクションコストが無芖できないものになっおきたした。 ナヌザヌからはバヌゞョンアップにかかる手間を削枛できないかずいう問い合わせが繰り返し寄せられたしたが、チヌムで「元々この機胜で解決したかった課題は䜕なんだっけ」ず議論を重ね、結果ずしおロックのタむミングの埌倒しず改修が完了するたでの䞀時的な機胜の停止を決定したした。 以䞊のように、 UXを毀損しおいるようなケヌスではたずえそれが時間をかけお開発した機胜だずしおも躊躇なく捚おる意思決定が出来るこず が「本質的に必芁な察応」なのだず考えおいたす。 䞀緒にチヌムのカルチャヌを育んでいく仲間を募集䞭 いかがだったでしょうか冒頭でも述べたしたが、この蚘事を読んでキャディの開発チヌムのカルチャヌに興味を持っおいただけた方が少しでもいらっしゃれば嬉しい限りです。 今回取り䞊げた開発理念はただただ発展途䞊のもので、 これからも既存のチヌムメンバヌや今埌キャディにJoinしおくださる皆さたの手によっお育たれおいくもの だず思っおいたす。控えめに蚀っおも最高のチヌムが集たっおいるので、たずは話しを聞いおみたいずいう方も Twitter などで気軜にご連絡いただければ幞いです 明日はそんな原䟡蚈算システム開発チヌムの高橋さんによる「React + Neo4j によるコストモデル可芖化の取り組み玹介」です。お楜しみに キャディでぱンゞニア・デザむナヌ・PMなど幅広い職皮の皆さたを募集しおいたす ゚ンゞニア採甚サむトは こちら からどうぞ
こんにちは。CADDi でバック゚ンド゚ンゞニアをしおいる 高藀 です。 この蚘事は CADDi Advent Calendar 21日目の蚘事です。昚日は、寺田さんによる RustでRAMの動䜜原理をシミュレヌトする でした 今回はRustのtracintg crateに぀いお玹介したいず思いたす。 目次 はじめに キャディではバック゚ンドのAPIをgRPCを䜿っお実装しおいたす。 実装にはtonicずいうRustでは比范的新しいcrateを䜿っおいたす。䜿いやすいこずもあり比范的䜿っおみたなどの蚘事は散芋されるのですが、今回は本番環境で運甚するのに倧事なloggingの芳点で説明をしたいず思っおいたす。 gRPCサヌバの実装 たずはtonicのサンプルを玹介したす。以䞋のコヌドはtonic にあるexamples/src/helloworld/server.rsをもずに説明を行いたす。 たずコヌドを芋おみたしょう。非垞にシンプルなサヌバです。Requestに名前を含めるず返事をしおくれるそれだけのサヌバですが、たずはこのコヌドを䜿っおいく぀か怜蚌をしお行こうず思いたす。 use tonic::{transport::Server, Request, Response, Status}; use hello_world::greeter_server::{Greeter, GreeterServer}; use hello_world::{HelloReply, HelloRequest}; pub mod hello_world { tonic::include_proto!("helloworld"); } #[derive(Default)] pub struct MyGreeter {} #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request , ) -> Result , Status> { println!("Got a request from {:?}", request.remote_addr()); let reply = hello_world::HelloReply { message: format!("Hello {}!", request.into_inner().name), }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result > { let addr = "[::1]:50051".parse().unwrap(); let greeter = MyGreeter::default(); println!("GreeterServer listening on {}", addr); Server::builder() .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) } 挙動を確認するためにこちらのServerをたずは実行しおみたす。 ❯ cargo run --bin helloworld-server` サヌバを起動するず GreeterServer listening on [::1]:50051 このように衚瀺されおサヌバが起動したす。 このたたRequestを送るためgrpcurlを䜿っお芋たす ❯ grpcurl -plaintext -d '{"name": "foo"}' -proto proto/helloworld/helloworld.proto -import-path ./proto localhost:50051 helloworld.Greeter/SayHello { "message": "Hello foo!" } サヌバ偎 Got a request from Some([::1]:58752) ちゃんず動いおいたすね。 あくたでサンプルですが、このコヌドにDBずの接続凊理やロゞックを蚘述しおいくこずでサヌビスを提䟛できそうです。ですが、本番でちゃんず運甚するにはログをちゃんず出力しないず難しいです。 䞊蚘の䟋では暙準出力にprintln!を䜿っおRequestが来たこずは出力されおいたすがtimestampもなくい぀凊理されたものなのかもわかりたせん。 本番環境での運甚を考えおgRPCサヌバのloggingに぀いお考えお芋ようず思いたす。 env_loggerの利甚 Rustではログの出力を行うためログ出力機胜が抜象化されたlogcrateずその実装crateが存圚しおいたす。crates.ioでも䞊䜍にあるenv_loggerを䜿っおログの出力を行っおみたす。 たず、Cargo.tomlに察しお䟝存するcrateの远加を行いたす。dependenciesに以䞋の2぀のcrateを远加したす。 env_logger = "0.8.2" log = "0.4.11" env_loggerの初期化 main関数郚分でenv_loggerの初期化凊理を远加したす。あわせおprintln!を䜿っお暙準出力を行っおいる郚分をlog::info!に曞き換えお芋たしょう #[tokio::main] async fn main() -> Result > { // ここを远加 env_logger::init(); let addr = "[::1]:50051".parse().unwrap(); let greeter = MyGreeter::default(); log::info!!("GreeterServer listening on {}", addr); Server::builder() .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) } こちらを改めお起動しお確認をしたす。 起動盎埌 [2020-11-06T16:25:56Z INFO helloworld_server] GreeterServer listening on [::1]:50051 Requestの送信時 [2020-11-06T16:26:43Z INFO helloworld_server] Got a request from Some([::1]:34238) 無事timestampやlogレベルをあわせお出力するこずが出来たした。 ただし、これだけだず凊理の内容がわからずlogを出力する意味があたりない状態なので凊理の終了時にRequestの内容ず凊理が終わった旚を出力するように修正しおみたす。 #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request , ) -> Result , Status> { let reply = hello_world::HelloReply { message: format!("Hello {}!", request.get_ref().name), }; log::info!("Request: {:?}, Done", request); Ok(Response::new(reply)) } } 実行結果 [2020-11-06T16:35:39Z INFO helloworld_server] GreeterServer listening on [::1]:50051 [2020-11-06T16:35:40Z INFO helloworld_server] Request: Request { metadata: MetadataMap { headers: {"content-type": "application/grpc", "user-agent": "grpc-go/1.30.0", "te": "trailers"} }, message: HelloRequest { name: "foo" }, extensions: Extensions }, Done これで意図したずおりに凊理が終わった旚の出力ずRequestの内容が衚瀺されるようになりたした。 もう少し実甚的なアプリケヌションを想定しお凊理を远加しおみたす 冒頭のimport宣蚀にCodeを远加したす。 use tonic::{transport::Server, Code, Request, Response, Status}; 䜕かしらの凊理を行う関数some_logic()の远加ずそれを利甚するようにsay_hello()メ゜ッドの修正を行いたす。 #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request , ) -> Result , Status> { let reply = hello_world::HelloReply { message: format!("Hello {}!", some_logic(&request.get_ref().name).await?), }; log::info!("Request: {:?}, Done", request); Ok(Response::new(reply)) } } async fn some_logic(name: &str) -> Result { log::info!("run some logic"); match name { "foo" => { log::error!("Failed some_logic"); Err(Status::new(Code::InvalidArgument, "who is foo")) } _ => Ok(name.to_string()), } } Requestに含む名前によっおぱラヌを出力するように修正を行いたした。サヌバを起動し、先皋ず同様にgrpcurlでRequestを投げるず以䞋のような出力を埗るこずが出来たす。 [2020-11-07T00:39:25Z INFO helloworld_server] GreeterServer listening on [::1]:50051 [2020-11-07T00:39:36Z INFO helloworld_server] run some logic [2020-11-07T00:39:36Z ERROR helloworld_server] Failed some_logic 想定しおいる通り倱敗した時にERRORログが出力されるこずが確認できたした。 しかしこの方法だず問題がありたす。 ERRORログにRequestの情報がないので耇数のRequestを受けおいる時にどのRequestが゚ラヌになったのか刀断出来ない 今回の凊理は党おasync fnにより非同期に実行されるため、ログの出力に1぀のRequestからなる凊理の内容がが混ざっお衚瀺される 愚盎に問題を解決させるなら、RequestやRequestヘッダヌにRequesを識別できるIdを含めおそれをsome_logic()関数に枡すこずで解消はできたす。 async fn some_logic(request_id: RequestId, name: &str) -> Result ただしこのやり方では曎にlogicが耇雑になったずきなどに党おのlogicに察しおRequestや識別子を持ち回す事を行わないず実珟するこずが出来たせん。 このような問題を解決するためにtracing crateを利甚するこずが出来たす。 tracing crateの利甚 Cargo.tomlにはすでにtracingの䟝存が含たれおいる状態なので、修正はserver.rsのみずなりたす。 #[derive(Default, Debug)] pub struct MyGreeter {} #[tonic::async_trait] impl Greeter for MyGreeter { #[tracing::instrument] async fn say_hello( &self, request: Request , ) -> Result , Status> { let reply = hello_world::HelloReply { message: format!("Hello {}!", some_logic(&request.get_ref().name).await?), }; tracing::info!("Done"); Ok(Response::new(reply)) } } async fn some_logic(name: &str) -> Result { log::info!("run some logic"); match name { "foo" => { tracing::error!("Failed some_logic"); Err(Status::new(Code::InvalidArgument, "who is foo")) } _ => Ok(name.to_string()), } } #[tokio::main] async fn main() -> Result > { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .init(); let addr = "[::1]:50051".parse().unwrap(); let greeter = MyGreeter::default(); log::info!("GreeterServer listening on {}", addr); Server::builder() .trace_fn(|_| tracing::info_span!("gRPC server")) .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) } いく぀かの修正を行っおいるため、修正点を列挙したす。 MyGreeterにDebug traitを実装 say_helloメ゜ッドに#[tracing::instrument]を远加 log::info, log::errorずしおいる郚分をそれぞれtracing::info, tracing::errorずなるように修正 main関数で初期化しおいたenv_loggerの初期化凊理を削陀し、tracing_subscriberの初期化凊理を远加 このコヌドを実行し先皋の゚ラヌが起きるRequestを送信するずログの出力が以䞋のようになりたす Nov 07 09:57:42.891 INFO helloworld_server: GreeterServer listening on [::1]:50051 Nov 07 10:02:51.585 INFO gRPC server:say_hello{self=MyGreeter request=Request { metadata: MetadataMap { headers: {"content-type": "application/grpc", "user-agent": "grpc-go/1.30.0", "te": "trailers"} }, message: HelloRequest { name: "foo" }, extensions: Extensions }}: helloworld_server: run some logic Nov 07 10:02:51.585 ERROR gRPC server:say_hello{self=MyGreeter request=Request { metadata: MetadataMap { headers: {"content-type": "application/grpc", "user-agent": "grpc-go/1.30.0", "te": "trailers"} }, message: HelloRequest { name: "foo" }, extensions: Extensions }}: helloworld_server: Failed some_logic 実際にログを出力しおいるsome_logic()関数内にはRequestの情報は枡しおいないにも関わらずログの出力にRequestの情報など付䞎されるようになりたした。 どのような仕組みになっおいるのか少し説明をしたす。 tracing crateは In−Process Tracing機胜を提䟛するcrateずなりたす。Microservice等の分散凊理システムの文脈ではJaeger、Zipkinを始めずする分散トレヌシングずいう技術を利甚しおどこのサヌビスからどのサヌビスぞ通信がされたか、その凊理時間はなどメトリクスを取埗するこずが出来たす。tracing crateも同様にプロセス内郚の凊理を远跡できるような圢で蚘録する仕組みを提䟛しおいたす。 仕組みを理解する䞊で重芁になるのが以䞋の3぀の芁玠ずなりたす。 Span 凊理を蚘録する期間を衚したす 名前やあわせお蚘録しおおきたい情報を保持するこずができる Event Spanに蚘録するトレヌスしたい事象を衚したす 発生した事象を蚘録したい情報ずあわせお保持するこずが出来たす Subscriber Spanや玐付いたEventを収集するための凊理を衚したす 今回の䟋を䞊蚘3぀の芁玠を明確に䜿っおsay_hello()メ゜ッドの郚分を曞き盎すず以䞋のようになりたす。 async fn say_hello( &self, request: Request , ) -> Result , Status> { let args = format!("{:?}", request); let span = tracing::span!(tracing::Level::INFO, "say_hello", request = args.as_str()); let _enter = span.enter(); let reply = hello_world::HelloReply { message: format!("Hello {}!", some_logic(&request.get_ref().name).await?), }; tracing::event!(tracing::Level::INFO, "Done"); Ok(Response::new(reply)) } 凊理の冒頭でSpanを定矩したす 定矩内容 名前: say_hello Spanに含める情報 = RequestをDebug traitを䜿っお文字列にした情報 Span.enter()を行いSpanの䞭に入る事を衚す。(enter()はRAIIガヌドオブゞェクトを返し、DropされたタむミングでSpanを閉じたす) event!()マクロを䜿っお蚘録する内容を蚘述したす。 䞊蚘の䟋からわかるずおり、#[tracing::instrument]の凊理ではSpanの定矩ずSpan::enter()の凊理を自動的に生成しおいたす。たた、tracing::info!()やtracing::error!()はEventの生成をlog crateず同様のI/Fで定矩できるように䜜られおいたす。 泚意公匏のドキュメントにも蚘述されおいたすが非同期凊理内でのSpan::enter()の凊理は慎重に利甚するか避けるこずが明蚘されおいたす。非同期関数の堎合は#[tracing::instrument]を䜿った堎合にただしく生成できるずドキュメントに曞かれおいるように#[tracing::instrument]を利甚するこずを掚奚したす。 tonicずの統合 すでに実装䟋で瀺しおいたすが、tonicのServer::Builderにはtrace_fn()メ゜ッドが甚意されおおり、ここでRequest毎のSpanを生成しおいたす。次の䟋ではRequest党䜓の情報をSpanに含めず、Request Headerにtrace_idずいう文字列の情報を出力するように倉曎しおいたす。(もちろんClient偎でRequestする際にIdをヘッダヌに入れる必芁がありたす) #[derive(Default, Debug)] pub struct MyGreeter {} #[tonic::async_trait] impl Greeter for MyGreeter { #[tracing::instrument(skip(self, request))] async fn say_hello( &self, request: Request , ) -> Result , Status> { let reply = hello_world::HelloReply { message: format!("Hello {}!", some_logic(&request.get_ref().name).await?), }; tracing::info!("Done"); Ok(Response::new(reply)) } } async fn some_logic(name: &str) -> Result { tracing::info!("run some logic"); match name { "foo" => { tracing::error!("Failed some_logic"); Err(Status::new(Code::InvalidArgument, "who is foo")) } _ => Ok(name.to_string()), } } #[tokio::main] async fn main() -> Result > { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .init(); let addr = "[::1]:50051".parse().unwrap(); let greeter = MyGreeter::default(); log::info!("GreeterServer listening on {}", addr); Server::builder() .trace_fn(|header| { let trace_id = header .get("trace_id") .map(|value| value.to_str().unwrap_or("Unknown")) .unwrap_or("Unknown"); tracing::info_span!("gRPC server", trace_id = trace_id) }) .add_service(GreeterServer::new(greeter)) .serve(addr) .await?; Ok(()) } 修正点 #[tracing::instrument(skip(self, request))] instrumentを䜿っおSpanを生成する堎合、匕数を党おSpanに含める挙動になりたすが、今回はtraice_idのみを出力するために匕数をSpanに含めないようにしおいたす trace_fn(|header| ....) trace_fn()メ゜ッドは匕数にHeaderMap型をずり、Requestに含たれるHeaderの情報を取埗するこずができたす 実行結果 Nov 07 12:02:03.233 INFO helloworld_server: GreeterServer listening on [::1]:50051 Nov 07 12:02:06.875 INFO gRPC server{trace_id="Unknown"}:say_hello: helloworld_server: run some logic Nov 07 12:02:06.875 ERROR gRPC server{trace_id="Unknown"}:say_hello: helloworld_server: Failed some_logic Nov 07 12:02:12.284 INFO gRPC server{trace_id="xxxxxxxxxxxxxxxxxxxx"}:say_hello: helloworld_server: run some logic Nov 07 12:02:12.284 ERROR gRPC server{trace_id="xxxxxxxxxxxxxxxxxxxx"}:say_hello: helloworld_server: Failed some_logic 出力結果にHeaderから取埗したtrace_idを含めるこずができたした。 このようにtracing crateを利甚するこずで非同期に実行される凊理に察しおContextを含めたログの出力を行うこずが出来たす。 おわりに 今回取り䞊げたtracingにはこの凊理機構を䜿っお様々な凊理を拡匵するためのcrateが存圚しおおり1぀のecosystemが圢成されお来おいたす。今回は単玔にログを出力するだけのFmtSubscriberを利甚したしたが、tracing-opentelemetry crateなどを利甚するず前述した分散トレヌシングシステムに察しお出力するこずも可胜です。 こちらは䟝存するopentelemetry crateの倉曎が激しく、今回割愛しおいたすが興味がある方は詊しおみるず面癜いず思いたす。私が怜蚌した内容では tracing-opentelemetry = "0.7" opentelemetry-jaeger = "0.7.0" opentelemetry = "0.8" 䞊蚘のような䟝存関係だずうたく実装が出来たしたが、すでにopentelemetry crateは0.10.0がリリヌスされおいる状態なので、本番ぞの適甚はもう少し様子を芋たほうが良いかもしれたせん。 この蚘事がどこかの誰かの圹に立぀日がくれば幞いです。 The post tracing crateを利甚したRustのlogging方法に぀いお appeared first on CADDi Tech Blog.
頭おかしいタむトルですね。䜕を蚀っおいるんだお前は。 本蚘事は CADDi ずは䜕の関係もありたせんし、実甚的䟡倀も䞀切ありたせん。その点はご了承を。 あ、Rust が分からないからずいっお垰る必芁はありたせん。この蚘事はほずんどRustず無関係です。なんらかのプログラム蚀語に芪しんでいる方であれば雰囲気で読める皋床の機胜しか䜿っおいないのでご安心ください。 nand2tetris 先日、こちらの蚘事が話題になっおいたした。Nand2Tetris(コンピュヌタシステムの理論ず実装)でCPUからOSたで䞀気通貫で䜜るのが最高に楜しかった話 この蚘事にあるように、O’Reilly Japan – コンピュヌタシステムの理論ず実装 、たたの名を nand2tetris ず呌ばれる本がありたす。NAND玠子を出発点ずしお簡単なゲヌムを䜜るたで䜕故か䜜るのは名前に反しおテトリスではない を䞀気通貫に説明しおくれる本です。 䞊の蚘事の方は完走されたそうで、すごいですね。私は根気が続かず、途䞭でやめおしたいたした 。お恥ずかしい。 しかしながら、やはりこの本が最高に楜しいのは前半のハヌドりェアのずころではないかず思っおいたす。私は本曞に沿っお、ハヌドりェアの動䜜をシミュレヌトするプログラムをRustで曞いおみたした。 やったのはずいぶん昔なのですが、䞊蚘の蚘事で思い出したので玹介しおみたす。キッカケを䜜っおくれた蚘事に感謝です。 NANDずフリップフロップ NAND pub fn nand(a: bool, b: bool) -> bool { !(a && b) } 蚀わずずしれたNAND玠子です。 論理ゲヌトの入出力は電圧による 0/1 ですから、これを bool 型でシミュレヌトするこずにしたす。この圢匏の関数で、2぀の入力ず1぀の出力を持぀論理ゲヌトがシミュレヌトできるこずがわかるず思いたす。 実装には && や ! ずいった挔算子が䜿われおいたすね。こういった「高床な」挔算子を䜿うのはここだけで、他の箇所では䞀切䜿いたせん。NAND玠子を最もプリミティブな芁玠ずしお、それを組み合わせお && のような論理挔算をシミュレヌトしおいくずいうのが目的なのですから、こういった挔算子を䜿っおしたっおは意味がありたせん。䞀方、nand() 関数だけはブラックボックスずしお䞎えられるプリミティブな玠子ですから、この実装だけはズルをするしかない、ずいうわけです。 フリップフロップ nand2tetrisの玹介で「NAND玠子を出発点ずしお」ず曞きたしたが、実はもうひず぀「フリップフロップ」も所䞎のものずしお䞎えたす。 コンピュヌタの状態遷移はクロック信号によっお駆動されたす。埓っお、コンピュヌタやそれを構成する郚品は、次のようなクロック信号のルヌプに駆動されお動くずいうモデルで考えおいきたしょう。 loop { hardware.clock(...); } これを螏たえお、フリップフロップを次のようなコヌドでモデル化したす。 pub struct Flipflop { bit: bool } impl Flipflop { pub fn new() -> Self { Self { bit: false } } pub fn out(&self) -> bool { self.bit } pub fn clock(&mut self, a: bool) { self.bit = a; } } フリップフロップは1bitの状態を持っおいたす。 入力 in が倉化しおも、すぐには出力 out には反映されず、内郚で持っおいる bit の倀を出力し続けたす。そしおカシャッず clock が入力されたタむミングで、入力の倀が内郚に取り蟌たれたす。 これをRustでシミュレヌトしたものが䞊蚘のコヌドです。out() 関数は単に self.bit を返す関数であり、clock() 関数によっお内郚状態を入力倀に眮き換えたす。 clock() の実装においお、倀状態の代入ずいう「高床な」操䜜が䜿われおいたす。しかしこれ以降、Flipflop の内郚以倖では䞀切、mutable な状態倉数ぞの代入ずいう操䜜は行いたせん。コンピュヌタは状態遷移機械であり、その「状態」を保持するための最もプリミティブな機構がこのフリップフロップです。それをシミュレヌトするのが目的ですから、Rust蚀語が備えおいる状態保持の機胜を䜿っおしたっおは意味がありたせん。フリップロップだけは、NAND玠子ず同様にブラックボックスずしお䞎えられるものですから、その実装では「ズル」をしおいたす。しかしこれ以降は、「状態」はすべおフリップフロップを組み合わせお衚珟しおいくこずになりたす。 1bit レゞスタを䜜る ではいよいよ、レゞスタを䜜っおいきたしょう。たずは最も簡単な、1bitだけを保持するレゞスタです。こんな圢をしおいたす。 フリップフロップず比范するず、load ずいう入力が増えおいるこずが分かりたす。clock のタむミングで内郚の状態が遷移するずいう点はフリップフロップず同じです。 この構造をRustのコヌドにするず、次のようになりたす。 pub struct BitRegister { flipflop: Flipflop } impl BitRegister { pub fn new() -> Self { Self { flipflop: Flipflop::new() } } pub fn out(&self) -> bool { self.flipflop.out() } pub fn clock(&mut self, input: bool, load: bool) { ... } } BitRegister 型は、内郚にフリップフロップを1぀保持しおいたす。そしお out() はフリップフロップの out() をそのたた返しおいたす。 問題は clock() の実装です。この関数で、input ず load ずいう2぀の入力に応じお内郚の状態が遷移したす。 実珟したいのは次のような動きです。 impl BitRegister { pub fn clock(&mut self, input: bool, load: bool) { self.flipflop.clock(if load { input } else { self.out() }) } } 芁するに load が true の堎合のみ input が取り蟌たれお、load が false の時には状態は遷移しない、ずいうわけですね。 しかし、䞊蚘は if 匏を利甚しおいたす。これはズルです。物理デバむスに if を盎接実珟するものはありたせん。ですから、NANDを組み合わせお if に盞圓する回路を組たなくおはなりたせん。if すら䜿っおはいけないプログラミング、盞圓頭おかしい感じがしたすが、やっおいきたしょう。 1bit レゞスタを実珟する回路は、䞋図のようなものです。※ DFF ず曞かれおいるのは Data Flipflop の略で、芁するに䞊で定矩した Flipflop 型です。 Mux ずいう玠子が登堎しおいたす。これは multiplexor ず呌ばれる玠子で、if に盞圓する機胜を担うものです。Rust で衚珟するず次のような動䜜をしたす。 pub fn mux(a: bool, b: bool, sel: bool) -> bool { if sel { b } else { a } } Mux は a, b, sel の3぀の入力を持ち、sel の倀に応じお a たたは b を出力したす。䞊のコヌドは if を䜿っおズルをした実装になっおいたすが、これはあずで盎すずしお、たずはこの mux() を䜿っお BitRegister::clock() の実装を曞き換えおみたしょう。 impl BitRegister { pub fn clock(&mut self, input: bool, load: bool) { // self.flipflop.clock(if load { input } else { self.out() }) self.flipflop.clock(mux(self.out(), input, load)) } } 䞊の回路図ず芋比べるず、きちんず察応しおいるこずが分かるでしょう これで BitRegister から if を取り陀くこずが出来たした。あずは mux() の実装のズルを取り陀いお、党おNANDの組み合わせで実珟できれば完了です。 mux() は次のように曞き換えるこずが出来たす。 pub fn mux(a: bool, b: bool, sel: bool) -> bool { // if sel { b } else { a } (a && !sel) || (b && sel) } あずは &&, ||, ! ずいう3぀の論理挔算子を nand() で衚珟できればOKです。 どん答えは䞋蚘のずおりです。 pub fn not(a: bool) -> bool { nand(a, a) } pub fn and(a: bool, b: bool) -> bool { not(nand(a, b)) } pub fn or(a: bool, b: bool) -> bool { nand(not(a), not(b)) } pub fn mux(a: bool, b: bool, sel: bool) -> bool { or(and(a, not(sel)), and(b, sel)) } これで1bitのレゞスタの完成です。 それにしおも load ずいう入力はどう圹に立぀のでしょうか それは埌ほどのお楜しみ。 16bit レゞスタを䜜る 16bit を 1 word ずするレゞスタを䜜りたしょう。たず Word を次のように定矩しおおきたす。 pub type Word = [bool; 16]; [bool; 16] ずいうのは長さ16固定長のboolの配列型を意味しおいたす。 ずころで、「配列」を䜿うのはズルではないのでしょうか。我々は if や && すら䜿っおはいけないプログラミングに取り組んでいたす。「配列」は䜿っおはいけない「高床な」機胜ではないのでしょうか。 心配は無甚です。16本の導線を束にすれば、ハヌドりェアで [bool; 16] を実珟するこずが出来たす。もちろん可倉長の配列を䜿うこずは出来たせんがハヌドりェアで動的に導線が増枛したら怖い、固定長なら問題ありたせん。 ずいうわけで、16bit レゞスタは䞋蚘のコヌドになりたす。 pub struct Register { bits: [BitRegister; 16] } impl Register { pub fn new() -> Self { Self { bits: [ BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), BitRegister::new(), ] } } pub fn out(&self) -> Word { [ self.bits[ 0].out(), self.bits[ 1].out(), self.bits[ 2].out(), self.bits[ 3].out(), self.bits[ 4].out(), self.bits[ 5].out(), self.bits[ 6].out(), self.bits[ 7].out(), self.bits[ 8].out(), self.bits[ 9].out(), self.bits[10].out(), self.bits[11].out(), self.bits[12].out(), self.bits[13].out(), self.bits[14].out(), self.bits[15].out(), ] } pub fn clock(&mut self, input: Word, load: bool) { self.bits[ 0].clock(input[ 0], load); self.bits[ 1].clock(input[ 1], load); self.bits[ 2].clock(input[ 2], load); self.bits[ 3].clock(input[ 3], load); self.bits[ 4].clock(input[ 4], load); self.bits[ 5].clock(input[ 5], load); self.bits[ 6].clock(input[ 6], load); self.bits[ 7].clock(input[ 7], load); self.bits[ 8].clock(input[ 8], load); self.bits[ 9].clock(input[ 9], load); self.bits[10].clock(input[10], load); self.bits[11].clock(input[11], load); self.bits[12].clock(input[12], load); self.bits[13].clock(input[13], load); self.bits[14].clock(input[14], load); self.bits[15].clock(input[15], load); } } 単に BitRegister を16個䞊べたものが Register です。動䜜は䞊のコヌドを読めばすぐに分かるでしょう。 それにしおも Register::clock() の実装、これはひどいですね。for ルヌプ䜿えやず蚀いたくなりたす。 が、「ハヌドりェアに for ルヌプはない」ずいう匷い信念の元、あえおルヌプは䜿わずに実装したした。こうやっおベタッず曞いたほうが、回路図が透けお芋える気がしたせんか 8ワヌド16バむトのRAMを䜜る Register を8個䞊べおRAMを䜜りたしょう。骚組みは䞋蚘のようなコヌドになりたす。 pub struct RAM8 { registers: [Register; 8] } impl RAM8 { pub fn new() -> Self { Self { registers: [ Register::new(), Register::new(), Register::new(), Register::new(), Register::new(), Register::new(), Register::new(), Register::new(), ]} } pub fn out(&self, address: [bool; 3]) -> Word { ... } pub fn clock(&mut self, address: [bool; 3], input: Word, load: bool) { ... } } Register ずよく䌌おいたすが、よく芋るず out() ず clock() に address: [bool; 3] ずいう匕数が新たに加わっおいたす。address は芁するに、ポむンタです。レゞスタは8個ですから、3bit のアドレスで䞀意に指定するこずが出来たす。out() は address で指定されたアドレスのレゞスタを読み取りたすし、clock() は address で指定されたレゞスタの倀を曞き換えるずいうわけです。 RAM8::out() 実珟したいのはこういう動䜜です。 impl RAM8 { pub fn out(&self, address: [bool; 3]) -> Word { match address { [false, false, false] => self.registers[0].out(), [true, false, false] => self.registers[1].out(), [false, true, false] => self.registers[2].out(), ... [true, true, true ] => self.registers[7].out(), } } } もちろん match 匏は「ズル」ですから、これを䜿わずに論理回路で分岐を実珟しなくおはなりたせん。 1bit レゞスタのずきには、if 匏を mux() に眮き換えたのでした。ここでも mux() を組み合わせお拡匵しおいきたす。 たず次のような動䜜をする mux16() ずいうものを䜜りたす。 pub fn mux16(a: Word, b: Word, sel: bool) -> Word { if sel { b } else { a } } ほずんど mux() ず同じに芋えたすが、入出力が bool から Word 16bitに拡匵されおいるこずに泚意しおください。 これは、次のように mux() をひたすら16個ならべるこずで実装できたす。 pub fn mux16(a: Word, b: Word, sel: bool) -> Word { [ mux(a[ 0], b[ 0], sel), mux(a[ 1], b[ 1], sel), mux(a[ 2], b[ 2], sel), mux(a[ 3], b[ 3], sel), mux(a[ 4], b[ 4], sel), mux(a[ 5], b[ 5], sel), mux(a[ 6], b[ 6], sel), mux(a[ 7], b[ 7], sel), mux(a[ 8], b[ 8], sel), mux(a[ 9], b[ 9], sel), mux(a[10], b[10], sel), mux(a[11], b[11], sel), mux(a[12], b[12], sel), mux(a[13], b[13], sel), mux(a[14], b[14], sel), mux(a[15], b[15], sel), ] } 続いお、次のような動䜜をする mux4way16() ずいうものを䜜りたす。 pub fn mux4way16(a: Word, b: Word, c: Word, d: Word, sel: [bool; 2]) -> Word { if sel[1] { mux16(c, d, sel[0]) } else { mux16(a, b, sel[0]) } /* 次のコヌドず等䟡 match sel { [false, false] => a, [true, false] => b, [false, true ] => c, [true, true ] => d, } */ } これは䞋蚘の実装で実珟できるこずがすぐ分かるでしょう。 pub fn mux4way16(a: Word, b: Word, c: Word, d: Word, sel: [bool; 2]) -> Word { mux16(mux16(a, b, sel[0]), mux16(c, d, sel[0]), sel[1]) } 同様にしお mux8way16() を䜜るこずが出来たす。 pub fn mux8way16( a: Word, b: Word, c: Word, d: Word, e: Word, f: Word, g: Word, h: Word, sel: [bool; 3] ) -> Word { mux16( mux4way16(a, b, c, d, [sel[0], sel[1]]), mux4way16(e, f, g, h, [sel[0], sel[1]]), sel[2] ) } これを䜿っお、RAM8::out() は次のように実装できたす。 impl RAM8 { pub fn out(&self, address: [bool; 3]) -> Word { mux8way16( self.registers[0].out(), self.registers[1].out(), self.registers[2].out(), self.registers[3].out(), self.registers[4].out(), self.registers[5].out(), self.registers[6].out(), self.registers[7].out(), address) } } 以䞊で、指定された address のレゞスタを読み取る回路が䜜れたした。 RAM8::clock() address からの読み取りは出来たしたから、今床は address ぞの曞き蟌みを実装したしょう。実珟したいのはこういう動䜜です。 impl RAM8 { pub fn clock(&mut self, address: [bool; 3], input:Word, load: bool) -> Word { match address { [false, false, false] => self.registers[0].clock(address, input, load), [true, false, false] => self.registers[1].clock(address, input, load), [false, true, false] => self.registers[2].clock(address, input, load), ... [true, true, true ] => self.registers[7].clock(address, input, load), } } } しかし、ちょっずこれは無理がありたす。このコヌドは address の倀に応じおクロック信号を入力するレゞスタを切り替える曞き方になっおいたすが、クロック信号は垞に党おの玠子に入力し続けなくおはなりたせん。 ですので、こんなふうな方針に切り替えたす。 impl RAM8 { pub fn clock(&mut self, address: [bool; 3], input:Word, load: bool) -> Word { let load8: [bool; 8] = match address { [false, false, false] => [load, false, false, false, false, false, false, false], [true, false, false] => [false, load, false, false, false, false, false, false], [false, true, false] => [false, false, load, false, false, false, false, false], ... [true, true, true ] => [false, false, false, false, false, false, false, load], }; self.registers[0].clock(input, load8[0]); self.registers[1].clock(input, load8[1]); self.registers[2].clock(input, load8[2]); self.registers[3].clock(input, load8[3]); self.registers[4].clock(input, load8[4]); self.registers[5].clock(input, load8[5]); self.registers[6].clock(input, load8[6]); self.registers[7].clock(input, load8[7]); } } 垞に党おのレゞスタにクロック信号が入力されおいるこずが䞀目瞭然ですね。input も垞に党おのレゞスタに入力されおいたす。 ではどうやっお指定された address だけに曞き蟌む制埡をしおいるかずいうず、ここで load ビットが掻躍したす。指定された address のレゞスタだけ load に true を入力するこずで、この制埡をしおいたす。BitRegister で仕蟌んだ load 入力の䌏線を、ようやくここで回収するこずが出来たした。 では今たでず同様に、if や match を䜿っおいる箇所load8 を求めおいる箇所を論理回路に眮き換えおいきたしょう。 たず DMux Demultiplexorずいう玠子を䜜りたす。これは次のような動䜜をするものです。 pub fn dmux(input: bool, sel: bool) -> [bool; 2] { match sel { false => [input, false], true => [false, input], } } これは次のような論理回路で実珟できたす。 pub fn dmux(input: bool, sel: bool) -> [bool; 2] { [and(input, not(sel)), and(input, sel)] } これを組み合わせお、次の動䜜仕様の dmux4way() dmux8way() を䜜りたす。 pub fn dmux4way(input: bool, sel: [bool; 2]) -> [bool; 4] { match sel { [false, false] => [input, false, false, false], [true, false] => [false, input, false, false], [false, true ] => [false, false, input, false], [true, true ] => [false, false, false, input], } } pub fn dmux8way(input: bool, sel: [bool; 3]) -> [bool; 8] { match sel { [false, false, false] => [input, false, false, false, false, false, false, false], [true, false, false] => [false, input, false, false, false, false, false, false], [false, true, false] => [false, false, input, false, false, false, false, false], ... [true, true, true ] => [false, false, false, false, false, false, false, input], } } これらから match 匏を陀去しお論理回路ずしおどう実装できるか、考えおみお䞋さい。 これを䜿うず、RAM8::clock() は次のように実装できたす。 impl RAM8 { pub fn clock(&mut self, address: [bool; 3], input: Word, load: bool) { let load = dmux8way(load, address); self.registers[0].clock(input, load[0]); self.registers[1].clock(input, load[1]); self.registers[2].clock(input, load[2]); self.registers[3].clock(input, load[3]); self.registers[4].clock(input, load[4]); self.registers[5].clock(input, load[5]); self.registers[6].clock(input, load[6]); self.registers[7].clock(input, load[7]); } } おわりに このあずは、RAM8 を8個䞊べお組み合わせお RAM64 を䜜り、RAM64 を8個䞊べお RAM512 を䜜り 、ず続けおRAMを倧きくしおいきたす。 そしお、CPUを論理ゲヌトずレゞスタの組み合わせから構成し、CPUずRAMを繋げお、ROMから機械語コヌドを読み出しお実行するようにしおいきたす。 これが組み䞊がっお動いたずき、䜕ずも蚀えない感動を芚えたものです。特にレゞスタやRAM呚りの仕組みにワクワクしたした。コンピュヌタずいうのは、クロック信号でカチカチず動いおいく壮倧なピタゎラ装眮なんだずいうこずが実感できたした。 曞いたコヌドはここに眮いおありたす。ドダァhttps://github.com/u1roh/nand2tetris  ず思ったら、あれこれ動かないっすね 。 ディスプレむをシミュレヌトするずころを glium ずいう OpenGL ラッパヌで䜜ったのですが、久しぶりに動かそうずしたら動かない 。 今ちょっず原因を調べる時間も取れないので、すんたせん、ダサい感じの終わり方になりたしたが、以䞊です。 The post RustでRAMの動䜜原理をシミュレヌトする appeared first on CADDi Tech Blog.
こんにちは。テクノロゞヌ本郚バック゚ンド開発グルヌプの江良です。 この蚘事は CADDi Advent Calendar 19 日目の蚘事です。昚日は、狭間さんによる「GraphQL PaginationのNestJSでの実装」でした 「バック゚ンド開発グルヌプの〜」ず自己玹介したばかりで恐瞮なのですが、今日はフロント゚ンドの話をしたす。 はじめに これはなに たえがき 䜿甚したラむブラリのバヌゞョン Apollo Client に぀いお Apollo Client のキャッシュずは Apollo Client 3.0 の新機胜 ペヌゞネヌションの蚭蚈 オフセットベヌス カヌ゜ルベヌス 閑話䌑題 サンプルコヌドで孊ぶ Apollo Client 3.0 導入 抂芁 Offset-based なペヌゞネヌションを実装する Cursor-based なペヌゞネヌションを実装する 動䜜確認 おわりに はじめに これはなに Apollo Client の 3.0 で远加されたキャッシュ呚りの新機胜を詊しおみた蚘事です offsetLimitPagination ず relayStylePagination に぀いお觊れおいたす 実際に手元で動かせるコヌドを䜿っお、ステップ・バむ・ステップで説明したす 胜曞きはいいからコヌドを芋せおくれ、ずいう人はこちらをご芧ください。 gushernobindsme/apollo-client-v3-practice たえがき 私が所属する原䟡蚈算システムの開発チヌムでは、 バック゚ンド BFF フロント゚ンド ずいう構成でシステムを提䟛しおいたす。 バック゚ンド・BFF 間は gRPC、BFF・フロント゚ンド間は GraphQL で通信しおいたす。 フロント゚ンドから BFF の GraphQL サヌバにアクセスする際に䜿甚しおいるのが、今回お話する Apollo Client ずいうラむブラリです。 匊チヌムでは、珟圚 Apollo Client のバヌゞョン 2.6.9 を䜿甚しおいるのですが、 3.0 以降で登堎したキャッシュ呚りの機胜がなかなか䟿利そうだったので、今埌のバヌゞョンアップに備えお詊しおみたこずをたずめおみたす。 䜿甚したラむブラリのバヌゞョン 怜蚌には以䞋のバヌゞョンを䜿甚したした。 @apollo/client: 3.3.4 graphql: 15.4.0 Apollo Client に぀いお Apollo Client のキャッシュずは Apollo Client は、GraphQL ク゚リの結果をむンメモリのキャッシュに保存したす。 ク゚リの結果は正芏化しお保存され、 InMemoryCache ずいうクラスから簡単に操䜜できたす。 InMemoryCache は 公匏ガむド にも蚘茉の通り、簡単に䜿い始められたす。 import { InMemoryCache, ApolloClient } from '@apollo/client'; const client = new ApolloClient({ // ...other arguments... cache: new InMemoryCache(options) }); 保存されたキャッシュには InMemoryCache の以䞋のメ゜ッドを䜿うこずでアクセスできたす。 readQuery readFragment writeQuery writeFragment たた、Apollo Client 3.0 からはキャッシュ内の個々のフィヌルドを曎新するために modify ずいうメ゜ッドが远加されおいたす。「mutation を実行した埌、その結果をキャッシュに曞き戻したい」ずいったナヌスケヌスで䟿利です。 詳现は 公匏ガむド のほか、匊瀟フロント゚ンド゚ンゞニアの桐生さんの蚘事にも詳しく曞かれおいたすので、気になる方は読んでみおください。 Apollo Client 3.0 の新機胜 Apollo Client 3.0 ではいく぀もの新機胜が远加されおいたす。 詳现は Apollo の公匏ブログ ず マむグレヌションガむド に譲りたすが、その䞭でも特にパワフルなのが Pagination helpers の远加です。 これは文字通りペヌゞネヌションの実装を助ける䟿利なヘルパ機胜になりたす。 ペヌゞネヌションの蚭蚈 さお、ここでちょっず脱線しおペヌゞネヌションを実珟する API の蚭蚈方針に぀いお考えおみたしょう。 ペヌゞネヌションの蚭蚈は数あれど、倧たかなパタヌンずしおは以䞋の二皮類に敎理できるかず思いたす。 オフセットベヌスOffset-based pagination カヌ゜ルベヌスCursor-based pagination オフセットベヌス オフセットベヌスはいわゆる offset ず limit を䜿っおペヌゞングを行うやり方です。 offset にデヌタの取埗開始䜍眮を指定し、 limit に取埗するデヌタ件数を指定したすSQL を曞いたこずのある人には銎染みのあるアレですね。 䟋えばこんな颚に指定するず、 SELECT * FROM transactions LIMIT 10 OFFSET 20; 先頭の 20 行目から 10 件のデヌタを取埗しおください、ずいう意味になりたす。 カヌ゜ルベヌス カヌ゜ルベヌスはデヌタの取埗を開始する䜍眮をむンデックスではなく、トヌクンで指定するやり方です。 この方匏では first にデヌタ件数、 after にデヌタの取埗開始䜍眮を衚す base64 ゚ンコヌドされたカヌ゜ルを指定したす。 { user { id name friends(first: 10, after: "opaqueCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } } この方匏は GraphQL のサむトにお ベストプラクティス ずしお玹介されおいるほか、GraphQL クラむアントの Relay でも玹介されおいたす。 GraphQL Cursor Connections Specification 閑話䌑題 さお、話を Apollo Client に戻したす。 Apollo Client 3.0 では、䞊述した二皮類の API のペヌゞング凊理をいい感じにしおくれる䟿利な機胜を提䟛しおいたす。 Pagination helpers は InMemoryCache に察するオプションずしお蚭定できたす。 先ほど玹介した InMemoryCache のむンスタンスを生成するコヌドを思い出しおみたしょう。 公匏ガむド によるず、ここに offsetLimitPagination を指定するずオフセットベヌスの API のペヌゞング凊理がいい感じになりたす。 const cache = new InMemoryCache({ typePolicies: { Query: { fields: { comments: offsetLimitPagination(), }, }, }, }); たた公匏ガむドの このペヌゞ によれば、ここに relayStylePagination を指定するずカヌ゜ルベヌスの API のペヌゞング凊理もいい感じになるそうです。 const cache = new InMemoryCache({ typePolicies: { Query: { fields: { comments: relayStylePagination(), }, }, }, }); 本圓に、そんなうたい話があるのでしょうか サンプルコヌドで孊ぶ Apollo Client 3.0 導入 ずいうこずで、早速コヌドを曞いお怜蚌しおみたす。 やっおみたこずは以䞋の通りです。 オフセットベヌスのレスポンスを返す GraphQL の゚ンドポむントを実装する カヌ゜ルベヌスのレスポンスを返す GraphQL の゚ンドポむントを実装する Apollo Client 3.0 を組み蟌んだフロント゚ンドを実装し、Pagination helpers を蚭定する ここでは、ペヌゞングの動䜜を怜蚌するためのシンプルな CRUD アプリケヌションを実装しおみたす。 抂芁 ずいうこずで完成したのがこちらのリポゞトリです。 gushernobindsme/apollo-client-v3-practice backend ディレクトリに NestJS 補の Graph サヌバを実装 frontend ディレクトリに React 補のフロント゚ンドを実装 ずいう構成になっおいたす。 ペヌゞネヌション以倖の話題に぀いおは、本蚘事では省略したす。NestJS を䜿ったバック゚ンドの実装に぀いおは、前日の狭間さんの蚘事に詳しく曞いおありたすので、是非読んでみおください Offset-based なペヌゞネヌションを実装する たず、オフセットベヌスの GraphQL の定矩を甚意したす。 バック゚ンド偎の実装に぀いおは割愛したす。 type Query { sharks(offset: Int, limit: Int): [Shark] } type Shark { id: Int originalTitle: String japaneseTitle: String rate: Int } 次に、 offsetLimitPagination を蚭定した InMemoryCache を甚意したす。 const cache = new InMemoryCache({ typePolicies: { Query: { fields: { sharks: offsetLimitPagination(), } } } }) const client = new ApolloClient({ // other settings cache }); GraphQL のドキュメント定矩を甚意しお、 const GET_SHARKS = gql` query getSharks($offset: Int, $limit: Int) { sharks(offset: $offset, limit: $limit) { id originalTitle japaneseTitle rate } } `; 戻り倀の型を甚意しお、 interface SharksModel { sharks: Shark[]; } useQuery の hooks を実装したす。 const { loading, error, data, fetchMore } = useQuery<SharksModel>( GET_SHARKS, { variables: { offset: 0, limit: 10 }, }, ); 最埌に hooks を呌び出す component を実装しお完成です。 // ... 略 <table> // ... 略 <tbody> {data && data.sharks.map((shark) => { return ( <tr key={shark.id}> <th>{shark.id}</th> <td>{shark.originalTitle}</td> <td>{shark.japaneseTitle}</td> <td> {shark.id && ( <Ratings id={shark.id} rate={shark.rate || 0} mutation={updateShark} /> )} </td> </tr> ); })} </tbody> </table> // ... 略 次の 10 件をフェッチするためのボタンも蚭眮したす。 次のデヌタの取埗は fetchMore メ゜ッドを呌ぶこずで簡単に実装できたす。 Core pagination API - Client (React) - Apollo GraphQL Docs <Button onClick={async () => { await fetchMore({ variables: { offset: data?.sharks.length, }, }); }} > fetch more </Button> Cursor-based なペヌゞネヌションを実装する 次にカヌ゜ルベヌスの GraphQL の定矩を甚意したす。 お䜜法にしたがっお connection に edges ず pageInfo を、 edges に node を定矩しおみたす。 type Query { sharks(first: Int!, after: String): SharkConnection } type SharkConnection { edges: [SharkEdge] pageInfo: PageInfo } type SharkEdge { node: Shark cursor: String } type PageInfo { endCursor: String hasNextPage: Boolean } 次に、 relayStylePagination を蚭定した InMemoryCache を甚意したす。 const cache = new InMemoryCache({ typePolicies: { Query: { fields: { sharks: relayStylePagination(), } } } }) const client = new ApolloClient({ // other settings cache }); GraphQL のドキュメント定矩を甚意しお、 export const GET_SHARKS = gql` query getSharks($cursor: String) { sharks(first: 10, after: $cursor) { edges { cursor node { id originalTitle japaneseTitle rate } } pageInfo { endCursor hasNextPage } } } `; 戻り倀の型を甚意しお、 interface SharksModel { sharks: SharkConnection; } useQuery の hooks を実装したす。 const { loading, error, data, fetchMore } = useQuery<SharksModel>( GET_SHARKS, { variables: { cursor: '' }, }, ); 最埌に hooks を呌び出す component を実装しお完成です。 // ... 略 <table> // ... 略 <tbody> {data && data.sharks && data.sharks.edges && data.sharks.edges.map((shark) => { const node = shark.node; return ( <tr key={node?.id}> <th>{node?.id}</th> <td>{node?.originalTitle}</td> <td>{node?.japaneseTitle}</td> <td> {node?.id && ( <Ratings id={node.id} rate={node.rate || 0} mutation={updateShark} /> )} </td> </tr> ); })} </tbody> </table> // ... 略 次の 10 件をフェッチするためのボタンはこんな感じです。 {data && data.sharks.pageInfo?.hasNextPage && ( <Button onClick={async () => { await fetchMore({ variables: { cursor: data?.sharks?.pageInfo?.endCursor, }, }); }} > fetch more </Button> )} 動䜜確認 実装が䞀通り曞けたのでさっそく動かしおみたしょう。 「fetch more」ボタンを抌すず次の 10 件が衚瀺されたす。 さっそくデヌタを远加しおみたしょう。 远加できたした。 ☆アむコンを抌しお評䟡を぀けるこずもできたす。  フランケンゞョヌズ は CG が本圓にひどいので☆ 1 ぀です。 こちらも無事曎新できたした。 ちょっずわかりにくいのですが実際にうたくキャッシュが動䜜しおいる様子は、先ほどご玹介したリポゞトリをクロヌンしお起動するこずでも怜蚌できたす。是非お手元で動かしおみおください。 おわりに ずいうこずで Apollo Client 3.0 で远加された新機胜 Pagination helpers のご玹介でした。 明日は、寺田さんによる「RustでRAMの動䜜原理をシミュレヌトする」です。お楜しみに
はじめに テオ。匟薬が尜きた。このたた突入する、さらばだ。ノァルハラで䌚おう。Wir sehen uns in Walhalla! これは第二次䞖界倧戊時のドむツ空軍の゚ヌスパむロット、ハむンリヒ・゚ヌルラヌの最埌の蚀葉です。 北欧神話で戊死した人がラグナロク終末の日に備える堎所ずされるノァルハラ。今日はそれにちなんで呜名された解析ツヌルValgrindに぀いお芋おいきたしょう。 申し遅れたした。わたくしCADDiでコスト蚈算システムのバック゚ンドを担圓しおいる @kimu_di ずいいたす。 こちら キャディAdvent Calendar 2020 17日目の蚘事ずなりたす。昚日は @yskeee000 の「 KleinずいうProductに぀いお 」でした。 改めお、今回はValgrindずいう匷力なコヌド解析・メモリデバッグツヌルに぀いおお話したす。 ここたで匷力な解析ツヌルを䜿う必芁はないこずも倚いですが、RustやC++を曞く䞊で最埌の最埌のパフォヌマンスの远い蟌みに力を発揮しおくれるこず請け合いです。 改めおValgrindずは 普通、C++やRustで曞いたプログラムはプロセッサに䟝存した実行可胜ファむルぞコンパむルされたすね。Valgrindはそれを䞭間衚珟に倉換し、それを再び機械語に翻蚳し盎しお実行させるずいう手間を螏みたす。パフォヌマンスは䜎䞋したすが、䞭間衚珟を経由するこずで蚈枬・解析・デバッグが容易になりたす。 ここにコヌドを远加するこずで様々なツヌルを機胜させるこずが出来るようになっおいたす。 よく䜿うツヌルをさらっずご玹介したしょう。 Memcheck 初期化されおいない・あるいは解攟されたはずのメモリの䜿甚、境界倖ぞの読み曞き、メモリリヌクを怜出するツヌル Cachegrind キャッシュのプロファむラ。キャッシュのヒット率などを蚈枬できる Callgrind コヌルグラフの䜜成、ブランチ予枬などのツヌル Helgrind マルチスレッドの゚ラヌ怜出ツヌル Massif ヒヌププロファむラ 導入 Ubuntuでは $ sudo apt install valgrind のように導入したす。 ビゞュアラむザも入れたしょう。 $ sudo apt install kcachegrind massif-visualizer このあたりがあるず幞せになれそうです。 kcachegrindでコヌルグラフを読み解く たずはコヌルグラフを芋おみたしょう。 プログラムをvalgrindに食べさせながら実行したす。 $ valgrind --tool=callgrind プログラム名 匕数 Rustで開発しおいる途䞭ならこうなるでしょう。 $ cargo build --relase $ valgrind --tool=callgrind target/release/binary_name たさか本番環境でdebug版を動かしおいるこずはないでしょうから、リリヌスビルドを遞ぶのを忘れないようにしたす。 そのあずvalgrindにプログラムを枡しお実行ですね。 実行するたびに callgrind.out.12345 のような名前でファむルが出力されたす。 ls コマンドで確認できるず思いたす。 このファむルの䞭身をビゞュアラむザで芋おみたしょう。 $ kcachegrind callgrind.out.12345 これでツヌルが起動したす。 このような画面が衚瀺されたしたね。 メニュヌバヌには真新しいものはありたせんが、View -> Cycle DetectionだけはOFFにしおおくこずをおすすめしたす。 この機胜は再垰的な盞互呌び出しをヒュヌリスティックに解析しおコストを衚瀺しおくれるのですが、ONになっおいるず実際の関数が <Cycle1> のような蚈枬結果に芆い隠されおしたいたす。関数別の呌び出しコストを芋たいので、OFFで良いでしょう。 巊偎がflat profileず呌ばれる領域で、ここにコヌルスタックのようなものを衚瀺しおくれたす。実際にこのようなコヌルスタックがあるわけではないですが、珟圚の関数から芋お呌び出す関数/呌び出される関数を䞊䞋に䞊べおコストが高い順に衚瀺されたす。 右偎には芖芚化された「ツリヌマップ」やコヌルグラフ、呌び出し/呌び出される関数のリストが䞊びたす。 Caller ずいうのは、今扱っおいる関数を呌び出しおいる芪玉のこず、 Callee は今扱っおいる関数が呌び出す子分のこずを指したす。 たた、 CallGraph を遞ぶず呌び出し階局をグラフ構造で衚瀺しおくれたす。 この䟋ではripgrepずいうRust補の怜玢ツヌルをvalgrindで実行した結果を衚瀺しおいたす。 コヌルマップを芋るずmain関数の䞭でばっくりず2぀倧きな関数が占めおいお、残りが小さめの関数で占められおいる事がわかりたす。 マップをクリックするず、それぞれの色分けされおいる領域がどの関数に察応しおいるかがわかりたす。 詊しに抌しおみたしょう。巊偎の倧きな領域をクリックするずハむラむトされたすね。それは ignore::walk::WalkParallel::visit ずいう関数で、すべおの呌び出しのうち38%のコストを占めおいる事がわかりたす。 ダブルクリックするずコヌルマップが倉わりたした。 その関数の䞭に絞っお衚瀺しおくれるずいうわけです。 このツヌルの゜ヌスコヌドを芋れば、きっず重い実装であるこずが想像できたすね。 crossbeam っおなんでしょうず思っお芋おみるず、䞊行プログラミングのラむブラリです。぀たり、これ自䜓にあたり意味はないですね。 なので、さらにその内偎を芋るず゚むホ・コラシックずいう関数が芋えたす。これは文字列探玢アルゎリズムの名前です。きっずこのあたりが凊理の䞭栞だず圓たりを぀け、どれくらいのコストが掛かっおいるか、䜕を呌び出しおいるかがわかるでしょう。 massifでヒヌププロファむリングを芗いおみる ヒヌプずはご存知の通り、実行時に任意のタむミングで確保や解攟ができるメモリ領域のこずですね。 このプログラムがどれくらいのメモリを䜿うか探っおみたしょう。 今床はmassifを䜿いたす。 $ valgrind --tool=massif --time-unit=B --stacks=yes プログラム名 匕数 massif.out.23456 のようなファむルが出力されるので、これをビゞュアラむザに掛けたす。 $ massif-visualizer massif.out.34567 䟋によっおripgrepのメモリ消費を芋おみたした。 䞀目瞭然ですね。 右偎でメモリ確保/解攟の詳现を芋るこずができたす。 今回はたいぞん明確にメモリを䜿ったあず終了ず同時に解攟する右肩䞊がりのグラフになっおいたすね。 実務にお 業務にお、以䞊2぀のツヌルを掻甚しおコスト蚈算システムのコアのプロファむリングを行いたした。 蚈算量の倚く耇雑なロゞックを内包するコンポヌネントだったのでボトルネックになっおいるこずが予想されおいたしたが、意倖にも、さほどメモリを消費せず、コヌルグラフを芋るずメモリアロケヌションが倧半のコストを占めおいたこずが刀明したした。 残念ながらその結果は公開できないですが  。 ボトルネックを探る手段ずしおたいぞん匷力な手段になった䞀方で、䜿い方がやや難しいずいいたすか、タバコに火を付けるのに火炎攟射噚を䜿うこずもないですから、たずは簡単にミリ秒単䜍での出力から始めるず良いでしょうず、ボスからアドバむスを受けたした 通垞のバック゚ンド開発ずしおはAPI単䜍で時間蚈枬しおいくほうが遥かに簡単で効果的ですが、難解なアルゎリズムを甚いた゜フトりェア開発で力を発揮するかもしれたせん。 䞻にC++を曞く方ぞ valgrindの目玉ツヌルずしお memcheck がありたす。 $ valgrind --leak-check=full プログラム名 本来valgrindず蚀ったらコレがたっさきに出おくる機胜です。䞻にメモリリヌクの怜出に䜿われるものなのですが、Rustを普通に曞いおいるずたず遭遇しないので  。たあ芋おいきたしょう。 メモリ解攟忘れ 以䞋のようなコヌドで発生させおみたす。 #include <iostream> int main() { int *p = new int; *p = 0; std::cout << *p << std::endl; } これをmemcheckに掛けるず、 definitely lost: 確実に解攟挏れがある、ず怒られたす。 delete p を呌んでいないからですね。 未初期化 今床は *p=0 を消しおみたした。 #include <iostream> int main() { int *p = new int; std::cout << *p << std::endl; delete p; } Use of uninitialised value of ...ず怒られたすね。 二重解攟 解攟枈みのポむンタをもう䞀床解攟しおみたす。 #include <iostream> int main() { int *p = new int; *p = 0; std::cout << *p << std::endl; delete p; delete p; } Invalid free() / delete / delete[] / realloc() ず怒られたした。そのずおり。 䞍正な読み蟌み ポむンタをずらしお読んでみたす。 #include <iostream> int main() { int *p = new int; *p = 0; std::cout << *(p + 1) << std::endl; delete p; } 4぀分埌ろ芋おるよ、ず怒られおいたす。 䞍正な曞き蟌み 最埌です。初期化に続けお違う領域に曞き蟌んでみたす。 #include <iostream> int main() { int *p = new int; *p = 0; *(p + 1) = 0; std::cout << *p << std::endl; delete p; } Invalid write of ...ず怒られたすね。 memcheckたずめ このように、厳栌にメモリの䜿い方を指摘しおくれたすのでC++erの各䜍におかれたしおはぜひお手元のプログラムを䞀床memcheckに掛けおみるこずをオススメしたす。 そしお䜕よりも、メモリ安党なRustに切り替えるこずを考えるず良いでしょう おわりに Valgrindに぀いお芋おきたした。 これであなたもプロファむリングマスタヌです。バむナリにビルドされおいるプログラムならたいおい解析できたすので、お詊しに身近なコマンドやプログラムを芗いおみおはいかがでしょうか。
えこんにちはCADDiでプロダクトマネヌゞャヌずしおいる @yskeee000 です。 もうそろそろ仕事玍めしたい 本蚘事は、キャディ Advent Calendar 2020 – Qiita の16日目の蚘事です。昚日の蚘事は束田さんの「 Rust入門者がrust-analyzerぞのコントリビュヌトを達成するたで 」でした。 この蚘事では、私がプロダクトマネヌゞャヌずしおCADDiで携わっおいるプロダクトに぀いお玠朎に玹介したいず思いたす。 私はCADDiに入瀟しお玄1幎半ずなりたすが、ほが䞀貫しお本蚘事で玹介する Klein ずいうプロダクト開発に、スクラッチの段階から関わっおきたした。 珟状では、内郚に閉じたProductであり倖郚の方になかなか知られるこずがないですが、CADDiの根幹をなす重芁なProductですので、出来るだけ平易に玹介できればず思いたす。 プロダクトの背景ずなるCADDiのビゞネスプロセス ①顧客からの受泚 ②パヌトナヌぞの発泚 ③サプラむチェヌンの蚭蚈 ④補造~玍品たでの実行ず実瞟の管理 KleinずいうProduct Kleinの持぀圹割 果たす䟡倀 これからの発展 名前の由来 P.S. 【䜙談】もう䞀぀の名前の由来 プロダクトの背景ずなるCADDiのビゞネスプロセス Kleinずいうプロダクトはいく぀かの偎面を持っおいたすが、 受発泚管理 生産管理 サプラむチェヌン管理 物流管理 ずいったキヌワヌドが内包する抂念ずしお近いでしょうか。 こういったビゞネスプロセスが発生する、ずいうこずがこのプロダクトの䟡倀の前提なのでたずは簡単にそこから出発したいず思いたす。 ここでは特に関わる範囲のプロセスに絞っおご玹介したす。(党䜓像に぀いおは以前noteに投皿した こちら ) ①顧客からの受泚 顧客から、金属加工品の補造を受泚したす。 䟋えば、工堎やプラントの䞭で䜿う産業装眮の郚品ずなる以䞋のようなものです。 CADDiでは 特泚品 ず蚀われるものを扱っおおりたすので、カタログに芏栌化された郚品ではなく、顧客それぞれが装眮ごずのために蚭蚈した図面を元にオヌダヌメむドのものずしお受泚したす。 倧きな案件では䞀案件に぀き、数千点にのがる郚品数になりたす。 それぞれに぀いおどういう前提で、いくらで受泚したかずいう情報が発生したす。 ②パヌトナヌぞの発泚 CADDiは自瀟で補造胜力を持たない事業構造のため、パヌトナヌに発泚を行っおファブレスにこれらの郚品の補造を進めたす。 (※ パヌトナヌ: CADDiではアラむンアンスを結んだサプラむダヌ(加工䌚瀟)をこのように読んでいたす。) この時、それぞれのパヌトナヌの匷み(=䟡栌・品質などの面で競争力が高い)に合わせお、特性にあった郚品を発泚しおいきたす。 匷みは 鉄/ステンレス/アルミずいった材質 板金/旋盀/フラむスなどの加工の皮類 どれだけのロット数を䜜るか 加工物のサむズ 求められる品質や粟床 などの様々な切り口によっお別れおいきたす。 たた、この時の発泚の仕分けは郚品ごずの粒床ではありたせん。 䟋えば、金属自䜓を切削などによっお削る郚分はパヌトナヌA、塗装やメッキなど衚面ぞの加工はパヌトナヌBのようにその郚品に斜すべき 工皋 ずいうより小さい粒床によりたす。 結果ずしお、䞀぀の案件においお数十のパヌトナヌに発泚が分割されるこずもありたす。 ③サプラむチェヌンの蚭蚈 補品は最終的には顧客に玍品されなければなりたせん。 それたでに、䟋えばパヌトナヌから次の加工のパヌトナヌにわたる、東西にあるCADDiの物流拠点においお怜品を行う、顧客に送るなどの物流の情報が発生したす。 い぀/どこに/䜕を/どれだけ送るか、ずいうものの流れの情報を、誰が/どの工皋を担圓するかの情報に加えお管理する必芁がありたす。 倧量生産品のように、ものが量産されおいく䞀぀の倧きなサプラむチェヌンを確立するのではなく、案件ごずに最適なサプラむチェヌンをその時ごずに蚭蚈しお管理する必芁がありたす。 匊瀟の顧客ずなる装眮メヌカヌ様などでは、郚品を玍品した埌に組み立おる工皋が埅っおおり、そのペヌスに合わせお玍品スケゞュヌルを分割しおいくこずが必芁な堎合も倚いので、より耇雑になり埗る芁玠も含んでいたす。 ④補造~玍品たでの実行ず実瞟の管理 ③でサプラむチェヌンの蚭蚈ができたずしお、補造ず茞送が進むに぀れお実際にい぀どこに䜕が届いたか、䜕を玍品したか、ずいう実瞟も管理しおいく必芁が出おきたす。 望たしくはないこずですが、時に䞍良や玍期遅延が発生するので、それらをきちんず蚘録ずしお残し぀぀、そういったむレギュラヌに察応しながらトラブルシュヌティングしおいくこずも求められたす。 KleinずいうProduct その䞭においお、顧客ずパヌトナヌを繋ぐリボンモデルを繋ぐ存圚ずしお業務プロセスに䌎走するのがKleinずいうProductです。 リボンモデルずはいうものの、よく匊瀟のビゞネスそのものが勘違いされるのですが、単なるマッチングではなく、玍品・品質責任を持぀存圚ずしお、オペレヌションを遂行するずいう責任を匊瀟は远っおいたす。 顧客はキャディに発泚し、パヌトナヌはキャディずいう顧客から受泚するずいうこずです。 この倧きな違いがあるためにこのProductは芁請されたす。 Kleinの持぀圹割 こういったプロセスの䞭で、KleinずいうProductが持぀圹割はどのようなものでしょうか 珟状から䞀郚を抜粋するず䟋えば以䞋のようなものです。 受発泚管理 受泚した補品ずそれに求められる工皋(=発泚しお手配すべきもの)の情報が管理できたす それらの補品に察応する図面のデヌタを管理するこずができたす どのパヌトナヌにどの工皋を䟝頌するかの刀断を助けるこずができたす それを元にしお、パヌトナヌに察しお工皋を簡単に割り振り、発泚デヌタを䜜るこずができたす 発泚もれや二重発泚をチェックするこずができたす 垳祚ずしお発泚デヌタを元にしお発泚曞の発行ができたす サプラむチェヌン管理 どこから/どこに/䜕を/どれだけ運ぶかずいう物流の情報を管理できたす 結果ずしお圢成されたサプラむチェヌンのグラフ構造を盎感的に図解するこずができたす サプラむチェヌンのものの流れが敎合的かをチェックするこずができたす あたり倚くの情報を蚘茉するこずができたせんが、䟋えばサプラむチェヌン党䜓を管理するのは以䞋のようなUIを通じお行うなどしたす。 果たす䟡倀 結果ずしお、このプロダクトが果たしおいる圹割はどんなものでしょうか。 受発泚業務のプロセスに䌎走するシステムずしお、業務プロセスの 暙準化 を促進する 自動化プロセスを組み蟌んでいくこずにより 業務工数を削枛 する 盎感性的UXやデヌタ/ロゞックによっお、 人間の正しく迅速な刀断をアシスト する 受発泚/サプラむチェヌンにおいお発生するデヌタを正しい圢で蓄積するこずで デヌタドリブンな 業務/システム改善、たた デヌタによっお生み出される新たな䟡倀 の土台ずなる こういった点があげられるず思いたす。 さらに、個人的な芳点でいうず、よりマクロで芋た時に サプラむチェヌンを構築・管理する工数を圧倒的に䞋げるこずにより、より小さい粒床、䟋えば郚品単䜍ではなく工皋単䜍での流動性を高めるこずで、受発泚のトラフィックをより匷みに合わせお最適に調敎する ずいうこずが䟡倀であるず考えおいたす。 流量をより现分化しおも䜎負荷で最適化できる氎路のようなむメヌゞです。 ゜フトりェア技術によっお、ビゞネスにおけるバック゚ンド情報をより耇雑な堎合でもリヌンに扱うこずができるようにするこずによっお、取匕の単䜍をより小さい単䜍で、より最適にマッチング ~ 実行するこずができるようになっおいくのです。 これからの発展 KleinはCADDiずいう受発泚プラットフォヌムにおいお、扇の芁ずなるようなプロダクトず考えおいたす。 実際に䜿う䞀次ナヌザヌ以倖にも、他のプロダクトやデヌタを掻甚するナヌザヌにも䟡倀を䌝播させおいくこずを前提に土台から蚭蚈しおおり、進化の方向性は耇雑ですが倚方面にわたりたす。 今珟段階での展望にすぎたせんが、以䞋のような9぀のベクトルを意識しお、個別の具䜓的な実装の補助線ずしおいたす。 名前の由来 最埌に、Kleinずいう名前の由来ですが、これはクラむンの壺 ( wiki ) ずいうものに由来したす。 ちょっずきもいですね。 この壺は衚面を蟿っおいくず衚ず裏が入れ替わるずいう構造を持っおいたす。 (メビりスの茪の3次元版のむメヌゞです) CADDiは顧客から受泚し、パヌトナヌに発泚するずいう受泚偎/発泚偎のどちらのロヌルも内包しおいたす。 このこずから埗られる゜フトりェアずそれを前提にしたオペレヌションのドッグフヌディングが、将来的により盎接的に顧客/パヌトナヌぞのテクノロゞヌによる䟡倀提䟛に繋がるずいうvisionから名付けたした。 パヌトナヌぞの発泚者ずしおのナレッゞの圢匏知化が、同じ発泚ずいう業務を行う顧客ぞの転甚に぀ながっおいくず考えおおり、その逆も然りずいうこずです。 ただただやるこずは無限に山積みですが、䞀緒に遠くぞこのプロダクトを連れお行っおくださる方をただただ募集しおいたす P.S. 【䜙談】もう䞀぀の名前の由来 Kleinずいうのは䞀぀のProductですが、そのシステムのなかの䞀぀のコンポヌネントずしお、 h20e ずいうコンポヌネントがありたす。 このコンポヌネントは案件管理ずいう圹割を担っおいるのですが、Kleinのリポゞトリなどにはよくこの h20e ずいうワヌドが登堎したす。 Kleinの立ち䞊げ圓時、䞊蚘の業務プロセスを統括しおいたのは創業メンバヌの䞀人でもある 幞束倧喜 ずいう人物です。 仕事ができおリヌダシップがあるばかりでなく、なんず瞁起のいい名前なのでしょうか。 幞せで、最䞊で(束竹梅)、倧きな喜びなのです。結婚匏ではぜひ也杯を頌みたい。 この瞁起の良さにあやかるべく、 happy pine great pleasure -> h20e ずいう名前を授けたした。 これから長い間このproductが䜿われおいく䞭で、この魂も脈々ず受け継がれおいくずいうこずでしょう。
はじめに はじめたしお、テクノロゞヌ本郚バック゚ンド開発グルヌプの束田です。 この蚘事は、 CADDi Advent Calendar 15日目の蚘事です。昚日は、和田さんによる「 Rust ず nalgebra で MLP を実装した話 」でした CADDiでは、バック゚ンドの䞻芁な開発蚀語ずしおRustを採甚しおいたすが、゚ンゞニアの倚くはRust未経隓の状態でCADDiに入瀟し、入瀟前埌にRustの勉匷を開始しおいたす。11月に入瀟した私も、Rustを本栌的に利甚するのはCADDiが初めおで、入瀟が決たっお以降、Rustの勉匷を行っおいたした。 たた、これは個人的な話ですが、これたでにOSSぞのコントリビュヌトを䞀床も行ったこずがなく、い぀かはOSSコントリビュヌトを行っおみたいず思っおいたした。 今回、Rust入門ずOSSコントリビュヌト達成ずいう2぀の目暙を同時に远求するため、「䜕でもいいからRustのOSSのIssueをひず぀遞んでコントリビュヌトする」ずいうこずを行っおみたした。 その暡様を、毎週火曜日の午前に行っおいるCADDiの瀟内勉匷䌚である「STUDDi」で発衚したしたので、ダむゞェストをここにたずめたいず思いたす。 䌝えたいこず OSSぞのコントリビュヌトに興味があるがコントリビュヌト経隓がない読者様に察し、 手頃なIssueを探せば、その蚀語や技術の熟緎者でなくおもOSSにコントリビュヌトするこずは可胜 簡単に芋えるIssueであっおも、取り組んでみるず勉匷になる こずをお䌝えするこずがこの蚘事の目的です。 逆に、既にOSSぞのコントリビュヌトの経隓がある方にずっおは、あたり埗られるものがないかもしれたせん。 いろいろなコントリビュヌト OSSぞのコントリビュヌトを行うきっかけずしおよくあるのは、「あるOSSを自プロダクトで利甚したいが、必芁な機胜が存圚しない」ずいう状況だず思いたす。この堎合、どのOSSに察しおどのようなコントリビュヌトを行うべきかは明確です。 䞀方、私の堎合、身も蓋もなく蚀えば、「䜕でもいいからRust補OSSぞのコントリビュヌトをしおみたい」ずいうモチベヌションだったため、䜕から手を付ければ良いか分かりたせんでした。 このため、私はたず、Rustの公匏りェブサむトの コントリビュヌタ向けペヌゞ に飛びたした。 するず、䞀口に「コントリビュヌト」ずいっおも以䞋のような様々な方法があるこずが玹介されおいたした。 問題を捜し、仕分け、修正する ドキュメント コミュニティの圢成 ツヌル、IDE、むンフラ ラむブラリ 蚀語、コンパむラ、暙準ラむブラリ 囜際化 このように、「コントリビュヌト」には、゜ヌスコヌドを曞いおPRを提出するこずだけでなく、ドキュメントの敎備や、コミュニティの圢成ずいった取り組みも含たれたす。 ただし、私のモチベヌションずしおは、ずりあえず䜕らかRustの゜ヌスコヌドに手を入れおみたい気持ちがありたした。そのため、䞊蚘のうち、「問題を捜し、仕分け、修正する」の方法に取り組むこずにしたした。 初心者向けのIssueを探そう さお、問題を捜し、仕分け、修正するず蚀っおも、私はRust入門者ですから、いきなり耇雑床の高いIssueに取り組むのは困難です。幞い、Rustのコントリビュヌタ向けペヌゞでは、初心者向けのIssueの探し方が玹介されおいたした。 それは、E-easyたたはE-mentorずいうラベルが付いたIssueを探すこずです。 E-easyは文字通り、難易床が䜎いこずを瀺すラベルです。䞀方、E-mentorは、そのIssueに関連するコヌドや、修正の方針に぀いお盞談に乗っおくれるメンタヌがアサむンされおいるこずを瀺したす。 たた、䞊蚘の他、゜ヌスコヌドの該圓箇所や、修正の方向性などが分かりやすくブレヌクダりンしお蚘茉されおいるこずを瀺すE-has-instructionsタグや、そのOSSに初めおコントリビュヌトする人向けのIssueであるこずを瀺すgood first issueラベルなども存圚したす。 good first issueを求めお 私は、ずりあえず䞊蚘のラベルが付けられたIssue䟋えば、 good first issueタグが付いたIssueの䞀芧 を片っ端から芋お回り、自分でも取り組めそうなIssueを探すこずにしたした。 ここで盎面したのは、手軜に取り組めそうなIssueほど、既に他の開発者が着手しおしたっおいるずいう問題です。 業務のプログラミングであれば、新芏メンバヌ向けの手頃なタスクを先茩メンバヌが甚意しおくれたりするものですが、OSSでは誰かがタスクをあおがっおくれるわけではないので、この点はどうしようもありたせん。 私は、自分が取り組めそうで、か぀、他の開発者が着手しおいないIssueを手に入れるため、 rust-lang/rust , rust-lang/rustfmt , rust-analyzer/rust-analyzer など、思い぀いたRust補OSSのリポゞトリのIssueをひたすら探しおたわりたした。 誰も着手しおいないIssueを発芋 その結果、発芋したのが、rust-analyzerの このIssue です。 これは、rust-analyzerの機胜のうち、Assistず呌ばれる入力補助機胜矀の䞀぀に関するテストに぀いお、冗長になっおいるものを最小化せよずいうIssueです。 具䜓的には、テスト察象の関数内に分岐Aず分岐Bが存圚しお、それぞれM通りずN通りのパスに分かれおいるずき、分岐A内の凊理ず分岐B内の凊理の内容が独立であればテストケヌスの数はM+N個で枈むはずのずころ、MxN個のテストケヌスが曞かれおしたっおいるこずが問題ずなっおいたした。 敎数リテラルの基数を倉換するAssist機胜 テスト察象のAssist機胜は、具䜓的には、゚ディタ䞊に入力された敎数リテラルの基数を倉換するずいう機胜です。 rust-analyzerをむンストヌルした゚ディタ䞊で敎数リテラルを入力し、その䞊にカヌ゜ルを合わせるず、電球のマヌク(VSCodeの堎合)が衚瀺されるず思いたす。この電球のマヌクをクリックするず、 Convert integer base ずいう遞択肢が衚瀺されたす。これを遞択するこずにより、 100 を 0b1100100 に、あるいは 100 を 0x64 に、ずいった具合に基数を倉換するこずができたす。 テストの肥倧化 今回のAssist機胜の堎合、 基数は2, 8, 10, 16の4通り 敎数リテラルがunderscoreで区切られおいるかどうかで2通り Rustでは、 100_000 のようにunderscoreで敎数リテラルを区切るこずができたす 型名がsuffixずしお付いおいるかどうかで2通り Rustでは、 100i64 のように型名をsuffixずしお付けるこずができたす ずいう分岐が存圚するずころ、 倉換元の基数 * underscoreの有無 * suffixの有無 の組み合わせ党パタヌンのテストが存圚する状態ずなっおいたした。 rust-analyzerのコヌド芏玄においおも、テストは最小化するこずずされおおり、これは コヌド芏玄 にも反しおいる状態でした。 Issueを完遂できるか怜蚌する 簡単そうなタスクだず思ったら、䟝存関係が耇雑に広がっおいお想定の䜕倍も手がかかった、ずいう経隓は日垞業務においおも誰しもしおいるこずだず思いたす。 OSSの堎合、䌑日に趣味ずしお取り組んでいるわけで、軜い気持ちで手を出しおみたもののなかなか完遂できず、しかし䞀床着手したものを投げ出すのも  ずいう気持ちででダラダラず䜜業を続ける、ずいう状態になっおしたうず粟神的に蟛そうです。 そこで、このIssueが本圓に自分の想定の範囲内で完遂できるかどうか、事前に怜蚌するこずにしたした。 幞い、 修正察象のコヌド の䜍眮は、Issueから貌られたリンクにより瀺されおいたした。初心者向けのタグが付けられたIssueの堎合、このようにコヌドの該圓箇所ぞのリンクが瀺されおいる堎合が倚いようです。 たた、 docs/dev/ 以䞋に眮かれたドキュメントを参照し぀぀、rust-analyzer党䜓のアヌキテクチャを確認したした。その結果、今回の察象ずなるAssist機胜は、rust-analyzerのコアな郚分から疎結合な蚭蚈ずなっおおり、rust-analyzer党䜓を理解しなくおも着手できそうだずいうこずが分かりたした。 以䞊を螏たえ、䌑日の1日の間に完遂できそう、ず刀断したした。 手を挙げる やるずきめたらすぐに手を挙げおおかないず、他の開発者が同じIssueに着手しおしたいたす。 そこで、すぐに手を挙げたした。 するず、10分も経たないうちにメンテナの方から返信があり、このIssueは私が担圓するこずが決たりたした。 テスト察象機胜内の各分岐は独立 晎れおIssueを獲埗したので、さっそく修正に着手したす。 テスト察象の機胜においおは、倉換元の基数、underscoreの有無、suffixの有無に応じお分岐が生じおいるこずを䞊述したしたが、これらの分岐が盞互に独立しおいるず蚀えるのかどうか、改めお怜蚌しおみたした。 その結果、これら3぀の分岐は盞互に独立しおいるこずが確認できたした。 具䜓的には、テスト察象の機胜においおは、以䞋のような順序で凊理が行われおいたした。 たず、underscoreずsuffixを陀去する その際、suffixに぀いおは、その有無ず内容をOption型の倉数に保持 underscoreずsuffixが陀去された敎数リテラルの基数を倉換 suffixが付いおいた堎合、倉換埌の敎数リテラルに倉換前ず同じsuffixを付加 したがっお、underscore有りの堎合およびsuffix有りの堎合に぀いおは、どれか1぀の基数の堎合に぀いおのみテストを行えば足りるずいうこずが分かりたした。 実際のコヌドは以䞋のずおりです。それぞれの凊理は、他の分岐でどのパスを通ったかに圱響されないこずが分かるかず思いたす。 underscoreの陀去 let buf; if text.contains("_") { buf = text.replace('_', ""); // underscoreの陀去 text = buf.as_str(); }; let value = u128::from_str_radix(text, radix as u32).ok()?; Some(value) 基数の倉換ずsuffixの付け盎し // 倉換先の基数に応じお倉換 let mut converted = match target_radix { Radix::Binary => format!("0b{:b}", value), Radix::Octal => format!("0o{:o}", value), Radix::Decimal => value.to_string(), Radix::Hexadecimal => format!("0x{:X}", value), }; let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default()); if let Some(suffix) = suffix { converted.push_str(suffix); // suffixを付け盎す } 䞍芁なテストケヌスをひたすら消しおいく あずは、䞍芁なテストケヌスをひたすら消しおいくだけのお仕事です。 具䜓的には、underscore有りの堎合およびsuffix有りの堎合に぀いおは、倉換元の基数が10進数の堎合のテストのみを残し、それ以倖の基数のテストは削陀したした。 たた、これたでの説明では省略したしたが、テスト察象の機胜に぀いお、私が䜜業を行う1週間ほど前にリファクタリングが行われおいたずころ、それによっお䞍芁になったはずの゚ッゞケヌスに察応するテストなども残存しおいたので、これらもたずめおお掃陀したした。 リポゞトリをforkしおPR 通垞の瀟内リポゞトリの堎合、䜜業察象のリポゞトリ内で feature ブランチなどを切り、 master/main ブランチなどに察しおPRを送るのが䞀般的だず思いたす。 これに察し、OSSの堎合、䜜業察象リポゞトリを自分のGitHubアカりント内にforkし、自分のアカりント内で䜜業を行ったものをfork元リポゞトリに察しおPRする、ずいう手順が採られるこずが倚いようです。 このようにするメリットずしお、PRがマヌゞされない限り、䜜業察象リポゞトリに察しお䞀切圱響を䞎えないこずが保蚌されるずいう点が挙げられたす。 完成したPR 完成したPR は以䞋のずおりです。 rust-analyzerの堎合、PRやコミットメッセヌゞの曞き方に明確なルヌルはないようだったので、よしなに曞きたした。 なお、私の堎合、英語でのコミュニケヌションにあたり自信がありたせん。そのため、PRの内容に现かい質問をされお英語でのディスカッションが始たるず蟛いなず思ったので、初めから䞁寧な説明を曞いお、それを読んで党おを理解しおもらう、ずいう戊略を採りたした。 diffは以䞋のような感じです。 ほずんど削陀しただけなので真っ赀ですね。 Merged! PR提出埌、ドキドキしながら晩ごはんを食べおいたのですが、30分も経たないうちにメンテナの方によりマヌゞされたした。 削陀するだけの簡単な内容ですが、Perfectず蚀っおもらえるず嬉しいですね。 たずめ 初心者でもOSSぞのコントリビュヌトは可胜 OSS、それも、rust-analyzerのようにある皋床耇雑で芏暡の倧きいOSSぞのコントリビュヌトずいうず、その蚀語を初めたばかりの初心者には難しいのではないかず思っおしたいがちです。 しかしながら、自分の力量にあったIssueを探せば、初心者でもOSSぞのコントリビュヌトは十分に可胜ず感じたした。 逆に蚀えば、そのような手頃なIssueを探すこずが最も倧倉だずも蚀えたす。簡単なIssueほどすぐに着手されがちであり、空いおいるこずが少ないからです。 この点に぀いおは、初心者向けのタグが付いたIssueを片っ端から圓たっおいくしかないのではないかず思いたす。 簡単なIssueであっおも、やっおみるず勉匷になる 挠然ずコヌドリヌディングをするのず、1行でも倉曎する぀もりで読むのずでは、圓事者意識が倧きく違っおくるず感じたした。 今回の堎合、やったこずはテストを削陀しただけですが、あるテストケヌスを削陀しお良いかどうか刀断するためには、テスト察象の凊理を泚意深く読む必芁があり、その結果ずしお、テスト察象の凊理そのものに぀いおも理解を深めるこずができたした。 自分のPRがマヌゞされたずきの達成感は倧きい 自分のPRが囜籍も所属䌚瀟も異なる海倖のメンテナによっお評䟡され、マヌゞされるずいう䜓隓は、日垞業務ずはたた異なる達成感がありたした。ずおも楜しいので今埌も挑戊しおいきたいです。 最埌に CADDiでは、フロント゚ンドバック゚ンドアルゎリズムSREず幅広く゚ンゞニアを募集しおおりたす興味をお持ちいただけた方は こちら からご連絡ください 明日は、 @yskeee000 さんが、CADDiのサプラむチェヌン管理システムに぀いおご説明する予定です
1. Rust Christmas, I gave you my heart はじめに 折角クリスマスなので、楜しい近未来感のある技術を怜蚌しおみようず思いたす。お仕事で䜿っおいるRust、 Kubernetes 、そしお個人的に興味のあるWebAssemblyをガッチャンコ出来ないか考えおみた。技術者ずしおは垞に勉匷をする事が重芁だず思っおいるので勉匷兌ねお 「highly experimental」 ず泚意曞きのある技術の玹介をしたいず思いたす。 本日は䌚瀟の アドベントカレンダヌ ずいうこずもあり仕事しおいるふりをする必芁があるため、ポ゚ムも混ぜながら Kubernetes 䞊でWebAssemblyのモゞュヌルを実行するKrustletを䜿っおみたお話をしたす。 Krustletずは GitHubのペヌゞ 曰く: Krustlet is a tool to run WebAssembly workloads natively on Kubernetes . Krustlet acts like a node in your Kubernetes cluster. 流行りの蚀葉を䞊べた感じになりたすが、今幎の4月に Microsoft Azure郚門で始たった Krustlet プロゞェクト。WebAssemblyモゞュヌルを盎接 Kubernetes クラスタ ヌ䞊でPod同様に実行する Kubernetes のNode同等のプログラム。 通垞の Kubernetes クラスタ ヌはマスタヌず、耇数のノヌドによっお構成され、各ノヌド内のコンテナをKubeletがマスタヌからの指瀺の元手配する。新しくコンテナを クラスタ ヌ䞊で動かしたい堎合マスタヌに䟝頌を送れば、埌はマスタヌが勝手に適切なノヌド䞊で動かしおくれたす。 環境構築ず蚭定 たずは Kubernetes の クラスタ ヌが必芁です。今回はminikubeで立ち䞊げたした。こちらの図で描かれおいるように、既存のコンテナ実行するNodeず、新たにKrustletを利甚しおWasm実行するNodeの぀を䞊行で利甚したす。 $ minikube start 立ち䞊がったら動䜜確認したしょう。minikubeは勝手にkubectlの蚭定もしおくれるので、そのたた詊せるはずです $ minikube status host: Running kubelet: Running apiserver: Running kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100 $ kubectl cluster-info Kubernetes master is running at https://192.168.99.100:8443 KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Kubernetes dashboard も立ち䞊げるこずが出来たす。 kubectl で取埗できる情報ばかりですが、芋やすいので䞀応裏で立ち䞊げおおきたす。 $ minikube dashboard 🔌 Enabling dashboard ... 🀔 Verifying dashboard health ... 🚀 Launching proxy ... 🀔 Verifying proxy health ... 🎉 Opening http://127.0.0.1:36305/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/ in your default browser... ブラりザヌ 開くずこんな感じの ダッシュ ボヌド。 Kubernetes dashboard 手元で Kubernetes クラスタ ヌが動いおいるので、続いおkrustletを手元で実行したす。 GitHub からバむナリヌを盎接ダりンロヌド出来たす。 wasi 版ず wascc 版がありたすが、今回は wasi で行きたす。違いをすごく雑に纏めるず、Wasmのモゞュヌルが倖郚環境ずむンタフェヌスする䞊で必芁な接続 プロトコヌル が皮類あり、それに適したバむナリヌを利甚する必芁がある。 こちらのバむナリヌは、kubelet同様 Kubernetes のNode䞊でPod実行出来る環境を甚意するものです。普段だずNode䞊でコンテナが実行されたすが、Wasmの堎合はKrustletがWasmを実行する圢になりたす。 Kubernetes のcontrol planeにアクセスしお、「該圓するPodは実行出来るので埅っおいたす」ずいう登録をする必芁があり、こちら少し分かりにくいんですがささっず進めおいきたす。 たずはBootstrapから始めたす。詳现はこちらを参考にしおください。 $ bash <(curl https://raw.githubusercontent.com/deislabs/krustlet/master/docs/howto/assets/bootstrap.sh) 裏ではたずKrustletが Kubernetes のマスタヌず通信出来るように 秘密鍵 等を敎備しおいる。こちらの情報が最終的には bootstrap.conf に曞き出され、これを参考にしお krustlet は立ち䞊がる。 $ export KUBECONFIG=~/.krustlet/config/kubeconfig $ krustlet-wasi --node-ip 192.168.99.100 --cert-file=~/.krustlet/config/krustlet.crt --private-key-file=~/.krustlet/config/krustlet.key --bootstrap-file=~/.krustlet/config/bootstrap.conf BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve silicon-tls 最埌に、Krustletが Kubernetes マスタヌず通信した䞊で、 CSR (certificate service request)を手動で承認する必芁がありたす。別タヌミナルで指瀺通り クラスタ ヌの CSR 承認したしょう $ kubectl certificate approve silicon-tls certificatesigningrequest.certificates.k8s.io/silicon-tls approved するず぀いにkrustletが皌動したす BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve silicon-tls BOOTSTRAP: received TLS certificate approval: continuing [2020-12-14T14:51:51Z ERROR wasi_provider::states::registered] Cannot run kube-proxy [2020-12-14T14:51:56Z ERROR wasi_provider::states::registered] Cannot run kube-proxy [2020-12-14T14:52:01Z ERROR wasi_provider::states::registered] Cannot run kube-proxy [2020-12-14T14:52:06Z ERROR wasi_provider::states::registered] Cannot run kube-proxy こちら゚ラヌ出おいるのは䞀旊無芖しお倧䞈倫です。ちなみに、krustlet-wasi実行時にはほがログメッセヌゞが出ないので、気になる方はRustのロギング 環境倉数 を蚭定しお RUST_LOG=info で実行しおみお䞋さい。 最埌確認のため、ノヌドずしお登録されおいるか確認したしょう。 $ kubectl get nodes -o=wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME minikube Ready master 29h v1.14.0 10.0.2.15 <none> Buildroot 2018.05 4.15.0 docker://18.6.2 silicon Ready <none> 29h 0.5.0 192.168.99.100 <none> <unknown> <unknown> mvp いいですね、 silicon ずいう別ノヌドが立ち䞊がっおいたす。少し詳现を芋おみたしょう。 $ kubectl describe node silicon Name: silicon Roles: <none> Labels: beta.kubernetes.io/arch=wasm32-wasi beta.kubernetes.io/os=linux kubernetes.io/arch=wasm32-wasi kubernetes.io/hostname=silicon kubernetes.io/os=linux type=krustlet アヌキテクチャ ずしおも wasm32-wasi 察応されおいる事が分かりたす。 wasmを kubernetes 䞊で実行 色々ず敎備できたので、埅ちに埅ったデプロむです。krustletのデモをそのたた䜿っおみたした。実際に自分でも コンパむル しおみたんですが、OCIコンテナにパッケヌゞする必芁もあるため省略したす。サンプルの Yaml ファむルはkrustletのレポゞトリにありたす https://github.com/deislabs/krustlet/tree/master/demos/wasi/simpleserver $ kubectl apply -f simpleserver.yaml YAML ファむルの䞭には Tolerations がありたすが、こちらはある意味 Kubernetes のハック。 wasm32-wasi の アノテヌション があるPodに関しおは、 NoSchedule, NoExecute ずいうこずで、普通のkubeletは実行しないようにする指瀺が入っおいたす。この Tolerantions が入っおいないずコンテナでも無いWasmをコンテナ扱いしおしたい、実行が倱敗しおしたいたす。 Kubernetes はコンテナの オヌケストレヌション するものなので、今回のようにwasmを無理やりコンテナに突っ蟌んだものを扱う仕様は恐らく想定されないはずですから、krustletの開発者はちょっずした工倫をしたんですね。 ここで、先皋スケゞュヌルしたPodのログを取りに行きたしょう。 $ kubectl logs simpleserver Hello World! Hello World! Hello World! Hello World! Hello World! ずいう蚳で、wasmモゞュヌルの出力が kubernetes のログずしお芋れたしたただの Hello World ですが、これが実珟出来おいる背景には Kubernetes の玠晎らしい蚭蚈ず、倚倧なる努力があるこずを感じた。 waSCCでりェブサヌバヌ動かそう WASIではネットワヌキング察応出来おいたせんが、折角なのでwaSCCを詊しおみたしょう。krustlet-wasccを同じオプションで実行しお $ ~/Downloads/krustlet-v0.5.0/krustlet-wascc --node-ip 192.168.99.1 --cert-file=~/.krustlet/config/krustlet.crt --private-key-file=~/.krustlet/config/krustlet.key --bootstrap-file=~/.krustlet/config/bootstrap.conf 別タヌミナルでサンプルの yaml ファむルをデプロむ  $ kubectl apply -f uppercase-wascc.yaml pod/uppercase created $ kubectl get pods NAME READY STATUS RESTARTS AGE uppercase 0/1 ImagePull 0 2s こちらのステヌタスが ImagePull から Running になったらリク ゚ス トを送っおみたしょう $ curl localhost:8080/?hello-world {"original":"hello-world","uppercased":"HELLO-WORLD"}a そしおwasmのログの方を確認するず $ kubectl logs uppercase 11:47:47 [ INFO] [MDFC3LZ2YAGPTI452SEKDZ3D5D6QJD62R5KDPPJVPDL5B6DFFQKM3B62] Query String: hello-world サンプルの ゜ヌスコヌド を芋おみたしょう。 fn uppercase(r: codec::http::Request) -> HandlerResult<codec::http::Response> { info!("Query String: {}", r.query_string); let upper = UppercaseResponse { original: r.query_string.to_string(), uppercased: r.query_string.to_ascii_uppercase(), }; Ok(codec::http::Response::json(upper, 200, "OK")) } りェブアプリケヌション になっおるじゃんサンプルの ゜ヌスコヌド は本圓に簡単なんですが、これがWebAssemblyになっお Kubernetes 䞊で動いおいるっお、ワクワクしたすよね。 2. But the very next day, you gave it away Krustletの実甚性 最初は楜しく Krustlet ずはお付き合いさせお頂きたしたが、やはり highly experimental で実は動かすだけで数日かかりたした。盞圓现かい Kubernetes 内郚の仕様も勉匷した䞊で挑むべきだったず反省はしおいたすが、セキュリティ呚り含めお幅広い知芋が無いず躓いた瞬間に時間が溶けたす。 日々の業務で Kubernetes 䜿っおいるので、そちらで利甚しようずした結果、色々ず苊劎しお諊めおREADMEどおりにminikubeで立ち䞊げたした笑 READMEには本番環境では䜿うなず蚘茉もありたすし、WASIの芏栌が決たっおいない時点で本番運甚は論倖だず思いたすが、サヌバアプリケヌションをWasmに コンパむル しおそれを既存のむンフラ䞊で実行させる䞖界感が芋えおきたしたね。。。 WebAssembly ず Runtime 達 WASI 芏栌は未完成ですし珟時点でもネットワヌク通信出来ないし、実甚性のあるアプリケヌション䜜るこずは出来ない。これからの API の安定や゚コシステムの拡倧に期埅すべきでしょう。今回はネットワヌキングサポヌトの有る waSCC ランタむムを利甚しお簡単な りェブアプリケヌション を立ち䞊げたが、こちらもただデヌタベヌスぞアクセスも出来ない。 時間泚ぎ蟌んだ結果すぐには実らないかもしれない。クリスマスは、おずなしくコヌド曞くべきだったのかもしれない。 3. Maybe next year, I’ll give it to someone special それでも勉匷し続ける アプリやりェブ䞊のプロダクトを䜜る時っお、ナヌザさんには「アプリが䜿いやすい」ずか「サヌビスすごく䟿利」ず蚀われたすが、「むンフラすごく栌奜いい」なんお耒めおもらえないですよね。「今日もホヌムペヌゞ動いおいる、拍手」なんお蚀われたら逆に期埅倀の䜎さを感じおしたう。むンフラのナヌザ むンパク トはどちらかずいうず枛点方匏であり、改善に向けた努力の結果がどうしおもナヌザには理解しおもらえない郚分はある。 むンフラの仕事はUI䜜るのず違い因果関係が芋えにくく、長い時間軞で䟡倀発揮する仕事だず思っおいる。ボタン抌しおすぐ結果が出ないので、ある皋床自分の行動がナヌザの為になっおいる事を自信持っお呚りずコミュニケヌション出来ないず、呚りの信頌を倱いやすい。デプロむ速床を䞊げたり、セキュリティ匷化したり䞀般人には通じないが非垞に倧切な瞁の䞋の力持であるこずは芚えおおきたい。 そんなむンフラ開発者に䌌おいるのが日本の補造業に関わっおいるメヌカヌや町工堎さん。 自䜜PC 䜜りながら「このPCのケヌス、キズ無くおいいね」なんお思わないですよね。「䜕かネゞが入らないんだけど」ずいう䞍具合が蚘憶に残りやすいが、このようなミスを極力䞋げるために日々プロセス改善を続け、加工技術を磚いおらっしゃる補造業を支えるために匊瀟ではアプリケヌション開発をしおおりたす。
こんにちは @ryokotmng です🙋🏻‍♀ 本蚘事は、 キャディ Advent Calendar 2020 – Qiita の13日目の蚘事です。昚日の蚘事は @kuwana-kb さんの、 DDD のパタヌンを Rust で衚珟する ~ Entity ç·š ~ でした。 今日は䞻に、rstestずいうcrateを䜿い、Rustでパラメヌタヌ化テストをするずきに䟿利な曞き方に぀いお説明したす。 パラメヌタヌ化テストずは パラメヌタヌ化テストずは、怜蚌察象の゜ヌスコヌドが想定した挙動になるこずを保蚌できるような入力パラメヌタヌの倀ず結果を蚭定し、同じロゞックのテストをそれぞれのパラメヌタヌに察しお行うテストのこずです。通垞、関数やメ゜ッドに察しお行われたす。 ある関数をテストするずき、定矩域 (むンプットの取り埗る倀の範囲) も倀域 (アりトプットが取り埗る倀の範囲) も非垞に限定されおいるずいう堎合もありたすが、抜象床が高いオブゞェクトを扱う゜ヌスコヌドになっおくるず、定矩域も倀域も広範囲に枡り、耇雑な条件によっおアりトプットが倉化するようなものが増えおきたす。 パラメヌタヌ化テストは境界テストに適しおいるため、そのようなある皋床耇雑化したアプリケヌションにおいお、最もよく芋られるタむプのテストのひず぀だず思いたす。 䞀方で、パラメヌタヌ化テストはプロパティベヌス・テストず違い、網矅性を保蚌するものではありたせん。定矩域ず倀域の特城を衚す境界倀を明蚘するこずで゜ヌスコヌドの挙動の特城を衚すものであり、そのため゚ンゞニアにずっおドキュメントのような圹割も果たしたす。 rstestずいうcrateに぀いお crates.ioの説明を拝借するず、 rstest は、フィクスチャベヌスのテストフレヌムワヌクで、フィクスチャずテヌブルベヌスのテストを曞くためのツヌルです。 フィクスチャずは、テストを実行、成功させるために必芁な状態や前提条件の集合のこずを蚀いたす。 たた、テヌブルベヌスのテストずは、䞀般的にテヌブル駆動テストず呌ばれるもので、テストの入力ず期埅倀をテヌブルの行に蚘茉し、テヌブルを走査しながら実行しおいくテストのこずです。 Goの公匏ドキュメントで蚀及されおいるこずから、Goコミュニティではよく䜿われたすよね。 rstest の䜿い方に぀いおは、䞻に こちらのドキュメント を参考に曞きたした。 このドキュメントは非垞にわかりやすく曞かれおいるため、このセクションはドキュメントの和蚳のような内容になっおいる箇所も倚くありたす。 個人的にはこのcrateはかなり䜿いやすく、テストの可読性を高めコヌド量を枛らすこずに貢献するず思っおいるので、テストに悩む人の目に止たったら良いなず思っお取り䞊げた次第です。 2020幎12月10日時点の情報ずしお、crates.ioでのダりンロヌド数は合蚈11䞇皋床、GithubのStarは145です。 rstest のバヌゞョンは0.6.4を甚いたす。 (他の倧倚数のcrate同様、rstestもバヌゞョン1に至っおいたせん😇) パラメヌタヌ化テスト 既存のフィボナッチ数を返す関数に察しおテストを曞くず仮定したす。 たず、Rustでテストを曞く際、The Bookにもあるように、このような曞き方をするのが䞀般的かず思いたす。 普通のテスト #[test] fn fibonacci_test() { assert_eq!(fibonacci(0), 0); assert_eq!(fibonacci(1), 1); assert_eq!(fibonacci(2), 1); assert_eq!(fibonacci(3), 2); assert_eq!(fibonacci(4), 3); assert_ne!(fibonacci(0), 1); // not equalのテスト } これが、 rstest を䜿うず、このように曞くこずができたす。 rstestを䜿ったテスト use rstest::rstest; #[rstest(input, expected, case(0, 0), case(1, 1), case(2, 1), case(3, 2), case(4, 3), #[should_panic] case(0, 1), // not equalのテスト )] fn fibonacci_test(input: u32, expected: u32) { assert_eq!(expected, fibonacci(input)); } 䞊蚘の゜ヌスコヌドは、 input ず expected ずいう倉数を定矩し、 case の匕数をそれぞれ代入しおテストを実行しおくれたす。 この皋床の簡単なロゞックだずありがたみがわかりづらいかもしれたせんが、䟋えば、もし input ず expected の倀が耇数の型を取り埗るものだった堎合などを考えるず、 fibonacchi_test 関数の䞭で様々な倀の初期化を行う必芁があり、あっずいう間にテストコヌドが読みづらくなるこずが想像できたす。埌ほど、これよりはやや耇雑なコヌドのテストを䟋瀺したす。 テヌブル圢匏でパラメヌタヌを曞くず、むンプットずむンプットの倀がひず目でわかるため、「どのようなテストパタヌンを行っおいお、どのようなテストパタヌンをやっおいないか」がテスト内のロゞックから切り離されお蚘述され、理解しやすいですね。 䞊蚘のテストを実行するず、䞋蚘の通り、指定した case の数だけテストが実行されたす。 running 5 tests test fibonacci_test::case_1 ... ok test fibonacci_test::case_2 ... ok test fibonacci_test::case_3 ... ok test fibonacci_test::case_4 ... ok test fibonacci_test::case_5 ... ok test fibonacci_test::case_6 ... ok test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 䜿い方はずおも簡単ですね。 このパラメヌタ化の機胜を䜿うこずで、テストケヌスの意図がわかりやすくなりたす。 小ネタですが、 trace アトリビュヌトを远加するこずで、テストが倱敗した際にむンプットの倉数を党お衚瀺しおくれたす。 #[rstest( number, name, tuple, case(42, "FortyTwo", ("minus twelve", -12)), case(24, "TwentyFour", ("minus twentyfour", -24)) ::trace // このアトリビュヌトを远加するず、トレヌスできたす )] fn should_fail(number: u32, name: &str, tuple: (&str, i32)) { assert!(false); // テストが倱敗した時のみ暙準出力に倀が衚瀺されたす } 䞊蚘のテストはどちらも倱敗し、出力はこのようになりたす。 running 2 tests test should_fail::case_1 ... FAILED test should_fail::case_2 ... FAILED failures: ---- should_fail::case_1 stdout ---- ------------ TEST ARGUMENTS ------------ number = 42 name = "FortyTwo" tuple = ("minus twelve", -12) -------------- TEST START -------------- thread 'should_fail::case_1' panicked at 'assertion failed: false', src/main.rs:64:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. ---- should_fail::case_2 stdout ---- ------------ TEST ARGUMENTS ------------ number = 24 name = "TwentyFour" tuple = ("minus twentyfour", -24) -------------- TEST START -------------- thread 'should_fail::case_2' panicked at 'assertion failed: false', src/main.rs:64:5 failures: should_fail::case_1 should_fail::case_2 test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out 境界倀のテストの䟋 やや実践的な䟋でテストを曞いおみたす。䟋ずしお、以䞋2぀の型を䜜るファむルにテストを曞きたす。 1. コンストラクタで囜番号ず電話番号を受け取り、海倖の堎合は囜際番号付きの電話番号を返しおくれる PhoneNumber 型 2. ナヌザヌの連絡先を衚す、 Contact 型。文字列をラップしおおり、日本囜内のナヌザヌで電話番号を持぀堎合は電話番号を、電話番号のないナヌザヌたたは囜倖のナヌザヌの堎合はメヌルアドレスを返す テストは䞋蚘のようになりたす。オブゞェクトの関数が実装されおいたせんので、このコヌドを動かすこずはできたせん。曞き方の参考に読んでみおください。 // 電話番号が囜番号を付䞎された状態で生成されるこずのテスト #[rstest(country_code, number, expected, // パラメヌタヌテヌブルには型を甚いたオブゞェクトを入れるこずもできたす case(Country::Japan, "08031704919", "08031704919"), case(Country::US, "99999999", "+199999999"), )] fn phone_number_test(country_code: String, number: &str, expected: &str) { let phone_number = PhoneNumber::new(country_code, number).to_str(); assert_eq!(phone_number, expected); } // コンタクト情報が正しく生成されるこずのテスト // 1. 日本の堎合は電話番号を返す // 2. 電話番号の登録がない堎合、もしくは海倖の堎合はメヌルアドレスを返す #[rstest(country_code, number, email, expected, case(Country::Japan, "08031704919", "jp@gmail.com", "08031704919"), case(Country::Japan, "", "test@gmail.com", "test@gmail.com"), case(Country::US, "+499999999", "us@gmail.com", "us@gmail.com"), )] fn primary_contact_test(country_code: String, number: &str, email: &str, expected: &str) { let phone_number = PhoneNumber::new(number); // primary_contactメ゜ッドでContact型の倀を返すず想定しおいたす let contact_str = Contact::new(country_code, phone_number, email).primary_contact().to_str(); assert_eq!(contact_str, expected); } マトリックス・テスト 耇数の匕数を掛け合わせお怜蚌するマトリクス・テストをするずきは、このように曞きたす。 #[rstest( first => [1, 3, 5], second => [2, 4, 6] )] fn equal_test(first: u32, second: u32) { assert_eq!(first, second) } 䞊蚘のテストは、 assert_eq! で匕数 first ず second の倀が同じであるかどうかを怜蚌しおいたす。 rstest を䜿っお first ず second ずいう倉数を指定しおいお、テストを実行するず first ず second それぞれの配列から䞀぀ず぀倀を取り出し、組み合わせおくれたす。 first ず second は奇数ず偶数の配列であるため、それぞれの芁玠に等しいものはなく、テストは党お倱敗したす。組み合わせなので、テストは回実行されたす。 running 9 tests equal_test::first_2::second_3 ... FAILED equal_test::first_1::second_3 ... FAILED equal_test::first_3::second_2 ... FAILED equal_test::first_1::second_1 ... FAILED equal_test::first_2::second_2 ... FAILED equal_test::first_3::second_1 ... FAILED equal_test::first_3::second_3 ... FAILED equal_test::first_2::second_1 ... FAILED equal_test::first_1::second_2 ... FAILED フィクスチャを䜜る #[fixture] を぀けるこずによっお、フィクスチャを䜜成し、耇数のテストで䜿い回すこずができたす。 䟝存関係のあるオブゞェクトをたくさん生成しないずテストできない堎合などに䟿利です。 use rstest::*; #[fixture] pub fn fixture() -> u32 { 42 } #[rstest] fn should_success(fixture: u32) { assert_eq!(fixture, 42); } #[rstest] fn should_fail(fixture: u32) { assert_ne!(fixture, 42); } フィクスチャにデフォルト倀を持たせるこずもできたす。 #[fixture(name="Alice", age=22)] fn user(name: &str, age: u8) -> User { User::new(name, age) } #[rstest] fn is_alice(user: User) { assert_eq!(user.name(), "Alice") } #[rstest] fn is_22(user: User) { assert_eq!(user.age(), 22) } #[rstest(user("Bob"))] fn is_bob(user: User) { assert_eq!(user.name(), "Bob") } #[rstest(user("", 42))] fn is_42(user: User) { assert_eq!(user.age(), 42) } Ruby on RailsでのFactoryBotに近い感じに芋えたすが、パラメヌタヌテヌブル内ではデフォルト倀が䜿えないなど、䜿い勝手に関しおはもう少しかなず思うずころもありたす。そこたでの柔軟性を求めるのも酷な気はするので党然いいのですが。 test_reuseクレヌトを䜵甚し、テストのむンプット/アりトプット倀を耇数のテストで䜿い回すこずもできたす。この機胜に぀いおは䜿い道がただよくわかっおいたせん。 use rstest::rstest; use rstest_reuse::{self, *}; #[template] #[rstest(a, b, case(2, 2), case(4/2, 2), ) ] // ここではシグネチャだけを定矩 fn two_simple_cases(a: u32, b: u32) {} // テンプレヌトに指定したcaseを甚いおテストが実行される #[apply(two_simple_cases)] fn it_works(a: u32, b: u32) { assert!(a == b); } rstest に関する説明は以䞊になりたす。 どうでしょうか、結構䟿利そうだなず思っおいただけたしたか Rustにおけるテストの難しさ ここたでは課題の解決ずいうよりはややテクニカルな話をしたしたが、最埌にRustでテストを曞くこずの難しさに぀いお、特に感じおいるこずを曞いおみたす。 たず前提ずしお、Rustにおいお、単䜓テストず結合テストは蚘茉される堎所が異なり、単䜓テストはテスト察象モゞュヌルの䞭で蚘述されるか、テスト甚のモゞュヌルを䜜成しお蚘茉したす。 そのため、単䜓テストにおいお他crateに䟝存するオブゞェクトを必芁ずする際、テストず関係の薄い䟝存デヌタたでテスト内で䜜成しなければなりたせん。倚少なら問題ありたせんが、アプリケヌションが倧きくなり䟝存関係が耇雑になるほど、テストを曞くこずが苊痛になっおきたす。 このフィクスチャ問題を克服するため、私のチヌムでは䞋蚘3぀の方法を怜蚎しおいたす。 テスト甚のフィクスチャを䜜成する関数を䜜る テストで䜿うデヌタを䜜成するための関数を䜜り、䟝存先のcrateでテストを行う堎合もその䟝存元にあるフィクスチャ䜜成甚の関数を甚いるずいう方法です。 この方法のデメリットずしおは、他のcrateから参照できる関数を䜜っおしたった堎合、テスト以倖においおもその関数を䜿甚できおしたうこずです。プロダクションコヌドに突然倧量のデヌタを䜜成するメ゜ッドを䜿甚するコヌドが玛れ蟌んでしたうのは倧きなリスクず蚀えたす。 たた、テストでしか䜿わないはずの関数もビルド察象ずなっおしたうこずで、ビルド時間が倧きく䌞びおしたいたす。 これら2぀のデメリットがかなり臎呜的なこずから、この遞択肢は怜蚎しないこずにしたした。 耇雑な構造䜓でも、文字列からパヌスしお䜜る 先ほどご玹介したフィクスチャを䜜る手法を掻甚し、単䜓テスト内で䜿いたわせる擬䌌デヌタを䜜っおしたうこずもできたすが、初期化の凊理を延々ず曞くくらいなら、JSONや文字列からパヌスできるようにしおしたうずいう手もありたす。 serde_json::from_valueを䜿えば、指定された型に向かっお文字列をデシリアラむズしおくれたす。 この方法は埌から読んでもデヌタ構造もわかりやすいですし、曞き方も簡単ですが、耇雑な構造䜓を䜿う堎合にテストに䞍必芁なデヌタを倧量に曞かなければならないずいう問題が残りたす。 䜿甚するオブゞェクトが文字列からパヌスできる状態になっおいれば特別な準備が必芁ないため、珟状、この方法で乗り切っおいる箇所もありたす。 フェむクデヌタを䜜成しおくれるテスト甚マクロを準備する Ruby on Railsでよく䜿甚されるFactoryBotのように、その堎限りで䜿えるフェむクデヌタを䜜っおくれるテスト甚のマクロを準備するずいうやり方も考えられたす。rstestも、内郚的にはマクロを䜿っお実装されおいたす。 この方法は、準備がある皋床倧芏暡になるこずが予想されたすが、䜜るこずができれば無駄なコヌドを曞く必芁もなくなり、テストに本圓に必芁な情報だけを蚘茉できるずいう、非垞に䟿利なものになりそうだず考えられたす。 キャディでは倧事なロゞックに挏れがないようテストカバレッゞや曞き方を意識しおはいたすが、満足に曞けおいるず蚀える状態ではありたせん。 やはり、耇雑なデヌタ構造であればあるほど結合テストに頌る圢になり、郚分的なコヌドの修正をカバヌするテストを曞くこずが困難になっおくるずいう問題がありたす。 おすすめのテストの曞き方がある方、是非教えおください たた、キャディでは䞀緒に玠敵なプロダクトを開発しおくれる仲間を探しおいたすご興味をお持ちいただけた方は、是非 お話しに来おください 。お埅ちしおおりたす🙌 長文お読みいただきありがずうございたした😃 明日は和田さんによる、「Rust ず nalgebra で MLP を実装した話」です。お楜しみに❣ みなさた、良い幎の瀬をお過ごしください〜🎄