Timee Product Team Blog

タイミー開発者ブログ

PyCon JP 2024参加レポート

みなさんこんにちは。タイミーのデータエンジニアリング部 データサイエンスグループ所属の菊地と小関です。

2024年9月27日(金)、28日(土)に開催されたPyCon JP 2024に参加してきました。今回はPyCon JP 2024の雰囲気と、特に興味深かった&勉強になったトークセッションをいくつかピックアップしてお届けしようと思います!

PyCon JPとは

PyConJPは1年に1度開催されていて、今年はTOC有明コンベンションホールにて9月27日(金)、28日(土)の2日間にわたって開催されました。

概要については、PyCon JP 2024の「What is PyCon JP」をそのまま引用させていただきます。

PyCon JP は、Python ユーザーが集まり、Python や Python を使ったソフトウェアについて情報交換、交流をするためのカンファレンスです。 
PyCon JP の開催を通じて、Python の使い手が一堂に集まり、Python にまつわる様々な分野の知識や情報を交換し、新たな友達やコミュニティとのつながり、仕事やビジネスチャンスを増やせる場所とすることが目標です。

当日のタイムテーブルはこちらを参照いただければと思います。

PyCon JPの雰囲気

今年の参加者数は600名超で、日本国内のみならず、海外からの参加者も多く、非常に活気がありました。昼食についても様々な文化圏の方に配慮して用意されていた印象です。

また、企業ブースも多数出展しており、トークセッションで気になった企業様の発表内容を更に詳しく伺うことができました。スタンプラリーや書籍販売、Pythonに関する求人が掲載されている等、ビジネスチャンスを増やせるよう、非常に配慮された設計だったと感じています。

個人(菊地)的には、企業ブースにいたGrooveX社の「LOVOT」がとてもかわいかったです(写真を撮っていないのが心残りです…)。

特に興味深かった、勉強になったトークセッション

まず、小関が興味深かった&勉強になったトークセッションを紹介します。

FastAPIでのasync defとdefの使い分け

speakerdeck.com

このセッションでは、タイトル通りのFastAPIにおける実装方法の使い分けを、Pythonにおける並列処理・並行処理・非同期処理の性質を説明した上で解説しており、非常にわかりやすかったです。

要点は以下の通りでした。

  • マルチスレッド、非同期処理ともにI/Oバウンドな処理の場合に高速化が期待できるが、マルチスレッドはスレッドが増えるとスレッド切り替え分だけ処理速度が低下してしまう
    • つまり、スレッドが多くなるような処理だと非同期処理の方が処理速度が速くなる場合がある
  • Fast APIにおけるdefasync defの違いと使い分け
    • def
      • 並行処理 (マルチスレッド)
      • 同期処理を行いたい場合はこちらを使う
    • async def
      • 非同期処理
      • マルチスレッドだとスレッドの切り替え時間がかさむぐらいのリクエスト量をさばきたい時に有効

低コストで実現する社内文書RAG機能を搭載したAIチャットボット開発

speakerdeck.com

このセッションでは、RAG機能を用いたAIチャットボットの開発について、主に開発前の実装スコープの決め方や実装方法の詳細の観点からお話しされていました。

開発前にpros & consを言語化して、目的にあったHowとして今回のRAG + Chat botを選択した過程がわかりやすくまとまっていたり、システムのアーキテクチャ自体も丁寧に説明されていたりしたので、今すぐにでも社内で試せそうな感じでした。

弊社だとNotionのドキュメント量が膨大なので、indexingするドキュメントの選定基準をどう決めたのかが特に気になりました。Notionのページ階層が綺麗にまとまっている組織だとindexingする対象を選択する時の手助けになるのかなとも思いました。

LlamaIndexは未履修なので、近々個人で触ってみようと思います!

次に、菊地が興味深かった&勉強になったトークセッションをピックアップして紹介します。

PandasAI:生成AIがデータ分析業務にもたらすパラダイムシフト

speakerdeck.com

このセッションでは、機械学習・生成AI・データ分析の基礎の基礎を説明した後に、PandasAIについて紹介しています。

PandasAIとは、自然言語でデータ分析をしたり可視化を行ったりできるデータ分析のためのAIエージェントで、OSSとして公開されています。OpenAIのAPIキーがあれば使用できるとのことです。

また、SaaSとしても提供しており、登録・課金を行えばAPIキーがなくとも使用できるそうです。

データコネクタとしては、CSVやParquet, XLSX等はもちろん、PostgreSQL, MySQL, BigQuery, Databricks, Snowflakeといった各種クラウドサービスのデータソースへ接続できます。

内部の処理としては、自然言語をPythonコードやクエリに変換してデータを操作し、結果をユーザーへ返却しており、悪意のあるコードの実行に対する制御もできるそうです(ホワイトリストとして特定のモジュールのみ実行可能にする等)。

OSSとして提供されているので一度試してみたいなと思っているのですが、弊社でも導入しているLookerやその他BIツールに搭載されているAI機能とどのように差別化していくのかは気になりました。

データサイエンスのフルサイクル開発を実現する機械学習パイプライン

speakerdeck.com

このセッションでは、まずCARTA MARKETING FIRM社におけるデータサイエンティスト像「フルサイクルデータサイエンティスト」について紹介し、類似概念「フルスタック」との違いを説明しています。

その上で、理想状態とのギャップを課題として整理し、データサイエンティストがより本質的な価値創出ができる状態に向けて、これまでのデータ分析基盤の歴史(luigi → Amazon SageMaker → Prefect)が紹介されています。

タイミーのデータサイエンティストも「フルサイクルデータサイエンティスト」に近い働き方をしている点や、ML基盤の歴史が似通っていたり、チームの規模感や構成が近いなど、多くの共通点がありました。「わかる〜」と心の中でうなずきながら、セッションを聴かせていただいておりました。

あえてApache AirflowではなくPrefectを選定した経緯などは、機会があればぜひ伺ってみたいなと思いました。

終わりに

いかがでしたか?PyCon JP 2024の雰囲気が少しでも伝わっていますと幸いです。次回は広島開催とのことで、ぜひ来年も参加させていただきたいなと思っています。

ちなみに、タイミーには「TDE10」という、プロダクト開発やデータ職種メンバーを対象とした成長支援制度があります。今回は「世界中で開催されているすべての技術カンファレンスに無制限で参加できる『KaigiPass』」を利用してPyConJPに参加しました。

今後もこのような機会があれば、積極的に技術カンファレンスに参加していきたいと考えておりますし、外部登壇も積極的に行っていきたいです。

We’re hiring!!!

現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています!

また、気軽な雰囲気でのカジュアル面談も随時行っておりますので、ぜひお気軽にエントリーしてください。↓

product-recruit.timee.co.jp hrmos.co hrmos.co hrmos.co hrmos.co

新規プロジェクトでのルールベース手法の活用

こんにちは、株式会社タイミーでデータサイエンティストをしている貝出です。直近はカスタマーサポートの業務改善に向けたモデルやシステムの開発を行っております。

新規プロジェクトを始めるにあたって、予測や検知の機能といった複雑な課題に取り組む必要がある際、機械学習モデルを使うことを検討される方も多いと思います。しかし、Google の定めた Rules of Machine Learning: Best Practices for ML Engineering でも記載されているように、必ずしも最初から機械学習モデルを使うことが最適とは限りません。データの不足や問題の不明確さから、機械学習が効果的に機能しない場合もあります。

その一方で、ルールベースなどのシンプルなアプローチを用いることで、早期にプロダクトやサービスを開発し、ユーザーの反応を得られます。この段階でデータサイエンティストや機械学習エンジニアが関与することで、後々の機械学習モデルの導入をスムーズに進めることが可能です。

本記事では、新規プロジェクトでのルールベースの活用方法と、その段階でデータサイエンティストができることについて書きたいと思います。

新規プロジェクトにおける早期フィードバックと早期成果

構築・計測・学習のフィードバックループ

新しいプロダクトやサービスを開発する際には、できるだけ早く最小限の機能を持つ製品(MVP:Minimum Viable Product)を作成し、ユーザーからのフィードバックを得ることが重要です。これは、エリック・リースの『リーン・スタートアップ』で提唱されている手法で、迅速なサイクルで仮説検証を行うことで、無駄を最小限に抑えながら製品を改善していく考え方です。

早期にユーザーの反応を得ることで、問題設定や解決策が適切かを迅速に判断できます。もしユーザーに使われなかったり、期待した効果が得られなかったりする場合は、素早く方向転換(ピボット)することが可能です。このように、早期フィードバックはプロジェクトの成功確率を高める重要な要素となります。

具体的な成果物を早く提供することで、ステークホルダーからの信頼を得やすくなり、追加のリソース確保やプロジェクトの優先順位向上にもつながります。

機械学習モデル構築の課題

機械学習モデル(今回は教師あり学習の場合に限定します)を構築する際には、データの質と量が極めて重要です。しかし、新規プロジェクトでは利用可能なデータが限られていたり、ラベリングルールが明確でなかったりすることも多く、高性能なモデルを開発するのが困難である場合があります。

例えば、数千から数万件のラベル付きデータが必要な場合、データの収集とラベリングには数週間から数ヶ月、さらにはそれ以上の時間と多大なコストがかかることがあります。また、専門知識が必要なラベリング作業では、ラベルの一貫性を保つために教育や管理も必要です。

データが少ない場合、モデルは訓練データに過度に適合し、未知のデータに対しては性能が低下する「過適合(オーバーフィッティング)」が起こりやすくなります。逆に、データのパターンを十分に学習できず、訓練データでも性能が低い「過小適合(アンダーフィッティング)」の状態になることもあります。

このような状況では、データの追加収集やラベリングにかかる時間とコストを考慮し、新規プロジェクトではまずシンプルなルールベースの方法を検討することが有効です。

ルールベースと機械学習ベースの違い

ルールベースと機械学習ベースのアプローチの違いを簡単に整理します。

ルールベース 機械学習ベース
定義 人間が設定したルールで動作 データからパターンを学習
メリット ・簡易性
・迅速な開発
・解釈性
・高い精度
・適応性
・高度なパターン認識
デメリット ・柔軟性の欠如
・拡張性の限界
・開発コスト
・解釈の難しさ

ルールベース

  • 定義:人間が設定したルールや条件に基づいて動作する
  • メリット
    • 簡易性:実装が比較的容易で、結果が理解しやすい
    • 迅速な開発:短期間での開発・デプロイが可能
    • 解釈性:なぜその結果になったのか説明しやすい
  • デメリット
    • 柔軟性の欠如:複雑なパターンや例外への対応が難しい
    • 拡張性の限界:ルールが増えると管理が煩雑になる

機械学習ベース

  • 定義:データからパターンを学習し、予測や分類を行う
  • メリット
    • 高い精度:大量のデータから複雑なパターンを学習可能
    • 適応性:新しいデータや状況にも柔軟に対応
  • デメリット
    • 開発コスト:データ収集やモデル構築に時間とリソースが必要
    • 解釈の難しさ:モデルがブラックボックス化しやすく、結果の説明が難しい場合がある

事例:スパムメールのフィルタリング

  • ルールベースの場合:特定のキーワードや送信元をブロックリストで管理。実装が簡単で明確な基準で判定可能。
  • 機械学習モデルの場合:メールの内容を解析し、ナイーブベイズや深層学習モデルでスパムを分類。大量のデータで高精度を実現。

ルールベースモデルの活用

迅速な実装とデプロイ

ルールベースモデルはシンプルなため、短期間で実装・デプロイが可能です。これにより、ユーザーからのフィードバックを早期に得られ、製品の改善に活かせます。

ルールベースモデルにおけるデータサイエンティストの役割

データサイエンティストは、ルールベースのアプローチでも以下のような重要な役割を果たせます。

  • データ分析によるルール策定支援:データの傾向やパターンを分析し、効果的なルールの策定をサポートする
  • 性能評価とモニタリング:モデルの性能を定期的に評価し、改善点を提案する
  • エラー分析とルール改善:誤判定の原因を特定し、ルールの精度向上に貢献する
  • 将来の機械学習モデルへの布石:データの蓄積とラベリング戦略を立案し、スムーズな機械学習モデルの導入を支援する

段階的な開発プロセス

段階的な開発プロセスの一例として、以下のような進め方が考えられます。

段階的な開発プロセスのイメージ

  1. ルールベースでの MVP 開発
    • シンプルなルールを用いて基本機能を実装
    • 迅速なデプロイを目指す
  2. フィードバックの収集
    • ユーザーや領域専門家(ドメインエキスパート)から意見を収集
    • ルールの有効性や不足点を把握
  3. 評価と改善の繰り返し
    • 性能指標を用いてモデルを評価
    • エラー分析を行い、ルールを最適化
  4. 機械学習モデルの導入検討
    • ルールベースの限界が見えた段階で機械学習の導入を検討
    • データの蓄積状況やビジネス価値を総合的に判断

まとめ

ルールベースと機械学習モデルの双方にメリットとデメリットがあります。新規プロジェクトにおいて、データが十分に揃っていない初期段階では、ルールベースなどのシンプルなアプローチを活用することで迅速に価値を提供し、フィードバックを得られます。

データサイエンティストが早期から関与し、評価やエラー分析、データ品質の向上に努めることで、プロダクトの質を高めつつ、将来的な機械学習モデルの導入をスムーズに進めることが可能となります。

ビジネス価値が確認できた段階で機械学習モデルを導入することで、より高度な機能や精度の向上を実現できます。この段階的なアプローチは、リソースの有効活用とプロジェクトの成功に寄与することが期待されます。

おわりに

今回はルールベースのアプローチの活用を中心に紹介しましたが、技術的にルールベースでは実現が難しいケースもあります。例えば、画像内の文字を機械が読み取れる形式に変換する光学文字認識(OCR)は、ルールベースでの実装が困難です。そのようなケースでは、ルールベースではなく、既存の機械学習モデルや Google の Cloud Vision API などの SaaS を最初のアプローチとして利用することになるかもしれません。

新規プロジェクトでは、限られたリソースや時間の中でいかに迅速にユーザーに価値を提供するかが重要です。シンプルなアプローチから始めて、段階的に機械学習モデルを導入することで、プロジェクトのリスクを最小限に抑えつつ、より高度な機能を実現できます。

私はタイミーにおけるデータサイエンティストとして、このプロセスに積極的に関与し、ビジネスと技術の橋渡し役を目指しています。

現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています!

product-recruit.timee.co.jp

また、気軽な雰囲気でのカジュアル面談も随時行っておりますので、ぜひお気軽にエントリーしてください。↓

hrmos.co

hrmos.co

dbt Coalesce 2024 のKeynote「Innovating with dbt」現地レポート

2024年10月7日から10日(現地時間)にかけて、dbt Coalesce 2024がラスベガスで開催されています。

私たち株式会社タイミーからは、4名が現地参加しました (※)。 今回は、カンファレンスの最初のKeynoteについてご紹介したいと思います。

このKeynoteでは、dbtのビジョンや今後の新機能リリースに関する熱いトピックが多く取り上げられ、非常に刺激的な内容となりました。

以下がアジェンダです。


ここからはKeynoteのレポートをお届けします。

発表内容とその他を切り分けるため弊社メンバーの今回の発表に対するコメントや確認した内容はこのマークアップスタイルで表記します。

イントロ

2024年のdbt Coalesceには、世界中から2000人以上の参加者がラスベガスに集まり、オンラインでは8000人もの方が参加していました。分析エンジニアやデータエンジニアはもちろん、アナリスト、CDO、財務、マーケティング、プロダクト担当者など、さまざまな役割を持つ人々が一堂に会し、dbtのコミュニティの成長を感じました。

既に5万以上のチームが本番環境でdbtを利用しており、毎日1350万回もの実行が行われ、過去1年間で49億ものモデルが構築されているそうです。

データ変換の課題が解決されつつある一方で、品質やオーナーシップ、そしてステークホルダーのリテラシーといった新たな課題が浮上しています。特に、データシステムをソフトウェアシステムと同様に扱う必要性がますます強調されていることが印象的でした。

dbtは、ソフトウェアエンジニアリングのベストプラクティスを取り入れた「ADLC(Analytics Development Lifecycle)」という新しい概念を提案し、分析の全過程をカバーするエンドツーエンドのワークフローを提供しています。このアプローチが、今後どのようにデータ活用を進化させていくのか、とても楽しみです。

画像はhttps://www.getdbt.com/resources/guides/the-analytics-development-lifecycleより抜粋

新機能の紹介

今回の Keynote では dbt Cloud の新機能として dbt Copilot、 Advanced CI、ビジュアルエディティング、クロスプラットフォーム連携などが紹介されました。

dbt Cloud はオーケストレーションやオブザーバビリティ、カタログやセマンティクスの機能を持った Data Control Plane と説明されていました。

今回発表された新機能も Data Control Plane の一部とのことで、抜粋しながら紹介していきたいと思います。

画像はhttps://www.getdbt.com/blog/coalesce-2024-product-announcementsより抜粋

dbt Copilot

LLMはコード作成に優れており、データワークを大幅に簡素化できる可能性がある。dbt Copilotは、モデル、テスト、メトリクス、ドキュメントなどを支援してくれます。

ログ分析による根本原因分析、クエリプロファイルの分析と改善の提案によるコスト削減、ビジネス上の質問などを行うための自然言語インターフェースの提供など、さまざまな機能を持つチャット画面も追加されます。

  • OpenAIのAccesskeyを登録することで利用が可能になるそうです。

dbt開発に慣れ親しんでいないメンバーがこれを使ってすぐに開発者になることは難しそうですが、これまでdbt開発をしていたメンバーがアシスタントとして並走してもらう形で活用することで、開発スピードが向上させられそうです。

Visual Editing Experience

  • NoCodeでブロックを繋げるだけでdbtモデルを作成可能となる機能が発表されました。

ドラッグ&ドロップ・インターフェースをベースとして、元データからブロックを繋げるだけで変換、計算、フィルターなど少し風雑なモデルも作ることができます。ビジュアル編集モデルはSQLに変換されgitによって管理されます。作ったモデルはdbt上の基本機能であるテストとドキュメントに接続できるため、dbt上と同じ信頼性を担保できます。ベータ期間に申し込むのはこちらから。

DataMesh的な思想で開発を分散可能になりうる部分は素晴らしいと感じましたが、ビジネスメンバーに機能を譲渡する前にしっかりガードレールを敷くような開発を行わないとカオスが発生しうると感じたため、機能利用可能時にはその辺りは深くチェックしたいです!
弊社はSQLに慣れ親しんでいないビジネスメンバーが大きなボリュームを占めているので、この機能がしっかりしたガードレールの元でハマることがあれば開発スピードを大きく高めることができそうです!

Advanced CI

これまでのdbt Cloudではslim CIの概念に則って、変更差分とその影響範囲をテストして開発速度を向上させてきましたが、「CIでテストが通ってビルドが通ること」と「期待した変更が正しくされたこと」が一致しないケースも多々あり、今回の advanced CIはそちらを解決する機能として紹介されていました。

そのPRで変更対象となったモデルの実行結果と、直近の本番ビルドの実行結果と比較して「作成されるモデルがこのPRでどのように変化したのか」を確認できるような新しいCIです!

以下の内容を本番とCIの差分として確認できます。

  • 追加、変更、または削除された行または列
  • 列内の値の変更
  • 列のデータ型または列の順序の変更
  • 主キーの変更または重複
  • データモデル全体と比較して、変更、追加、または削除された行の割合

紹介されている画面

  • dbt Cloud上のCI Job詳細画面

  • PullRequest上に投稿されるdbt Cloudからのコメント画面

こちらの機能によって以下のメリットがあると説明されています。

  • 不正なデータを本番環境にデプロイされることを防止できることによるデータ品質の向上
  • 行レベルの変更を確認可能となることで、行レベルテストなどが不要になることによる開発者の速度向上
  • 本番環境の手前で消化活動が可能なため、事後対応が減ることによるコスト効率の向上


早速ちょっとやってみました。(色々な事情ありが成功版と中身をお見せできず、すみません)CI環境のJobで Run compare changes のオプションをONにします。
この機能をONにしないとCI Jobの画面で以下のような表示になります。

pull request上にadvaced CIのコメントがしっかりされました!

(advanced CIに対するコメント)
全てのケースを網羅するテストを書くことは難しいため、コードやデータの変更を自動的に確認できるようになるこのCIは、「これこそ顧客が求めていた機能だ」と感じました。
ただ、本番環境のビルドに関してもCIを意識しなければならないため、ビルド戦略は少し複雑になるかもしれません。それでもビルドの信頼性の向上によって、より安定したリリースプロセスに近づくはずです。

Data health tileの埋め込み

自動公開とそれに対応するData health tileにより、信頼できるデータセットを保証できます。

Tableauワークブックやiframeで埋め込めるData health tileで信頼性を可視化することが可能です。

こちらTableauとの連携が主にフィーチャーされていましたが、こちらLookerStudioにも埋め込み可能なことを確認しました。

このようにdbt Cloud exploreからexposureを選択してiframeを取得

iframe内のtokenパラメータにdbtのBearerトークンを渡した状態で、LookerStudioに埋め込むことで以下のようにLookerStudio上に表示されることを確認できました!

dbtセマンティックレイヤーのコネクタ追加

Tableau(updated)、Sigma、Power BIなど、さまざまなBIツールとの統合が強化されました。

クロスプラットフォームのためのdbt Mesh

これまでの dbt では、複数のデータプラットフォームをまたいだプロジェクトの構成が困難でした。例えば、あるプロジェクトで Databricks を利用していて、後続のプロジェクトで Snowflake を使っていたとしても、後続のプロジェクトから source として Databricks のプロジェクトのテーブルを参照することはできませんでした。

今回の Keynote でクロスプラットフォーム dbt Mesh の機能が発表されて、プラットフォームをまたいだプロジェクトの構成が現実味を帯びてきました。

実現に至ったのは、 Apache Iceberg に対応したデータプラットフォームが増えたことがきっかけでした。

Apache Iceberg とは Open Table Format と呼ばれる、主にデータレイクハウスでの利用が想定されたオープンなテーブルフォーマットです。Apache Iceberg 自体の詳細の説明は割愛しますが、データレイクハウスにおいて Iceberg 形式でテーブルを作成することで、他のデータプラットフォームから読み取りが可能になる仕組みです。

2024年10月に入って dbt が Apache Iceberg のサポートを開始したことで、クロスプラットフォームでの連携が可能になりました。

弊社では BigQuery を利用しており Iceberg 互換でデータを保持することはできないため、残念ながら今の環境で試すことはできませんが、企業間のデータ連携などを見据えると触っておきたい機能だと思いました。

さらに Apache Iceberg 形式への対応に加えて、 Amazon Athena や Azure Synapse Analytics といったアダプターが新しくサポートされました。

プラットフォーム間の連携が容易になるとのことで、柔軟な形式でデータを保持できるデータレイクハウスの強みがこういった形で出てくるのは印象深いです。さらにアダプターの追加によって dbt とデータレイクハウスの相性がますます良くなったように感じました。

まとめ

dbt Coalesce 2024のKeynote「Innovating with dbt」は、dbtのビジョンと共に新規機能がいち早く聞ける非常に刺激的な内容でした。特に、advanced CIやdbt Copilotによってさらに効率的に分析・開発が推進できそうですし、ビジュアルエディティングによって開発に関われるデータ関係者の幅が広がりそうで可能性を感じました。 セッション内で繰り返し強調されるOne dbt というワードの名の通り、dbtがカバーできる新しい領域が沢山できたと思える内容でした。

タイミーでも、こういった新機能を活用することでdbt開発の効率化や品質向上を図り、データ基盤の信頼性とアジリティをさらに高めていきたいです。

※今回私たちは、タイミーのKaigi Pass制度を利用してdbt Coalesce 2024に参加できました。

References

One dbt: the biggest features we announced at Coalesce 2024
https://www.getdbt.com/blog/coalesce-2024-product-announcements

About dbt Copilot
https://docs.getdbt.com/docs/cloud/dbt-copilot

Build with speed and confidence
https://www.getdbt.com/product/develop

Advanced CI
https://docs.getdbt.com/docs/deploy/advanced-ci

Data health tile
https://docs.getdbt.com/docs/collaborate/data-tile

Real-World Product Deployment of Adaptive Push Notification Scheduling on Smartphones を読んでみた

株式会社タイミーでデータサイエンティストをしている渡邉です。

タイミーでは、スマートフォンへのプッシュ通知を利用してタイミーを利用されているワーカーの方にキャンペーン情報やおすすめのお仕事を通知しています。通知した情報をワーカーの方が開封することで初めて詳細な情報を確認することができるのですが、タイミーのアプリ以外のアプリでもプッシュ通知を利用されていると、様々なアプリから日々大量の通知を受け取っているという状況が発生しているかと思います。 このような状況下では、ワーカーの方々が重要な情報を見逃したり、通知の頻度が多すぎてストレスを感じたりする可能性があります。そのため、より効果的でユーザーフレンドリーな通知システムの構築が必要だと考えています。

そんな中でこの問題について有効と思われる手段を提案されている論文 「Real-World Product Deployment of Adaptive Push Notification Scheduling on Smartphones」[1] を見つけたので、本ブログではこの論文の内容を紹介したいと思います。

論文の概要

本論文では、プッシュ通知技術のアプローチである「適応型通知スケジューリング」について詳細に報告しています。慶應大学と Yahoo! JAPAN が実施した大規模研究(382,518人のユーザー、28日間)を通じて、この新しいアプローチの仕組みとその効果が検証されました。 まず従来のプッシュ通知システムの問題点を明らかにし、それらを解決するための適応型通知スケジューリングの手法を提案しています。この手法は、ユーザーの行動パターンを学習し、最適なタイミングで通知を配信することで、通知の効果を大幅に向上させることを目指しています。 システムの設計から実装、そして大規模な実験結果までが詳細に記述されており、この新しいアプローチがユーザーエンゲージメントにどのような影響を与えるかを明らかにしています。さらに、この技術の実用化に向けた課題や今後の展望についても議論されています。

従来のプッシュ通知手法の問題点

従来のプッシュ通知システムには、以下のような問題点があると述べられています。

  1. ユーザーの状況を考慮しない一方的な通知配信

    ユーザーが何をしているかに関係なく通知を送信するため、作業の中断や集中力の低下につながる可能性がある。

  2. 頻繁な割り込みによるユーザーの生産性低下とストレス増加

    頻繁に作業を中断されることで、ユーザーにストレスや不満を与える可能性がある。

  3. 通知の無視や設定オフにつながるユーザーの疲弊

    インタラプション過負荷の状態になると、ユーザーは通知を無視したり、通知設定をオフにしたりする可能性がある。これは、プッシュ通知本来の目的である情報伝達を阻害する要因となる。

  4. クリック率や反応時間の低下によるマーケティング効果の減少

    通知が頻繁に送られてくることで、ユーザーは通知に対して鈍感になり、クリック率や反応時間の低下につながる可能性がある。

これらの問題は、ユーザーエンゲージメントの低下と、プラットフォームの価値低下につながる危険性があります。

適応型通知スケジューリングの概要

適応型通知スケジューリングの大事なポイントは、ユーザーの「ブレークポイント」を検出することです。ブレークポイントとは、ユーザーが一つのタスクを終え、次のタスクに移る瞬間のことを指します。この瞬間は、ユーザーの認知負荷が低く、新しい情報を受け取るのに適しているとされています。 本論文では、スマートフォンのセンサーデータと機械学習を組み合わせて、リアルタイムでブレークポイントを検出するシステムについて述べられています。このシステムは、ユーザーの活動状態、時間帯、デバイスの状態などの多様なコンテキスト情報を活用しています。 具体的には、474 の特徴量を用いた分類モデルを構築し、ユーザーの状態をリアルタイムで評価します。

主な特徴量には以下が含まれます。

  • 時間帯情報
  • デバイスの状態(充電状況、音量設定など)
  • ユーザーの活動状態(Google Activity Recognitionを使用)
  • アプリケーションの使用状況

モデルは線形回帰をもとにしており、日々のユーザーデータを用いて更新されます。また、平日と週末で異なる行動パターンに対応するため、コンテキストに応じた重み付けを導入しています。 そして、ブレークポイントが検出されるまで通知の配信を遅延させ、最適なタイミングで通知を表示します。

システムアーキテクチャ

システムは主にクライアント側とサーバー側のコンポーネントで構成されています。

クライアント側:

  • センサーデータの収集
  • 特徴抽出
  • リアルタイムのブレークポイント検出
  • 通知の遅延と表示

サーバー側:

  • ログデータの収集と分析
  • 日次モデルの更新
  • 新しいモデルのクライアントへの配信

このアーキテクチャにより、ユーザーの行動パターンを日々学習し、モデルを継続的に改善することが可能となります。

実験と結果

382,518人の Android 利用ユーザーを対象に実験を実施しています。実験では、ユーザーを Test 群と Control 群にランダムに分割し、Test 群には新しい適応型通知システムを適用し、Control 群には従来の通知システムを使用しています。

主な結果は以下の通りです。

  1. クリック率の平均が 23.3% 向上(最大 41.6%)

    適応型通知スケジューリングを導入した結果、ユーザーのクリック率は平均で 23.3% 向上し、最大で 41.6% の向上率が確認されました。

  2. ターゲット通知のクリック率の平均が 30.6% 向上(最大 60.7%)

    特に、ユーザーの興味関心にもとづいてパーソナライズされたコンテンツを含むターゲット通知では、平均 30.6%、最大 60.7% と、より高いクリック率の向上が見られました。

  3. 一般通知のクリック率の平均が 11.1% 向上(最大 42.9%)

    一方、すべてのユーザーに同じ内容が配信される一般通知でも、平均 11.1%、最大 42.9% のクリック率向上が確認されました。

  4. 通知配信後 120 秒以内のクリック率が 2.6〜2.86 倍に向上

    適応型通知スケジューリングでは、通知配信後 120 秒以内のクリック率が、大幅に向上しました。

    • 一般的な通知の場合:クリック率が2.60倍に向上 (実験群のクリック率 / 対照群のクリック率)
    • ターゲット通知の場合:クリック率が2.86倍に向上 (実験群のクリック率 / 対照群のクリック率)

    これらの結果はユーザーのブレークポイントに合わせて通知が配信されることで、ユーザーの関心を高め、即座に反応しやすくなったためと考えられます。特にターゲット通知(パーソナライズされた内容)の場合、より大きな効果が見られました。この結果は、適切なタイミングとパーソナライズされた内容の組み合わせが、ユーザーエンゲージメントを大幅に向上させる可能性を示唆しています。

  5. 速報ニュースが多い日でも高いパフォーマンスを維持

    速報ニュースが多い日でも、適応型通知スケジューリングは高いパフォーマンスを維持しました。これは、ユーザーのブレークポイント以外のタイミングで配信される速報ニュースが多い日でも、適応型通知は、ユーザーの適切なタイミングで配信されるため、クリック率への影響が少なかったと考えられます。

これらの結果は、適応型通知スケジューリングの有効性を強く示唆しています。

考察

実験結果から、以下の点が考察されます。

  1. ユーザーの状況を考慮した通知は、大幅にエンゲージメントを向上させる
  2. パーソナライズされた内容(ターゲット通知)と適切なタイミングの組み合わせが最も効果的
  3. 適応型システムは、緊急時の通知と日常的な通知のバランスを効果的に管理できる
  4. ユーザーの即時反応性の向上は、情報のタイムリーな伝達に有効

これらの知見は、マッチングプラットフォームにおける通知戦略の最適化に大きく寄与すると考えられます。

今後の課題と展望

今後の主な課題と展望として、以下が挙げられています。

  1. さらなるパーソナライゼーションの追求(通知内容と配信タイミングの最適化)
  2. クロスプラットフォーム対応(iOS, ウェブなど)
  3. リアルタイム処理の効率化

これらの課題に取り組むことで、より効果的でユーザーフレンドリーな通知システムの実現が期待されます。

まとめ

適応型通知スケジューリングは、ユーザーの状態を考慮したアプローチです。大規模実験の結果は、この手法が従来の問題点を大きく改善し、ユーザーエンゲージメントを向上させることを示しています。 マッチングプラットフォームにおいても、求職者と求人情報のマッチング精度の向上、ユーザー満足度の向上、そしてプラットフォーム全体の価値向上が期待できます。今後もこのような論文から得られた知見をもとに、タイミーアプリの継続的な改善を重ね、ユーザーとビジネスの双方に価値をもたらすことに貢献していきたいと考えています。

現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています!

また、気軽な雰囲気でのカジュアル面談も随時行っておりますので、ぜひお気軽にエントリーしてください。↓

product-recruit.timee.co.jp

hrmos.co

hrmos.co

参考文献

[1] : Okoshi, T., Tsubouchi, K., & Tokuda, H. (2019). Real-World Product Deployment of Adaptive Push Notification Scheduling on Smartphones. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining (KDD ’19) (pp. 2792-2800), DOI: 10.1145/3292500.3330732 (2019)

DroidKaigi 2024登壇レポート

ふなち(https://x.com/_hunachi)です。

DroidKaigi 2024で登壇してきました!

初めてのDroidKaigiでの登壇はとても緊張しましたが、良い経験になりました。

登壇内容

タイムテーブル

2024.droidkaigi.jp

登壇資料

speakerdeck.com

登壇動画

www.youtube.com

この内容で登壇した経緯

昨年から今年の頭にかけて、基本一人でもくもくPDFを表示するコードを書く機会がありました。

話はそれますが、iOSでPDFを表示させたいとなった時、公式のFrameworkであるPDFKit(https://developer.apple.com/documentation/pdfkit)を使うことができます。これが高性能で、リンクの表示や文字列検索など色々できてしまうのです。

しかしAndroidは… 調査した結果、公式から出ているAPI(PdfRenderer)では、Bitmap生成とページ数取得くらいしかできませんでした。 また、実装するときに気にすることも色々あり少し大変な道でした。Bitmapを大量に扱うので何も考えないで実装すると簡単にメモリリーク等させることができるのです。 しかもネット上の記事にも、(当時は特に)英語のもの含めPDFビュアーに関してメモリを考慮して実装したというようなものはありませんでした。

そんな困難に見舞われながらも実装することができたので一旦よかったのですが、一部悔しさはありました。

そんな時です。 Android 15のリリース予告記事でPDF周りのAPIがかなり強化されると発表されました🎉 記事を読んでみると、文字列検索などもできるらしく、革命だ!と思いました。

そのような中、Android 15(Vanilla Ice Cream)をエミュレーターで動かせるようになったタイミングで、休日も使い色々実装してみました。

ハッカソンで作ったようなコードと見た目なので今後も公開はしない予定ですが、PDFを要約する機能や、特定の文字列がある部分を枠で囲ってくれる機能、フォームの内容を変更できる機能を試すことができるアプリを作りました。 色々できるようになっていることを知れて、嬉しかったのを覚えています。

しかもこの時、運よくDroidKaigiのプロポーザル提出期間でした。 もともと緊張するタイプの私にとって、40分という時間のハードルはそこそこ高く、特に発表したいことがなければ提出するつもりはなかったのですが、PdfRendererの素晴らしい進化を伝えたいという気持ちに負けて、プロポーザルを提出しました。

その結果嬉しいことに、採択されました🙌

登壇準備

採択された後は、発表資料を作成するため、今までやってきた経験をもとに一から簡易的なPDF Viewerを作り、そのコードを使い発表資料を作成しました。 コードを書く方に時間を費やしすぎて、焦って資料作りすることになったので今後は気をつけようと思います。

資料を作る中で、やらかしたかも!と思う出来事もありました。 プロポーザルの編集期限が過ぎた後に、GDEの方のSNSでの発言により、androidx配下にpdf/pdf-viewerというディレクトリが爆誕していることに気がついたのです。プロポーザルに書き損ねてしまったことに後悔していたら、資料作成途中の8月頭にandroidx.pdf:pdf-vieweralphaがリリースされました。

そこで、プロポーザルに書いていないが話さないわけにはいかないなと思い、発表資料にandroidx.pdf:pdf-viewerのことも組み込むことになりました。

このandroidx.pdf:pdf-viewerの内部実装について紹介しようか迷いましたが、このライブラリと同じようなことをしたいのであればこのライブラリを使えばいいなと思い、重要な部分(プロセス分離の部分)を除いて内部実装については発表しないことにしました。

また同時通訳対象だったため、日英両方でスライドを書いたり、事前にスライドを提出したりする必要がありました。 大変な部分もありましたが、英語のチェックを社内の方や家族にしてもらえて助かりました🙇

発表数日前にはタイミーのメンバーの前で発表練習もさせてもらいました。 その際に色々と質問等をもらえたおかげで、ブラッシュアップすることができました。 忙しい中時間を作っていただき感謝です!

余談ですが、弊社には登壇や執筆をサポートする制度もあります。

productpr.timee.co.jp

今回私は入社前に登壇準備をすることになったため、あまり利用していませんが、今後はこちらの制度も活用したいです!

発表当日

発表では、めちゃくちゃ緊張していましたが、なんとか発表を無事終えることができました。 緊張で止まってしまったりすることもなく、発表時間もちょうどで終わったようでよかったです。

ただ、早く喋り過ぎたので同時通訳の方には負荷をかけただろうなと思い反省しています。次に同じような機会に恵まれた際には気をつけます。

少しニッチな内容なので、Ask the Speakerも暇になるかなと思っていましたが、 「今PDF関連のことを触っていて〜」や「ちょうど調べている内容なので助かりました!」と英語話者の方も含め質問していただいたり、声をかけていただいたりして嬉しかったです。

当日だけじゃなく後日含め、SNSやブログでもピックアップしてコメントをくださった方々もありがとうございます。 直接の反応はあまりできていませんが、見るたびにとても嬉しい気持ちになっています。

写真はDroidKaigiが公開しているアルバムから引用

引用元:https://x.com/DroidKaigi/status/1840692565538136507

発表以外の感想

実は今年もスタッフをしており、主にセッション進行のお手伝いを行なっていました。

また、スタッフ業務をしながら他の方のセッションを見ていたのですが、そこにも多くの学びがありました。 まだ全てのセッションを見ることができてないので時間を作って見てみようと思います。

アフターパーティなどでも他の登壇者の方や私の発表を聞いてくださった方、初めましての方や久しぶりにお会いした方々とお話しすることができ、とても楽しかったです。

まとめ

私の発表を聞いてくださった方々、登壇のサポートをしてくださった方々、スタッフの方々など、皆様に感謝しています。ありがとうございます!

40分の登壇を経験したことで、他の登壇している人たちをさらに尊敬できるようになりました。 とても良い経験でした。

これからも積極的にインプットとアウトプットをしていこうと思います!

余談

発表中緊張して途中で声が出なくなるといけないので、いざという時にお面をつけて落ち着こうと考えてました。お面は前日の夜に緊張具合を考慮し作成しました。 登壇直前のマイク確認でお面をつけて喋ってもマイクが通るかの確認もしました。 確認の結果、お面をしていてもマイクに音は乗ることがわかりました。 このいざという時の備えができた安心感も発表を乗り切れた理由の一つかもしれません。 お面参戦なら登壇できるかもという方は、来年以降プロポーザル提出チャレンジをしてみると良いと思います!

また、他のメンバーによるDroidKaigi 2024参加レポートも公開しています。是非こちらも読んでみて下さい↓

tech.timee.co.jp

DroidKaigi 2024 参加レポート

9/11~9/13 にかけて DroidKaigi 2024 が開催され、タイミーの Android アプリエンジニアチームが参加してきました。

はじめに

9月にジョインされた hunachi が登壇しています。Android の PDF Viewer に関する歴史や詳細な実装からライブラリの紹介まで PDF Viewer を網羅したセッションとなっているのでぜひアーカイブでご覧ください。

2024.droidkaigi.jp

タイミーではブースも出しておりノベルティやタイミンの写真を撮りに来られる方など盛況でした。足を運んでいただいた皆様ありがとうございます!

この DroidKaigi から配布するノベルティに新しく「マイクロファイバークロス」が追加されました。めちゃめちゃかわいいデザインになっているので手に入れた方はぜひ使ってみてください!

また、ネイルブースの横に出していたので「せっかくだし」と初めてネイルを体験しましたが、とてもかわいくテンションも上がって最高でした。

今回は DroidKaigi 2024 に参加した Android アプリ開発メンバー(tick-taku, murata, haru, nashihara)が気になったセッションの感想などをレポートします。

セッション紹介

nashihara

Day1: From 0 to 100 with Kotlin and Compose Multiplatform

2024.droidkaigi.jp

Kotlin Multiplatform / Compose Multiplatform について、初学者向けにどうやって書いたらいいのかや platform ごとの書き分けの方法などが解説されていました。

Day1のセッションはこちらのみでワークショップ形式での講演でした。

ワークショップの内容は、platform ごとのコードを書く場所の説明から始まり、platform ごとに使えるAPIの種類(例えば kotlin api は全ての platform で使えるが android api は android platform でしか使えない)の説明やそれぞれの Lifecycle がどうなっているかなど、主にこれから KMP/CMP を触り始める人を対象とした内容でした。

自分は今回初めて触る内容が多く新鮮で面白かったです。

特にワークショップの課題として実際に Compose で書いたUIを web / desktop / android platform ごとに実行し確認する、という内容がありましたが、いつも android でUIを作る感覚でUIを書くだけで、 web / desktop アプリを作れてしまうのは、わかっていても感動しました。

Lifecycle に関しても、ちゃんとハンドリングできるようになっていて、web であれば focus → onResume、 blur → onPause 、ios であれば viewWillAppear → onStart、 viewDidDisapper → onStop となっており、 android アプリ開発者が見慣れた形で実装できる点がすごく良いです。

また、collectAsStateWithLifecycle を使っておけばいい感じになる、という便利メソッドも教えてもらいました。

初学者にとって、KMP / CMP の良い導入になるワークショップで楽しいセッションでした。

tick-taku

デザインからアプリ実装まで一貫したデザインシステムを構築するベストプラクティス

2024.droidkaigi.jp

デザインシステムにおけるFigma との連携やプラクティス、導入することのメリットや心構えなどが解説されています。 最近タイミーではデザインシステムを導入しました。その時にリードしてくれたデザイナーの方が話していた内容が、エンジニア目線から解説されていてより理解が深まりました。

個人的には Fimga を中間言語として会話できるようになる ことにとても共感しています。 UI 実装においてデザインファイルがスナップショットになっており、実装との差が発生することでデザイナーとのコミュニケーションが都度発生し、開発スピードが遅くなったり心理的ハードルになったりしていました。デザインシステムを導入することで Figma を起点としてデザイン製作時にエンジニアとデザイナー双方のエッセンスが考慮されたコンポーネントで作られることになり、これらの課題のコスト軽減が期待できます。

また Figma の property と Composable の引数を同期させれば、 Figma 上に実装のために必要な情報が全て書かれています。それだけ見ればある種脳死で実装できるようになり、より統一感のある UI 実装がスピーディ & スムーズに行えるようになります。

Figma は素晴らしいツールですね!

デザインシステム導入時に非常に勉強になるポイントがちりばめられたセッションでした。スライドもとても分かりやすく見やすかったです。

2024年のナビゲーション・フォーカス対応:Composeでキーボード・ナビゲーションをサポートしよう

2024.droidkaigi.jp

Android のアクセシビリティにおける focus についての紹介や実装のポイントが解説されているセッションでした。

実装についてはアクセシビリティを意識したユースケースの紹介と実装や、xml を何年も書いてない人のために Compose での実装方法や tips も紹介されていました。

特に「宣言順序とレベルによって focus が流れる」という話は、なんとなくそうなんだろうなと思っていたところもあって納得できました。focus のことを考えるとやはり ConstraintLayout を多用するのはよくないのかも。 やっぱり Modifier 順序問題は難しいですね...

また動作確認方法についても触れられていました。 特に Android Studio 上で物理デバイスのミラーリングができることは個人的に初耳で知れて非常に良かったです。

いつもスプリントレビューなどでチームに画面共有する際に、ブラウザと並べて表示するため scrcpy を使っていましたが IDE だけで済みそうです。

ソフトウェアキーボード体験改善の tips はまた今度とのことなのでそちらも楽しみに待っています。

haru

Jetpack ComposeにおけるShared Element Transitionsの実例と導入方法 またその仕組み

2024.droidkaigi.jp

Shared Element Transition, 皆さんは使っていますか?

私たちタイミーのアプリ内でもいくつかの場所で使用しており、Compose化するときに泣く泣くShared Element Transitionを使わない方法で実装し直したりしていましたが、ついにComposeでも取り入れられるようになってきました。

このセッションでは、実際にGoogle PhotosのようなUIをComposeで実装していくことによって実際の実装方法、つまづきポイント、その解決方法などを順番に紹介してもらうことができました。

Transitionにもいくつか種類があり、どちらの方が見え方が綺麗なのかといったところまで説明されていて実際に取り入れる際にもとても参考になるセッションでした。

murata

Kotlin 2.0が与えるAndroid開発の進化

2024.droidkaigi.jp

タイトル通り、Kotlin 2.0によって受けられる恩恵がこれでもかというくらい紹介されていたセッションでした!

数も多かったですが、特に個人的に刺さったものをピックアップして紹介します。

Power AssertをKotlinが正式にサポート 🎉

従来のUnitTestにおけるFailed Messageは非常にシンプルにExpectedとActualの値が表示されるだけのメッセージでした。

Expected :0
Actual   :6

Power Assertを導入した場合は、以下のようにテストが失敗した理由を事細かに表示してくれます。※公式ページより引用

Incorrect length
assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
       |     |      |  |     |               |
       |     |      |  |     |               3
       |     |      |  |     orl
       |     |      |  world!
       |     |      false
       |     5
       Hello

注意点として、assert式の書き方をPower Assertを意識した書き方に少し変える必要はありそうです。

例えば、 assert(isValidName && isValidAge) のような変数のみをassert式に入れてしまうと以下のようにFailed Messageの情報量も減ってしまう為、先ほどの例のように変数をインライン化する必要があります。

Assertion failed
assert(isValidName && isValidAge)
       |              |
       |              false
       true

ですが、導入することでテストを用いた開発やCIが落ちた時の要因調査が捗ること間違い無しの素晴らしい機能ですね!!

Jetpack Compose Strong Skip Mode enabled by default

Kotlin 2.0.20 より、ComposeのStrong Skip ModeがデフォルトでONになりました。

具体的には、Unstableな引数を使用していても、同一instanceであればRecomposeされなくなります。

Strong Skip Modeについては既に各所で話題になっていましたが、さらにこのセクションで紹介されていた以下のポイントが個人的に刺さりました。

Stability Configuration File

使用する箇所とは遠いところでStableの指定を行う行為自体に懐疑的でしたが、セッションで紹介されていた「java.time.LocalDateのようなJavaのクラスをStableだと認識させる」ようなユースケースではかなり有用だと感じました。

Lambdaの再生成の条件

Lambdaの中で一般的なViewModelのようなUnstableな変数を参照していても再生成されなくなるといった点は非常に便利だなと感じました。onClickのコールバックにてViewModelのメソッドを呼ぶようなこと、よくありますもんね。

代わりに、Lambdaの再生成を前提にしていたようなケースでは @DontMemoize を新たに指定する必要が出てくる点にも地味に注意が必要だと感じました。たまによくありそう。

Object equalsとInstance equals

従来はListは常にUnstableだと見なされていた為、タイミーではComposableの引数にリストを指定する場合はListではなくkotlinx.collections.immutable.ImmutableList を利用するルールとしていました。

ただし、セッションで紹介されている通り、パフォーマンス観点において「Object equalsはO(n)に対してinstance equalsはO(1)」となります。

よって、件数が多く複雑なlistである場合には、ImmutableListではなくListを指定することでinstance equalsとした方が速くなるケースが考えられる為、Strong Skip ModeがONになった際にはどちらを使用するのか都度検討する必要がありそうです。

これらの他にもKotlin 2.0 の良きポイントを理解できる情報がたっぷりな、とても良いセッションでした!! 発表いただき本当にありがとうございました!

まとめ

9月に新たにジョインされたメンバーも KaigiPass 制度を利用してみんなでワイワイ参加できました。カンファレンス後にチーム内でセッションのシェアやディスカッションを行うなど、非常に多くの学びやヒントを得ることができモチベーションへ繋がるカンファレンスとなりました。

ブースやアフターパーティーなどでさまざまな人と交流できたり、セッションのアーカイブのアップが早くその日に見直すこともできたり素晴らしい体験ができました。スタッフの皆様ありがとうございました!

次回の開催も楽しみにしています!

余談

今回タイミーはDroidKaigi 2024にゴールドスポンサーとして協賛し、冒頭のブース出展だけでなくDroidKaigiスカラーシップの活動もお手伝いさせていただきました。 そして、DroidKaigiスカラーシップでは企業訪問の取り組みに参加し、DroidKaigiに参加されていた学生さんたちをタイミーのオフィスにご招待しました!

タイミー社員と一緒にランチをしたり、オフィス内を一緒に周り紹介したりする中で、熱心に活動されている学生のみなさんとお話できたことは、私たちにとっても楽しく、とても良い機会でした。

タイミーはこれからも技術コミュニティへの協賛、協力を通して一緒に盛り上げていきます!それではまたどこかの勉強会・カンファレンスでお会いしましょう👋

Steep エラーリファレンスを作りました(2024/09/30 時点)

タイミーでバックエンドのテックリードをしている新谷(@euglena1215)です。

タイミーでは RBS の活用を推進する取り組みを少しずつ進めています。意図はこちら

メンバーと雑談していたときに「steep check でコケたときにその名前で調べても全然ヒットしないので型周りのキャッチアップが難しい」という話を聞きました。
いくつかのエラー名でググってみたところ、 Ruby::ArgumentTypeMismatchRuby::NoMethod など有名なエラーはヒットしますがほとんどのエラーはヒットせず、ヒットするのは Steep リポジトリの該当実装のみでした。
これでは確かにキャッチアップは難しいだろうと感じたので、Steep のエラーリファレンスを作ってみました。ググってヒットするのが目的なのでテックブログとして公開してインデックスされることを期待します。

 

各エラーの説明は以下のフォーマットで行います。

エラー名

説明: 簡単なエラーの説明

例:

エラーが検出される Ruby コード
steep check を実行して得られるエラーメッセージ

severity:
Steep のエラープリセットに対して、該当エラーの severity がどのように設定されているかの表

 


Ruby::ArgumentTypeMismatch

説明: メソッドの型が一致しない場合に発生します。

違反例:

'1' + 1
test.rb:1:6: [error] Cannot pass a value of type `::Integer` as an argument of type `::string`
│   ::Integer <: ::string
│     ::Integer <: (::String | ::_ToStr)
│       ::Integer <: ::String
│         ::Numeric <: ::String
│           ::Object <: ::String
│             ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ '1' + 1
        ~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::BlockBodyTypeMismatch

説明: ブロックの body の返り値の型が期待される型と一致しない場合に発生します。

違反例:

lambda {|x| x + 1 } #: ^(Integer) -> String
test.rb:1:7: [error] Cannot allow block body have type `::Integer` because declared as type `::String`
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::BlockBodyTypeMismatch
│
└ lambda {|x| x + 1 } #: ^(Integer) -> String
         ~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error warning error information nil

Ruby::BlockTypeMismatch

説明: ブロックの型が期待される型と一致しない場合に発生します。

違反例:

multi = ->(x, y) { x * y } #: ^(Integer, Integer) -> Integer
[1, 2, 3].map(&multi)
test.rb:2:14: [error] Cannot pass a value of type `^(::Integer, ::Integer) -> ::Integer` as a block-pass-argument of type `^(::Integer) -> U(1)`
│   ^(::Integer, ::Integer) -> ::Integer <: ^(::Integer) -> U(1)
│     (Params are incompatible)
│
│ Diagnostic ID: Ruby::BlockTypeMismatch
│
└ [1, 2, 3].map(&multi)
                ~~~~~~

severity:

all_error default strict lenient silent
error warning error information nil

Ruby::BreakTypeMismatch

説明: break の型が期待される型と一致しない場合に発生します。

違反例:

123.tap { break "" }
test.rb:1:10: [error] Cannot break with a value of type `::String` because type `::Integer` is assumed
│   ::String <: ::Integer
│     ::Object <: ::Integer
│       ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::BreakTypeMismatch
│
└ 123.tap { break "" }
            ~~~~~~~~

severity:

all_error default strict lenient silent
error hint error hint nil

Ruby::DifferentMethodParameterKind

説明: メソッドのパラメータの種類が一致しない場合に発生します。省略可能な引数の prefix に ? をつけ忘れることで発生することが多いです。

違反例:

# @type method bar: (name: String) -> void
def bar(name: "foo")
end
test.rb:2:8: [error] The method parameter has different kind from the declaration `(name: ::String) -> void`
│ Diagnostic ID: Ruby::DifferentMethodParameterKind
│
└ def bar(name: "foo")
          ~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::FallbackAny

説明: 型が不明な場合に untyped が使用されることを示します。一度 [] で値を初期化したのちに再代入するような実装で発生することが多いです。

違反例:

a = []
a << 1
test.rb:1:4: [error] Cannot detect the type of the expression
│ Diagnostic ID: Ruby::FallbackAny
│
└ a = []
      ~~

severity:

all_error default strict lenient silent
error hint warning nil nil

Ruby::FalseAssertion

説明: Steep の型アサーションが誤っている場合に発生します。

違反例:

array = [] #: Array[Integer]
hash = array #: Hash[Symbol, String]
test.rb:2:7: [error] Assertion cannot hold: no relationship between inferred type (`::Array[::Integer]`) and asserted type (`::Hash[::Symbol, ::String]`)
│ Diagnostic ID: Ruby::FalseAssertion
│
└ hash = array #: Hash[Symbol, String]
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::ImplicitBreakValueMismatch

説明: 引数無し break の値( nil )がメソッドの返り値の期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs () { (String) -> Integer } -> String
  def foo
    ''
  end
end

Foo.new.foo do |x|
  break
end
test.rb:9:2: [error] Breaking without a value may result an error because a value of type `::String` is expected
│   nil <: ::String
│
│ Diagnostic ID: Ruby::ImplicitBreakValueMismatch
│
└   break
    ~~~~~

severity:

all_error default strict lenient silent
error hint information nil nil

Ruby::IncompatibleAnnotation

説明: 型注釈が不適切または一致しない場合に発生します。

違反例:

a = [1,2,3]

if _ = 1
  # @type var a: String
  a + ""
end
test.rb:5:2: [error] Type annotation about `a` is incompatible since ::String <: ::Array[::Integer] doesn't hold
│   ::String <: ::Array[::Integer]
│     ::Object <: ::Array[::Integer]
│       ::BasicObject <: ::Array[::Integer]
│
│ Diagnostic ID: Ruby::IncompatibleAnnotation
│
└   a + ""
    ~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::IncompatibleArgumentForwarding

説明: 引数に ... を使ってメソッドの引数を forward する際に、引数の型が一致しない場合に発生します。

違反例:

class Foo
  # @rbs (*Integer) -> void
  def foo(*args)
  end

  # @rbs (*String) -> void
  def bar(...)
    foo(...)
  end
end
test.rb:8:8: [error] Cannot forward arguments to `foo`:
│   (*::Integer) <: (*::String)
│     ::String <: ::Integer
│       ::Object <: ::Integer
│
│ Diagnostic ID: Ruby::IncompatibleArgumentForwarding
│
└     foo(...)
          ~~~

severity:

all_error default strict lenient silent
error warning error information nil

Ruby::IncompatibleAssignment

説明: 代入の際の型が不適切または一致しない場合に発生します。

違反例:

# @type var x: Integer
x = "string"
test.rb:2:0: [error] Cannot assign a value of type `::String` to a variable of type `::Integer`
│   ::String <: ::Integer
│     ::Object <: ::Integer
│       ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::IncompatibleAssignment
│
└ x = "string"
  ~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error hint nil

Ruby::InsufficientKeywordArguments

説明: キーワード引数が不足している場合に発生します。

違反例:

class Foo
  def foo(a:, b:)
  end
end
Foo.new.foo(a: 1)
test.rb:5:8: [error] More keyword arguments are required: b
│ Diagnostic ID: Ruby::InsufficientKeywordArguments
│
└ Foo.new.foo(a: 1)
          ~~~~~~~~~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::InsufficientPositionalArguments

説明: 位置引数が不足している場合に発生します。

違反例:

class Foo
  def foo(a, b)
  end
end
Foo.new.foo(1)
test.rb:5:8: [error] More keyword arguments are required: b
│ Diagnostic ID: Ruby::InsufficientKeywordArguments
│
└ Foo.new.foo(a: 1)
          ~~~~~~~~~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::InsufficientTypeArgument

説明: 型引数に対する型注釈が不足している場合に発生します。

違反例:

class Foo
  # @rbs [T, S] (T, S) -> [T, S]
  def foo(x, y)
    [x, y]
  end
end

Foo.new.foo(1, 2) #$ Integer
test.rb:8:0: [error] Requires 2 types, but 1 given: `[T, S] (T, S) -> [T, S]`
│ Diagnostic ID: Ruby::InsufficientTypeArgument
│
└ Foo.new.foo(1, 2) #$ Integer
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::InvalidIgnoreComment

説明: steep:ignore:start コメントはあるが steep:ignore:end コメントがないなど、無効なコメントが存在する場合に発生します。

違反例:

# steep:ignore:start
test.rb:1:0: [error] Invalid ignore comment
│ Diagnostic ID: Ruby::InvalidIgnoreComment
│
└ # steep:ignore:start
  ~~~~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error warning warning warning nil

Ruby::MethodArityMismatch

説明: キーワード引数なのに順序引数としてメソッドの引数の型を記述しているなど、メソッドの引数の型が一致しない場合に発生します。

違反例:

class Foo
  # @rbs (Integer x) -> Integer
  def foo(x:)
    x
  end
end
test.rb:3:9: [error] Method parameters are incompatible with declaration `(::Integer) -> ::Integer`
│ Diagnostic ID: Ruby::MethodArityMismatch
│
└   def foo(x:)
           ~~~~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::MethodBodyTypeMismatch

説明: メソッドの返り値が期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs () -> String
  def foo
    1
  end
end
test.rb:3:6: [error] Cannot allow method body have type `::Integer` because declared as type `::String`
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::MethodBodyTypeMismatch
│
└   def foo
        ~~~

severity:

all_error default strict lenient silent
error error error warning nil

Ruby::MethodDefinitionMissing

説明: メソッドの型定義が存在するがメソッドの実装が欠落している場合に発生します。

違反例:

class Foo
  # @rbs!
  #   def bar: () -> void
end
test.rb:1:6: [error] Cannot find implementation of method `::Foo#bar`
│ Diagnostic ID: Ruby::MethodDefinitionMissing
│
└ class Foo
        ~~~

severity:

all_error default strict lenient silent
error nil hint nil nil

Ruby::MethodParameterMismatch

説明: メソッドのパラメータの型が一致しない場合に発生します。

違反例:

class Foo
  # @rbs (Integer x) -> Integer
  def foo(x:)
    x
  end
end
test.rb:3:10: [error] The method parameter is incompatible with the declaration `(::Integer) -> ::Integer`
│ Diagnostic ID: Ruby::MethodParameterMismatch
│
└   def foo(x:)
            ~~

severity:

all_error default strict lenient silent
error error error warning nil

Ruby::MethodReturnTypeAnnotationMismatch

説明: メソッドの戻り値の型注釈が期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs () -> String
  def foo
    # @type return: Integer
    123
  end
end
test.rb:3:2: [error] Annotation `@type return` specifies type `::Integer` where declared as type `::String`
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::MethodReturnTypeAnnotationMismatch
│
└   def foo
    ~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::MultipleAssignmentConversionError

説明: 複数代入の変換に失敗した場合に発生します。

違反例:

class WithToAry
  # @rbs () -> Integer
  def to_ary
    1
  end
end

a, b = WithToAry.new()
test.rb:8:8: [error] Cannot convert `::WithToAry` to Array or tuple (`#to_ary` returns `::Integer`)
│ Diagnostic ID: Ruby::MultipleAssignmentConversionError
│
└ (a, b = WithToAry.new())
          ~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::NoMethod

説明: 型定義が存在しないメソッドが呼び出された場合に発生します。

違反例:

"".non_existent_method
test.rb:1:3: [error] Type `::String` does not have method `non_existent_method`
│ Diagnostic ID: Ruby::NoMethod
│
└ "".non_existent_method
     ~~~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::ProcHintIgnored

説明: Proc に関する型注釈が無視された場合に発生します。

違反例:

# @type var proc: (^(::Integer) -> ::String) | (^(::String, ::String) -> ::Integer)
proc = -> (x) { x.to_s }
test.rb:2:7: [error] The type hint given to the block is ignored: `(^(::Integer) -> ::String | ^(::String, ::String) -> ::Integer)`
│ Diagnostic ID: Ruby::ProcHintIgnored
│
└ proc = -> (x) { x.to_s }
         ~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint information nil nil

Ruby::ProcTypeExpected

説明: Proc 型が期待される場合に発生します。

違反例:

-> (&block) do
  # @type var block: Integer
end
test.rb:1:4: [error] Proc type is expected but `::Integer` is specified
│ Diagnostic ID: Ruby::ProcTypeExpected
│
└ -> (&block) do
      ~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::RBSError

説明: 型アサーションや型適用に書かれたRBS型がエラーを生じる場合に発生します。

違反例:

a = 1 #: Int
test.rb:1:9: [error] Cannot find type `::Int`
│ Diagnostic ID: Ruby::RBSError
│
└ a = 1 #: Int
           ~~~

severity:

all_error default strict lenient silent
error information error information nil

Ruby::RequiredBlockMissing

説明: メソッド呼び出し時に必要な block が欠落している場合に発生します。

違反例:

class Foo
  # @rbs () { () -> void } -> void
  def foo
    yield
  end
end
Foo.new.foo
test.rb:7:8: [error] The method cannot be called without a block
│ Diagnostic ID: Ruby::RequiredBlockMissing
│
└ Foo.new.foo
          ~~~

severity:

all_error default strict lenient silent
error error error hint nil

Ruby::ReturnTypeMismatch

説明: return の型とメソッドの戻り値の型が一致しない場合に発生します。

違反例:

# @type method foo: () -> Integer
def foo
  return "string"
end
test.rb:3:2: [error] The method cannot return a value of type `::String` because declared as type `::Integer`
│   ::String <: ::Integer
│     ::Object <: ::Integer
│       ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::ReturnTypeMismatch
│
└   return "string"
    ~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error error error warning nil

Ruby::SetterBodyTypeMismatch

説明: セッターメソッドの戻り値の型が期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs (String) -> String
  def foo=(value)
    123
  end
end
test.rb:3:6: [error] Setter method `foo=` cannot have type `::Integer` because declared as type `::String`
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::SetterBodyTypeMismatch
│
└   def foo=(value)
        ~~~~

severity:

all_error default strict lenient silent
error information error nil nil

Ruby::SetterReturnTypeMismatch

説明: セッターメソッドの return の型が期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs (String) -> String
  def foo=(value)
    return 123
  end
end
test.rb:4:4: [error] The setter method `foo=` cannot return a value of type `::Integer` because declared as type `::String`
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::SetterReturnTypeMismatch
│
└     return 123
      ~~~~~~~~~~

severity:

all_error default strict lenient silent
error information error nil nil

Ruby::SyntaxError

説明: Ruby の構文エラーが発生した場合に発生します。

違反例:

if x == 1
  puts "Hello"
test.rb:2:14: [error] SyntaxError: unexpected token $end
│ Diagnostic ID: Ruby::SyntaxError
│
└   puts "Hello"

severity:

all_error default strict lenient silent
error hint hint hint nil

Ruby::TypeArgumentMismatchError

説明: 型引数が期待される型と一致しない場合に発生します。

違反例:

class Foo
  # @rbs [T < Numeric] (T) -> T
  def foo(x)
    x
  end
end
Foo.new.foo("") #$ String
test.rb:7:19: [error] Cannot pass a type `::String` as a type parameter `T < ::Numeric`
│   ::String <: ::Numeric
│     ::Object <: ::Numeric
│       ::BasicObject <: ::Numeric
│
│ Diagnostic ID: Ruby::TypeArgumentMismatchError
│
└ Foo.new.foo("") #$ String
                     ~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::UnexpectedBlockGiven

説明: ブロックが予期されない場面で渡された場合に発生します。

違反例:

[1].at(1) { 123 }
test.rb:1:10: [error] The method cannot be called with a block
│ Diagnostic ID: Ruby::UnexpectedBlockGiven
│
└ [1].at(1) { 123 }
            ~~~~~~~

severity:

all_error default strict lenient silent
error warning error hint nil

Ruby::UnexpectedDynamicMethod

説明: 動的に定義されたメソッドが存在しない場合に発生します。

違反例:

class Foo
  # @dynamic foo

  def bar
  end
end
test.rb:1:6: [error] @dynamic annotation contains unknown method name `foo`
│ Diagnostic ID: Ruby::UnexpectedDynamicMethod
│
└ class Foo
        ~~~

severity:

all_error default strict lenient silent
error hint information nil nil

Ruby::UnexpectedError

説明: 予期しない一般的なエラーが発生した場合に発生します。

違反例:

class Foo
  # @rbs () -> String123
  def foo
  end
end
test.rb:1:0: [error] UnexpectedError: sig/generated/test.rbs:5:17...5:26: Could not find String123(RBS::NoTypeFoundError)
│ ...
│   (36 more backtrace)
│
│ Diagnostic ID: Ruby::UnexpectedError
│
└ class Foo
  ~~~~~~~~~

severity:

all_error default strict lenient silent
error hint information hint nil

Ruby::UnexpectedJump

説明: 予期しないジャンプが発生した場合に発生します。

違反例:

break
test.rb:1:0: [error] Cannot jump from here
│ Diagnostic ID: Ruby::UnexpectedJump
│
└ break
  ~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::UnexpectedJumpValue

説明: ジャンプの値を渡しても値が無視される場合に発生します。

違反例:

while true
  next 3
end
test.rb:2:2: [error] The value given to next will be ignored
│ Diagnostic ID: Ruby::UnexpectedJumpValue
│
└   next 3
    ~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::UnexpectedKeywordArgument

説明: 予期しないキーワード引数が渡された場合に発生します。

違反例:

class Foo
  # @rbs (x: Integer) -> void
  def foo(x:)
  end
end

Foo.new.foo(x: 1, y: 2)
test.rb:7:18: [error] Unexpected keyword argument
│ Diagnostic ID: Ruby::UnexpectedKeywordArgument
│
└ Foo.new.foo(x: 1, y: 2)
                    ~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::UnexpectedPositionalArgument

説明: 予期しない位置引数が渡された場合に発生します。

違反例:

class Foo
  # @rbs (Integer) -> void
  def foo(x)
  end
end

Foo.new.foo(1, 2)
test.rb:7:15: [error] Unexpected positional argument
│ Diagnostic ID: Ruby::UnexpectedPositionalArgument
│
└ Foo.new.foo(1, 2)
                 ~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::UnexpectedSuper

説明: super を呼び出した際に親クラスに同名のメソッドが定義されていないなど、予期しない場面で super が使用された場合に発生します。

違反例:

class Foo
  def foo
    super
  end
end
test.rb:3:4: [error] No superclass method `foo` defined
│ Diagnostic ID: Ruby::UnexpectedSuper
│
└     super
      ~~~~~

severity:

all_error default strict lenient silent
error information error nil nil

Ruby::UnexpectedTypeArgument

説明: 予期しない型引数が渡された場合に発生します。

違反例:

class Foo
  # @rbs [T] (T) -> T
  def foo(x)
    x
  end
end

Foo.new.foo(1) #$ Integer, Integer
test.rb:8:27: [error] Unexpected type arg is given to method type `[T] (T) -> T`
│ Diagnostic ID: Ruby::UnexpectedTypeArgument
│
└ Foo.new.foo(1) #$ Integer, Integer
                             ~~~~~~~

severity:

all_error default strict lenient silent
error hint error nil nil

Ruby::UnexpectedYield

説明: yield が予期しない場面で使用された場合に発生します。

違反例:

class Foo
  # @rbs () -> void
  def foo
    yield
  end
end
test.rb:4:4: [error] No block given for `yield`
│ Diagnostic ID: Ruby::UnexpectedYield
│
└     yield
      ~~~~~

severity:

all_error default strict lenient silent
error warning error information nil

Ruby::UnknownConstant

説明: 未知の定数が参照された場合に発生します。

違反例:

FOO
test.rb:1:0: [error] Cannot find the declaration of constant: `FOO`
│ Diagnostic ID: Ruby::UnknownConstant
│
└ FOO
  ~~~

severity:

all_error default strict lenient silent
error warning error hint nil

Ruby::UnknownGlobalVariable

説明: 未知のグローバル変数が参照された場合に発生します。

違反例:

$foo
test.rb:1:0: [error] Cannot find the declaration of global variable: `$foo`
│ Diagnostic ID: Ruby::UnknownGlobalVariable
│
└ $foo
  ~~~~

severity:

all_error default strict lenient silent
error warning error hint nil

Ruby::UnknownInstanceVariable

説明: 未知のインスタンス変数が参照された場合に発生します。

違反例:

class Foo
  def foo
    @foo = 'foo'
  end
end
test.rb:3:4: [error] Cannot find the declaration of instance variable: `@foo`
│ Diagnostic ID: Ruby::UnknownInstanceVariable
│
└     @foo = 'foo'
      ~~~~

severity:

all_error default strict lenient silent
error information error hint nil

Ruby::UnreachableBranch

説明: if ,unless による到達不可能な分岐が存在する場合に発生します。

違反例:

if false
  1
end
test.rb:1:0: [error] The branch is unreachable
│ Diagnostic ID: Ruby::UnreachableBranch
│
└ if false
  ~~

severity:

all_error default strict lenient silent
error hint information hint nil

Ruby::UnreachableValueBranch

説明: case when による到達不可能な分岐が存在し、分岐の型が bot でなかった場合に発生します。

違反例:

x = 1
case x
when Integer
  "one"
when String
  "two"
end
test.rb:5:0: [error] The branch may evaluate to a value of `::String` but unreachable
│ Diagnostic ID: Ruby::UnreachableValueBranch
│
└ when String
  ~~~~

severity:

all_error default strict lenient silent
error hint warning hint nil

Ruby::UnresolvedOverloading

説明: オーバーロードが行われているメソッドに対して型が解決できない場合に発生します。

違反例:

3 + "foo"
test.rb:1:0: [error] Cannot find compatible overloading of method `+` of type `::Integer`
│ Method types:
│   def +: (::Integer) -> ::Integer
│        | (::Float) -> ::Float
│        | (::Rational) -> ::Rational
│        | (::Complex) -> ::Complex
│
│ Diagnostic ID: Ruby::UnresolvedOverloading
│
└ 3 + "foo"
  ~~~~~~~~~

severity:

all_error default strict lenient silent
error error error information nil

Ruby::UnsatisfiableConstraint

説明: RBSと型注釈の辻褄が合わないなど、どうやっても型制約が満たされない場合に発生します。

違反例:

class Foo
  # @rbs [A, B] (A) { (A) -> void } -> B
  def foo(x)
  end
end

test = Foo.new

test.foo(1) do |x|
  # @type var x: String
end
test.rb:9:0: [error] Unsatisfiable constraint `::Integer <: A(1) <: ::String` is generated through (A(1)) { (A(1)) -> void } -> B(2)
│   ::Integer <: ::String
│     ::Numeric <: ::String
│       ::Object <: ::String
│         ::BasicObject <: ::String
│
│ Diagnostic ID: Ruby::UnsatisfiableConstraint
│
└ test.foo(1) do |x|
  ~~~~~~~~~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint error hint nil

Ruby::UnsupportedSyntax

説明: Steep としてサポートされていない構文が使用された場合に発生します。

違反例:

(_ = []).[]=(*(_ = nil))
test.rb:1:13: [error] Unsupported splat node occurrence
│ Diagnostic ID: Ruby::UnsupportedSyntax
│
└ (_ = []).[]=(*(_ = nil))
               ~~~~~~~~~~

severity:

all_error default strict lenient silent
error hint information hint nil

 


狙ったエラーを引き起こすというのは今年の RubyKaigi であった Ruby "enbugging" Quiz に近い感覚でした。難しい。

基本的には Steep リポジトリにあるテストケースを見ながら埋めていったんですが、中にはテストケースがないものもあったので soutaro さんに直接質問をしながら進めていきました。

また、副産物として Steep で使われなくなったが定義として残っているルールを発見し、削除する patch を作れたのも個人的には良かったです。

github.com