Column

Androidタブレットは白紙の画像を送っていた:クインクエでの1か月で学んだこと

インターン期間の半ばで、私はあることに気づきました。Androidタブレットが、誰の手書き文字も読んでいなかったのです。ドライバーが認識ボタンを押すたびに、アプリは毎回同じ、1ピクセルの白紙の画像を送っていました。私が前の週に完成させた機能は、その画像を前提に動いていました。

この出来事は、2026年9月にクインクエでソフトウェアエンジニアのインターンとして学んだことの多くを表しています。「動いているソフトウェア」と「完成したソフトウェア」は同じではありません。そして、その違いを見分ける方法は、実際に確かめることだけです。

目次

どんなプロジェクトか

私はインドからリモートで、神戸のチームと一緒に「ラピロジ」の開発に参加しました。ラピロジは、倉庫に到着するトラックの受付を管理するシステムです。ドライバーは門にあるタブレットで受付をします。倉庫スタッフは、リアルタイムで更新される待機リストを確認します。順番が来ると、システムがドライバーを呼び出します。

これまで2社が、それぞれ別々にコピーしたシステムを運用していました。このプロジェクトでは、それを1つのコードベースに統合しています。会社ごとの違いは、コードではなく設定で扱います。会社ごとに分かれていたコードを1つにまとめるこの取り組みを、チームでは「フォーク解消」と呼んでいます。

私が担当したのは、ドライバー側の機能です。具体的には、受付の流れ、手書き文字の認識、そしてドライバーがスマートフォンで確認できる受付票です。

最初の数日は、ほとんどコードやドキュメントを読んで過ごしました。コードベースは大きく、ドキュメントの多くは日本語で、どこから手をつければよいか分かりませんでした。助けになったのは、設計書が「システムが何をするか」だけでなく「なぜそうするのか」まで説明していたことです。1週目の終わりには、プロジェクトが迷路ではなく、地図のように見えるようになっていました。

特別扱いではなく、設定で対応する

最初のタスクは、2社目の受付の流れを追加することでした。1社目では、ドライバーはまず、荷主(荷物を出す側の会社)のグループを選びます。次に、会社名を入力する代わりに、短い一覧から自分の会社を選びます。

いちばん手っ取り早いのは、「どの会社が使っているか」を判定するコードを書くことです。しかし、このプロジェクトでは、その方法は使わないルールになっています。そこで、共通のコードに任意で使える設定を追加しました。たとえば、一覧にどの会社を表示するかを絞り込む設定です。そして、それを1社目の設定で有効にしました。これで今後どの会社でも、新しいコードを書かずに同じ流れを使えます。

テストの途中で、担当外の問題が2つ見つかりました。1つ目は、新しく作った1社目のアカウントに荷主グループが1つも登録されていなかったことです。そのため、ドライバーは選択肢のない必須項目を前に立ち往生してしまいます。2つ目は、管理画面で会社の情報を保存するたびに、新しい設定の1つが知らないうちに消えてしまうことでした。私はこの両方を修正し、荷主グループの名前も設定で管理するようにしました。

ここで得た教訓は、機能を作る前に「1社のためにどう動かすか」ではなく「共通のシステムに何が足りないか」を考える、ということです。

動いているのに、完成していなかった

2つ目のタスクは、手書き文字の認識でした。ドライバーはタブレットに会社名を手書きでき、システムがそれを読み取ります。

何かを変える前に、まず自分で認識サービスを呼び出してみました。すると、サービスはすでに、読み取り結果がどれくらい確からしいかを示す「信頼度スコア」と、「人が結果を確認すべき」というフラグを返していました。ところが、タブレットはその両方を無視していました。読み間違いも、正しく読めた場合とまったく同じに見えていたのです。さらに、サービスが止まっていると、ドライバーはエラー画面から先に進めませんでした。

そこで、信頼度スコアをシステム全体で受け渡すようにしました。今では、読み取りが不確かなときはタブレットがドライバーに注意を促します。スタッフの待機リストでは、確認が必要な受付に印が付きます。読み取りに失敗した場合は、行き止まりにならず、手入力に切り替わります。この作業を進めるうえで既存のバグが2つ障害になっていたので、それも修正しました。途中でさらに4つのバグを見つけましたが、変更の範囲を広げず、チーム向けに報告としてまとめました。

そして3週目に、冒頭の問題を見つけました。タブレットは、ドライバーが実際に書いた文字を一度も送っておらず、仮の画像だけを送っていたのです。つまり、私が扱っていた信頼度スコアは、何も測っていませんでした。私はタブレットを修正し、実際に書かれた文字を取り込むようにしました。ここで1つ重要な点がありました。認識サービスは透明な背景を黒として扱うため、文字は白い背景の上で取り込む必要があったのです。

その後、自分のテストでは見つからなかった問題を、コードレビューが見つけてくれました。ボタンを素早く2回押すと、リクエストが同時に2つ送られてしまうのです。私はこのチェックを、リクエストが終わるまで次の送信を受け付けないロックに置き換えました。テストは、自分が思いついたケースしか確かめられません。良いレビュアーは、自分が思いつかなかったケースを問いかけてくれます。

チームの役に立った小さな修正

役に立つ変更は、新機能だけではありません。新しく環境を作ると、タブレットの登録が必ず失敗し、「登録コードが正しくありません」という誤解を招くメッセージが出ていました。本当の原因は、どこにも書かれていない設定が1つ欠けていたことでした。私は設定ファイルのサンプルと環境構築の手順を追加しました。そして、新しく参加した開発者がそのファイルをコピーするだけで端末を登録できることを確認しました。

テスト中には、標準的なローカル環境で運用担当者のログインが失敗することにも気づき、小さな修正を用意しました。どちらも地味な変更です。それでも、ほかの開発者が半日を無駄にすることを防げます。

存在しなかったQRコード

最後のタスクは、リアルタイムで更新される受付票を、実際のスマートフォンでテストすることでした。ドライバーは受付を済ませたあと、タブレットに表示されたQRコードを読み取り、自分の待ち順を確認できるはずでした。

作業を始める前に、コードをタスクの要件や画面設計と照らし合わせました。受付票のページは存在し、動いていました。しかし、タブレットとスマートフォンをつなぐQRコードは、一度も作られていなかったのです。以前の変更で後回しにされ、その後の対応が行われないままになっていました。このままでは、タスクに書かれたとおりのテストはできませんでした。

私はこの状況を、根拠とあわせてチームに報告しました。実機でテストすれば見えてくるはずのほかの不足点も、一覧にまとめました。そのうえで、足りない部分を自分で作りました。最初に1つ、設計上の問題を解決する必要がありました。本番環境では、タブレットは自社の受付票ページのURLを知らないのです。そこでサーバーを修正し、受付のたびに、すぐに使える受付票のリンクを返すようにしました。タブレットは、それをQRコードとして表示するだけです。

初めてQRコードを読み取り、自分の受付と待ち順がスマートフォンに表示されたときは、この1か月でいちばんうれしい瞬間でした。タブレットからサーバーを通ってドライバーの手元まで、一連の流れが最初から最後まで動くのを見たのは、それが初めてでした。

あわせて、未解決の問題を1つチームに提起しました。受付完了画面は15秒でリセットされます。ドライバーがスマートフォンを取り出してQRコードを読み取るには、短すぎるように感じました。

学んだこと

  • システムが実際に何をしているかを確かめる。 完成しているように見えるコードが、実は何もしていないこともあります。サービスを実際に呼び出し、本物のデータを見ることで、コードを読むだけでは見落としていた問題が見つかりました。
  • 「完了」を疑う。 作業は、それを完了にした変更とだけでなく、元の要件と照らし合わせて確認します。
  • 変更の範囲を絞る。 自分の作業を妨げるものは直し、それ以外は、ほかの人が分かるように報告としてまとめます。
  • レビューも仕事の一部として捉える。 レビューは、自分の前提が試される場です。
  • 1社のためではなく、すべての顧客のために作る。 どの会社にも使える設定は、1社だけのための特別扱いより価値があります。

チームと働いて

私はプロジェクトリーダーのもとで、システムの別々の部分を担当するほかのインターンたちと一緒に働きました。チームのメンバーが私の変更をレビューしてマージしてくれました。そして、その問いかけによって、作業はいつも良いものになりました。

チームは、文書を大切にしていました。設計書、画面仕様書、そして変更ごとのチェックリストです。1か月だけ参加する私でも、1週目から貢献できたのはそのおかげです。

時差のある環境で、主に日本語で話すチームと働いたことで、文章の書き方が変わりました。問題、原因、根拠、テストの方法を1つのメッセージにまとめ、それだけで伝わるように書くことを覚えました。そうすれば、追加の質問のために誰かが1日待つ必要がなくなります。メモやコミットメッセージの一部は、日本語で書きました。

いちばん印象に残ったのは、チームがレビューをとても真剣に行っていたことです。小さな変更にも丁寧な問いかけがありました。それは決してあら探しではなく、システムを正しくするための問いでした。その姿勢に触れて、誰かに見てもらう前に、自分の作業を見直す習慣が身につきました。

これから

小さく切り分けられたタスクばかりの1か月になると思っていました。しかし実際には、新しい会社の受付の流れを作り、手書き文字の認識が本当に機能するようにし、タブレットからスマートフォンまでのドライバーの体験を完成させる手助けをしました。

問題を見つけ、その根本原因までたどることには慣れました。次は、設計の力を伸ばしたいと考えています。作る前に、機能をどんな形にするべきかを決める力です。受付票のリンクの仕組みを決めてからでないと、QRコードを作れなかったように。

レビューをしてくださり、辛抱強く支えてくださり、実際のプロダクトに携わる機会をくださったクインクエのチームの皆さまに感謝しています。この1か月で、自分がなりたいエンジニア像がよりはっきりしました。

以下原文。

The Kiosk Was Sending a Blank Image: Lessons from a Month at Quin Que

Halfway through my internship, I discovered that the kiosk was not reading anyone's handwriting. Every time a driver tapped the recognize button, the app sent the same blank, one-pixel image. The feature I had finished the week before depended on that image.

That moment sums up much of what I learned as a software engineering intern at Quin Que in September 2026: working software and finished software are not the same thing, and the only way to tell them apart is to check.

The Project

I worked remotely from India with the team in Kobe, Japan, on Rapilogi, a system that manages truck arrivals at warehouses. A driver checks in on a tablet kiosk at the gate, warehouse staff see a live waiting list, and the system calls each driver when it is their turn.

Two companies had been running separate copies of the system. The project is merging them into one codebase, where the differences between companies live in configuration rather than in code. The team calls this fork elimination.

My work focused on the driver's side of the product: the check-in flow, handwriting recognition, and the receipt a driver can follow on their phone.

My first days were mostly reading. The codebase was large, many documents were in Japanese, and I was not sure where to start. What helped was that the design documents explained not just what the system does, but why. By the end of the first week, the project felt less like a maze and more like a map.

Configuration, Not Special Cases

My first task was to add a second company's check-in process. At Logitem, a driver first chooses a shipper group, then picks their company from a short list instead of typing its name.

The quickest route would have been code that checks which company is using the system. The project does not allow that. Instead, I added optional settings to the shared code, such as a filter for which companies appear in the list, and switched them on in Logitem's configuration. Any future company can now use the same flow without new code.

Testing turned up two problems outside my task. Newly created Logitem accounts had no shipper groups, so drivers faced a required choice with no options. And the admin screen quietly erased one of the new settings every time someone saved a company. I fixed both, and kept the shipper group names in configuration too.

The lesson I took from this: before building a feature, ask what the shared system is missing, not how to make one customer work.

When Finished Is Not Finished

My second task was handwriting recognition. Drivers can write their company name on the tablet, and the system reads it.

Before changing anything, I called the recognition service myself. It already returned a confidence score and a flag saying a person should check the result. The kiosk ignored both. A misread looked exactly like a correct read, and if the service was down, the driver was stuck on an error screen.

I carried the confidence score through the whole system. The kiosk now warns the driver when a read looks uncertain, the staff waiting list marks entries that need checking, and a failed read falls back to typing instead of a dead end. Two existing bugs blocked this work, so I fixed them as well. I found four more along the way and wrote them up for the team rather than widening the change.

Then, in my third week, I found the problem from the start of this post. The kiosk had never sent the driver's actual handwriting, only a placeholder image, so my confidence scores measured nothing. I changed the kiosk to capture the real drawing. One detail mattered: the recognition service turns a transparent background black, so the drawing had to be captured on white.

Code review then caught something my own testing had not. Two quick taps could send two requests at once. I replaced the check with a lock that holds until the request finishes. Tests cover the cases I think of; a good reviewer asks about the ones I did not.

Small Fixes That Helped the Team

Not every useful change is a feature. On a fresh setup, registering a kiosk tablet always failed with a misleading "incorrect registration code" message. The real cause was a missing setting that was not documented anywhere. I added an example settings file and setup instructions, then confirmed that a new developer could register a device just by copying that file.

While testing, I also found that operator logins failed on the standard local setup, and prepared a small fix. Neither change was glamorous, but each one saves other developers from losing an afternoon.

The Missing QR Code

My final task was to test the live receipt on a real phone. After checking in, a driver should scan a QR code on the kiosk and follow their place in the queue.

Before starting, I compared the code against the task's requirements and the screen designs. The receipt page existed and worked, but the QR code that connects the kiosk to the phone had never been built. An earlier change had set it aside for a follow-up, and that follow-up never happened. The task could not be tested as written.

I wrote this up for the team with the evidence and a list of other gaps that device testing would expose. Then I built the missing piece. One design question came first: in production, the kiosk does not know the web address of its company's receipt page. So I changed the server to return a ready-made receipt link with each check-in, and the kiosk simply shows it as a QR code.

The first time I scanned the code and saw my own check-in appear on my phone, with my place in the queue, was the most satisfying moment of the month. It was the first time I saw the whole journey work from start to finish, from the kiosk, through the server, to the driver's hand.

I also raised an open question with the team. The confirmation screen resets after 15 seconds, which felt too short for a driver to take out a phone and scan.

What I Learned

  • Check what the system actually does. Code that looks complete can still be doing nothing. Calling the service and looking at real data found problems that reading the code missed.
  • Question "done". Compare work against its requirements, not only against the change that closed it.
  • Keep changes focused. Fix what blocks you, and write up the rest clearly for others.
  • Treat review as part of the work. It is where assumptions get tested.
  • Build for every customer, not one. A setting that serves any company is worth more than a special case that serves one.

Working with the Team

I worked under a project leader, alongside other interns who each owned a different part of the system. Teammates reviewed and merged my changes, and their questions consistently made the work better.

The team relied on written documentation: design documents, screen specifications, and a checklist for every change. For someone joining for only a month, that made it possible to contribute from the first week.

Working across a time difference with a team that communicates mostly in Japanese changed how I write. I learned to make every message self-contained, with the problem, the cause, the evidence, and how I tested, so nobody had to wait a day to ask a follow-up question. I also wrote some of my notes and commit messages in Japanese.

What stood out most was how seriously the team took review. Even small changes got careful questions, and those questions were never about finding fault. They were about making the system right. That changed how I review my own work before I ask anyone else to look at it.

Looking Ahead

I expected a month of small, isolated tasks. Instead, I delivered a new company's check-in flow, made handwriting recognition work for real, and helped complete the driver's journey from the kiosk to their phone.

I became comfortable finding problems and tracing them to their root cause. Next, I want to grow on the design side: deciding how a feature should be shaped before building it, the way the receipt link had to be settled before the QR code could exist.

I am grateful to the Quin Que team for their reviews, their patience, and the chance to work on a real product. This month gave me a clearer picture of the engineer I want to become.

目次