Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スキャン音は鳴るのにカートに入らない — レジと物理デバイスの事件簿

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

スキャン音は鳴るのにカートに入らない — レジと物理デバイスの事件簿

Mobile Mob #2 の登壇資料です
https://bitkey.connpass.com/event/400743/

Avatar for Kenta Enomoto

Kenta Enomoto

August 26, 2026

More Decks by Kenta Enomoto

Other Decks in Technology

Transcript

  1. この不具合の何が嫌か • 構成が特殊 — スキャナーがプリンター経由。再現には、まずこの物理構成を組むところ から • タイミング依存 — ゆっくりスキャンすると再現しない

    • 失敗が静か — スキャン音は鳴る。エラー表示は次のスキャンで即消える Q. みなさんなら、どこから調べますか? 6
  2. 手がかり①: 経路の「差分」を読む スキャナー入力の経路は2系統 スキャナー iPad(レジ) ① 直挿し(HID): 改行までバッファ → フレーミングあり

    スキャナー プリンター 有線 iPad(レジ) Bluetooth classic ② プリンター経由: 前後トリムのみ → フレーミングなし 🤔「複数のバーコードが、1回の didReceive にまとまって届いているのでは?」 (※didReceive はプリンターメーカーの SDK で呼ばれるメソッド) 7
  3. 手がかり②: デバイス境界に「生ログ」を仕込む 単発スキャン(正常) didReceive count=14 utf8="2002226945019\n" → 照合ヒット → カート追加成功

    高速連続スキャン(不具合) didReceive count=42 utf8="2002226945019\n2002226945019\n2002226945019\n" → 照合失敗 → カート未追加(でもスキャン音は鳴る) • 3回分のスキャンが、改行連結で1回にまとまって届いていた • 前後トリムでは中間の改行が残り、「この世に存在しない商品コード」が誕生 • この1行で「取りこぼし」「デコード失敗」の仮説も同時に棄却できた 8
  4. 原因の本質: フレーミングの欠如 ストリーム型の入力には、メッセージの境界がない • 「1回の受信イベント = 1メッセージ」は、受信側が勝手にしていた思い込み • 区切りを解釈して1件ずつに切り出す —

    フレーミングは受信側の責務 • HID の経路には考慮が入っていたが・・・ “Notify that the data is received” didReceive の公式リファレンスの説明 — これで全文。区切りも集約も、何も書かれていない BLE の notify / TCP / シリアル — ストリームを受けるコードには必ず潜む問題 9
  5. 修正と検証 • 修正: 受信ペイロードを改行で分割し、1件ずつ処理する。それだけ • 工夫: 分割ロジックを static 関数に切り出し →

    実機なしでユニットテスト (単一 / 複数連結 / CR・CRLF 混在 / 空セグメント) • 検証: 実機の高速連続スキャンで before / after を動画に記録して確認 「観測された事実の範囲で直す」— 線引きを PR に明記 10
  6. Take Away: 物理デバイスとの付き合い方 • ストリーム受信を見たら、フレーミングを疑う 「1回の受信 = 1メッセージ」はどのプロトコルでも思い込み • タイミング依存の不具合には、デバイス境界の「生ログ」

    加工前のバイト数と生文字列。1行で複数の仮説を棄却できる • 経路の「差分」を探す 動く経路に在って、壊れる経路に無いものが、答えの近くにいる • 再現環境をケチらない 報告と同じ物理構成を組んで再現できた 11