TECH PLAY

株式会社RevComm

株式会社RevComm の技術ブログ

174

はじめまして。フロントエンドエンジニアをしている伊藤と申します。 この記事では、チームで週に1回開催しているペアプロという名の技術共有会について、お届けします。 技術共有の中身や始めたきっかけなどをお伝えできればと思っています。 目次 目次 ペアプロとは ゆるーいペアプロとは スケジュール 事前準備 活動内容 Vue Fes Japan 2025 composition 化観察 開発しているプロジェクトの実装相談 記事を紹介するなど技術共有 きっかけ まとめ ペアプロとは まず、記事の中身に触れる前に、本来のペアプロのやり方について記載します。 AI による説明は以下です。 「ペアプロ」とは、2人のプログラマーが1台のコンピューターを共有し、協力してコードを作成する開発手法です。 具体的には、実際にコードを書く「ドライバー」と、コードをチェックしながら助言する「ナビゲーター」の役割を、定期的に交代しながら進めます。 ペアプロのイメージは、黙々とコードを書いていき、指摘された箇所を修正する真面目な印象が強いです。 本来のペアプロを実施すると、私を初め苦手意識を持つ人もいるかもしれません。 ゆるーいペアプロとは 私のチームが行なっているペアプロは、かなりゆるいです。 技術共有会だとイマイチなネーミングだと思い、なんとなくペアプロと名乗っています。 チームメンバーが集まり、雑談を交えつつ実装した箇所の説明やA,Bどちらのパターンで実装すべきかなどを話します。 コーディングの時間よりも、話している時間の方が多いと思います。 スケジュール 週に1回の頻度で行っています。時間は1時間くらいです。 決まって開催する曜日はなく、週の初めに各メンバーの予定を見て、日程を組みます。 大型連休や予定が多い場合などはスキップする場合もあります。 2023/12/14 から始まり、まもなく100回目を迎えようとしています🎉🎉🎉 事前準備 週の初めに、各メンバーの予定を見て1時間のミーティングをセットします。 話したいことを書くためのメモを用意し、共有します。 各メンバーがメモの中に、議題を書いていきます。 下記は議題の参考例です。 Vuex から Pinia への移行 カスタムバリデーションをどのファイルに書くか AI 使用時・不使用時の作業時間比較 コードをシェアするときは、「Visual Studio Live Share」を使いました。 visualstudio.microsoft.com 活動内容 議題に対してメンバーが意見を出し合いながら、コードを見て実装等をしていきます。 過去に議題に上がった主なものをご紹介します。 Vue Fes Japan 2025 10 月末にチームで参加したVue Fes Japanについて話し合いました。 どのセッションを聞くかなど、チームで収穫の多いものにするため作戦を立てました。 composition 化観察 コードのcomposition 化(最新の記法に書き直す)作業を見てもらっていました。 こちらは本来のペアプロの意味に近い内容をしたと思います。 開発しているプロジェクトの実装相談 PRのレビューをしてもらう前に、開発している実装の相談をしました。 こちらは開発したコードに対して、他の提案をしてもらったり、アドバイスをもらったりしていました。 他のメンバーの意見を聞くことで、違う角度からコードを見ることができます。 記事を紹介するなど技術共有 技術の紹介や既存コードに対しての新しい書き方などを共有します。 これまで共有してきたものは以下があります。 CSS Flexboxの使い方を徹底解説 GA の使い方講座 CSSアニメーション Findy Team+ Vitest tech.revcomm.co.jp きっかけ はじめたきっかけとしては、表向きはチームのコミュニケーションを促進しようと思ったからです。 弊社はフルリモートの会社なので、チームのコミュニケーションの場所がほとんどありませんでした。 せっかくチームで動いているのだから、何かできないかという思いでスタートさせました。 同じプロダクトで、同じ技術を用いているので、技術共有もしやすく、先輩エンジニアからの知識も借りられます。 実装で詰まった箇所を相談もでき、私個人としては大変ありがたく活用させてもらっています。 裏側の理由としては、チームのコミュニケーションを促進させると評価が上がると書いてあったので、スタートさせました。 まとめ チーム内で行っている活動のため、こういった記事にでき、日の目を浴びれたのが嬉しいです。 執筆の機会をもらったことに感謝すると共に、毎週参加してくださるチームメンバーにも感謝しています。 週一回ですが、自分はとても有意義に活用できています。 一人では解決できないことや、ちょっとした悩みも雑談ベースだと聞きやすいため、自分の性格にもあっているなと思います。 無理のない範囲で、よければチームに取り入れてみてください。 私たちは、これからも細く長く、活動していきたいと思います。
アバター
著者: Researchチーム 春日 1. はじめに 「レビューがいつまでも終わらない」「人によって指摘の粒度がバラバラ」「些細な指摘で心理的安全性が削られる」……。 開発規模の拡大や専門性が深まるにつれ、ドキュメントやコードのレビューにまつわる悩みは、どの現場でも尽きないテーマではないでしょうか。Bacchelliら *1 の研究でも示されている通り、現代のレビューには単なる欠陥発見以上に「知識の共有」や「チームの認識合わせ」という高度な役割が期待されています。特に私たちResearchチームにおいては、 プロジェクト完了後の最終レポート作成が重要な文化として定着しており 、その際に質の高いドキュメントレビューが不可欠です。しかし、一般的なリモートワークの課題に加え、フルリモートを前提とする私たちのチームでは、レビュアーとの物理的・心理的な『距離』がより大きなコミュニケーションの壁となっています。その結果、 文脈の共有や質の高いフィードバックの維持という課題 *2 がより顕著になっています。 また、Googleの「Project Aristotle *3 」が明らかにしたように、チームのパフォーマンスを支えるのは「心理的安全性」です。レビューにおける攻撃的な指摘や不透明な基準は、この土台を揺るがしかねません。 そこで私たちResearchチームは、これらの課題への一手として最終レポートなどのレビュープロセス(特に一次レビュー)へのAI (LLM) 導入を検証しました。本記事では、実際にGeminiを活用したレビューフローを構築し、チームメンバーによる評価で明らかになった「AI導入の効果」と、そこから見えてきた「活用の勘所」を、実例とともに共有します。 想定読者 コードレビューやドキュメントレビューの工数削減に関心があるエンジニア・研究者 チームの心理的安全性を保ちながら、成果物の品質を上げたいマネージャー 業務プロセスへの生成AI導入・活用を検討している方 2. 現状の課題とAI導入の狙い 従来の人間が行うレビュープロセスは、どうしても属人的になりがちで、主に以下の3つの課題を抱えていました。 レビュー工数の増大 : 規模拡大や専門領域の細分化により、確認に時間がかかる。 指摘内容のばらつき : 担当者のスキルや気分によって指摘が異なり、一貫性が欠如する。 心理的安全性の低下 : ネガティブなフィードバックが続くと、レビュアー・レビュイー双方がストレスを抱えやすい。 AIレビューによるソリューション 私たちは「一次レビューの自動化」としてAIを導入することで、人間が本質的な議論に集中できる環境を目指しました。これによって以下の利点があると予想されます。 工数削減 : 単純なミス(Typo)や論理矛盾をAIが事前に修正指示。 品質の均一化 : 常に一定の基準で、客観的なフィードバックが提供される。 心理的安全性の確保 : AIからの指摘は感情的な抵抗が少なく、客観的な事実として受け入れやすい。 3. 関連研究: 学術界におけるAIレビューの現在地 今回の調査にあたり、私たちはまず学術論文の査読(ピアレビュー)プロセスにおいて、現在AIがどのように活用されているかを調査しました。そこから見えてきた「AIレビューの分類」と「透明性へのトレンド」について紹介します。 論文レビューにおけるAI活用の3つのフェーズ Chenらのサーベイ論文 *4 によると、論文レビューにおけるAI活用は大きく3つのカテゴリに分類されます。 Pre-Review(事前レビュー) : 原稿の初期評価や、専門分野の特定、適切な人間のレビュアーとのマッチングなど、査読に入る前の準備作業を効率化するフェーズです。 In-Review(レビュー中) : レビューレポートの自動生成や、人間のレビュアーの支援を行うフェーズです。数値スコアの予測や、書面による評価コメントの生成が含まれます。 Post-Review(事後レビュー) : 論文採択後の影響度評価(引用分析)や、プロモーション(要約や解説ビデオの生成)を行うフェーズです。 今回の私たちの取り組みは、レポートの品質向上とフィードバックの提供を目的としているため、この中の 「In-Review」 領域 に位置づけられます。 AIによる採択予測の可能性と限界 「AIは論文の良し悪しを判断できるのか?」という問いに対して、興味深いデータがあります。 ICLR 2022の論文データセットを用いた研究("The AI Scientist") *5 では、GPT-4oを使用したAIモデルが、 論文の採択・不採択を70%程度の精度で予測 できたと報告されています。また、本来採択されるべき論文を不採択にしてしまう「偽陰性率」に関しては、人間よりも低い値(つまり見落としが少ない)を記録しました。さらに、AI(LLM)が算出した評価スコアは、無作為に選んだ一人の人間のスコアよりも、 「人間のレビュアー全員の平均スコア」に近い という結果が示されました。これは、人間とAIの「評価の性質の違い」を浮き彫りにしています。人間の場合、どうしても「その人の好み」「その時の気分」「得意・不得意」といったバイアスが入ります。その結果、ある人は「満点」を出し、別の人は「不合格」を出すといった具合に、 誰が担当するかによって評価が大きくブレる(当たり外れがある) ことがあります。一方でAIは、個人のような強い感情や特定のバイアスを持ちません。そのため、特定の個人の意見というよりは、 「審査員全員の意見をならした平均値(コンセンサス)」に近い、極端さのない安定した評価を導き出す傾向 があります。 ただし、この手法をそのまま社内のレビューに適用することには慎重であるべきです。なぜなら、これらの成果は「明確なレビュー基準」と「大量の学習データ」が存在するコンペティション(学会)環境だからこそ実現できたものであり、社内のドキュメントレビューにそのまま転用するのは難しいためです。 レビューの透明化(Transparent Peer Review) もう一つの重要なトレンドが、 「レビュープロセスの透明化」 です。 これまで査読コメントは非公開が一般的でしたが、近年ではレビューコメントやそれに対する著者の返答も「論文の一部」とみなして公開する動きが広がっています。 例えば、世界的な学術誌であるNatureは、2025年から全ての新規投稿論文について、論文公開時に査読レポートと著者の回答を併せて公開すると発表しました *6 。また、CS(コンピュータサイエンス)分野では OpenReview *7 というプラットフォームが有名で、ここでは以前からレビューコメントが公開されています。 4. 実践したAIレビューの手法 今回私たちのAIレビューでは、Notionで書いたレポートのテキストを手動でコピーアンドペーストしたものを入力として、Googleの Gemini 2.5 Flash を使用してレビューを行いました。レビュープロセスにおいて工夫した点は 「プロンプトのシンプルさ」 と 「透明性の確保」 です。 プロンプト戦略 「レポートをレビューして不明な点を列挙してください。」 上記のような非常にシンプルな指示を採用しました。近年のLLMのサービスでは複雑な役割定義をしなくても、シンプルな指示で自動的に調整してくれるためです。 レビュープロセスの透明化 Nature誌などが採用している「Transparent Peer Review」の流れを参考にしました。 単にAIが修正案を出すだけでなく、 「AIのレビュー指摘」に対して「著者がどう返答・修正したか」までをセットにしてドキュメント管理 します。これにより、意思決定のプロセスが可視化され、ナレッジとして蓄積されます。 5. アンケート評価 私たちは実際にResearchチーム内でAIレビュー導入を行い、メンバー6名を対象にその有用性を評価するアンケートを実施しました。 定量評価は大きく「レビューの品質(Quality)」と「懸念点(Challenges)」の2つの軸で行われました。また、定性評価として幾つかの自由記述の質問項目を用意しました。 AIレビューに対する評価アンケートの例 AIレビューの品質評価 (Quality of AI Review) まず、AIによるレビューが業務にどの程度貢献したかを測るため、以下の6項目について5段階評価(1: 全くそう思わない 〜 5: 強くそう思う)を行いました。 Q1. 有用性: AIのコメントは自分の仕事に役立ったか。 Q2. 正確性: フィードバックは技術的・論理的に正確だったか。 Q3. 提案の具体性: 改善提案は具体的で分かりやすかったか。 Q4. 見落としの検出: 人間が見落としがちな点(Typoや規約など)を指摘できたか。 Q5. 新規視点: 自分になかった新しい視点やアイデアを提供してくれたか。 Q6. 品質の向上: 最終的な成果物の品質は向上したか。 ▼ 評価結果 AIレビューの品質評価の結果 結果として、 「Q6. 最終的なアウトプットの品質の向上」 および 「Q1. 自身の仕事への有用性」 が非常に高いスコアを記録しました。特に、Typoや単純なミスといった「人間が見落としやすい点」の検出(Q4)についても高評価が得られています。 一方で、 「Q3. 改善提案の具体性」 や 「Q5. 新たな視点やアイデアの提供」 のスコアは相対的に低くなりました。 これについては、 「欠点は指摘できるが、具体的な代案を出す能力はまだイマイチである」 という現状が浮き彫りになりました。また、自由記述では 「自分では思いつかないアイデアが出ることは稀だが、自分の考えに対する自信(裏付け)を持つ助けにはなる」 といった意見も寄せられました。 AIレビューに対する懸念点 (Challenges and Concerns) 次に、AIをレビューに導入することに対する不安や懸念についても調査しました(複数回答可)。 ▼ 質問項目と結果 AIレビューに対する懸念点の評価結果 もっとも多かった懸念は 「文脈や背景の無理解に基づいた的外れなフィードバック」 でした。 レポートには書かれていない背景事情(例:実験を早く進めるためにあえて特定の条件を無視している等)をAIは理解できないため、指摘が的外れになるケースがあるという声が上がっています。この課題は、RAGによってある程度解消できると見込んでいます。 次いで多かったのが 「ハルシネーション(もっともらしい嘘)を誤って信じるリスク」 です。実際に「関連論文を教えて」と聞いた際に存在しない論文を捏造するケースも確認されており、情報の正確性には引き続き注意が必要です。 また、過半数は超えませんでしたが、 「AIへの過度な依存による人間自身のレビュー能力の低下」 を懸念する声もありました。 これらの懸念に加えて自由記述として、 「AIのレビューの過信は避け、レビュイー自身が主体的に考える必要性」 を唱えるコメントがありました。 AIレビューのこれから:現場の声と今後のロードマップ (Future Use of AI Review) アンケートの最後には、今後のAIレビューに期待する機能や、具体的な活用方法についてのアイデア(Future Use)を自由記述形式でヒアリングしました。また、それらを踏まえた私たちの今後の技術的なロードマップについても共有します。 現場からの改善要望とアイデア チームメンバーからは、AIをより実用的な「パートナー」にするための具体的な機能要望が多く寄せられました。 入力の強化(マルチモーダル化) : テキストだけでなく、画像や表などの情報も読み込ませることで、より正確なレビューを実現したいという要望がありました。 擬似的なクロスチェック : 1つのAIだけでなく、レビューの観点が異なる複数のプロンプト(ペルソナ)を用意し、多角的な視点でチェックを行いたいというアイデアが出ました。 指摘から「修正」へ : 単に問題を指摘するだけでなく、具体的な修正案の雛形(ドラフト)までをAIに書かせることで、修正工数をさらに削減できる可能性があります。 レビューの「甘さ」の改善 : 「少し甘口すぎる」という意見もあり、プロンプトを変更することなどによって、より厳格なレビューにすることが求められています。 上記から、よりAIに正確かつ過不足なく情報を与え、より多角的・批判的・具体的なレビューをさせる工夫が必要であることが窺えます。 AIをレビューに活用することについてのその他のコメント AIレビューの限界 : 現状ではAIはそのタスクやチーム・会社の方針に対する背景を十分に理解した上でのレビューができないため、今のところでは人間のレビュワーの完全な代替とはならないというコメントがありました。 AIレビューの効率性 : 人間がレポートやコードを細部までレビューするのは労力を要し、特に情報が不完全だったり読みづらい場合は多大な時間がかかります。AIによる事前レビューでレポートやコードをブラッシュアップすることで、大幅な労力削減が可能であるという、 「レビュープロセスの前工程」 としてのAI活用が推奨されていました。 小規模チームにおける利点 : 現在の私たちのResearchチームのような小規模チームでは複数人によるダブルチェックやクロスチェックが難しいですが、AIを活用することで擬似的にこれらを実現できる利点も述べられていました。 主体的判断の重要性 : 人間のレビューと同様に、AIの提案をそのまま受け入れることが常に最善とは限りません。レビュイーは自身で主体的に判断し、適切な改善を行う必要があることを戒める必要性があります。 AIとの対話も「成果物」に : レポート提出時には、AIによるチェックとそれに対する修正履歴も含めて「レポートの一部」とみなす運用が推奨されていました。 6. 考察:AIは「優秀な校正者」だが「共著者」ではない アンケート結果から導き出された、現段階でのAIレビュー活用のポイントは 「適材適所」 です。以下のようなAIの得意不得意を把握した上で、レビュイー自身が主体性を持つことが重要であると言えます。 活用のための3つの原則 今回の検証を通じて、私たちは以下の3原則を定めました。 鵜呑みにしない : 最終的な判断と責任は必ず人間が持つ(主体性の維持)。 得意を任せる : 機械的なチェック作業はAIに任せ、人間は本質的な設計や議論に集中する。 文脈を伝える : 重要な背景情報や制約条件がある場合は、プロンプトで可能な限り言語化して伝える。 今後の技術的ロードマップ これらのフィードバックを受け、私たちはAIレビューの精度と深度を高めるために、以下の3つの方向性で開発・改善を進めていくことが考えられます。 プロンプトエンジニアリングの深化 : 現在のシンプルな指示から、レビュー観点をより明確化し、具体的かつ多角的なアクションプランを出力できるよう改善します。 外部情報(RAG)との連携 : AIの最大の弱点である「文脈理解」を補うため、社内文書や関連文献を検索・参照させるRAG(Retrieval-Augmented Generation)の仕組みを取り入れます。 マルチエージェントシステムの導入 : 単一のAIではなく、異なる専門性や役割を持った複数のAIエージェントが協調してレビューを行う仕組み *8 も有効である可能性があります。 7. おわりに 検証の結果、AIレビューはチーム全体の生産性と品質向上に確かに寄与することが確認できました。ただし、現状ではAIはあくまで「優秀な校正者」であり、人間のサポーターです。AIを「信頼しすぎず、上手に使いこなす」。 このバランス感覚を持つことで、私たちのレビュープロセスはより効率的で、創造的なものへと進化していくはずです。 参考文献 *1 : Bacchelli, A., & Bird, C. (2013). Expectations, outcomes, and challenges of modern code review. Proceedings of the 35th International Conference on Software Engineering (ICSE). *2 : Sadowski, C., Söderberg, E., Church, L., Sipko, M., & Bacchelli, A. (2018). Modern Code Review: A Case Study at Google. Proceedings of the 40th International Conference on Software Engineering (ICSE). *3 : Google re:Work. (n.d.). Guide: Understand team effectiveness. (Project Aristotle). *4 : Chen, Q., Yang, M., Qin, L., et al. (2025). AI4Research: A Survey of Artificial Intelligence for Scientific Research. ArXiv, abs/2507.01903. *5 : Lu, C., Lu, C., Lange, R. T., et al. (2024). The AI Scientist: Towards Fully Automated Open-Ended Scientific Discovery. ArXiv, abs/2408.06292. *6 : Nature. (2025). Transparent peer review to be extended to all of Nature's research papers. Nature. *7 : OpenReview. https://openreview.net/ *8 : D'Arcy, M., Hope, T., Birnbaum, L., & Downey, D. (2024). MARG: Multi-Agent Review Generation for Scientific Papers. ArXiv, abs/2401.04259.
アバター
RevComm Advent Calendar 2025 2日目です。 qiita.com AI Div. Research Group で機械学習のサービスの開発・運用(+MLOps)を行うチームでエンジニアリングマネージャーをしている高橋です。我々のチームは、EKSで音声解析や自然言語処理に関するサービスを提供したり、MLモデルの改善サイクルを回すための仕組みの構築を行っています。現在、あるプロジェクトでKubernetes の持つ宣言的定義でシンプルに管理しつつ、動的に状態を管理することができる Kubernetes Operator を活用して、推論プラットフォームを発展させる活動を行っています。 この記事は Kubernetes Operator の入門のための記事です。Kubernetes Operator に関しては、 kubebuilder-training など素晴らしいサイトがありますが、この記事ではさらにその入門として、Kubernetes の基本的な話も含めて解説しています。Kubernetes の基本概念に自信がない人は、この記事を読むことで kubebuilder-training をスラスラ進みやすくなれば良いなと思っています。逆にいうと、 kubebuilder-training を読める方はそちらをみていただければこの記事を読む意味はあまりないと思います。 Operator に初めて触れると、 Reconcile, Controller とは何なのか どういう仕組みで Operator が動作するのか といった点が把握しにくいことがあります。 この記事では、 まず Kubernetes 自体の Reconciliation (調整) を丁寧に理解し、その延長として Operator を自然に理解できる流れ を重視しています。 1. Operator とは何か? Operator は、 「Kubernetes に自作の調整ロジック(コントローラー)を追加する仕組み」 です。 Kubernetes には標準で多くのコントローラーが存在しており、ユーザーが宣言した状態にクラスタを自動で寄せてくれます。 例:Deployment(replicas=3) Pod が2つ → Controller が1つ増やす Pod が4つ → Controller が1つ減らす 最終的に3つに“収束”する Operator は、この「自動調整」の仕組みを自作のリソースでも実現するための手段です。 *後で書くように厳密には、Deployment のコントローラーが直接 Pod の数を調整するのではなく、Deployment は ReplicaSet を管理し、ReplicaSet のコントローラーが Pod の管理を行います。 2. リソースとコントローラー ― Kubernetes の基本概念 Operator を理解するには、Kubernetes の根本アイデアである リソース と コントローラー を押さえる必要があります。 2.1 リソースとは Kubernetes のクラスタ上の情報はすべて「リソース」という形で管理されています。 Pod Deployment Service ConfigMap Node など、 kubectl get で一覧できるものはすべてリソースです。 Operator が扱う CR(Custom Resource)も、 この仲間に加わるだけ です。 2.2 コントローラーとは コントローラーは次のようなプログラムです: リソースの状態を監視し、クラスタを“望ましい状態”へ調整する Deployment Controller を例にすると: Deployment を監視 Pod の状態を読み取り 差分があれば調整 何度でも実行される(Reconcile Loop) この仕組みが Kubernetes 全体を構成しています。 3. API Server とは何か? 「API Server」は Kubernetes の中心にある“窓口”ですが、初心者には突然登場するため混乱しやすいポイントです。 そこで kubectl との関係から説明 します。 3.1 kubectl は常に API Server を叩いている kubectl apply kubectl get kubectl describe これらすべては API Server に対する HTTP(S) リクエストです。 つまり、 kubectl は API Server と会話しているだけ です。 3.2 Controller も同じ構造 Controller も基本的に次の2つのことを実行します。 API Server からリソースを読む API Server にリソース作成/更新/削除を依頼する kubectl も Controller も、API Server を通じてクラスタ状態を操作している。 *厳密には、Controller が直接 API Server へリクエストを大量に送るということはなく、キャッシュなどを行うアクセスを最適化する Informer からデータを取得するが、Informer のデータソースも結局 API Server になっています。メンタルモデルとしては、Controller は API Server で読み書きをしているイメージがシンプルで分かりやすいですが、実際には Informer などで最適化されています。 4. Reconcile Loop ― Controller のコア すべての Controller(標準のものも Operator も)は Reconcile Loop の思想で動きます。 4.1 Reconcile が担当するのは3つだけ あるべき状態(ユーザーの宣言)を読む 現在の状態(クラスタの実情)を読む 差分を埋める Deployment Controller も ReplicaSet Controller も、Operator の Reconcile も同じです。 4.2 冪等性が重要 Kubernetes では Reconcile が何度も呼ばれます: イベントの重複 キャッシュ更新 再試行(Retry) そのため次のような「安全な書き方」が重要です。 存在しないときだけ作成 必要なときだけ更新 同じ状態なら何もしない 5. Operator の構造理解のための Deployment Controller Deployment Controller の流れは次のとおりです: Deployment(望ましい状態)を読む ReplicaSet を読む Pod を読む 差分があれば調整 収束まで繰り返す Operator はこれを自作の CR で行うだけです。 Deployment → 自作の CR Deployment Controller → 自作の Controller 構造は全く同じです。 6. 手元で動かせるクラスタ: kind(Kubernetes in Docker) Operator を学ぶうえで「手元で動かす」ことは非常に重要です。 今回は kind を使います。 6.1 インストール https://kind.sigs.k8s.io/docs/user/quick-start/#installation を参考にインストールしてください 6.2 クラスタ作成 kind create cluster --name operator-demo 確認: kubectl get nodes Node が表示されれば準備完了です。 7. Kubebuilder プロジェクトの作成 7.1 プロジェクト作成 mkdir operator-demo cd operator-demo kubebuilder init --domain example.com --repo example.com/operator-demo 生成物: . ├── cmd ├── config │ ├── default │ ├── manager │ ├── network-policy │ ├── prometheus │ └── rbac ├── Dockerfile ├── go.mod ├── go.sum ├── hack ├── internal │ └── controller # ロジックのコア部分 ├── Makefile ├── PROJECT ├── README.md └── test 7.2 CR と Controller の生成 kubebuilder create api \ --group demo \ --version v1alpha1 \ --kind Sample Create Resource [y/n] , Create Controller [y/n] と聞かれるので y で進んでください。 増えるファイル: api/v1alpha1/sample_types.go api/v1alpha1/groupversion_info.go internal/controller/suite_test.go internal/controller/sample_controller.go internal/controller/sample_controller_test.go 8. Operator ミニマム実装: ConfigMap 同期 Operator CR の内容に応じて ConfigMap を作成・更新する 単に「ConfigMap を作るだけ」では Reconcile の本質ががわかりにくいので、 CR の spec.message を読む ConfigMap に message を書き込む 差分がある場合にのみ更新する 同じであれば何もしない(冪等性) という“Reconcile そのもの”がわかりやすく、かつミニマムな例にしています。 8.1 CR の Spec に message を追加 api/v1alpha1/sample_types.go の SampleSpec にフィールドを追加: type SampleSpec struct { Message string `json:"message,omitempty"` } サンプル CR: config/samples/demo_v1alpha1_sample.yaml apiVersion : demo.example.com/v1alpha1 kind : Sample metadata : labels : app.kubernetes.io/name : operator-demo app.kubernetes.io/managed-by : kustomize name : sample-sample spec : message : "hello world" 8.2 Reconcile の本体(差分判定・更新・冪等性) operator-demo/internal/controller/sample_controller.go import ( ... corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ... ) ... func (r *SampleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error ) { // CR を取得(あるべき状態) var cr demov1alpha1.Sample if err := r.Get(ctx, req.NamespacedName, &cr); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } // 望ましい ConfigMap(desired) desired := corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-config" , Namespace: cr.Namespace, }, Data: map [ string ] string { "message" : cr.Spec.Message, }, } // 現在の ConfigMap(current) var current corev1.ConfigMap err := r.Get(ctx, client.ObjectKeyFromObject(&desired), &current) // 存在しない → 作る if apierrors.IsNotFound(err) { if err := r.Create(ctx, &desired); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil } if err != nil { return ctrl.Result{}, err } // 差分チェック → message が違う場合のみ更新 if current.Data[ "message" ] != cr.Spec.Message { current.Data[ "message" ] = cr.Spec.Message if err := r.Update(ctx, &current); err != nil { return ctrl.Result{}, err } } // 差分なし → 何もしない(冪等) return ctrl.Result{}, nil } Reconcile の本質の復習 ここで、Reconcile の以下の点を実感できるはずです。 “あるべき状態” を CR から取得 “現在の状態” をクラスタから取得 差分があれば調整 同じなら何もしない これは Kubernetes Controller が日常的に行っている動作と同じです。 9. ローカルで動かすコントローラー 9.1 make install 次をクラスタへ適用します: CR の定義(Customer Resource Definition: CRD) config/crd の内容が適用されます。 クラスタに「新しいリソース」が使えるようにするフェーズ 9.2 make run main.go をビルド コントローラーをローカルプロセスとして起動 この実行方法の場合、Operator 自体はクラスタ内で動かない(ローカルで動く) ローカルテストでない場合は、クラスタ内にデプロイします ( make deploy )が、この記事では扱いません 10. 動作確認 CR を適用します: kubectl apply -f config/samples/demo_v1alpha1_sample.yaml ConfigMap を確認: kubectl get configmap kubectl get configmap sample-sample-config -o yaml 次のような表示になっていれば成功です! hello world が確認できているところがポイントです。 apiVersion: v1 data: message: hello world kind: ConfigMap metadata: creationTimestamp: "2025-11-28T01:53:35Z" name: sample-sample-config namespace: default resourceVersion: "3604" uid: 680ec943-8527-4044-a8d6-b22c4020ccb6 次に CR を変更してみます: kubectl patch sample/sample-sample --type merge -p '{"spec":{"message":"updated"}}' ConfigMap の内容が自動で更新されます。確認してみましょう。 % kubectl get configmap sample-sample-config -o yaml apiVersion: v1 data: message: updated kind: ConfigMap metadata: creationTimestamp: "2025-11-28T01:53:35Z" name: sample-sample-config namespace: default resourceVersion: "3761" uid: 680ec943-8527-4044-a8d6-b22c4020ccb6 updated に更新されています。逆に、ConfigMap の方を直接編集してみましょう。 kubectl edit configmap sample-sample-config を実行して、 data.message を次のように更新します。 data: message: fixed_directly どうなるでしょうか? % kubectl get configmap sample-sample-config -o yaml apiVersion: v1 data: message: fixed_directly kind: ConfigMap metadata: creationTimestamp: "2025-11-28T01:53:35Z" name: sample-sample-config namespace: default resourceVersion: "3881" uid: 680ec943-8527-4044-a8d6-b22c4020ccb6 変化がありません。予想通りでしたでしょうか?これは、現状の設定だと Reconcile が実行されれば CR の内容に上書きされますが、ConfigMap の編集では Reconcile がトリガーされていない、と説明できます。 Controller が Watch していないリソースの変更は検知されない 点は重要なので、頭に入れておきましょう。ここでは、 OwnerReference を設定して、 SetupWithManager を次のように更新することでリソースの変更を検知できるようにします。 internal/controller/sample_controller.go // SetupWithManager sets up the controller with the Manager. func (r *SampleReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&demov1alpha1.Sample{}). Named( "sample" ). Owns(&corev1.ConfigMap{}). // これを追加 Complete(r) } また、ConfigMap にオーナーへの参照 (OwnerReference) を設定する必要があります。 internal/controller/sample_controller.go の Reconcile 関数の中身を少し修正します。 // 望ましい ConfigMap(desired) desired := corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: cr.Name + "-config" , Namespace: cr.Namespace, }, Data: map [ string ] string { "message" : cr.Spec.Message, }, } // [NEW] OwnerReference を設定 if err := ctrl.SetControllerReference(&cr, &desired, r.Scheme); err != nil { return ctrl.Result{}, err } // 現在の ConfigMap(current) var current corev1.ConfigMap err := r.Get(ctx, client.ObjectKeyFromObject(&desired), &current) 編集後、 make run を止めてコントローラを止めてください。また、 kubectl delete configmap sample-sample-config で一度 ConfigMap を削除してください。その後、再度 make run を実行します。そうすると、前回最後に CR 側で更新した updated の値になっています。 % kubectl get configmap sample-sample-config -o yaml apiVersion: v1 data: message: updated kind: ConfigMap metadata: creationTimestamp: "2025-11-28T02:07:38Z" name: sample-sample-config namespace: default ownerReferences: - apiVersion: demo.example.com/v1alpha1 blockOwnerDeletion: true controller: true kind: Sample name: sample-sample uid: 323bdd14-45ff- 4e98 -806a-4f82086616b0 resourceVersion: "4695" uid: d41f188f-c701-49d1-895e-9b86e63864e7 また、今回は ownerReferences が追加されています。この状態で、先ほどと同様に ConfigMap を書き換えてみましょう。 kubectl edit configmap sample-sample-config を実行して、 data.message を次のように更新します。 data: message: fixed_directly どうなるでしょうか? % kubectl get configmap sample-sample-config -o yaml apiVersion: v1 data: message: updated kind: ConfigMap metadata: creationTimestamp: "2025-11-28T02:07:38Z" name: sample-sample-config namespace: default ownerReferences: - apiVersion: demo.example.com/v1alpha1 blockOwnerDeletion: true controller: true kind: Sample name: sample-sample uid: 323bdd14-45ff- 4e98 -806a-4f82086616b0 resourceVersion: "4940" uid: d41f188f-c701-49d1-895e-9b86e63864e7 今度は CR の値に上書きされました。OwnerReference が設定されている ConfigMap を更新したことで Reconcile がトリガーされ、値が更新されたわけです。 (ちなみに、 kubectl delete sample/sample-sample とすると、作成されていた ConfigMap も消えていることもわかります。OwnerReference はこうしたトリガーのためだけのものではなく、 ガベージコレクション の際にも利用されます。) CRで宣言的に定義した内容が、Reconcile Loop によって実現 されていく様子を見ることができました。最後の方ではいつトリガーされるか、という点に関しても修正してみました。基本的には、Reconcile をコアロジックとしていつトリガーするかを工夫することで多くのことが実現でき、シンプルに管理できるという点を何となく感じ取れたかと思います。 11. まとめ この記事で押さえたポイント Operator のコアは 自作のコントローラー Operator のコアロジックは Reconciliation (調整) Reconcile 処理で、あるべき状態と現在の状態の差分を埋める(← 冪等性が重要) Operator 開発は、ハードルが高そうに見えますが、kubebuilder を活用し、kind を使うことで簡単に試したり、遊んだりすることができます。Kubernetes の “宣言的な自動調整” の思想を自分で実装できるので色々試して実運用に活かしたいものです。より実践的な例や他のトピックが気になる方は、 kubebuilder-training を手を動かして見るのが個人的にはおすすめです。
アバター
はじめに RevComm Advent Calendar 2025 1日目の記事です。 qiita.com 昨今では AI コーディングエージェントが話題です。AI コーディングエージェントを活用することで、フロントエンド開発においても生産性の大きな改善が期待できます。 AI コーディングエージェントの活用を広めていくためには、信頼性の高いテストコードがあるとより積極的かつ安全に活用や導入を進めていきやすいのではないかと考えています。 そこで、この記事ではフロントエンド開発において信頼性の高いテストコードを記述するための方法論などについて説明できればと思います。 テストダブルの使用について 前提: テストダブルとは? 大雑把に要約すると、テストにおいて本物のオブジェクトの代わりとして機能してくれるオブジェクトのことを指します。 テストダブルには、スタブ、モック、スパイ、フェイクなど、様々な種類があります。以下の Wikipedia ページに分類が解説されていますが、これらの定義を参考にテストダブルについて紹介します (これらの種別の用法や意味には正確な定義や標準が存在するわけではなく、チームや文脈などによって意味が異なる場合もあると思うため、各用語の意味については参考程度にとどめていただければと思います) en.wikipedia.org ja.wikipedia.org テストダブルの分類 フェイク まず比較的イメージしやすいと思われるフェイクから紹介します。 https://en.wikipedia.org/wiki/Test_double からフェイクの定義を引用します。 Fake — a relatively full-function implementation that is better suited to testing than the production version; e.g. an in-memory  database  instead of a database  server ) 置き換え対象の本物のオブジェクトの振る舞いを模倣したオブジェクトがフェイクであると考えられそうです。 例えば、ユーザーの永続化に関する責務を定義した UserRepository インターフェースがあったとします。これに対して、 ProductionUserRepository は本番コードでの利用が想定された UserRepository の実装であり、REST API を使用してオブジェクトを永続化します。 class ProductionUserRepository implements UserRepository { constructor ( client ) { this .#client = client; } async get ( id ) { try { const response = await this .#client.getUserById(id); return this .#makeUserFromResponse(response); } catch (error) { if (isNotFoundError(error)) return Promise . reject ( new UserNotFoundError(id)); throw error; } } async add ( user ) { await this .#client.updateUser(user); } #makeUserFromResponse () { // 省略... } } これに対して以下はフェイクの実装例です。データはインメモリで管理され永続化は行われないものの、外から見た際には ProductionUserRepository と概ね同じように動作をします。 class FakeUserRepository implements UserRepository { #userById = new Map (); get ( id ) { const maybeUser = this .#userById. get (id); if (maybeUser == null ) return Promise . reject ( new UserNotFoundError(id)); return Promise . resolve (maybeUser); } add ( user ) { this .#userById. set (user. id , user); return Promise . resolve (); } } 依存性の注入 (DI) と併用することにより、実際にコードを動作させる際は ProductionUserRepository を利用し、テストコードの実行時はフェイク実装である FakeUserRepository の方を利用するなど、柔軟に実装を切り替えることができます。 { // 本番コード const service = new UserService ( new ProductionUserRepository ( client )) ; // ... } { // テストコード const service = new UserService ( new FakeUserRepository ()) ; // ... } フェイクを実装する際は、本番向けの実装とフェイク実装とで共通のテストコードを実行しておくと、それぞれの振る舞いを維持することができ、より高い信頼性が期待できます。後述する Google のソフトウェアエンジニアリング においてもこの手法は推奨されており、 https://en.wikipedia.org/wiki/Test_double においては Verified fake と呼ばれています。 フェイクには他のテストダブルと比較して信頼性が高いというメリットがあります。しかし、デメリットとして、後述するスタブやモックなどと比較すると実装コストが高くなりがちであり、またメンテナンスも必要です。このフェイクの実装やメンテナンス作業というのはまさに AI コーディングエージェントが得意としている分野であると考えられるため、もしフェイクの利用を進める場合はぜひ活用を検討してみると良さそうです。 スタブ https://en.wikipedia.org/wiki/Test_double から定義を引用します。 Stub — provides static input 定義に基づいて考えると、スタブは以下のように実装することができます。ライブラリーなどを使用せずとも比較的容易に実装が可能で、フェイクと比較すると、実装がかなり簡単です。 class StubUserRepository implements UserRepository { get ( id ) { return Promise . resolve ( new User(id, "foobar" )); } add ( _user ) { return Promise . resolve (); // NOOP } } JavaScript は動的言語であり、例えば Jest の jest.spyOn() を使うと特定のメソッドのみをスタブに置き換えることも容易に行えます。 jest . spyOn ( localStorage , 'getItem' ) . mockImplementation (() => 'foo' ) ; スタブは実装がとても容易であるというメリットがありますが、先に紹介したフェイクと比較すると信頼性においては大きく劣るというデメリットがあります。乱用はし過ぎずに適度な利用がおすすめであると考えます。 モック https://en.wikipedia.org/wiki/Test_double から定義を引用します。 Mock — verifies output via expectations defined before the test runs 上記のモックの定義に従うと、 Sinon.JS における mock() が定義に近いと思います。 // ... const mock = sinon . mock ( userRepository ) ; // Expectations mock . expects ( 'add' ) . once () . withArgs ( user ) ; const userService = new UserService ( userRepository ) ; await userService . add ({ id : '1' , name : 'foobar' }) ; mock . verify () ; このように JavaScript においては、Sinon.JS や testdouble.js などのライブラリーを使うと比較的簡単にモックを実装できます。依存しているオブジェクト間でのコミュニケーションを検証することで、意図した副作用が発生していることをテストすることができます。例えば、特定のメソッドがある順番に従って呼び出されていることを検証したいケースにおいて利用ができます。ただし、テスト対象がどの順番で一連のメソッドを呼び出すかは実装の詳細であり、大抵の場合、過度にモックを乱用したり依存することは望ましくないと考えられます。 モックは便利な仕組みではあると思いますが、契約による設計が言語レベルでサポートされているような稀なケースを除いて、モックに過度に依存しすぎるのは避けた方が良いと筆者は考えています。特定のメソッドが呼ばれたかどうかを検証するのではなく、それによって引き起こされる状態遷移などを検証した方がテストの信頼性が高まると思います。 スパイ https://en.wikipedia.org/wiki/Test_double から定義を引用します。 Spy — supports setting the output of a call before a test runs and verifying input parameters after the test runs スタブやモックなどとの違いが少しややこしいですが、テストの実行後に入力パラメーターの検証が行えるという点が大きな違いであると思います。 スパイについても JavaScript では jest.fn() や vi.fn() , jest.spyOn() などを利用すると容易に実装できます。 const stubUserRepository: UserRepository = { get : jest.fn(( id ) => new User(id, "foobar" )), // スタブ add : jest.fn(), // スパイ } ; doSomethingWithUserRepository(stubUserRepository); expect (stubUserRepository. add ).toHaveBeenCalledWith( new User(id, "foobar" )); // 入力値の検証 どれを使えばいいの? テストダブルの使い分けについては基準が難しいところではありますが、まず前提として、テストダブルを使わなくともテストを書くことが可能なのであれば、それがコードが意図した通りに動作していることを保証するための最も信頼性の高い方法であると思います。ただし、現実には外部の REST API や サービスなどに対してテストから直接接続する場合、意図せぬ副作用が発生してしまったり、テストの実行時間が大きく増加してしまうことなども考えられます。そのような場合はテストダブルの使用を検討すると良いでしょう。 参考までに、 Google のソフトウェアエンジニアリング という本の13章においてテストダブルの使用に関して非常に詳しく解説されています。 この本ではまず「忠実性」の考えについて紹介されています。「忠実性」とはテストダブルが置き換え対象の本物のオブジェクトの挙動にどれくらい近いかを表す指標であると説明されています。 前提としてテストダブルを使用せずとも十分にテストが可能である際は、テストダブルを使用せずに本物のオブジェクトを使用することがこの本でも推奨されています。それがコード上に存在するバグを正しく検出してくれる可能性が高いケースが多いと考えられるためです。 もしテストダブルの使用が必要である際はフェイクの使用が推奨されています。フェイクは本物のオブジェクトの挙動を模倣したものであり、スタブやモックなどと比較して忠実性が高いためです。逆にスタブやモックを過度に用いることは、フェイクを使用した場合と比較してテスト対象の実装の詳細への依存度が高くなってしまいがちであり、脆いテストコードができてしまうリスクがあると説明されています。 jest.mock() / vi.mock() の使用はできるだけ避ける これらの前提に基づいて、まずは Jest における jest.mock() や Vitest における vi.mock() について考えてみます。これらの API は先ほどのテストダブルの定義に照らし合わせた場合、スタブに該当するものであると考えられます。そのため、フェイクと比較すると忠実度が低く、乱用しすぎると信頼性が低いテストコードができてしまう原因になってしまう可能性が考えられます。 jest.fn() や 後述する jest.spyOn() などを使用してスタブを実装する場合においても信頼性の低いテストコードができてしまうケースは考えられますが、 jest.mock() や vi.mock() に関しては実装の詳細へ強く依存したテストコードがより一層容易に記述できてしまうため、特に注意が必要であると考えます。 例えば、 apis/getUser モジュールを介して REST API を実行し、ユーザー情報を取得するフックがあったとします。 // src/hooks/useUser.ts import { getUser } from 'apis/getUser' ; export function useUser ( id ) { const [ user , setUser ] = useState( null ); const [ isLoading , setIsLoading ] = useState( false ); const [ error , setError ] = useState( null ); useEffect(() => { if (isLoading) return ; setIsLoading( true ); getUser(id) . then (( user ) => setUser(user)) . catch (( error ) => setError(error)) . finally (() => setIsLoading( false )); } , [ id ] ); return { error , user , isLoading } ; } jest.mock() を使うことにより、非常に簡単にスタブをセットアップすることができます。 // src/hooks/__tests__/useUser.spec.tsx import { renderHook, waitFor } from '@testing-library/react' ; import { useUser } from '@/hooks/useUser' ; jest.mock( 'apis/getUser' , () => { return { getUser : ( _id ) => Promise . resolve (dummyUser), } ; } ); test ( 'useUser()' , async () => { const { result } = renderHook(() => useUser(dummyUser. id )); expect (result. current .isLoading).toBe( true ); await waitFor(() => expect (result. current .user).toEqual(dummyUser)); expect (result. current .isLoading).toBe( false ); } ); jest.mock() を使うことにより、見かけ上はシンプルにテストを記述することができました。ではこれの何が問題なのでしょうか? 例として、ここで apis/getUser モジュールを apis/users/get にリネームしたとします。それに伴い、 src/hooks/useUser.ts の import も修正する必要があります。 // src/hooks/useUser.ts - import { getUser } from 'apis/getUser'; + import { getUser } from 'apis/users/get'; export function useUser(id) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (isLoading) return; setIsLoading(true); getUser(id) .then((user) => setUser(user)) .catch((error) => setError(error)) .finally(() => setIsLoading(false)); }, [id]); return { error, user, isLoading }; } ソースコードは適切に修正されており、プロダクションコードは意図通りに機能し続けます。 しかし、この状態で src/hooks/__tests__/useUser.spec.tsx を実行すると、テストは失敗してしまいます。 apis/getUser.ts から apis/users/get.ts へのリネームは適切に行われており、 getUser() の振る舞いも変わってはいないので、本来であればこの状況でテストが失敗してしまうことは望ましくありません。これはテストコードが脆い状態に陥ってしまっており、信頼性が低下してしまっていることを示唆しています。 この問題が発生してしまうのは、 src/hooks/__tests__/useUser.spec.tsx が テスト対象である src/hooks/useUser.ts モジュールの実装の詳細である「モジュール間の依存関係」に強く依存してしまっていることが原因です。 jest . mock ( 'apis/getUser' , () => { return { getUser : ( _id ) => Promise . resolve ( dummyUser ) , } ; }) ; モジュール間の依存関係というのは、実装の詳細の中でもかなり詳細度の高いものであると考えられるため、これにテストコードが依存してしまうことは望ましくないと考えます。 改善案 Testing Library (詳細は後述します) がコンポーネントの実装の詳細への強い依存を避けることでテストコードの信頼性を高めることを重視していることと同様に、この問題においてもテストコードがテスト対象の実装の詳細へ強く依存しすぎてしまうことを避けることで改善することができます。具体的に2つの解決策について紹介します。 1. ユーザーの取得に関する責務を抽象化する (フェイクを使用した改善例) useUser() において重要なのは、何かしらの手段によってユーザー情報を取得し、それに関する状態管理を行うことです。どのようにしてユーザーを取得するかについては実装の詳細にあたり、重要ではないと考えられます。 そこで、このユーザー情報の永続化に関する責務を表現する interface を用意します。 export interface UserRepository { get ( id : UserID ): Promise < User > ; add ( user : User ): Promise < void > ; } UserRepository の実装は Context を介して注入するように変更します。 // src/hooks/useUser.ts import { useContext } from 'react' ; export function useUser ( id ) { const [ user , setUser ] = useState( null ); const [ isLoading , setIsLoading ] = useState( false ); const [ error , setError ] = useState( null ); const userRepository = useContext(UserRepositoryContext); useEffect(() => { if (isLoading) return ; setIsLoading( true ); userRepository. get (id) . then (( user ) => setUser(user)) . catch (( error ) => setError(error)) . finally (() => setIsLoading( false )); } , [ id ] ); return { error , user , isLoading } ; } こうすることで、テスト時はフェイク実装によって代用することが可能です // src/hooks/__tests__/useUser.spec.tsx import { renderHook, waitFor } from '@testing-library/react' ; import { useUser } from '@/hooks/useUser' ; import { FakeUserRepository } from '@/repositories/user.fake.ts' ; test ( 'useUser()' , async () => { const userRepository = new FakeUserRepository(); await userRepository. add (dummyUser); const { result } = renderHook(() => useUser(dummyUser. id ), { wrapper : ( { children } ) => ( < UserRepositoryContext.Provider value = {userRepository} > { children } </ UserRepositoryContext.Provider > ), } ); expect (result. current .isLoading).toBe( true ); await waitFor(() => expect (result. current .user).toEqual(dummyUser)); expect (result. current .isLoading).toBe( false ); } ); このように interface によって責務を抽象化し、依存注入によって依存関係を取り扱うパターンはフレームワークとして Angular などを採用されている場合は比較的一般的なパターンではないかと思われます。しかし、そうではない場合は ここまでやらなくても十分なケースも多いと思われるため、最終的にはプロジェクトの規模やチームの方針などに応じて決めると良いと思います。 ここではもう一つの方法として msw を使った方法についても紹介します。 2. mswを使う msw とは HTTP に関するスタブライブラリーです。 github.com 元の例における src/hooks/__tests__/useUser.spec.tsx は src/hooks/useUser.ts が apis/users/get.ts に依存しているということを前提に記述されていました。モジュール間の依存関係というのは、実装の詳細の中でもかなり詳細度が高いものであると考えられ、テストコードがその詳細に依存することによって信頼性が低下してしまっています。msw を使うことによって、テストコードが「このコードは apis/users/get.ts モジュールに依存し、それを使って HTTP リクエストを送信している」という強い前提への依存から「このコードは何らかの方法で HTTP リクエストを送信している」という前提への依存へ緩めることができます。 // src/hooks/__tests__/useUser.spec.tsx import { renderHook, waitFor } from '@testing-library/react' ; import { useUser } from '@/hooks/useUser' ; import { http, HttpResponse } from 'msw' ; import { setupServer } from 'msw/node' ; const server = setupServer( http. get ( '/api/users/:id' , () => HttpResponse.json(dummyUser)), ); beforeAll (() => server.listen( { onUnhandledRequest : 'error' } )); afterEach (() => { server.resetHandlers(); } ); afterAll (() => server. close ()); test ( 'useUser()' , async () => { const { result } = renderHook(() => useUser(dummyUser. id )); expect (result. current .isLoading).toBe( true ); await waitFor(() => expect (result. current .user).toEqual(dummyUser)); expect (result. current .isLoading).toBe( false ); } ); jest.mock() を使用した例と比較して記述量は増えるものの、大きなメリットとして、 useUser() の実装を TanStack Query などを使用して書き換えたとしても、そのままテストが動作してくれます (ただし、 renderHook を呼ぶ際の Provider の指定は必要です) msw を使用する際は意図せぬ API リクエストの送信を検知できるよう、 onUnhandledRequest に error の設定をオススメします。 beforeAll (() => server . listen ({ onUnhandledRequest : 'error' })) ; msw を使うことにより、 jest.mock() を使用した場合と比較して、テストコードがテスト対象コードの実装の詳細へ強く依存しすぎてしまう状況を緩和することができました。 ただし、この msw を使用した例においてもテストコードは依然として「テスト対象が何らかの方法で HTTP リクエストを送信する」という実装の詳細へ依存した状態です。1つ目のフェイクを使用した改善例と比較すると実装の詳細への依存度は高い状態であると考えられます。しかし、あまりにも抽象化することを意識しすぎてしまうと、今度は過度な抽象化を招いてしまうリスクも考えられます。フロントエンド開発においては、大抵の場合はこの msw を使用してスタブをセットアップする解決策で十分なケースが多いのではないかと考えています。 また、msw を利用する場合も、可能であれば本物の API の振る舞いに近づけるとより信頼性が高まることが期待されます。必要に応じて検討すると良いでしょう。 const users = [] ; const server = setupServer( http. get ( '/api/users/:id' , ( { params } ) => { const user = users. find (( x ) => x. id .equals(params. id )); if (user == null ) return new HttpResponse( null , { status : 404 } ); else return HttpResponse.json(serializeUser(user)); } ), http.post( '/api/users' , async ( { request } ) => { const { name } = await request.json(); const id = makeUserID(); users. push ( new User(id, name )); return HttpResponse.json( { id , name } ); } ), ); jest.mock() / vi.mock() の使用が適したケースについて 筆者としては jest.mock() の使用は極力避けた方が良いとは考えていますが、まだテストコードが導入されておらず、これから導入していきたいというようなケースにおいては、どうしても jest.mock() を使わないとなかなかテストを追加することが難しいというような場合もあると思います。そういったケースにおいては jest.mock() は非常に便利な機能であると思うため、用途や場面を限定して使用すると良いと思っています。 jest.spyOn() / vi.spyOn() の使用について 先ほどの定義に基づいて考えると、 jest.spyOn() や vi.spyOn() はスタブやスパイなどのセットアップに利用できる機能です。つまり、テストにおいて本物のオブジェクトを使用する場合やフェイクを使用する場合と比較して、忠実性は低下してしまいます。 例として localStorage に依存した useSidebar() フックをテストするケースについて考えてみます。 function useSidebar () { const [ isCollapsed , _setIsCollapsed ] = useState(() => JSON . parse (localStorage. getItem ( 'isSidebarCollapsed' ) ?? 'false' )); const setIsCollapsed = useCallback(( isCollapsed ) => { localStorage. setItem ( 'isSidebarCollapsed' , JSON . stringify (isCollapsed)); _setIsCollapsed(isCollapsed); } , [] ); return { isCollapsed , setIsCollapsed } ; } これをテストする場合、例えば jest.spyOn() を使用して localStorage をスタブする方法が考えられます。 test ( 'useSidebar' , async () => { jest.spyOn(localStorage, 'getItem' ).mockImplementation(() => 'true' ); const setItem = jest.spyOn(localStorage, 'setItem' ); const { result } = renderHook(() => useSidebar()); expect (result. current .isCollapsed).toBe( true ); expect (setItem).not.toHaveBeenCalled(); act(() => result. current .setIsCollapsed( false )); expect (result. current .isCollapsed).toBe( false ); expect (setItem).toHaveBeenCalledTimes( 1 ); } ); 上記の例では jest.spyOn() を使用して localStorage をスタブしていますが、 jsdom には localStorage のフェイク実装がすでに含まれており、こちらに依存した方がより信頼性が高まるでしょう。特に localStorage は Web 標準の API であり、その振る舞いや API に破壊的変更が生じる可能性は比較的低いと考えられます。また、 localStorage はネットワークアクセスが発生するわけでもなく、高速に動作することが期待されます。そのため、わざわざフェイク実装やスタブを用意せずとも比較的信頼性の高いテストが書けそうです。 afterEach (() => localStorage. clear ()); test ( 'useSidebar' , async () => { localStorage. setItem ( 'isSidebarCollapsed' , 'true' ); const { result } = renderHook(() => useSidebar()); expect (result. current .isCollapsed).toBe( true ); act(() => result. current .setIsCollapsed( false )); expect (result. current .isCollapsed).toBe( false ); expect (localStorage. getItem ( 'isSidebarCollapsed' )).toBe( 'false' ); } ); このようにスタブを使用せずに、実際のオブジェクトとのやりとりも含めたインテグレーションテストを記述することで、より信頼性の高いテストが書けるケースもあります。 Testing Library を使ってコンポーネントのインテグレーションテストを記述する 次はコンポーネントに対するテストの観点から考えてみます。 Enzyme / Vue Test Utils について コンポーネントのテストという観点では、React においては Enzyme 、Vue.js においては Vue Test Utils のような高機能なテスト用パッケージがあります。 これらのパッケージはレンダリングされたコンポーネントの状態を直接問い合わせたり、CSS セレクターによるレンダリング結果の柔軟な問い合わせなどをサポートしてくれる便利なライブラリーです。 これらのライブラリーを使用して信頼性の高いテストコードを書くことも可能ではあると思います。しかし、そのためには注意深くテストの記述やレビューなどを行う必要があります。 React Testing Library や Vue Testing Library などのいわゆる Testing Library では最初から信頼性を念頭に置いて設計されており、これらのライブラリーを利用することでより信頼性の高いテストコードを記述しやすいです。 なぜ Testing Library を使うのか? 公式の Guiding Principles で解説されていますが、Testing Library の考えとして、テスト対象のコンポーネントに対して「ユーザーは実際にブラウザー上でどのようにして対話するか?」という観点からテストを記述できるようにすることで、テストコードが対象コンポーネントの実装の詳細に依存することを回避し、より信頼性の高いテストコードを記述できるようにしてくれます。 github.com Testing Library の説明は Web 上にすでにたくさん存在しているため、簡潔に特徴を紹介します。 例えば、Enzyme においてはコンポーネントのレンダリング結果に対して CSS セレクターを使用して柔軟に問い合わせを行うことが可能です。 const wrapper = shallow(< MyForm />); wrapper. find ( '.some-button' ).simulate( 'click' ); それに対して Testing Library では CSS セレクターによるレンダリング結果の問い合わせ機能が意図的に提供されていません。その代わりにアクセシビリティーの観点から問い合わせることが推奨されています。以下は React Testing Library を使用した例です。 import { render } from '@testing-library/react' ; import { userEvent } from '@testing-library/user-event' ; test ( 'MyForm' , () => { const user = userEvent.setup(); const screen = render(< MyForm />); // button ロールを持ち Save というアクセシブル名をもつ要素を問い合わせ、それをクリックします await user.click( screen .getByRole( 'button' , { name : 'Save' } )); } ); ユーザーが実際に UI を操作する際は CSS セレクターという実装の詳細に基づいて要素を認識しているわけではなく、個々の要素の役割やラベルなどに基づいて操作します。Testing Library ではこの考えに基づき、意図的に CSS セレクターによる問い合わせを禁止し、代わりにアクセシビリティーに基づいた問い合わせを推奨しています。 また、CSS セレクターによる問い合わせを避けることで、例えば、クラス名がリネームされた際に意図せずテストが失敗してしまうことを防止できます。 また、Enzyme においてはコンポーネントの現在の状態を直接問い合わせたり、またはコンポーネントで定義されたメソッドを直接呼ぶことも可能でした。しかし、コンポーネントの状態や定義されたメソッドというのは実装の詳細です。例えば、コンポーネントの状態がリネームされた場合、それにテストコードが依存していた場合、テストは容易に壊れてしまいます。 const wrapper = shallow(< MyForm />); wrapper. find ( '.some-input' ).simulate( 'click' ); expect (wrapper. state ( 'isSaving' )).toBe( true ); // コンポーネントの状態を直接問い合わせる (もし state がリネームされると、このテストは失敗します) Testing Library ではこのようなコンポーネントの状態の問い合わせやメソッドの直接的な呼び出しも廃止されており、先ほどの CSS セレクターの例と同様にアクセシビリティーの観点からレンダリング結果を問い合わせたり、要素を操作することによって対応することが想定されています。 const user = userEvent.setup(); const screen = render(< MyForm />); await user.click( screen .getByRole( 'button' , { name : 'Save' } )); expect ( await screen .findByRole( 'img' , { name : 'Saving' } )).toBeVisible(); // アクセシビリティーに基づいてレンダリング結果を問い合わせる このように Testing Library では意図的に実装の詳細への依存を回避することで、高い信頼性を提供してくれます。 Testing Library を利用する際の Tips どのクエリメソッドを使うべきか? 詳細については公式ドキュメントに記載されていますが、基本的には ByRole クエリーを使用して問い合わせをすることが推奨されています。 github.com 具体的には、以下の場合、 Save というアクセスシブル名を持つ button ロール を問い合わせます (大抵の場合、 Save というラベルが設定された button が見つかることでしょう) screen .getByRole( 'button' , { name : 'Save' } ); ByRole クエリーを使用することで、特定の要素をユーザーが特定・操作する際の意図に基づいて問い合わせることができます。極端な例ではありますが、ある UI コンポーネントフレームワークから別の UI コンポーネントフレームワークへ移行したとしても、 ByRole クエリーに基づいて要素を問い合わせていれば、ある程度はテストコードを変更せずにそのまま動作し続けてくれることが期待できます。 もし ByRole クエリーを利用することが難しい場合は、 ByLabelText または ByPlaceholderText などの代替手段を利用すると良いと思います。 ByTestId はどうしても他の手段では要素を問い合わせることが困難な場合に限定して使用すると良いです。 イベントを発火させる際は @testing-library/user-event を使う 例えば、React Testing Library には fireEvent() というAPIがあり、要素に対して任意のイベントを発火させることが可能です。例えば以下のように記述することで、特定要素に対して click イベントを発火させることができます。 fireEvent.click(someButton); しかし、実際にユーザーがブラウザーをマウスで操作してクリックする際は、まずマウスによってカーソルをボタンの上部まで移動させ 、その後、マウスの左キーをクリックするといったように、実際にはその背後では様々なイベントが発火されています。 @testing-library/user-event パッケージは、このようなユーザーが実際にブラウザー上で特定の要素を操作する際の一連の振る舞いを可能な限り再現してくれるパッケージです。 import { userEvent } from '@testing-library/user-event' ; // ... const user = userEvent.setup(); const someButton = screen .getByRole( 'button' , { name : 'Foo' } ); await user.click(someButton); 要素を操作する際は fireEvent() ではなく @testing-library/user-event を使用することで、より信頼性が改善されることが期待できます。 バグの修正時にはリグレッションテストを記述する 長年、プロダクトの開発や運用を続けていると、どうしてもバグ修正などが積み重なった結果、意図の不明瞭なコードなどができてしまいがちです。 しかし、AI コーディングエージェントはそれらの背景の情報を持っておらず、意図せずそういった不明瞭なコードを書き換えてしまう可能性が考えられます。 そういったケースへの対策や、バグの再発防止などのために、リグレッションテストを記述するとより安心して運用が行いやすくなると思います。 具体的には、あるバグが発見された際には、まずそのバグを再現するためのテストコード (リグレッションテスト) を記述すると理想的です。 例えば、与えられた数値の合計値を求める sum() を例に考えてみます。 const sum = (... numbers ) => numbers. reduce (( x , y ) => x + y); この sum() はある特定の状況下で例外が発生してしまうことが発覚しました。具体的には引数が一つも与えられていない場合に例外が起きてしまいます。この場合、 0 が返却されると望ましそうです。 sum() を修正する前にまずはバグを再現するテストコードを記述します。 test ( 'Regression test for issue #1234' , () => { expect (sum()).toBe( 0 ); } ); この状況でこのテストコードを実行してみます。失敗した場合、意図通りにテストコードによってバグを再現できています。 それでは実際にこのバグを修正してみます。 const sum = (... numbers ) => numbers. reduce (( x , y ) => x + y, 0 ); 修正後、再度テストコードを実行し、今度はテストが成功することを確認します。 こうすることにより、追加したリグレッションテストコードがバグの再発防止のための仕組みとして機能してくれます。もし AI コーディングエージェントによって本来の意図を損なう形でコード変更が行われてしまった際も、CI でテストコードを自動実行しておけば、事前に気づくことができます。 今回は解説のために単純な関数を使用した例で紹介しましたが、実際には React Testing Library などを活用することで、UI のバグなどに関するリグレッションテストを記述することも可能です。 まとめ ブラックボックステストを意識する テストダブルや Testing Library などを例に、信頼性の高いテストコードを記述するための方法について紹介しました。本番コードと同様にテストコードにおいても実装の詳細に強く依存してしまうと、テストコードの信頼性が低下してしまうことがあります。できる限りブラックボックステストとして記述することを意識すると良いと思います。 より安定したものに依存する テストコードにおいても本番コードと同様により安定したものに依存することを意識すると、信頼性を高めることが期待できます。 具体的には、サードパーティーライブラリーは「変わりやすいもの」の最たる例ではないかと思います。サードパーティーライブラリーに依存したテストコードを記述する際は、サードパーティーライブラリーが提供する API に対して jest.spyOn() や jest.mock() などを使用してスタブするよりも、以下の方法などを検討すると良いでしょう。 サードパーティーライブラリーが提供する API をスタブせずにインテグレーションテストを記述する サードパーティーライブラリーによって達成したい目的に基づいて interface を定義し、テスト対象コードをサードパーティーライブラリーではなくその interface に依存させる (これによってフェイクオブジェクトを注入したり、サードパーティーライブラリーの API に破壊的変更が加わった際のテストコードへの影響を避けることが期待できます) 終わりに 本記事で紹介した内容が少しでも役に立てば幸いです。この記事で度々紹介した Googleのソフトウェアエンジニアリング はオススメなので、今回紹介したような内容などに興味があればぜひ参照ください! また、明日も Advent Calendar の記事を公開予定のため、もしご興味があればぜひご覧ください! 参考文献・出典 書籍 『Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス』 Wikipedia "Test double"
アバター
こんにちは、ホセです! 日本語と英語話せるエンジニアです。PyCon JPには初めて参加しました! 本件はPyCon JP って何か、今年の気になったトーク、開発スプリントのテーマと個人感想を書かせていただきます! 今年のテーマ PyConは、Pythonに関する世界最大級のカンファレンスであり、日本では2011年1月末には、品川シーサイドで日本初のPyConである「PyCon mini JP」が開催されて以来、これまで14年間にわたり東京で開催されてきました。 今年は「多様性」をテーマに、初めて広島で行われました! 2025.pycon.jp 日程: 2025年9月26日(金)〜27日(土) 会場: 広島国際会議場 (広島県広島市中区中島町1−5) 主催: 一般社団法人 PyCon JP Association トーク [招待講演】PEP 750 の共同著者 青野高大 氏による Python3.14の新機能の紹介 github.com さすがの招待講演。新たな T-strings の開発者の一人から説明を聞くのが貴重な経験でした。リリースは楽しみですね! Financial analysis in Python 2025.pycon.jp Nicholas氏は用語の説明はうまくてわかりやすかったです!ファイナンスが興味ある方におすすめ! Pythonスレッドとは結局何なのか? ~CPython実装から見るNoGIL時代の変化~ 本日の発表資料です。PyConにてThreadについてのテーマで お話させていただきました。 とてもたくさんの方に聞いて頂きまして大変嬉しかったです。講演の後も10人の質問行列ができるなど、発表してよかったと感じました。 https://t.co/50ehUcvNbD #pyconjp_4 #pyconjp2025 — Shugo Manabe: Recustomer CTO (@curekoshimizu) 2025年9月26日 Manabeさんの深い知識に圧倒され続け、CPythonの歴史に関するお話もとても面白い講義でした。 Weaponizing MCP Servers: Production-Ready AI Agent Infrastructure with Python 2025.pycon.jp 本番に動いているMCPってどんな問題が発生するのかとMicrosoft エンジニアの方からの発表は興味深かったです。 Vijayさんもとてもフレンドリー、レクチャの後いろいろ話させてもらいました! 【Day 1 Keynote】Sebastián Ramírez 氏 Behind the scenes of FastAPI and friends for developers and builders 2025.pycon.jp Sebastian氏は前からYouTubeで発表をフォローしていて、実際にお会いできて一番驚いたのは「お気遣い」でした。 今回は日本での初めての発表ということもあり、分かりやすい英語で話してくれたことにオーディエンスは感謝していました。 発表も素晴らしくてメッセージも簡単でも深い:「Solve a problem」。問題があったら、解決を考えて解決してみろと。本当に解決できたら、いいプロダクトになると。エンジニアって結局プログラムを作って誰かに使って欲しいから、開発する前にちゃんとプロブレムを考えないと時間の無駄になります。 その他 Apache Arrowの Contributorからの説明:  https://2025.pycon.jp/en/timetable/talk/Q7YYNZ Pythonでモバイルアプリを作るの? https://2025.pycon.jp/en/timetable/talk/S8DUBR Django Ninja入門! https://2025.pycon.jp/en/timetable/talk/ZNSAA9 帰宅してからすぐに使わせてもらったpytestの10個のこつ  https://2025.pycon.jp/en/timetable/talk/ZFYREY 開発スプリント スプリントの日はチームを組んでコードを書くのがテーマです。今回は Tachibanaさん のチームに参加して Streamlit へのPRを投げました! github.com オフィシャルパーティー 会議間の枠は15分ぐらいしかないのであまり他の参加者と話す時間が少ないため、パーティーの時は最高でした。広島フードも美味しかったし、いろんな方と話して本当に良かった。 スペイン語圏同士でもあって、Sebastian氏ともいろいろ話しました! 感想 「多様性」というテーマは本当にふさわしいと感じました。外国人参加者は50人以上いたそうで、英語のライブ翻訳もきちんと用意されており、スタッフの皆さんも英語で案内していました。私の場合は日本語で話すことが多かったのですが、英語圏の方と話したときに「日本の温かさを感じた」と言ってもらえたのが印象的でした。 そして、来年の会場も広島に!プロポサールを準備して楽しみにしてます! まとめ 今年のRevcomm エンジニア参加は7名、その中で松土さんが登壇して 陶山さん がスタッフでした。 みんなと面白い時間を過ごして良かったです。
アバター
RevCommで主に音声認識・音声感情認識・話者分離の研究開発を担当している石塚です。 本記事では、 日本音響学会2025年秋季研究発表会 で発表した「Room Simulatorを用いたデータ拡張によるNeural Speaker Diarizationモデルの実環境適応」の研究について、解説します。 石塚賢吉(いしづか けんきち) プリンシパルリサーチエンジニア。筑波大学大学院博士後期課程卒業。博士(工学)。日本HP株式会社にて通信事業者向けのシステム開発、株式会社ドワンゴで全文検索システムの開発などに従事。2019年12月、株式会社RevComm入社。音声認識、音声感情認識、全文検索システムの研究開発を行なっている。 → 過去記事一覧 背景:「いつ、誰が話しているか」の推定は難しい AI技術の進歩は目覚ましく、その恩恵はビジネスシーンにも広がっています。特に、会議の音声を自動でテキスト化するアプリケーションは、議事録作成の効率化や情報共有の促進に大きく貢献しており、導入する企業が増加傾向にあります。これらのアプリケーションの中核を担う技術の一つが、Speaker Diarization (SD) です。これは、音声データの中から「いつ、誰が話しているか」を推定する技術であり、会議の参加者を識別し、発言内容を整理するために不可欠です。 しかし、現実は理想通りとは限りません。会議室の環境は千差万別であり、アプリケーションの利用環境によっては、音声品質が大きく劣化する可能性があります。例えば、話者とマイクの距離が遠い場合、音声が小さくなり、周囲の雑音に埋もれてしまうことがあります。また、室内の残響が大きいと、音声の輪郭がぼやけて聞き取りにくくなります。これらの要因が複合的に作用することで、SDの精度が低下し、結果として、テキスト化された議事録の信頼性が損なわれてしまうという課題がありました。 課題:SDモデルの学習には「大量の教師データ」が必要…でも、作成コストが高い SDモデルを特定の環境、例えば「カフェでの会話をスマートフォンで録音する」のような環境でうまく機能させるためには、その環境で録音された大量の音声データでSDモデルをファインチューンすることが有効です。 しかし、この学習データを作成するには、録音された音声を聞きながら「この区間はAさん、次の区間はBさん…」と、手でラベル付け(アノテーション)することとなり、膨大な時間とコストがかかるという問題がありました 。 解決策:「Room Simulator」でリアルな学習データを人工的に作り出す そこで注目したのが、 「Room Simulator」 という技術です。これは、部屋の広さや反響、マイクと話者の位置関係などをコンピュータ上で再現し、まるでその環境で録音したかのような音声をシミュレートできる技術です。 RevCommは、話者ごとに録音されたビデオ会議の音声データを大量に保有しています。本研究では、話者ごとに録音されたビデオ会議の音声データを元として、 PyRoomAcoustics という、 鏡像法に基づくRoom Simulator で対象の環境を想定した音声の学習データを大量に生成することを考えます。この手法の利点は以下の通りです。 大量の自然な会話データが使える : 合成音声などでなく、実際のビデオ会議の発話録音なので、会話の内容が自然です ラベル付けが不要 : 最初から話者ごとに音声が分かれているため、面倒な手作業のアノテーションが必要ありません 低コストで大量生産 : コンピュータ上でシミュレーションするため、大量の学習データを生成できます 評価実験: データセット構築 提案手法の有効性を確かめるため、まず対面会議をスマートフォンで録音する環境を想定した3種類のデータセットを構築しました。 1. Computer Simulation Dataset (CSD) - 人工的な学習データ MiiTelで行われたビデオ会議(2〜7名)の音声をもとに、会議室とカフェで行われる対面会議をスマートフォンで録音する環境をRoom Simulatorでシミュレートしながら生成した音声のデータセットです。 シミュレートした環境 : - 会議室 : 8畳の部屋 (4m×5m×2.5m) を想定し、反響や音の減衰を再現しました。話者の音源は会議の参加人数に応じて図1の丸の記号に付与された番号の順番で配置します。 仮想会議室の音源とマイクの配置の例 カフェ : 広い空間 (20m×20m×4m) を想定し、Musanデータセットの人混みの環境音や音楽をミックスして、より雑音の多い環境を再現しました。音源とマイクの位置関係は会議室と同じです。 このデータセットの長所と短所: 長所: 高速(AWS EC2 r5.2xlargeインスタンスで音声時間の0.0045倍の処理時間)かつ大量にデータを生成可能です 短所: 現実世界の複雑な音響特性を完全には反映できません 2. Human Annotation Dataset (HAD) - 人間がアノテーションした評価データ 対面会議(30件, 24h)をスマートフォンで録音した音声について、人間が手作業で「いつ、誰が話しているか」をアノテーションして構築した評価用データセットです。これは、最も現実に近い評価用データです。 このデータセットの長所と短所: 長所 : 現実の音響特性を最も忠実に反映できます 短所 : 作成に 音声の長さの約2倍から8倍 もの時間がかかり、コストが非常に大きいです 3. Loudspeaker Simulation Dataset (LSD) - 物理的に再現した評価データ 話者ごとに録音されたビデオ会議(125件, 112h)の音声を、図1の構成で複数のスピーカー装置で再生し、スマートフォンで録音することで、会議室での対面会議をスマートフォンで録音する環境を物理的にシミュレーションしながら構築したデータセットです。この方法は、 LibriCSS データセットの作り方を参考にしています。 このデータセットの長所と短所: 長所 : 低コストでCSDより現実的な録音環境を反映できます 短所 : スピーカーから再生される音声と人間の声道から発せられる音声との違いは反映されず、また録音に実時間分の時間がかかります 評価実験: SDモデルのファインチューニング 次に、PyAnnote Audio 3.1というSDツールキットの事前学習モデルをベースライン (モデルP) として、下記の4種類のファインチューン版のモデル(モデルA〜D)を構築し、精度の比較を行います。 モデルP: PyAnnote Audio 3.1の事前学習モデルです モデルA: モデルPを元のビデオ会議音声(386件, 309h)でファインチューン (FT) したモデルです モデルB: モデルPをCSD(386×2[会議室とカフェ]件, 647h)でFTしたモデル。386件の会議はモデルAと同じものです モデルC: モデルPをより大規模なCSD(1,516×2件, 2,536h)でFTしたモデルです モデルD: モデルPをより大規模なCSD(1,516×2件, 2,536h)と、中国語のSDデータセット AISHELL-4 (168件, 93h)の学習セットでFTしたモデルです 上記のSDモデルP,A~Dを用いて、学習に使用されていないLSDとHAD、および中国語のSDデータセットである AISHELL-4 のテストセットをSDした時の Diarization Error Rate (DER) を下記の図に示します *1 。Diarization Error Rateは、値が小さいほど精度が良いことを示します。 各データセットに対するDiarization Error Rate(DER、エラーバーは標準偏差) ご覧の通り、Room Simulatorで生成したデータで学習した モデルC と モデルD は、既存モデル(モデルP)や、シミュレーションなしのデータで学習したモデル(モデルA)よりも、HADとLSDでのエラー率が大幅に改善していることが分かります。 また、モデルDの結果を見ると、ターゲット環境のデータ (CSD) だけでなく、中国語のデータセットなども追加で学習させることで、特定環境への適応(LSDやHADでの高精度)と、他の環境への対応力(汎化性能)を両立した、より安定して堅牢(ロバスト)なモデルになることが示唆されました。 まとめ 本研究により、Room Simulatorという技術を活用することで、「いつ、誰が話しているか」を特定するAIモデルを、低コストかつ効率的に特定の実環境へ適応させられることがわかりました。 今回は、PyRoomAcousticsという、計算量の小さな鏡像法をベースとするRoom Simulatorを用いましたが、より精密な波動音響解析などのシミュレーション手法を用いた場合に、どのように結果が変わるのかにも興味が湧きます。 今後も、SDの精度改善に取り組んでいきます。 *1 : こちらのDERは時間の誤差許容量[ms] のcollar を500ms とし、オーバーラップを無視する設定で計算されていることにご注意ください。
アバター
イベント概要 https://2025.pycon.jp/ja 公式サイトから引用 2025年、Pythonカンファレンス「PyCon JP」は、「あつまれPythonのピース」をテーマに、広島で開催されます。初の地方開催となる今回は、会場が平和記念公園内の国際会議場。平和を願い、発信し続けてきたこの地で、Pythonが大切にしてきた「多様性」や「オープンさ」を、あらためて感じられるイベントになるでしょう。 関東や遠方の方も、少し足を伸ばして、凛とした空気の平和公園で技術トークを楽しむ時間には、きっと特別な価値があります。 全国から集まる仲間たちと、コードの話も、これからの社会の話もちょっぴりしてみませんか?お好み焼きも、牡蠣も待っています。9月、広島でお会いしましょう! 日程: 2025年9月26日(金)〜27日(土) 会場: 広島国際会議場 (広島県広島市中区中島町1−5) 主催: 一般社団法人 PyCon JP Association チケット申し込みはこちらから pyconjp.connpass.com 登壇情報 Pythonだけでつながるあなたのアイディア、フロントエンドもPythonで Pythonユーザーが作成したスクリプトやデータ分析、機械学習モデルなどの成果物を「見せる」「使ってもらう」には、アプリ化というハードルがあります。 本セッションでは、Pythonユーザーが自分のコードをすぐWebで共有できるようになるための手法として、PythonだけでGUIを構築できるWebアプリフレームワーク「Flet」の仕組みと使い方を紹介します。 Fletは、裏側でPyodideを使ってWebAssemblyを活用することで、従来のWeb開発の壁を取り払ってくれています。まずはこのフレームワークを通してPiodideとWASMを深掘ります。 次に実際に使えるサンプルを通じて、「見せられるPythonアプリ」を手軽に作る方法をデモを交えて解説します。 Pythonだけで、誰かとつながれる そんな開発体験を提案します。 日時: 2025年9月26日(金) 15:00 - 15:30 登壇者: Shintaro Matsudo リンク: https://2025.pycon.jp/ja/timetable/talk/S8DUBR
アバター
はじめに 基本知識・用語の確認 1. なぜ移行をしたのか 1.1 テストの実行時間がとにかく長い 1.2 Nuxt3移行に伴うビルド&テスト環境の統一 1.3 テストコードの見直しが必要 2. Vitest を選んだ理由 2.1 手軽 2.2 フロントエンドロードマップの Testing に変化 2.3 Jest との互換性も問題なし 3. 実装 3.1 Vitest 1. Vitest をインストール 2. Vitest の設定ファイルを追加 3. Jest で記述されている箇所を、vi に変更する 4. テストで使用する環境変数の読み込み 5. エラーの解消 6. テストを成功させる 7. Jest で使っていた記述を削除する 4. 結果 まとめ はじめに はじめまして。フロントエンドエンジニアをしている伊藤と申します。 私はプロダクトの動作を保証するものとして、テストは欠かせないものだと思っています。 私が所属するチームのプロダクトでも以下のテストを行なっています。 テストフレームワークを利用したユニット/コンポーネントテスト クラウドサービスを利用したE2Eテスト QA その中で、私が一番関わりが深いのは、テストフレームワークです。現在のチームに参画した時から、チームの JavaScript テストフレームワークは Jest が使われており、長年苦楽を共にしてきました。ただ、そんな親友の Jest ともついにお別れする時がきてしまいました。 本記事では、 経緯などを踏まえて、Jest → Vitest への移行について紹介していきます。 基本知識・用語の確認 Jest JavaScript コードの品質と信頼性を確保するために、テストの作成・実行・検証・分析を一つで完結できるように設計された便利なツール Vite モダンな JavaScript/TypeScript プロジェクトの開発とビルドを、信じられないほど速く、シンプルに、そして効率的に行うための次世代ツール Vitest Vite の高速性を活かし、Jest と高い互換性を持つことで、開発者体験とテスト効率を大幅に向上させるJavaScript/TypeScript向けの新しいテスティングフレームワーク 1. なぜ移行をしたのか 1.1 テストの実行時間がとにかく長い 所属するチームでは、Git のワークフローを使用しています。特定のブランチに向けて PR が作成されるとワークフローが開始されます。そのワークフロー内で、Jest で記述されたテストを実行しています。 ただ、そのテストの完了までがとにかく長いです。平均して15分ほどかかっていました。DevUX の観点からすると、とんでもない悪影響です。 開発者の満足度低下 生産性やイテレーション速度の低下 バグ発見の遅れ コード品質への影響 1.2 Nuxt3移行に伴うビルド&テスト環境の統一 Vue2/Nuxt2 から Vue3/Nuxt3 へ移行した際、ビルドツールについても変更を行いました。 Nuxt3 では標準で Vite が採用されているため、Webpack から Vite へ切り替えています。 ただし、テスト環境は従来どおり Jest(Webpackベース)を利用しており、本番環境との間に差異が残っていました。 そこで、より実行環境に近い形でテストを行えるように、Nuxt3と親和性の高いVitestへ移行が必要でした。 上記を解決することで、環境の一貫性が保たれ、テストの信頼性も向上すると考えました。 1.3 テストコードの見直しが必要 私が参画した後もテストコードは増えていく一方でした。 どんなテスト内容か、最適なテストができているかなどは年々不透明になっていました。 潜在的な課題とはなっていましたが、テストコードを見直すことは、他の新規開発を優先してしまったため、後回しになっていました。 2. Vitest を選んだ理由 2.1 手軽 Vitest は Vite がないプロジェクトでも活用することができます。 手軽に勧められる点がポイントです。 2.2 フロントエンドロードマップの Testing に変化 フロントエンドエンジニアが学ぶべきロードマップの中に、Testing という項目があります。 おそらく Jest を勧めていた箇所が、Vitest に変わっていました。 https://roadmap.sh/frontend 2.3 Jest との互換性も問題なし 下記の表は、簡単に Jest と Vitest を比較したものです。 比較をして、Vitest への移行をしても問題ないと思いました。 (独自で調べた内容で作成したため、誤っている場合もあります) 3. 実装 3.1 Vitest 1. Vitest をインストール npm install -D vitest 2. Vitest の設定ファイルを追加 Vite を使っているプロジェクトなら、vite.config.ts に設定を記述 使っていない場合は、vitest.config.ts を新規作成 (下記は、弊社の例です。) ``` /// <reference types="vitest" /> import path from 'path'; import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { config } from 'dotenv'; // .env.test を明示的に読み込む config({ path: path.resolve(__dirname, 'src/.env.test') }); export default defineConfig(() => { return { plugins: [vue()], resolve: { alias: { vue: 'vue/dist/vue.esm-bundler.js', '@@': `${__dirname}`, '@': `${__dirname}/src`, }, }, test: { setupFiles: ['src/tests/setup.ts'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], exclude: ['node_modules'], // テスト対象から除外するパターン environment: 'jsdom', globals: true, environmentOptions: { jsdom: { url: 'http://localhost/', // 必要であれば設定 }, }, cache: true, }, }; }); ``` 3. Jest で記述されている箇所を、vi に変更する 一括変換でもいいですし、AI エージェントに任せるのも手です。 4. テストで使用する環境変数の読み込み テスト用の環境変数を用意している場合は、vite.config.ts にその旨を記載します。 // .env.test を明示的に読み込む config({ path: path.resolve(__dirname, 'src/.env.test') }); 5. エラーの解消 mock を使用している箇所でエラーが出たので解消しました。 (エラー内容:mock を使うなら、一番最初の行に記述してください。) Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock 6. テストを成功させる 7. Jest で使っていた記述を削除する jest.config.ts など、Jest 関連のものを削除します。 4. 結果 テストの実行時間の大幅な短縮ができました。 Vitest への移行と、既存の不要テストの削除による成果だと思います。 以前まで、15分かかっていたテストが5分で終了するようになりました。 おかげでリリースまでの時間も縮小することができました。 もしかすると、Vitest への移行だけでここまで縮小することはなかったかもしれませんが、 プラスの成果はあったと思います。 まとめ Jest → Vitest 移行を行なったことで一番の収穫は、既存のテストを見直す機会を得たことです。 どうしても新規開発をしていると、テストコードにまで気が回らなくなってしまいます。 その結果、放置されてしまっていて、テスト内容が適正かなどの判断が後回しになっていました。 ただ、今回の移行でテストコードの見直しをするきっかけをもらったと思います。 AI を使った開発により、テストコードでテストしたいことが実現しやすくなりました。 本記事が、皆様の関わっているプロジェクトのテストコードを見直すきっかけになれば幸いです。 これからもより良いテストコードを目指していきます。
アバター
はじめに こんにちは、RevCommでエンジニアをしている加藤と申します。先日AWS Unicorn Day Tokyo 2025に参加してきました!今回はその中で特に印象に残ったセッションをお話ししたいと思います! 会場の様子/全体の雰囲気 会場は原宿駅からほど近い東郷記念館というところでした。オフィスは渋谷にあるので朝早めにオフィスで仕事をしてから会場まで歩くことにしました。暑かったですが、青山通りを抜けて行く感じは気持ちが良かったです。 東郷記念館 外から バルコニーが綺麗 今年は AWS Summit Tokyo(幕張メッセ)や Google Cloud のイベント(Google 渋谷オフィス)にも参加しましたが、今回の会場である東郷記念館はまた違った印象でした。結婚式場としても使われるだけあって華やかで、池には鮮やかな鯉が泳ぎ、外国人観光客が写真を撮る姿も多く見られました。ちょっとした観光地のような雰囲気の中でのイベントでした。 参加セッション & 学び 今回は参加セッションの中でも特に印象的だった3つについてご紹介します。 1. “AIエージェント時代の創業” — コネクシー株式会社 コネクシー社のSlackからPRまでのワークフロー 設立わずか2025年2月のコネクシー社による、「エージェントネイティブ」な開発体制の構築事例が紹介されました。特に意識されていたのは「モバイルから誰でも開発できること」のようです。エンジニアだけでなく CEO も Slack 経由で Devin にメッセージを送り、PR まで作ってもらっているそうです。 面白かったのは、Devin への指示がどんどん短くなっていったという話です。最初は「XX という機能を作りたいです。ヘッダーは YY に配置して、ボタンは ZZ に……」と詳細に指示していたのが、日が経つにつれ「XX を追加してください」といったシンプルな命令になっていったそうです。 人間の側がAI が文脈(コンテキスト)を理解していると自然に感じてしまう心理的変化が興味深く、コンテキストを確実に伝えるためにはDevin AI の Knowledge や Playbook の機能をきちんと整備する必要があると感じました。 2. “ID 管理基盤内製化の意思決定” — 株式会社カミナシ カミナシ社によるID基盤内製化 概略構成図 一般的には IDaaS が選ばれるケースが多い中、カミナシ社はあえて ID 管理基盤の内製を選んだそうです。直前のセッション「自社ブランドの共通 ID・シングルサインオンの設計と実装 - Amazon Cognito 設計パターン -」で Cognito の全体像を紹介した後に、「Cognito を使わない」という選択が語られたのが印象的でした。AWS的にはCognitoを使わなくてもAWSの別サービスを使っているからアリなんでしょうね。 もちろん内製には RFC 準拠の開発やドメイン知識が求められ、難易度は高いものの、今回の意思決定では「役員の迅速なトップダウン判断」が大きなポイントになったそうです。ID 基盤の移行は数年単位の大規模プロジェクトになることも多いため、将来的なプロダクトビジョンに基づく判断が有効だったのだと思います。 3. “AI エージェントとはそもそも何か? - 技術背景から Amazon Bedrock AgentCore での実装まで -” — AWS Japan speakerdeck.com 最後に聴講したのは、AWS が提供する Agent 構築環境に関するセッションでした。Agent構築においてAWSが重視しているのは「差別化に繋がらない重労働(Undifferentiated Heavy Lifting)」のようです。後で調べましたが、これはAWSではよく出る言葉で「サーバーのラッキング、積み上げ、電力供給などのデータセンターの手間のかかる運用作業」が Well-Architected フレームワーク に書いてありました。この考え方は、AWS Lambdaにも引き継がれ、そして現在は、エージェントの構築にもこの考え方が活かされているようです。 特に認証、ブラウザ利用、メモリ保存といったどのエージェントでも必須になるような機能をAWS側で提供してくれることは非常にありがたいなと思いました。 まとめ AWS Unicorn Day Tokyo2025に参加してきました。技術的な話だけでなく、事業戦略や意思決定の考え方まで踏み込んだイベントでした。 やはりAWSはエージェントという新しい機能をどうAWSで実行するかという内容を含んでいました。スタートアップに参加しやすいようなシステム作り、コミュニティづくりを目指しているように感じました。 私も業務でStrand AgentsやBedrock AgentCoreを触ったりしています。今回得られた知見を活かし、自社プロダクトにどのように適用できるかをさらに検討していきたいと思います!
アバター
9/10(水)〜12(金)に東北工業大学(仙台)で開催される日本音響学会秋季研究発表会に、プリンシパルリサーチエンジニアの石塚賢吉が登壇します。会場にいらした方は、是非お立ち寄りください。 登壇日時・内容 日時 : 9/12(金)13:00〜15:00(※石塚は13:00〜14:00に対応します) 場所 : ポスター会場 タイトル : 「Room Simulator を用いたデータ拡張による Neural Speaker Diarization モデルの実環境適応」 概要 : 対面での会議録音では、話者ダイアリゼーションの際に残響を考慮する必要があります。本研究は、 残響を考慮したデータセットを効率よく構築し、その有効性を示した ものです。 イベント概要 日程 : 9/10(水)〜12(金) 会場 : 東北工業大学 八木山キャンパス(宮城県仙台市) リンク : https://acoustics.jp/annualmeeting/program/ 登壇者 石塚 賢吉 株式会社RevComm プリンシパルリサーチエンジニア 筑波大学大学院博士後期課程卒業。博士(工学)。日本HP株式会社にて通信事業者向けのシステム開発、株式会社ドワンゴで全文検索システムの開発などに従事。2019年12月株式会社RevComm入社。音声認識、音声感情認識、全文検索システムの研究開発を行なっている。 → 過去記事一覧
アバター
はじめに 生成AIの急速な発展により、エンジニアを取り巻く環境は激変しています。特に注目すべきは、Coding Agentの登場によって多くの場面で生成AIが実用的なコードを書けるようになったことです。実際、単純な機能実装からバグ修正まで、Coding Agentに任せる仕事が日々増えています。 一方で、Coding Agentを効果的に使いこなすには、エンジニア自身の高い技術力が不可欠です。適切な指示を出し、生成されたコードの品質を評価し、システム全体の整合性を保つためには、従来以上の深い理解が求められます。また、アーキテクチャ設計、技術選定、チームマネジメントなど、未だにCoding Agentには任せられない重要な業務も数多く存在しています。 さらに重要なのは、Coding Agentが人間の成長速度を大きく上回るペースで発展し続けていることです。エンジニアとして社会に貢献する活動を続けたいなら、自身もこれまで以上のスピードで成長することが不可欠になっています。 そこで本記事では、このような時代背景を踏まえ、私がエンジニアとして社会に貢献し続けるために生成AI(主にChatGPT)を活用している手法を紹介します。 1. 自分には何が足りていないか、何をやるべきかをChatGPTを使って考える 1.1 エンジニアグレードの客観的評価 まず、自分の現在地を正確に把握するために、ChatGPTにエンジニアのグレード判定を依頼しました。以下のプロンプトを使用しています。 私に質問をしながらソフトウェアエンジニアとしてのレベルを把握し、シニアになるために必要な知識や技術を整理してください。 質問は問題でも構いません。 質問は20問として、1回に1つ行なってください。似たような質問にならないようにしてください。 このプロンプトの特徴は、対話式で進行し、ChatGPTが1つずつ質問を出してくることです。1回に1つ、という指示を入れることで1回のやりとりで大量に質問してくるのを防いでます。例えば以下のような質問がありました。 「システム設計でキャッシュを導入する際に、キャッシュの有効期限(TTL)を設定する場合、どのような考慮事項がありますか?具体例を交えて説明してください。」 「データベースにインデックスを作成する際、インデックスを追加することによる利点と欠点をそれぞれ1つずつ挙げ、具体的なシナリオを用いて説明してください。」 「分散システムで 'CAP定理'(Consistency, Availability, Partition Tolerance)の3つの要素を簡単に説明し、それぞれがトレードオフとなる理由を述べてください。」 回答後、ChatGPTからは以下のような評価を受けました: あなたのスキルは、すでに中堅エンジニアとして高いレベルにあります。ただし、シニアエンジニアとして求められる「より大規模で複雑なシステムの設計」や「チーム全体を見据えた意思決定」のスキルをさらに伸ばす余地があります。 このような客観的な評価により、自分の現在地と次のステップが明確になりました。 1.2 マンダラチャートによる成長戦略の立案 次に、エンジニアリングマネージャー、ソフトウェアアーキテクト、スタッフエンジニアの3つのキャリアパスを想定したマンダラチャートの作成を依頼しました。 マンダラチャートとしたのは、必要だと思われる要素を網羅的に抽出したかったからです。 5年目のSaaS開発のソフトウェアエンジニアが、*** になるためのマンダラチャートを作成してください ※ *** の部分には、ソフトウェアアーキテクト、エンジニアリングマネージャー、スタッフエンジニアなどのキャリアパスを入れて実行します。 このプロンプトは非常にシンプルで、キャリアパスごとに別々に実行することで、それぞれに特化したマンダラチャートを生成できます。SaaS開発の経験を前提としているため、より実践的で具体的なスキルセットが提案されます。 例えばソフトウェアアーキテクトでは以下のようなチャートが生成されました。 1.3 ポジショニングマップによる現状分析 マンダラチャートの各項目を抽象化し、強み・弱みを横軸、機会の多さを縦軸にしたポジショニングマップを作成しました。また、NLP(神経言語プログラミング)の学習5段階をカスタムした成熟度の自己評価を各項目に対して行いました。 無意識的未経験(知らないしやったこともない)→ 緑色 意識的未経験(知っていてもやったことがない)→ 青色 意識的経験(知っていてやったことはある)→ 黄色 意識的有能(考えるとできる)→ 赤色 この評価をマッピング時の色に反映させることで、優先的に取り組むべき領域を視覚化しました。 3,4ヶ月くらい前に作成したのですが、青色も業務でやっていないことはないので、もっとやっていないといけないという厳し目の自己評価だったのだろうと思います。 1.4 実践への落とし込み ポジショニングマップの分析結果から、主に真ん中よりも左側にある青色の項目は比較的機会もあり伸ばす領域なのでここに注力すると良さそうなことがわかります。これによって業務では以下の2点を意識してタスクを取ったり、課題を探したりしやすくなりました。 課題が明確で、取り組めるもの x 自分が伸ばしたい領域のもの 課題がありそうだが明確ではなく、調査が必要なもの x 自分が伸ばしたい領域のもの 加えて、設計は開発と比べてたくさんやれる機会があるわけではないので、後述する Architectural Katas を始めることにも繋がりました。 2. Architectural KatasとChatGPTを使ってシステムデザインを学ぶ 2.1 Architectural Katasとは Architectural Katas( https://www.architecturalkatas.com/ )は、プログラマーがプログラマーとしての実践の機会を必要とするのと同じように、ソフトウェアアーキテクトにはソフトウェアアーキテクトとしての実践の機会が必要であるという願望から生まれたサイトであり実践方法です。社内で設計を学びたいならこういうものがある、と教えていただきました。 Architectural Katas 自体はグループで行う前提となっていますが、私は試しに ChatGPT とやってみて一定価値を感じたのでその方法を共有します。 2.2 ChatGPTによるシステムデザイン面接の実践 ChatGPTで Architectural Katas を実施する方法は、海外のBig Tech 企業でもよくあるシステムデザイン面接を参考にしています。ChatGPT を面接官としてどのように振る舞って欲しいかをプロンプトで指定します。 私はソフトウェアエンジニアとして優れたアーキテクトになるべくArchitectural Katas を活用して勉強したいです。 あなたは以下の指示に従い、ソフトウェアデザインの試験官のように振る舞って私の学習をサポートしてください。 - まず、私が Architectural Katas の設問をソフトウェアデザインの試験官であるあなたに与えます。 - 与えられた設問について曖昧な部分は、試験官であるあなたが定義する必要があります。定義をするだけで私が質問するまで公表する必要はありません。 - 私が設問について質問した場合にソフトウェアデザインの試験官として適切な返答をしてください。 - その返答を参考に、私は設問に対して設計を行い、回答を仕上げます。 - 私の回答に対してレビューを行い、良い点、更に考慮すべき点、改善案を提示してください。更に考慮すべき点、改善案については、その考慮をしたり案を出したりすることが、どれくらいのレベルのソフトウェアエンジニアに求められるか、難易度がわかるようにしてください。 このプロンプトによって、ChatGPT は問題を把握しても質問や回答があるまでは、何かをユーザーに教えるようなことはありません。ユーザーは問題を確認し、前提条件や想定されるユーザー数やリクエスト数やデータサイズなどを見積って ChatGPT に認識の確認するとズレていないか回答してくれ、設計をして提出するとフィードバックを得ることができます。 2.3 実際のイメージ オンラインでドキュメントを作成・編集・バージョン管理・印刷でき、手動・自動決済もできるサービスをつくるというような問題が出たときの、実践模様を書いていきます。 システムプロンプトを渡したあと、問題を渡します。 ChatGPT が返事をしてきます。(確認の観点を先回りして発言していますが、そういうときもあります。) 以下のように前提条件を確認すると、ChatGPTから返事が来ます。長いので割愛します。 実際に設計してみた回答を ChatGPT に送ります。 ChatGPT からフィードバックが返ってきます。 良い点も挙げてくれますが、改善すべき点もレベルと一緒に教えてくれます。 以下はフィードバックの抜粋です。 総評 スコープ内のコア機能(ドキュメント編集・バージョン管理・印刷ジョブ・決済)を 4 つのサービスに分割しており、役割も明確です。 エッジに CDN を置き、リアルタイム更新は WebSocket 専用サービスに任せる設計も理にかなっています。 初期利用者「数千 → 数百万」を見据え、キューで印刷ジョブを非同期化しているのもスケールに有効です。 ~~~ さらに考慮すべき点 & 改善案 バージョン管理の実装(Mid) S3 バージョニング+version_id を RDS に参照保存差分保存(delta/DDS)で容量削減 リアルタイム編集方式(Senior) 単独編集ならロック制御 (optimistic/pessimistic) 将来協調編集を見込むなら OT/CRDT ライブラリ採用 印刷ゲートウェイ不足(Senior) Print Job Service の下に Printer Gateway を追加:IPP over TLS、Secure Release (PIN/NFC)、ドライバー隔離 (コンテナ) 決済の信頼性 (Mid) PSP からの Webhook 受信 → 冪等キーで payment_status 更新 サービス間通信は Outbox/Polling で二重送金を防止 更に考慮すべき点 & 改善案 で知らなかったものについてはチケット化し、別でインプットしたり掘り下げることで、幅広い知識と観点を養えることを期待しています。 週1回を目安に始めて、まだ1ヶ月程度ですがそれでも知らなかった技術に出会い、自分が見落としやすい観点を見つけ、どこの知識が足りないのか、知っていると思っていて実はちゃんと知らなかったものは何かがわかるので、とてもおすすめです。 3. 英会話をChatGPTとやる 3.1 背景と課題 生成AIによるサービスが急増するなかで英語を学ぶことに時間を使うかどうかは、人によって判断が分かれると思います。私自身は、特に予定はありませんが社会に貢献し続けるために将来的に海外で働いたり大学院に進学したりを、自分のしたいタイミングでできるよう、IELTS を定期的に受験しています。IELTSは Reading、Listening、Writing、Speaking 全てのテストがあり、Speaking は自己紹介とインタビュー、スピーチ、ディスカッションという3つのパートに分かれています。現在の私のスコアはOverall 6.0ですが、Speaking だけが5.0とボトルネックになっている状況です。弊社には英語話者も在籍していますが、私が所属しているチームや関わる方々の多くは日本語話者なので、日常的に英語を話す機会はありません。 3.2 ChatGPTをIELTS試験官として活用 弊社には多言語学習・会話レッスン補助制度があるので、オンライン英会話を受けていた時期もありましたが、ChatGPTに音声による会話機能が出てから、ChatGPTに移行しました。現在は以下のようなプロンプトを利用しています。 IELTS Speaking Testの練習を行います。以下の条件に従って練習を全力で手伝ってください。 - "OK, I'm ready for {パート番号}" と入ったらそのパートの問題を1つランダムに出題してください。 - 私が英語で回答するので、それに対してAnswer(私の回答), Mistakes and Corrections, Natural Corrections, High Score Rephrasing, Additional Points, Example Answer を整理して教えてください このプロンプトによってフィードバックが6つの項目に体系化されます。 スピーキング力の向上のためには、話す内容が思いついていないのか、英語に変換できないのか、を分離するため、英語で話す以前にそもそも日本語で同じ質問をされたときに十分な回答ができるのか?ということを振り返る必要があります。 このプロンプトでは、「Additional Points」を挙げるよう指示しており、そのとき自分が思いつかなかった観点を教えてくれるので、話す内容を思いつけていないことを自然と振り返れるようになっています。 また、話す内容を思い付いているけれどもっと他の言い回しはなかったのだろうか、というときには「High Score Rephrasing」で、同じ内容をより高いスコアが期待できる表現に変換してくれるため、自分では思いつかなかった表現を教えてくれます。 3.3 実際のイメージ 技術進歩がコミュニケーション方法にもたらした影響について聞かれたときの模様を書いていきます チャット形式になっていますが、実際は音声でやりとりしています。 回答を終えるとChatGPT からフィードバックが返ってきます。 実際に私が話した内容について、三人称単数形になっていないことや、より自然な言い回しを教えてくれています。加えて私が言及していない内容に触れ、それらを含めた回答を作成してくれています。 以下はフィードバックの抜粋です。 Mistakes and Corrections Instead of "technology enable us," it should be "technology enables us." "Communicate remotely" is great, but you can simplify "in the past, we could not communicate with other people remotely" to "in the past, we couldn't communicate over long distances." Instead of "bring our content of talking or texting," you can say "transmit our conversations and messages." ~~ Additional Points You could also mention how technology has introduced new forms of communication, like video calls, social media, and instant messaging. ~~ Example Answer “In my opinion, the most significant impact of technology on communication is the removal of physical barriers. In the past, people had to be in close proximity to interact, but today we can instantly transmit our conversations and messages across the globe. Furthermore, innovations like instant messaging, social media platforms, and video calls have transformed how we connect: we can chat in real time via apps, share life updates and multimedia on social networks, or hold face-to-face conversations with someone thousands of miles away. These developments have made communication more seamless, diverse, and engaging than ever before.” 3.4 実践結果 ChatGPTから添削された文章を音読して空で言えるようにする、というのを始めてからボキャブラリーが増えたと思います。優先度が下がって数ヶ月やらない期間があったりしながら、1年くらいこの方法でやっています。定期的に、といいましたが1年弱はIELTSを受けていない気がするので、そろそろ受けようかと思います。 まとめ 本記事では、エンジニアとして社会に貢献し続けるための、私の実践例を紹介しました。 おそらく似たようなアプローチを取っている方も多いでしょうし、中にはこれらの手法をアプリケーション化して運用している方もいらっしゃることと思います。 生成AIを業務に取り入れることはもちろん、うまく使って継続的に成長し続けることが重要だと思うので、ぜひ何かの参考になれば幸いです。
アバター
RevComm で音声処理を中心に研究開発を担当している加藤集平です。 私はADHD(注意欠陥・多動症)という障害を抱えています 。ADHDを持つ人は日常生活でさまざまな困難に直面するもので、もちろん仕事をしていく上でも困難があります(障害を持たない人と同じやり方では困難に直面します)。私も例に漏れずさまざまな困難に直面していますが、 2022年12月に本ブログで公開した記事 では、当時それらの困難にどのように対処しようとしていたのかを紹介しました。また、弊社の働き方の特徴であるフルフレックス・フルリモート環境が及ぼす影響についても取り上げました。 本記事では、 前回の記事の公開から2年半が経過し状況が変化したことを踏まえ 、私が現在直面している困難とそれに対する対処、弊社の働き方の特徴であるフルフレックス・フルリモート環境が及ぼす影響について 改めて整理してお伝えします 。 内容に前回の記事と重複するものが多くありますが、この記事単体で読んでいただけるようにしたいと思ってのことです(ADHDの方は特に、複数の記事を行き来するのはつらいかと思います)。ご容赦くださいませ。 加藤集平(かとう しゅうへい) シニアリサーチエンジニア。RevCommには2019年にジョインし、音声処理を中心とした研究開発を担当。ADHDと付き合いつつ業務に取り組む2児の父。 個人ウェブサイト X → 過去記事一覧 本記事を読むにあたっての注意 私はADHDを専門とする医師でもその他の専門家でもありません。 ADHDに関する正確な情報は、専門家の発信をご参照ください 。 ADHDの症状(困難に直面するポイントあるいは本人の特性)は人によって異なる ことが知られています。また、同じ症状に対して同じ対処が有効とは限りません。本記事で取り上げるのは私の症状と私が実践している対処法であり、 万人に通用するものではありません 。 ADHDの診断は医師のみが行うことができます 。自己判断はかえって困難を増大させるおそれがあります(例えば、症状が似た違う病気かもしれません)。他人を勝手にADHDだと断定することについても同様に、本人および周囲の困難を増大させるおそれがあります。 ADHDとは ADHD(注意欠陥・多動症)とは、精神障害のうち発達障害に分類されるものの一つです。発達障害とは、生まれつきみられる脳の働き方の違いにより、幼児のうちから行動面や情緒面に特徴がある状態です。発達障害の中でもADHDは不注意・多動性・衝動性の3症状を主な特徴としており、それらの症状の影響で日常生活・学業・仕事などに様々な困難が生じることがあります。かつては子供だけに見られる病気と考えられていましたが、現在では大人になっても症状が継続する場合があることが知られています。 私とADHD 私がADHDと診断されたのは、2017年(30歳頃)のことでした。当時は前年に発症した強迫性障害という病気の治療のために心療内科に通っており、通院・治療の過程でADHDであることが発覚しました。 強迫性障害とあわせて障害者手帳の交付を受けています(現在はカード型が選択できるようになりました)。 思えば物心ついた頃から忘れ物や物をなくすのは日常茶飯事で、部屋は常に散らかっており、コツコツ勉強することは決してなく、学校のテストではよく不注意で失点をしていました。 大人になり仕事を始めてからは、順序立てて仕事を処理することが苦手で締切に間に合わなかったり、他人に出した指示をすっかり忘れたり、単調な作業ですぐに寝てしまったり、体調に波があるために毎日8時間パフォーマンスを出し続けることが難しかったりして、仕事の遂行に支障をきたしていました。また、かつての勤務先では毎日オフィスに出社していたのですが、電話番をすることや周囲の話し声(雑音)が苦痛で頭がいっぱいになったりといった困難もありました。 診断を受けてからは、定期的に通院の上、服薬および日常生活の中での治療を続けています。 この状況は2年半前と特に変わっていません 。 フルフレックス・フルリモート環境における恩恵と困難 ADHDを持つ人にとって、弊社のようなフルフレックス・フルリモート環境は適しているのでしょうか?私の場合は恩恵のほうが大きく勝りますが、フルフレックス・フルリモートならではの、オフィス出社にはない困難も感じています。これらの恩恵と困難を紹介します。 恩恵 体調の波を吸収しやすい(フルフレックス) ADHDを持つ人すべてに当てはまるわけではないと思いますが、私は体調に比較的大きな波があります。つまり調子のいい日と悪い日の仕事のパフォーマンスの差が大きくなるおそれがあります。 現在は2年半前と比べてパフォーマンスの差は小さくなりましたが、依然として多少の波はあります 。 フルフレックスの制度下では体調に合わせて比較的柔軟に勤務時間(長さおよび時間帯)の調整ができます。当然、打合せやプロジェクトの進行状況などの制約条件があるので完全に自由に調整できるわけではありませんが、それでも 毎日絶対に決まった時間に仕事をしなければならない状況よりは安心感が違います 。 静かな環境で仕事ができる(フルリモート) 自宅や家族構成などの諸条件に左右されますが、オフィスよりも静かな環境を用意することができる場合があります(私は用意できています)。私の場合は雑音が多い環境が苦手なので、 静かな環境は集中力を高めるのに役立っています 。 困難 自主的にやる気を管理する必要がある(フルフレックス・フルリモート) フルリモート環境では、オフィスのように衆人環視の中で仕事をするわけではありません。人の目がない環境だとどうしても怠けやすくなります。しかし怠けすぎると、仕事の成果が出ず問題になります。 逆に、やる気に満ちあふれている時には過剰な長時間労働をするおそれもあります。フルフレックスの制度下では(法令の範囲内で)極端な時間の使い方をすることも不可能ではありませんが、健康の観点や、組織の一員として周囲と協調しつつ働く観点からは望ましくないでしょう。 ADHDを持つ人にはやる気のある時とない時の差が激しい人が少なくありませんが、やる気のない時に最低限のやる気を出すことと、やる気に満ちあふれている時に働きすぎないようにする工夫は、体調を整えつつ安定したパフォーマンスを出す上で重要だと考えています。 私の場合は、先述したように子育ての関係で ある程度決まった時間に働くことになっており、2年半前と比較して体調とパフォーマンスをより安定させることに繋がっていると感じています 。 家事などの私生活と仕事のバランスを意識して取る必要がある(フルフレックス・フルリモート) フルフレックス・フルリモート環境では、仕事中にいつでも私用を挟むことができます。特に在宅勤務の場合は、仕事の合間に家事をすることは珍しくないでしょう。 ところが、ADHDを持つ人には一度集中したら他のタスクになかなか移り難い傾向のある人が少なくありません(過集中)。つまり、家事を始めたらいつまでも仕事に戻れなかったり、逆に仕事に熱中して家事が疎かになったりすることがあります。 仕事に戻れないことは当然問題になりますし、家事が疎かになることも私生活においては問題になりえます。 フルフレックス・フルリモートとは関係のない一般的な困難 膨大なタスクを適切に管理する 前回の記事を公開した2年半前と異なり、現在の私は多数のプロジェクトに少しずつ関わるという働き方をしています。このような働き方においては、 自ずとタスクの数は増え、しかもそれらを同時並行でこなす 必要があります。ADHDを持つ人にとって、多くのタスクを同時並行でこなすことは一般に苦手なことの一つだと思います。私も苦手なので、対処する必要があります。 私が困難に対処している方法の例(★は前回の記事以降に新たに始めたこと) 「自主的にやる気を管理する必要がある」に対して 仕事前に着替える 在宅勤務では、打合せがなければパジャマのままでも仕事をすることが可能です。打合せがあっても、下半身はパジャマのままでもバレません。しかし、私の場合は気持ちを仕事に切り替えるために、仕事前に必ずパジャマから着替えることにしています。オフィスに出社していれば通勤時間で気持ちを切り替える人も多いかと思いますが、在宅勤務は通勤時間がないので代わりにしっかり着替えることにしています。パジャマよりも寝心地が悪いので、安易な昼寝を防止する効果も期待できます。 筆者の仕事着の例。下は白いですがジーンズです。 専用の仕事部屋で仕事をする 誰もが実践できる方法ではありませんが、私はほぼ仕事専用の部屋を用意しています。私生活の場と空間を分けることで、仕事に対するやる気を出しやすくなります。やる気に乏しい日でも、机に座ってしまえば仕事ができることは珍しくありません。 特にADHDの人には、トリガーが大事であることは経験的にご理解いただけるかと思います 。 ★毎日ある程度決まった時間に働く フルフレックスの精神に反すると感じられる方もいらっしゃるかもしれませんが、 自分で決めた時間でいいので毎日ある程度時間を決めて働くことは、体調の安定に資すると実感しています 。 私の場合は、2年半前と違って子供が保育園に通っている時間に大半の仕事を終える必要があり、自ずと毎日ある程度決まった時間に働くことになっています。自ら望んで決まった時間に働いているというよりは、そうせざるを得なかったので決まった時間に働いているだけなのですが、結果としてはいい方向に作用していると感じています。 ★原則として規定の労働時間しか働かない フルフレックスでも、1か月あたりの規定の労働時間や、それを超えた場合の残業という概念はあります。毎日ある程度決まった時間に働き、原則として1か月あたりの規定の労働時間の範囲内で働くことで、 締切効果 (締切直前だけ頑張れるアレです)が働きます。 私の場合は、子供が保育園に通っている時間に大半の仕事を終える必要があるため、自ずと労働時間に制限がかかります。この 制限をうまく活かして、集中力を発揮する ことに成功しています。 ★ポモドーロ・テクニックを活用する ポモドーロ・テクニックとは、25分間の作業と5分間の休憩を繰り返すことで、集中力を維持して生産性を向上させるやり方です。 定期的なリズムと短時間の集中が、やる気をうまく発揮し、かつ疲れすぎないことに繋がっていると感じています 。 2年半前には既にリマインダーで過集中を防いでいましたが、対処がより洗練された方法になった感じです。 「家事などの私生活と仕事のバランスを意識して取る必要がある」に対して 私生活の時間はカレンダーをブロックしてしまう 前回の記事では、昼食を取り損ねるために昼食の時間 (12:00 – 13:00) のカレンダーをブロックしていました。 現在は昼食が自然と取れるようになったのでその時間はブロックしていませんが、 起きてから子供を保育園に送るまでの時間と、迎えの後に子供が寝るまでの時間はブロック しています。こうすることで、その時間はしっかりと家事・育児に集中することができます。 「膨大なタスクを適切に管理する必要がある」に対して ★タスクをリマインダーで管理する さまざまなベストプラクティスがあると思いますが、私が現在使っているのは広く知られたやり方の一つであるGetting Things Done (GTD) という方法です。単にタスクを列挙するだけでは特にADHDを持つ人にはつらいかと思いますが、GTDではタスクをシステマティックに管理することができます。タスクを管理する媒体は人それぞれですが、私はiPhoneのリマインダーを利用しています。 詳細は書籍や解説記事をご覧いただければと思いますが、個人的にはGTDを採用することで認知負荷が劇的に減り、 目の前のタスクに安心して集中できる ようになりました。これにより、より多くのタスクを、より少ない疲労で完了させることに成功しています。 2年半前はやっていたが、現在はやらなくなったこと 朝起きたら布団を畳む 仕事中に寝ることがほとんどなくなったので、やめました(行儀としては畳むべきかもしれません)。 コンテンツブロッカーを使う コンテンツブロッカーを使わなくてもネットサーフィンをすることが減ったので、やめました。 適度に打合せを入れる 状況の変化により意識しなくても打合せが入るようになったので、わざわざ入れることはやめました。 労働時間をトラッキングする タスク管理で十分に仕事が終わるようになったので、やめました。 スマートスピーカーに頼る(タイマー・アラーム) 2年半前は「洗濯をしたのに、つい仕事に熱中して何時間も干し忘れる」ことへの対策でタイマーを活用していましたが、自動で乾燥まで行ってくれる洗濯機に買い替えたので、やめました。 おわりに 2年半前とは生活も業務内容も随分と変わり、それに伴って困難や対処も変わりました。 全体としては、日々工夫を重ねることで、より上手に対処できるようになったと感じています 。 なお、以上の困難や工夫は私にとって一部であり、他にも仕事・私生活を問わず様々な困難に対してさまざまな工夫を日々行っています。また、周囲の方々の支援なくして良好な社会生活を送ることはできません。 改めて家族やRevCommの仲間をはじめとする周囲の方々に感謝いたします 。
アバター
RevCommで音声処理を中心とした研究開発を担当している加藤集平です。昨年3月に第二子が生まれて、1年間の育児休業を取得しました。私は男性ですが、男性の育児休業取得率・取得期間ともにここ数年急速に伸びている実感があります。しかし、1年間の育児休業を取得する例はまだまだ少ないように思います。本記事では、 男性として実際に1年間の育児休業を過ごした経験から、正直どうだったのか について共有します。 加藤集平(かとう しゅうへい) シニアリサーチエンジニア。RevCommには2019年にジョインし、音声処理を中心とした研究開発を担当。ADHDと付き合いつつ業務に取り組む2児の父。 個人ウェブサイト X → 過去記事一覧 なぜ1年間の育児休業を取得することにしたのか? 第一子のときは1か月間だった 第一子もRevComm在籍中に生まれたのですが、その際は里帰り出産への同行+1か月間の育児休業(生後2か月目の1か月間)という形でした。なお、妻は産後休暇と、子供が1歳になるまでの育児休業を取得しました。 ところが、1か月間の育児休業は、 家庭内環境の激変による部屋の模様替えと片付け(出産・育児で物が増えたので片付けてスペースを空ける必要がありました) いただいた出産祝の管理(内祝を返す必要があるので表にしていました) 来客対応(第一子ということもあり多かったのです) に追われているうちに、あっという間に終わってしまいました。妻が体力の回復と子供の世話に集中するのに役に立ったとは思いますが、 自ら子育てをしたか?と言われると疑問符の残る状況 でした。 さらに、生まれたばかりの子供というのは、昼夜を問わず「寝る→起きる→泣く→授乳→寝る→…」を3時間〜4時間ごとに繰り返します。私は昼間業務があるので夜はまとめて寝かせてもらっていましたが、妻は細切れにしか睡眠が取れないので毎日大変です。個人差は大きいと思いますが、第一子の場合、夜に比較的まとまった睡眠を取るようになったのは生後4か月目頃からでした。 第二子は当初3か月間の予定だったが、1年間に延長した というわけで、第二子の誕生に際して、 当初は3か月間 の予定で育児休業に入りました。なお、妻は今回も産後休暇と、子供が1歳になるまでの育児休業です。私が育児休業中は、2人で休業していることになります。 ところが、実際にやってみると3か月間もやはりあっという間に過ぎていきます。上に第一子がいますから子育ての難易度としては上がっており、なかなか心身の調子が整いません。育児休業も3か月目に入る頃、そのように思い悩んでいました。色々考えましたが、だったら思い切って1年間休んだほうが、自分にとっても家族にとっても会社にとっても最終的には利益になるのではないかと考え、当初の計画を延長し、 1年間の取得 とすることにしました。 1年間何をしていたのか? 子供の成長フェーズによって世話の負担は変わる 男性が1年間育児休業を取得する例は現状では少ないので、何をしていたのか気になると思います。まず言えるのは、 生後1年間の子供の世話の負担は一様ではない ということです。あくまで私の子育ての経験 (n=2) の話ではありますが、おおむね以下のようでありました。 1〜3か月: とにかく忙しくて眠い およそ3か月目までは、前述のとおり子供はしょっちゅう寝たり起きたりして、授乳間隔も短い状態です。養育者全員(うちの場合は夫婦)が毎回付き合う必要はありませんが、睡眠はどうしても浅くなりがちです。また、 公的手続や儀礼が多数あり 、それらを確実にこなすにはかなりの労力が必要です。実際、第一子の時は完璧にやり終えたのですが、第二子の時は健康保険の手続を失念して少々困ったことになりました。今回は第二子ということで、赤ちゃん返りする上の子のケアも欠かせません。とにかく忙しくて眠い日が続きました。 4〜6か月: 空き時間が最も多い 生後4か月頃になると、(うちの場合は)昼夜のリズムがだんだんとできてきて授乳間隔も伸び、したがって大人も比較的寝られるようになります。子供はというと、首はすわったけれど、ハイハイはできないような時期です。つまり、その場から動くことはできません。 子の安全を常に監視する必要はありますが、危険は比較的少ない状態 です。うちの場合は、子供が息をしているか感知するセンサーを布団の下に仕込んでいました。そうすると、少しばかり空き時間ができます。この空き時間に心身を休めることも重要ですが、余裕があれば他のことをすることができます。 7〜9か月: 動き始めて目が離せなくなる 個人差が大きいですが、この時期にハイハイ(ずり這いを含む)を始める子が多いと思われます。 ハイハイを始めると、子供が危険に遭遇する確率はグッと上がります 。ずっと目を離さないのも疲れるので実際にはベビーサークルに入れたりしていましたが、目を離せない時間が増えるのは間違いなく、空き時間はその分減ります。 10〜12か月: 復職準備 スムーズに復職したければ、どうしても準備が必要 です。最低限、すっかり変わってしまった生活リズムを元に戻すことは欠かせません。子育てに何とか慣れてきたところではありますが、最後の3か月は、だんだんと復職に向けた動きをすることになるでしょう。 私がしていたこと 上記のように、育児休業といえど100%の時間を子供の世話に費やすわけではありません(うちは夫婦で休業していたので、少なくともそうです)。むしろ、業務と子育ての両方で忙しい普段よりもまとまった時間が取れることもあるでしょう。私は以下のようなことをしていました。 業務に関する学び直し 私は業務で機械学習(特に深層学習)を扱いますが、深層学習が普及したのは私が大学院を卒業した後であり、体系的に学ぶ機会がありませんでした。深層学習はそれ以前の機械学習とは根本的に性質の異なる面があり、いわゆるアンラーニングが必要な状況でもありました。 そこで、育休の4〜6か月目を中心に、オンライン動画の講座を利用して、深層学習について体系的に学ぶことにしました。ついでに、機会学習のための数学・機械学習全般・敵対的生成ネットワーク (GAN)・自然言語処理・デジタル信号処理についても同様に学び直しを行いました。 さらに、復職準備の頃には、かねてより伸ばしたいと思っていたソフトスキルの講座を受け始めました(現在継続中)。 なお、育児休業中に育児以外のことをするのは賛否あるかと思います。個人的には、育児休業法の理念に照らしても、空き時間を活用してよりよい復職のための準備を行うことは、決して悪いことではないと考えています。 育児・介護休業法 第三条の2 子の養育又は家族の介護を行うための休業をする労働者は、その休業後における就業を円滑に行うことができるよう必要な努力をするようにしなければならない。 実家への長い帰省 いつもお盆と正月に短期間滞在するだけの実家ですが、普段より長めの帰省を行いました(私はついでに学会の聴講をしていたのですが…)。うちの場合は夫婦ともに実家が遠方であり、貴重な機会となったと考えています。 復職はスムーズだったか?復職して思うことは? 復職は比較的スムーズだった 復職準備には前述のように3か月間を充てることができたので、余裕を持って準備をすることができました。うちの場合は4月1日にいきなり第二子を保育園に預ける生活が始まるわけでもちろん混乱はありましたが、何とか乗り切ることができました。なお、私は子供の誕生日の前日(3月)の復職、妻は慣れ保育(慣らし保育)が終わった4月中旬の復職と復職時期をずらすことで、一つ一つの変化を小さく抑える工夫をしました。 また、1年間業務から離れていましたが、子育てに忙しい中でも比較的余裕を持って心身を整えることができ、慌てずに復職することができました。もっとも、2人の子供を育てる中で、多少のことでは動じない心がいつの間にか身についていたのかもしれません。 復職して思うことはたくさんある 1年間の育児休業を取得するという選択は、 1年間業務に従事しないという選択 でもあります。嫌々業務に取り組んでいるなら別ですが、業務を離れるということは多少なりともつらい思いがありますし、悔しいものでもあります。 比較的スムーズに復職することができて、現状では育児休業に入る前よりもむしろよいパフォーマンスを出せていると実感しています。これは、業務を長期間離れることでよい意味で心身がリセットされたこと、空き時間に行った学び直しを応用できていること、子育てを通じてよりタフになったことなどが関係していると考えています。 一方で、もし業務を離れていなければ、自分なりの貢献ができたのではないかと思う事象に遭遇することもあります。離れていた時間が戻ってくることはありませんし、よいパフォーマンスで今から貢献するしかないのですが、申し訳なくさみしく思う気持ちはゼロではありません。 長期の育児休業がハードスキルとソフトスキルに与える影響について 真摯に子育てに取り組んだことでソフトスキルが伸びた あくまで私の個人的な経験でしかありませんが、 真摯に子育てに取り組んできたことは、ソフトスキルを伸ばすことに役に立った と考えています。私の場合は妻と共同で行っているので、妻との間で子育ての方針から細かな点まで合意形成をする必要があります。たとえ夫や妻がいなくても、子育ては一人でできるものではなく、どうしても周りの人や社会的な支援を受けながら行うことになります。夫や妻がいれば、互いに助け合うことになります。どのような支援を受けるか、あるいはどのように助け合うか、 主体的に考え調整する 必要があります。 このような作業は時に面倒で大きな労力を要しましたが、真摯に取り組んだことで、特に合意形成や調整といった面でソフトスキルが伸びたと考えています。前述のように別途ソフトスキルの講座は受けていますが、それだけでは足りない実践力が身につきました。さらに言うと、 子の成長を見守るというのも大事な経験 でした。無理やり成長させようというのは不可能です。粘り強く見守る力も身についたのかもしれません。 ハードスキルはキャッチアップが必要 一方で、 ハードスキルについてはキャッチアップする必要 がありました。私の取り組んでいる機械学習(特に深層学習)の世界は、まさに日進月歩。本当に1年間離れていれば浦島太郎です。絶え間なくキャッチアップする必要はありませんが、どこかでキャッチアップする必要はあります。離れるのは育児休業だから仕方ありませんし、浦島太郎になることは別に悪いことではないと思いますが、元に戻るためにはキャッチアップが必要です。ただ、個人的にはハードスキルは勉強すればいい話でソフトスキルを伸ばすほうが難しいと考えているので、 長期の育児休業を取ったことは(少なくとも結果的には)よかった と考えています。 さいごに: 自分の選択を尊重しよう 育児休業を取得するという選択も、取得しない選択も、どちらも大きな決断を伴います。それが1年間という長期間であればなおさらです。何かを選択するということは、他の選択肢を諦めるということであり、何かを得る代わりに何かを失うということです。 私は第二子の誕生にあたり1年間の育児休業を取得する選択をしました。会社から一人が一年間離れるというのは、決して小さなことではなく、さまざまな人に影響を与える選択でした。しかし、(選択をした当時としては)最善を尽くした選択だったと考えており、結果として現在はよい状態とパフォーマンスで業務にあたることができています。もちろん、万人に1年間の育児休業を勧めているわけではなく、他の方は他の選択をされるかもしれません。育児休業に限った話ではありませんが、大きな選択であればあるほど 最善を尽くして選択を行い、少なくとも自分自身がその選択を尊重される ことを願います。
アバター
はじめに 昨今、パッケージなどのエコシステムをターゲットとしたサプライチェーン攻撃が増加しています。 各種プログラミング言語向けのパッケージマネージャーやレジストリにおいては、インストールするパッケージのバージョンを固定したり、チェックサムを検証したりすることにより、サプライチェーン攻撃被害のリスクを軽減する仕組みが導入されています。 もちろんGitHub Actionsにおいても、サードパーティー製のワークフローを利用する場合に、サプライチェーン攻撃の被害を受けるリスクが生じますが、このような仕組みを導入するには少々作業が必要になります。 そこで本記事では、pinactを利用して簡単にGitHub Actionsにおけるサプライチェーン攻撃被害のリスクを軽減する方法を紹介します。なお、本記事で紹介する内容は、弊社で最近実施されたものです。 なぜ実施したのか? 弊社の一部リポジトリで利用している tj-actions/changed-files において、サプライチェーン攻撃が発生しました: www.stepsecurity.io 具体的には、 tj-actions/changed-files の運用のためのボットで利用していたPATが流出し、それを悪用して攻撃者による悪意のあるコミットが紛れ込み、各タグなども改竄されてしまったようです。 時差の関係もあって運よく被害は発生しなかったのですが、仮に被害が発生した際の影響は大きなものになりえます。今後のリスク軽減のために、以下で紹介する対策を実施することにしました。 実施したこと pinact の導入 pinact とは? GitHub Actionsのワークフローにおける各種依存アクションのバージョンを固定してくれるCLIツールです。 github.com 使い方 pinact はHomebrewなどで導入可能です。 $ brew install pinact 導入したら、以下を実行します。 $ pinact run すると、リポジトリ内の各種ワークフローにおける依存アクションを検出し、以下のようにバージョンの定義を書き換えてくれます。 steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 with: node-version: '22' - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 pinactの実行結果 このように、 pinact によってサードパーティー製ワークフローのコミットハッシュによる参照を強制することで、意図せず改竄されてしまったバージョンのワークフローが実行されるリスクを軽減することができます。 pinact の導入に関する選択肢 今回、 pinact の導入に当たって実現したいことは以下の内容です。 CI (GitHub Actions) で pinact run --check を実行したい。 pinact run --check を実行すると、GitHub Actionsのワークフローにおいてバージョンが固定されていない依存アクションを検出してくれます。 目的はサプライチェーン攻撃へのリスクを低下させることなので、できるだけ安全な方法で pinact を導入したい。 その上で、 pinact を導入する方法としては以下のあたりが選択肢として考えられそうです。 Homebrew メリット 公式から Homebrew/actions が提供されており、GitHub Actionsからの利用が容易である。 pinactによって推奨されるインストール方法の一つである。 使い慣れているユーザーが比較的多いと思われる。 デメリット インストールするツールのバージョンを固定できない。 aqua メリット 公式から aquaproj/aqua-installer が提供されており、GitHub Actionsからの利用が容易である。 pinactによって推奨されるインストール方法の一つである。 依存ツールのバージョンの固定が可能である(後述)。 aqua-checksums.json によるチェックサムの管理・検証が可能である。 デメリット Homebrewや後述する mise と比較すると、まだ使用例は多くないと思われる。 mise メリット 作者によって jdx/mise-action が提供されており、GitHub Actionsからの利用が容易である。 aqua バックエンド を利用すれば pinact を導入可能である。 aqua と同様に、依存ツールのバージョンの固定が可能である。 今回導入予定の pinact 以外に、Node.jsなどのさまざまなランタイムのバージョン管理もできる。 デメリット aquaバックエンドは、実験的サポートの段階である (※)。 今回の目的とメリットを踏まえると、 aqua か mise がよさそうです。 mise の aqua バックエンドはまだ実験的サポート(※) であったことと、 aqua-checksums.json によるチェックサム管理の仕組みがあることなどから、本記事では aqua を試してみることにしました。 ※ ⚠️ 記事の執筆を開始した当初は、 mise の aqua バックエンドはまだ実験的サポートの段階でしたが、本記事の公開時点ではすでに実験的という表記は削除されています ( af36cfd )。 今回は aqua を採用しましたが、 mise も選択肢として有望だと思います。 aqua とは CLIツール向けのパッケージマネージャーで、サプライチェーン攻撃に対する対策が強く意識されているのが特徴です。 slsa-verifier によりパッケージが検証される( SLSA はサプライチェーン攻撃への保護を目的としたフレームワーク・仕様です) チェックサムが検証される プロジェクトごとに依存ツールのバージョンが固定される 導入方法 ローカルに導入する際は、Homebrewや公式のインストーラーなどで導入可能です。 $ brew install aqua GitHub Actionsで aqua を利用したい場合は、 aquaproj/aqua-installer を使用します。 - uses : aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with : aqua_version : 'v2.46.0' skip_install_aqua : "true" - uses : actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with : path : '~/.local/share/aquaproj-aqua' key : v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys : | v2-aqua-installer-${{runner.os}}-${{runner.arch}}- aqua の設定 まず、設定ファイルである aqua.yaml を生成します。 $ aqua init (推奨) aqua.yaml を生成したら、まずチェックサムの検証を有効化することを推奨します。 # aqua.yaml checksum : # See https://github.com/aquaproj/aquaproj.github.io/blob/4709985f3f10c1c257fc812d9f791ab595cad266/docs/reference/config/checksum.md#require_checksum for details enabled : true require_checksum : true # 以下は必要に応じて調整します (`aqua update-checksum`の実行時に該当の環境向けのチェックサムが登録されます) supported_envs : - darwin - linux/amd64 この aqua.yaml と後述する aqua-checksums.json は、バージョン管理に含めます。 パッケージの追加 aqua g -i <パッケージ名> でパッケージを追加できます( aqua のパッケージレジストリは こちら にあります)。 # 例) pinactを追加 $ aqua g -i suzuki-shunsuke/pinact # 例) actionlintを追加 $ aqua g -i rhysd/actionlint すると、 aqua.yaml にパッケージの定義が追加されます。 packages : - name : suzuki-shunsuke/pinact@v2.0.4 - name : rhysd/actionlint@v1.7.7 aqua.yaml で定義されている各種パッケージをインストールするには、下記コマンドを実行します。 $ aqua i ( 推奨 ) aqua.yaml でチェックサムの検証を有効化している場合、依存パッケージの追加や更新などを行なった際に、 aqua update-checksum で aqua-checksums.json を更新しておく必要があります。 $ aqua update-checksum actionlint を導入する pinact に加えて、 actionlint もGitHub Actionsにおけるセキュリティを改善する上で有用なツールです。今回はあわせて導入します( actionlint についてはすでにWeb上に情報が十分にあるため、詳細は割愛します)。 actionlint は aqua でも導入可能です。 $ aqua g -i rhysd/actionlint pinact と actionlint をGitHub Actionsで実行する aqua によって pinact と actionlint を導入し、GitHub Actionsによって実行を自動化します。 name : Lint workflows on : push : branches : - main paths : - '.github/**/*.yml' - '.github/**/*.yaml' pull_request : branches : - main paths : - '.github/**/*.yml' - '.github/**/*.yaml' jobs : lint : name : Lint workflows runs-on : ubuntu-latest steps : - uses : actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses : aquaproj/aqua-installer@e2d0136abcf70b7a2f6f505720640750557c4b33 # v3.1.1 with : aqua_version : 'v2.46.0' skip_install_aqua : "true" - uses : actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with : path : '~/.local/share/aquaproj-aqua' key : v2-aqua-installer-${{runner.os}}-${{runner.arch}}-${{hashFiles('aqua.yaml')}} restore-keys : | v2-aqua-installer-${{runner.os}}-${{runner.arch}}- - name : Run pinact run : pinact run --check - name : Run actionlint run : actionlint 一連の対策によってGitHub Actionsに関するサプライチェーン攻撃へのリスクを軽減することが期待されます。 おわりに 以下のブログ記事では、今回紹介したものよりもさらに踏み込んだ対策が紹介されています(ちなみにこの記事は、今回紹介した aqua や pinact の作者の方によって書かれています)。 zenn.dev 参考になる記事だと思いますので、興味がありましたらぜひ上記の記事もご覧ください。
アバター
2025年4月12日 (土)に開催される「ふりかえりカンファレンス2025」にバックエンドエンジニアの大谷紗良が登壇します。 イベント概要 名称: ふりかえりカンファレンス2025 日程: 2025年4月12日 (土) 9:00〜18:00 会場: 株式会社 フィードフォース confengine.com 登壇情報 大人数会議のカオス化を防ぐふりかえりフレームワークを考えてみた 「15人以上で1年規模のプロジェクトのふりかえりを1時間でしよう!!!!!!(えっ」 大人数での会議はカオス化しがちだと思います。 基本的にはまず適切な人数にできないかを考えるのが良いと思いますが、 大人数の会議で抱えている問題は本当に大人数の会議だけの問題でしょうか? ファシリテーターの腕に自信がなくてもフレームワークの工夫でカオス化を乗り切る一例を紹介します。 登壇者: 大谷紗良 株式会社RevComm バックエンドエンジニア 日時: 2025年4月12日 (土) 16:35〜16:40 参加申し込み 参加申し込みやイベントの詳細などについては下記ページから確認いただけます。オンラインでの参加も可能なため奮ってご参加ください。 retrospective.connpass.com
アバター
はじめに Recoilからの移行先について Jotai について 移行の方針 1. MiiTel Phoneにおいて使用されているRecoilのAPIを一通り洗い出して、それぞれのAPIにおけるJotaiへの移行方法を調査する 2. 依存関係としてjotaiパッケージを追加する 3. MiiTel Phoneにおける特定の機能において、RecoilからJotaiへの移行を実施する 4. 検証環境で様子を見る 5. 問題のない機能から順次、リリースを実施する 6. 3〜5のステップを繰り返す 7. 一通り移行が完了したら、依存関係からrecoilパッケージを削除する Recoil と Jotai の各APIの対応と移行方法について jotai/utilsモジュールについて atom() useRecoilState() useSetRecoilState() useRecoilValue() useResetRecoilState() selector() 非同期selector useRecoilValueLoadable() useRecoilCallback() useRecoilRefresher_UNSTABLE() Atom Effects atomFamily()/selectorFamily() 悩みどころ/ハマったところ atomFamily()/selectorFamily()の移行について RecoilとJotaiにおける更新タイミングの微妙な差異について おわりに 参考 はじめに 今年のはじめにRecoilのGitHubリポジトリがアーカイブされ、話題になりました。 > This repository has been archived by the owner on Jan 1, 2025. It is now read-only. https://t.co/uiv2W0Hd04 — Daishi Kato (@dai_shi) 2025年1月5日 github.com 弊社で提供している MiiTel Phone においても、フロントエンドの状態管理のためにRecoilを採用していました。 Recoilのメンテナンス停止に伴い、今後、バグや脆弱性などに関するリスクが増加してしまう可能性があることや、React v19や周辺ライブラリのアップデートなどに当たって問題となる可能性もあります。そのため、RecoilからJotaiへの移行を実施することにしました。 Recoilからの移行先について Recoilから別のライブラリへの移行については、すでに他の企業でも事例がありそうです。 speakerdeck.com 弊社においては以下の理由から、Recoilからの移行先としてはJotaiが最も有力な選択肢と判断し、移行先として選択しました。 JotaiはAPIや思想がRecoilに近く、移行コストを抑えやすい RevCommにおける他のサービスにおいてすでにJotaiの利用実績があり、十分に安定して動作することが期待できる JotaiのリポジトリやJotaiの作者である dai-shi さんの ブログ などにおいてアウトプットが活発に行われており、今後、採用事例などがより増えることが期待できる Jotaiはドキュメントが充実しており、またRecoilと比較するとライブラリのサイズも大幅に小さいです。調べたいことや気になることなどがあった際に、ドキュメントやソースコードなどから調査が行いやすいと考えられます。 Jotai について Jotaiが開発された経緯や概要については、作者である dai-shi さんによる解説記事が公開されています。これらの記事を参照するのがおすすめです。 zenn.dev zenn.dev zenn.dev 移行の方針 まず、RecoilからJotaiへの移行にあたって、ビッグバンリリースは避けたいと考えていました。できる限り、すでに存在する機能への影響やバグの発生を最小限に抑えつつ、段階的に移行が行えると理想的です。そこで、以下のような方針で移行を進めていくことにしました。 MiiTel Phoneにおいて使用されているRecoilのAPIを一通り洗い出して、それぞれのAPIにおけるJotaiへの移行方法を調査する 依存関係として jotai パッケージを追加する MiiTel Phoneにおける特定の機能において、RecoilからJotaiへの移行を実施する 検証環境で様子を見る 問題のない機能から順次、リリースを実施する 3〜5のステップを繰り返す 一通りの機能で移行が完了したら、依存関係から recoil パッケージを削除する 1. MiiTel Phoneにおいて使用されているRecoilのAPIを一通り洗い出して、それぞれのAPIにおけるJotaiへの移行方法を調査する まずは、Jotaiへの移行が現実的に可能であることや具体的な移行方針を決めやすくするために、MiiTel PhoneにおけるRecoilの各APIの使用方法を洗い出して、それらのAPIのJotaiへの移行方法を調査しました。幸いなことに、Jotaiが提供する jotai/utils モジュール (詳細は後述します) において、Recoilが提供する機能はほとんどカバーされていることがわかりました。調査を通して懸念や移行方法などは概ね把握できたため、実際に移行を進めていくことにしました。 2. 依存関係として jotai パッケージを追加する RecoilからJotaiへの移行に当たり、重要度や影響度合いの低い機能から優先して段階的に移行が行えると理想的です。幸いなことに、JotaiはRecoilと比較してフットプリントがかなり小さいです。そこで、移行期間中は jotai パッケージと recoil パッケージがプロジェクトに共存した状態で移行を進めていくことにしました。 recoil パッケージについては、一通り移行が済んでから削除します。 3. MiiTel Phoneにおける特定の機能において、RecoilからJotaiへの移行を実施する 重要度や影響が低めの機能から優先して、順次、RecoilからJotaiへの移行を実施します。MiiTel Phoneでは Qase というサービスを使って手動のテストケースを管理しています。そこで、Qaseによってテストがしやすい単位ごとにプルリクエストを分割して、各機能におけるRecoilを使用したコードをJotaiへ段階的に移行していきました。 また、もしAtom Effectsや selector などのRecoilにおける高度な機能を使用している箇所については、移行に先立ってユニットテストを用意しておくとより安全に移行が行えます。 React Testing Library の renderHook() を使用して該当の atom / selector もしくはそれらを利用するカスタムフックに対してテストを記述しておくと、Jotaiへ移行する際のテストの書き換えをできる限り抑えられて良いと思います (下記の例だと、 RecoilRoot と useRecoilState() をそれぞれJotaiの Provider と useAtom() へ置き換えるだけで移行できるはずです) import { renderHook } from '@testing-library/react' ; import { RecoilRoot, useRecoilState } from 'recoil' ; import { act } from 'react' ; describe ( 'preferencesState' , () => { afterEach (() => localStorage. clear ()); it ( 'persists state to localStorage' , () => { const { result } = renderHook( () => { const [ preferences , setPreferences ] = useRecoilState(preferencesState); return { preferences , setPreferences } ; } , { wrapper : RecoilRoot, } , ); expect (localStorage. getItem ( 'preferences' )).toBe( null ); act(() => result. current .setPreferences( { theme : 'dark' } )); expect (result. current .preferences).toEqual( { theme : 'dark' } ); expect (localStorage. getItem ( 'preferences' )).toBe( JSON . stringify ( { theme : 'dark' } )); } ); } ); 4. 検証環境で様子を見る 今回のRecoilからJotaiへの移行に当たって、新機能の開発や要望への対応などはストップせずに、それらのタスクと並行しながら進めました。RecoilからJotaiへの移行を実施した機能については、すぐにはリリースをせずに一週間ほど検証環境にデプロイをして様子を見ることにしました。 5. 問題のない機能から順次、リリースを実施する 検証環境で様子を見て特に問題がなさそうであれば、他の機能や修正などと合わせて少しずつJotaiへ移行したコードをリリースしていきました。 6. 3〜5のステップを繰り返す 一通り移行が完了するまで、関連した機能ごとにRecoilからJotaiへの移行を行い、少しずつ段階的にリリースを進めていきます。重要度や影響度の高い機能については移行を後回しにして、最後にまとめて移行をすることにしました。 7. 一通り移行が完了したら、依存関係から recoil パッケージを削除する すべてのRecoilのコードをJotaiへ移行し終えたら、ようやく recoil パッケージを削除できます。今回の移行に当たって段階的に移行を進めていたことや、RecoilとJotaiは全体的に思想やAPIがよく似ていて移行が行いやすかったこともあり、特に障害が発生することもなく無事に移行をすることができました。 Recoil と Jotai の各APIの対応と移行方法について Recoil の各APIごとに、Jotaiへの移行方法について紹介いたします。 jotai/utils モジュールについて Recoilが提供する高度なAPIの多くは jotai/utils モジュールによってカバーされています。この記事でも jotai/utils モジュールから提供されているAPIをいくつか紹介しますが、紹介していない機能もまだまだあります。 jotai/utils モジュールは atom の活用方法の観点からもとても参考になるため、一度、内容を調べてみるのも良いかもしれません。 atom() atom の移行は単純で、基本的には key を削除して、デフォルト値を atom() の引数に指定するよう書き換えることで移行できます。 - import { atom } from 'recoil'; + import { atom } from 'jotai'; - export const isLoadingState = atom<boolean>({ - key: 'users/isLoading', - default: false, - }); + export const isLoadingState = atom(false); ただし、 useResetRecoilState() を使用している atom についてはこの方法では移行できず、後述する atomWithReset() を使用するとよいです。 useRecoilState() Recoilの useRecoilState() はJotaiの useAtom() へそのまま置き換えることができます。 + import { useAtom } from 'jotai'; - import { useRecoilState } from 'recoil'; ... - const [counter, setCounter] = useRecoilState(counterState); + const [counter, setCounter] = useAtom(counterState); useSetRecoilState() Recoilの useSetRecoilState() はJotaiの useSetAtom() へそのまま置き換えることができます。 + import { useSetAtom } from 'jotai'; - import { useSetRecoilState } from 'recoil'; ... - const setIsLoadinge = useSetRecoilState(isLoadingState); + const setIsLoading = useSetAtom(isLoadingState); useRecoilValue() Recoilの useRecoilValue() はJotaiの useAtomValue() へそのまま置き換えることができます。 + import { useAtomValue } from 'jotai'; - import { useRecoilValue } from 'recoil'; ... - const isLoading = useRecoilValue(isLoadingtate); + const isLoading = useAtomValue(isLoadingState); useResetRecoilState() useResetRecoilState() を使用した atom をJotaiへ移行するには、 jotai/utils モジュールによって提供される atomWithReset() を使う必要があります。 - import { atom } from 'recoil'; + import { atomWithReset } from 'jotai/utils'; - export const isLoadingState = atom<boolean>({ - key: 'users/isLoading', - default: false, - }); + export const isLoadingState = atomWithReset(false); atomWithReset() によって定義された atom は、 jotai/utils の useResetAtom() によってデフォルト値へのリセットが可能です。 + import { useResetAtom } from 'jotai/utils'; - import { useResetRecoilState } from 'recoil'; ... - const resetIsLoading = useResetRecoilState(isLoadingState); + const resetIsLoading = useResetAtom(isLoadingState); selector() Recoilの selector については、Jotaiにおいては derived atom によって同様のことが実現できます。例えば以下のような selector があったとします: import { atom, selector } from 'recoil' ; const countState = atom( { key : 'count' , default : 0 , } ); const isEvenState = selector( { key : 'isEven' , get : ( { get } ) => get(countState) % 2 === 0 , } ); const doubledCountState = selector( { key : 'doubledCount' , get : ( { get } ) => get(countState) * 2 , } ); この場合、Jotaiでは以下のようにして同じことが実現できます: import { atom } from 'jotai' ; const countState = atom< number >( 0 ); const isEvenState = atom< boolean >( ( get ) => get(countState) % 2 === 0 , ); const doubledCountState = atom< number >( ( get ) => get(countState) * 2 , ); 非同期 selector Recoilの非同期 selector については、 非同期Atom を作成することで同様のことが実現できます: // Recoilの非同期selector import { selector } from 'recoil' ; export const myProfileState = selector< MyProfile >( { key : 'myProfile' , get : async () => { const profile = await client.getMyProfile(); return profile; } , } ); 以下のように atom() に Promise を返却する関数を渡すことで、同様のことが実現できます: // Jotaiの非同期atom import { atom } from 'jotai' ; export const myProfileState = atom< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); useRecoilValueLoadable() Recoilの useRecoilValueLoadable() は非同期 selector に関する状態を問い合わせるためのAPIです: const loadable = useRecoilValueLoadable< MyProfile >(myProfileState); switch (loadable. state ) { case 'loading' : return < Loading /> ; case 'hasError' : return < Error error = { loadable.contents } /> case 'hasValue' : return < Profile profile = { loadable.contents } /> ; } Jotaiにおいて同様のことを実現したい場合、まず jotai/utils で提供されている loadable() というAPIによって非同期 atom をラップします: import { loadable } from 'jotai/utils' ; import { atom } from 'jotai' ; export const myProfileState = atom< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); export const myProfileLoadableState = loadable(myProfileState); loadable() によって返却された atom に対して useAtomValue() を呼ぶことで、 useRecoilValueLoadable() とほぼ同様のことが実現できます: const loadable = useAtomValue(myProfileLoadableState); switch (loadable. state ) { case 'loading' : return < Loading /> ; case 'hasError' : return < Error error = { loadable. error } /> ; case 'hasData' : return < Profile profile = { loadable.data } /> ; } useRecoilCallback() Recoilの useRecoilCallback() によって、状態を柔軟に操作することができます: const runTaskIfNeeded = useRecoilCallback( ( { snapshot } ) => async ( taskId : TaskId ) => { const isTaskInProgress = await snapshot.getPromise(isTaskInProgressState); if (isTaskInProgress) return ; snapshot. set (isTaskInProgressState, true ); try { await runTask(taskId); } finally { snapshot. set (isTaskInProgressState, false ); } } , [ runTask ] , ); useRecoilCallback() は jotai/utils モジュールから提供される useAtomCallback() に置き換えることができます: import { useAtomCallback } from 'jotai/utils' ; // ... const runTaskIfNeeded = useAtomCallback( useCallback( async ( get , set , taskId : TaskId ) => { const isTaskInProgress = get(isTaskInProgressState); if (isTaskInProgress) return ; set(isTaskInProgressState, true ); try { await runTask(taskId); } finally { set(isTaskInProgressState, true ); } } , [ runTask ] ), ); 注意点として、Jotaiの公式ドキュメントにも記載されていますが、 useAtomCallback() に渡す関数は、基本的に上記のように useCallback() を適用しておく必要があります ( https://github.com/pmndrs/jotai/blob/v2.12.2/docs/utilities/callback.mdx ) もし、 useRecoilCallback() の引数として渡されるオブジェクト ( CallbackInterface )の refresh 関数に依存している場合は、次に紹介する方法へ移行する必要があります。 useRecoilRefresher_UNSTABLE() Recoilの useRecoilRefresher_UNSTABLE() は非同期 selector を再評価したい場合に利用できます。 const refresh = useRecoilRefresher_UNSTABLE(myProfileState); Jotaiで同様のことが実現したい場合は、まず jotai/utils モジュールで提供される atomWithRefresh() を使用して atom を作成します: import { atomWithRefresh } from 'jotai/utils' ; export const myProfileState = atomWithRefresh< Promise < MyProfile >>( async () => { const profile = await client.getMyProfile(); return profile; } , ); そして、この atom に対して useSetAtom() を呼ぶことで、 useRecoilRefresher_UNSTABLE() と同等のことが実現できます: const refresh = useSetAtom(myProfileState); Atom Effects JotaiにはAtom Effectsに相当する機能はありません。しかし、 atom を2つ用意するなどの工夫をすることで、Atom Effectsと同様のことが実現できます。 例えば、以下のように状態の更新時にAtom Effectsを活用してロギングを行なっている atom があったとします: import { atom } from 'recoil' ; export const countState = atom< number >( { key : 'count' , default : 0 , effects : [ ( { onSet } ) => { onSet(( newValue ) => { logger. info ( 'countState has been updated to %d' , newValue); } ); } , ] , } ); この場合、Jotaiにおいては2つの atom を組み合わせることで同様のことが実現できます。このように2つ以上の atom を組み合わせて複雑なことを実現するパターンは jotai/utils モジュールの内部においても頻繁に利用されています。 import { atom } from 'jotai' ; const baseAtom = atom< number >( 0 ); export const countState = atom< number , [ number ] , void >( ( get ) => get(baseAtom), ( get , set , newValue : number ): void => { set(baseAtom, newValue); logger. info ( 'countState has been updated to %d' , newValue); } , ); 他にも、Jotaiの jotai/utils モジュールでは atom の状態を localStorage へ同期してくれる atomWithStorage などのAPIも提供されています。このようなAPIを活用することで、RecoilにおいてAtom Effectsを利用していたコードを置き換えることも可能です。 atomFamily() / selectorFamily() 注意: ここでは jotai/utils の atomFamily() を使用した例を紹介しますが、後述するように jotai/utils の atomFamily() はユースケースによってはメモリリークを引き起こす可能性があるため、適切なタイミングでクリーンアップする必要があります。 import { atomFamily } from 'recoil' ; export const taskState = atomFamily< Task , string >( { key : 'task' , default : ( id ) => ( { id , state : 'todo' } ), } ); jotai/utils モジュールから atomFamily() が提供されており、概ね同じような用途で使用できます: import { atom } from 'jotai' ; import { atomFamily } from 'jotai/utils' ; export const taskState = atomFamily(( id : string ) => { return atom< Task >( { id , state : 'todo' } ); } ); また、 selectorFamily() についても似たような方法で移行ができます: import { selectorFamily } from 'recoil' ; export const tasksByProjectIdState = selectorFamily< string | undefined , Array < Task >>( { key : 'tasksByProjectId' , get : ( projectId : string ) => async ( { get } ) => { const filter = get(tasksFilterState); const tasks = await fetchTasksByProjectIdAndFilter(projectId, filter); return tasks; } , } ); Jotaiにおいては、 jotai/utils モジュールから提供される atomFamily() と非同期 atom を併用することで、概ね同じことが実現できます: import { atom } from 'jotai' ; import { atomFamily } from 'jotai/utils' ; export const tasksByProjectIdState = atomFamily( async ( projectId : string ) => { return atom< Array < Task >>( ( get ) => { const filter = get(tasksFilterState); const tasks = await fetchTasksByProjectIdAndFilter(projectId, filter); return tasks; } , ); } , ); atomFamily() はデフォルトでパラメーターの比較を同値性に基づいて行います。そのため、パラメーターとしてプリミティブ値ではなくオブジェクトを指定したい場合は、 atomFamily() の第2引数にオブジェクト同士の深い比較を行う関数を指定する必要があります。 Jotaiの公式ドキュメントでは fast-deep-equal を使用した例が掲載されています。 github.com 悩みどころ/ハマったところ atomFamily() / selectorFamily() の移行について 先ほども紹介しましたが、Jotaiが提供する jotai/utils モジュールには atomFamily() というAPIがあります。これは名前が示す通り、Recoilの atomFamily() とよく似た振る舞いをしてくれます。 しかし、一つ注意点があります。Jotaiの公式ドキュメントにおいても記載されていますが、 atomFamily() は内部において作成された atom の一覧を Map を用いて管理しています。この Map で保持されている atom の一覧は、該当の atom が unmount されたとしても破棄されることはないため、ユースケースによっては意図せぬメモリリークが発生してしまう可能性があります。 github.com このメモリリークへの対策としては、以下のいずれかが考えられると思います: AtomFamily#remove を用いて、不要になった atom を削除する Recoilからの移行に当たり、 atomFamily() の使用をやめる 1. AtomFamily#remove を用いて、不要になった atom を削除する Jotaiの atomFamily() が返却する AtomFamily オブジェクトは remove というメソッドを提供しています。 atomFamily() の内部では Map を使ってパラメーターと作成された atom の紐付けを管理しています。 github.com AtomFamily#remove メソッドにパラメーターを指定することで、 Map から指定されたパラメーターのエントリーを削除することができます。適切なタイミングで AtomFamily#remove を呼ぶことで、 Map に無制限にエントリーが残り続けてしまう問題を回避できます。 AtomFamily#getParams メソッドと併用することで、例えば、 atomFamily() が内部にキャッシュするエントリー数に制限を掛けることなどもできそうです。 また、 AtomFamily オブジェクトには setShouldRemove というメソッドもあります。このメソッドには、 atom の作成日時 及び atomFamily() に渡されたパラメーターの2つの値を引数として受け取り、 boolean を戻り値として返却する関数を指定します。この関数が true を返却した場合、 atomFamily() の内部で管理されている Map から該当のパラメーターに対応するエントリーが削除されます。古くなったパラメーターに紐づく atom を削除したいケースにおいて役立ちます。 MiiTel Phoneにおいては、できる限り移行のコストを軽減することや、移行に当たって意図せぬリグレッションなどを防止することを優先して、この方法を採用しました。しかし、Jotaiの使い方としては、この方法よりも次に紹介する方法の方がより理想的なのではないかと思っています。 2. Recoilからの移行に当たり、 atomFamily の使用をやめる Jotaiにおいて、 atom() から返却される値の実体はプレーンなオブジェクトです github.com Jotaiの Store はこのプレーンなオブジェクトから状態へのマッピングを WeakMap によって管理しています。 公式ドキュメントでも言及されているように、 useMemo() などとの併用は必要ですが、Jotaiの atom はコンポーネントのレンダリングフェーズにおいても作成することが可能です。この性質をうまく活用すると、 atomFamily() を使用せずに同様のことをより直感的に実現することも可能そうです。 github.com github.com MiiTel Phoneにおいても、徐々にこの方式への移行を検討していきたいです。 RecoilとJotaiにおける更新タイミングの微妙な差異について RecoilからJotaiへ移行するに当たって、微妙なタイミングのずれから useEffect が意図したタイミングで発火せずに不整合が起きてしまうバグに遭遇しました。 しっかりとした調査ができているわけではないので自信はないですが、Recoilは useSyncExternalStore を使っているようで、それが原因で再レンダリングなどのタイミングが微妙にJotaiとは異なっている可能性があるのではないかと推測しています。 github.com おわりに Recoilはとても便利なライブラリであり、MiiTel Phoneでもたくさん活用していました。そのため、メンテナンスが停止されてしまい残念には思いましたが、これほどの規模や需要を持つライブラリをメンテナンスし続けることは実際には非常に大変なことなのではないかと思いました。 今回、移行先として選択したJotaiは、全体的にとてもシンプルで使い勝手の良いライブラリだと思いました。ドキュメントも充実しており学習も行いやすく、とても良いライブラリです。Recoilのメンテナンス停止に伴い、今後さらに人気が増すのではないかと思います。 参考 github.com blog.logrocket.com
アバター
概要 こんにちは、RevCommのエンジニア、加藤(涼)です。今回はMiitelでAWS CognitoでSAML/OIDC SSOを汎用化した件についてお話ししようと思います。 背景 MiitelではOIDCの認証プロトコルかつ、GoogleとMicrosoft Azureのプロバイダーを用いたSSOのみにしか対応していませんでした。しかし今回、お客様からのご要望に伴いSAMLプロトコルやその他のプロバイダーに対応することとなりました。まず各用語について確認していきたいと思います。 OIDC とは OIDC (OpenID Connect)は、OAuth 2.0をベースにした認証プロトコルです。OAuth 2.0は RFC 6749 にて規定されています。 OIDCの主な特徴は以下の通りです: OAuth 2.0の認可フローに加えて、IDトークン(JWT)を使用したユーザー認証情報のやり取り Claim(ユーザー属性情報)の標準化された取得方法を提供 Authorization Code Flow、Implicit Flow、Hybrid Flowなどの認証フローが規定されており、用途に応じて適切なフローを選択します。 SAML とは SAML(Security Assertion Markup Language)は、XMLベースの標準規格で、組織間でユーザー認証情報を安全に交換するためのプロトコルです。主にエンタープライズ環境での Single Sign-On (SSO) に使用されます。SAMLは RFC 7522 で規定されています。 SAMLの主な特徴は以下の通りです: XMLベースのメッセージフォーマットを使用し、セキュリティアサーションを交換 IdP(Identity Provider)とSP(Service Provider)の間で認証情報を安全に伝送 SAMLでは、ユーザーがサービスにアクセスする際、IdPが認証を行い、認証結果をSPに対してXML形式のアサーションとして送信します。これにより、ユーザーは一度の認証で複数のサービスにアクセスすることが可能になります。 AWS Cognito とは AWS Cognitoは、AWSが提供するユーザー認証・認可サービスです。Webアプリケーションやモバイルアプリケーションにおけるユーザー管理、認証、アクセス制御を実装することができます。 Miitelではユーザー管理にCognitoを利用しています。SAML/OIDCプロバイダーをユーザープールに設定することができるため、今回は自前で実装せず、その機能をメインで使うことにしました。 構成 before 課題を再確認します。 GoogleとMicrosoft Azure のプロバイダーにしか対応していない。(DBやAPIでEnumでの管理) OIDC プロトコルのみ ユーザーがMiitel管理者にSSO設定依頼をする必要があった。 これらの問題の影響でSAMLでのご要望に応えられなかったり、SSOの設定に時間がかかり、ヒューマンエラーが発生することもありました。 After 上記の課題から ユーザのSSO設定フローを変更 SAMLの設定を可能に GoogleとMicrosoft Azure以外のプロバイダーをサポート するよう変更しました。 変更後のイメージはこのような感じです。 ユーザーはMiitel Admin上でSSOを自由に設定できるようになりました。 OIDC/SAMLおよびプロバイダーの種類に制限がなくなりました。 開発時の注意点 AWS Cognitoの1ユーザーにリンクされたID数は5つまで 開発時に陥ったエラーです。Cognitoでは1ユーザーに紐づくIDプロバイダーの数が5つまでに設定されています。 これはSSOログインをする度にユーザー属性の identities にログインのプロバイダーが登録されていき、6つ目のプロバイダーではログインしようとするとできないようになっています。 実際6以上のプロバイダーを使うユーザーはほぼほぼいないのですが、開発時には何個も登録するためこのクォータに引っかかりました。 上記の解決方法ですが aws cognito-idp admin-disable-provider-for-user を使うことによりユーザーとSSOの連携を解除できます。 create/update-identity-providerのProviderDetailsオプションが複雑 ユーザープールにidentity providerを設定するには create-identity-provider / update-identity-provider で可能です。 ただ、SAML, OIDCでパラメータが大きく異なります。 ProviderDetails というオプションがあるのですが、この中にSAMLとOIDCの詳細を全て追加します。OIDCはスネークケースのキー名なのに対し、SAMLはパスカルケースになっています。 https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/create-identity-provider.html —saml-provider-details / —oidc-provider-details に名前を分けてほしいですね。 まとめ 今回はMiitelでOIDC/SAMLプロトコルの両方に対応し、プロバイダーも複数選べるようになった件についてお話ししました。 認証方法については「ログイン・パスワード」・「Google」というのは良くみますが、SAMLプロトコルを用いたSSOにしか対応していないお客様も少なくありません。本記事が参考になれば幸いです。 以上、アカウントチームより、加藤がお話しさせていただきました。 参考 https://auth0.com/intro-to-iam/saml-vs-openid-connect-oidc https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-disable-provider-for-user.html https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/quotas.html#resource-quotas
アバター
2025年1月21日(火)に開催されたML@Loft #16にリサーチエンジニアの石塚が登壇しました。 今回はイベントの振り返りとして登壇資料と登壇者の感想を紹介します。 ml-loft.connpass.com 登壇振り返り 発表タイトル: トーク解析AI MiiTelの音声処理について 発表スライド: https://speakerdeck.com/ken57/aws-yin-sheng-ji-pan-moderu-tokujie-xi-ai-miitelnoyin-sheng-chu-li-nituite 発表者: 石塚賢吉(→ 過去記事一覧 ) 登壇者の感想 ML@Loft#16音声基盤モデルのイベントでの発表は三件で、第一発表者の株式会社レアゾン・ホールディングスの末永様からは音声認識処理の基礎的な話や、商用利用可能な高精度音声認識モデルを含めたプロダクト「ReazonSpeech」を活用した音声認識の実践的な方法についての講演がありました。そして、第二発表者のKotoba Technologies, Inc.の笠井様からは、OpenAIの音声認識モデルWhisperの高速化や、TextToSpeech、同時通訳システムに関する講演がありました。 双方とも非常に興味深いお話であったと思います。 最後に、株式会社RevCommの私から、トーク解析AI MiiTelの音声処理について講演をさせていただきました。 発表後は3つのグループに分かれて議論を行いました。 主に話者ダイヤリゼーション機能や音声感情認識機能の実応用について、興味深い議論ができたと思います。 総じて、非常に有意義な会であったと思っています。 今後も、折を見てこのような会に参加し、社外の方々とも意見交換をさせていただきたいです。
アバター
はじめに 2025年01月21日(火)に開催される「ML@Loft #16 音声基盤モデル」にRevCommプリンシパルリサーチエンジニアの石塚 賢吉が登壇します。 イベント概要 ml-loft.connpass.com 名称: ML@Loft #16 音声基盤モデル 日程: 2025年01月21日 (火) 会場: AWS Startup Loft Tokyo 主催: アマゾンウェブサービスジャパン合同会社 登壇者 石塚 賢吉 株式会社RevComm プリンシパルリサーチエンジニア 筑波大学大学院博士後期課程卒業。博士(工学)。日本HP株式会社にて通信事業者向けのシステム開発、株式会社ドワンゴで全文検索システムの開発などに従事。2019年12月株式会社RevComm入社。音声認識、音声感情認識、全文検索システムの研究開発を行なっている。 → 過去記事一覧 参加登録 参加登録やイベントの詳細などについてはconnpassページより確認いただけます。奮ってご参加ください。 ml-loft.connpass.com
アバター
皆さんこんにちは。RevComm の CTO の平村 ( id:hiratake55 , @hiratake55 ) です。今年もあと数日となりました。この記事では、2024 年の RevComm の開発チームの振り返りを行いたいと思います。 この記事は、 RevComm Advent Calendar 2024 の 25 日目の記事です。 1 月: 組織体制変更 1 月には、エンジニア組織の組織変更を行いました。2023 年 12 月まではマトリックス型の組織を採用し、フロントエンドやサーバサイド、インフラ、モバイルなど、それぞれの技術スタックの専門性を活かしながら各開発プロジェクトに所属して開発を進める組織形態でした。 しかし主力の MiiTel Phone に加え、MiiTel Meetings (オンライン会議解析) や MiiTel RecPod (対面商談解析) など製品が増えてきたこともあり、プロダクト単位の開発組織への変更を行い、よりわかりやすい組織体制に変更しました。新しい組織体制ではプロダクト別の組織に加えて、組織横断で最適化を行う CTO 室で構成されています。 2 月: 開発者向けサイト MiiTel Developers を発表 開発者向けサイトの MiiTel Developers を発表しました。2023 年には、Incoming Webhook や Outgoing Webhook を開発者向けにリリースし、MiiTel にデータを登録したり、外部のサービスに連携することが容易になりました。 MiiTel Developers は、このような機能を開発する開発者向けにチュートリアルや API ドキュメントを整備することで、Developer Friendly な製品へ前進しました。 MiiTel Developers 日本語版: https://developers.miitel.com/ MiiTel Developers 英語版: https://developers.en.miitel.com/ www.revcomm.co.jp 3 月: MiiTel Phone Mobile バージョン 3 をリリース MiiTel Phone Mobile バージョン 3 をリリースしました。バージョン 3 では、プラットフォームを Flutter に変更するため、全てのコードを書き直しました。約 1 年間にわたって技術検証と開発を進め、これまで数多く寄せられていた多数の要望にも併せて対応しました。 MiiTel Phone Mobile は、バックグラウンドやロック画面での処理など、VoIP アプリならではの苦労もありましたが、4 名のチームでリリースを成功しました。 4 月: 会話コーチング機能のリリース 「会話コーチング機能」は、生成 AI を活用して、ユーザーの会話の傾向が他のユーザーと比較してどのような状態にあるのかを、システムが自然な文章で会話の改善点を学校の通知表のように届ける機能です。 これまでは、ダッシュボードを操作して傾向を把握する必要がありましたが、このリリースにより自分自身の会話の改善点やよくできている点を簡単に具体的に把握することができるようになりました。 www.revcomm.co.jp 5 月: SMS 機能のリリース MiiTel の SMS 機能は通話終了後や通話中に SMS をユーザーが取引先のお客様に送信できる機能です。また、コールセンターでオペレーターにつながるまでに時間を要する場合や、オペレータの数が限られている場合に、SMS を送信しお客様をお待たせしないようにするための機能です。 この機能の開発は多くのメンバーが関わりました。音声通信システムを開発するチーム、ブラウザ上の通話アプリを開発するチーム、応対履歴データを管理するチーム、料金計算を担当するチーム、キャリアから回線の仕入れを行うチームなど社内の多数のチームがコラボレーションすることで、短期間でリリースを成功させました。 www.revcomm.co.jp 7 月: MiiTel Scan To Call のリリース MiiTel Scan To Call をリリースし、記者発表会を開催しました。MiiTel Scan To Call は、QR コードをスキャンするだけで、通話料無料、アプリのインストールを必要とせず、モバイルブラウザから電話による通話が可能な革新的なサービスです。また、どの媒体を見て発信したかをトラッキングできるため、これまで困難とされていた電話の広告効果測定が可能になりました。 www.revcomm.co.jp 8 月: 全社オフサイトミーティング RevComm では約 260 名の社員がフルリモート・フルフレックスで業務にあたっています。オフサイトミーティングでは、フルリモート・フルフレックス勤務のレブコムにとって、年に1度、全社員が通常業務から離れ、部署を超えたコミュニケーションを取ることのできる貴重な機会です。オフサイトミーティングでは、CEO の會田、経営企画の鈴木、そして CTO の私から、経営や事業に関するプレゼンテーションを行った後、懇親会で交流を深めました。 note.com 10 月: 経団連へ入会 経団連へ入会しました。スタートアップが経団連に入会したというニュースには驚いたメンバーも多く、私から入会目的や狙いを説明しました。 入会の理由としては、国内外の経済動向や政策に関する情報収集を強化し、事業成長を加速させること。また、日本の経済界や各業界のリーダーと連携し、AI や音声テクノロジーを中心としたイノベーションを推進すること。経団連というと、重厚長大系のお堅い企業群というイメージがありますが、ここに新しい風を吹かせることがスタートアップに期待されていること、グローバルで日本を代表して活躍する企業を目指していくことです。 www.revcomm.co.jp 10 月: インドネシア出張 インドネシアのジョグジャカルタで開催された PyCon APAC 2024 で RevComm から 3 名のエンジニアのプロポーザルが採択され、プレゼンテーションを行うためインドネシアへ出張しました。 また、ジャカルタにあるインドネシア子会社の RevComm Indonesia のオフィスにも訪問し、同時にユーザー会のイベントや顧客訪問のため出張で滞在していたプロダクトマネージャーや私も合流して交流会を開催しました。 RevComm Indonesia では販売とサポートを行い、日本のメンバーとはリモートでインドネシアチームと新機能の企画やお客様との対応についてディスカッションを行っていますが、実際に現地にインドネシアの社会課題やカルチャー、テクノロジーの浸透度を肌で感じることができ、モチベーションが高まりました。 note.com note.com 11月: 総務大臣賞受賞 「第18回 ASPIC クラウドアワード 2024」で表彰を受けた全約 130 社中、最高位の総務大臣賞を受賞し、阿達総務副大臣より表彰を受けました。受賞の背景として、ユーザーが最新のテクノロジーを日々のビジネスに活用できるようになっている点、ユーザー数や導入企業数の増加度合い、海外進出をしている点を高く評価されました。 表彰の概要は 総務省のサイト にも掲載されました。 www.revcomm.co.jp 12 月: re:Invent 参加 米国ラスベガスで開催された AWS の re:Invent に 3 名のエンジニアが最新技術の調査のため参加しました。生成 AI やデータベースの新機能、機械学習モデルを効率的に学習・推論するための仕組みについて、これまでリリースされていたものの知らなかった機能や、量子コンピューターやブロックチェーン、スポーツにおける IT の活用、IoT など業務では扱うことない知識を得ることができ、新サービスや既存サービスの効率化を考える上でのインスピレーションになりました。 まとめ 2025 年も魅力的な新サービス、新機能のリリースを予定しています。世界で活用される MiiTel のサービスの開発に興味のある方は、ぜひ応募をお待ちしております。
アバター