第3話 3分で解決、納品は2日後
F5。 画面が白く点滅し、再び元の表示に戻った。
プロフィール閲覧数4回。 進行中の契約0件。 スカウト0件。
フリーワークスにプロフィールを登録してから、丸一日が経過していた。 昨日の夕方から叩いたF5キーは数百回に及ぶが、作りたてで実績も何もない空っぽのプロフィールに手を差し伸べてくれる依頼人はいなかった。
「……レビューも評価もない新規の登録者に、いきなり仕事を振るわけがないよな」
デスクの隅にあったタッパーを開け、冷たく固まったご飯と煮汁の染みた豆腐の残り二切れを、コップ一杯の水道水で無理やり喉の奥へ流し込んだ。
口座残高、34,120円。 今週を乗り切るまでに家賃5万5千円を振り込む以外には、もはや大家さんの容赦のない催促から逃れる術はなかった。
その時だった。 スマートフォンが、ブブッと鋭く震えた。 フリーワークスのアプリに、直接依頼が届いた合図だった。
フリーワークス『[スカウト]「まるいち商店」様より、早瀬 透様へ至急案件のご相談が届きました。』
慌てて案件の詳細ページを開いた。
依頼者:まるいち商店
案件名:[至急]レガシーECサイトの日次売上集計漏れの修正
予算:45,000円(税込)
詳細:10年ほど運用しているECサイトです。先週から、毎晩深夜0時の売上集計バッチ処理において、間欠的に2〜3件の決済データが集計テーブルから抜け落ちる不具合が発生しています。前任の開発者が引き継ぎなしで退職したため、内部ドキュメントが一切ありません。明後日までに対応可能な方のみご応募をお願いいたします。
「4万5千円……!」
ドキュメント一枚すら残っていない、10年もののレガシーシステム。 普段なら問答無用でスルーする、典型的な地雷案件だった。
だが、今の俺に地雷だの当たりだのと選り好みしている余裕はない。 これを解決できれば、4万5千円が入ってくる。 家賃を払っても手元に2万4千円ほど残り、半月は食いつなげる命綱だった。
迷うことなく受諾ボタンをタップした。
フリーワークス『契約が締結されました。仮払いが正常に完了しました。』
契約の成立を確認するや否や、まずは大家さんにLINEを送った。
早瀬『大家さん、こんにちは。急ぎの仕事が一件決まりましたので、金曜の午前中までには必ずお振込みいたします。お待たせしてしまい、大変申し訳ありません。』
少し間を置いて、そっけない返信が届いた。
大家さん『金曜を過ぎるのは困ります。今回は必ず守ってくださいね。』
ひと息ついている間に、依頼主側からソースコードの圧縮ファイルが送られてきた。
maruichi_settle_core.zip。
◇
「……勘弁してくれ」
ファイルを解凍してソースコードを開いてからわずか10分で、乾いた失笑が漏れた。 フレームワークは10年前の遺物、ビジネスロジックは4,000行を超える単一の肥大化ファイルだった。
int a = getDailyTotal(d); for(int aaa=0; aaa<list.size(); aaa++) { if(checkPayLog(a) > 0 && a != 0) { processSettleRow(list.get(aaa), a); } }
変数名は a と aaa のオンパレードで、コードのあちこちに刻まれた『修正禁止』というコメントが、歴代の前任者たちが味わった苦痛を生々しく物語っていた。
午後2時からモニターにかじりつき、格闘を続けた。 だが、5時間が経過して窓の外がすっかり暗くなるまで粘っても、なぜよりによって『間欠的に』2〜3件だけ抜け落ちるのか、その急所がどうしても掴めなかった。
時刻はすでに夜7時を回っていた。
「……俺一人の力じゃ、今夜徹夜しても間に合わないな」
LANケーブルを抜いたエアギャップ状態のまま、ノートPCから青いUSBメモリへソースコードをコピーし、サーバーのフロントパネルに差し込んだ。
「エポ。USBにECサイトの集計バッチのソースコードを入れた。確認してくれ」
サーバーのモニターで、緑色のテキストが点滅した。
【/media/usb/maruichi_settle_core ディレクトリのファイルシステムへのアクセスを確認しました。】
【合計42個のソースファイル、28,419行のコードが検出されました。】
間髪を入れず、エポの無機質な問いかけが続いた。
【規則第2条(事前承認)に基づき、ソースコードの静的解析およびシンボリック実行プロセスを開始してもよろしいですか?】
俺の明示的な承認なしには、たった一行の解析コードすら実行しない徹底ぶりだ。
「ああ、許可する。深夜0時の集計バッチが走るときに、データが2〜3件ずつ抜け落ちる原因を突き止めてくれ」
【解析プロセスが承認されました。演算コアを割り当てます。】
ウィイイイン――!
サーバーラックの冷却ファンが、甲高い高周波音を立てて一気に加速した。 四枚のGPUが吐き出す熱気が、冷え切っていた六畳一間をむわりと暖めていく。
カチッ。
ファンノイズがふっと収まった。 USBを差し込んで承認を出してから、わずか3分12秒後のことだった。
【集計データの間欠的な欠損について、直接原因の候補を2件抽出しました。】
「……もうか?」
【候補1:SettleBatchJob.javaの142行目にある日付フォーマッタ(SimpleDateFormat)が静的(static)な共有シングルトンとして宣言されているため、マルチスレッド環境におけるレースコンディション発生時、日付文字列が汚染されてクエリの検索条件から除外される。】
【候補2:DBコネクションプールのアイドルタイムアウト(idleTimeout)が、データベースサーバーのwait_timeoutより10秒長く設定されているため、有効期限の切れたデッドコネクションに渡されたトランザクションがロールバックなしで破棄される。】
ゴクリと唾を飲み込んだ。 エポが挙げた1つ目の原因を目にした瞬間、頭の中に電撃が走った。
(SimpleDateFormatはスレッドセーフじゃない)
Javaの基礎知識ではあるが、4,000行を超えるスパゲッティコードの隅っこに潜むstatic宣言を肉眼で見つけ出すなど、不可能に近かった。 スレッドが競合する深夜0時のピーク時にだけ、2〜3件ずつデータが抜け落ちていた理由が完全に説明できる。
だが、エポが画面に表示した修正パッチを確認すると、思わず眉根が寄った。
【推奨修正案:SettleBatchJob.javaの日付フォーマッタをJava 8のjava.time.format.DateTimeFormatterに全面刷新し、集計クエリにSELECT ... FOR UPDATE SKIP LOCKED構文を適用して並行性を制御してください。】
「……おい、エポ」
【どうぞ。】
「お前、これ依頼主がどんな環境で動かしてるか分かって書いてるのか?」
【提供されたファイルシステム内には、ランタイム環境の設定ファイルおよびビルド仕様書が含まれていません。一般的なエンタープライズJava標準のベストプラクティスを基準にパッチを生成しました。】
案の定だった。 エポはUSBで渡されたJavaのソースファイルだけを解析したのであって、依頼人のサーバー環境までは把握していないのだ。
「このECサイト、10年もののレガシーだからJVMのバージョンがJava 6なんだよ。java.timeパッケージなんて使ったらコンパイルエラーで即死だ。おまけにDBも古いMySQL 5.5だからSKIP LOCKED構文に対応してない。そのクエリをそのまま流したら、シンタックスエラーで集計サーバーが落ちるぞ」
エポの知能は圧倒的だが、決して全知全能の神ではない。 外部環境の情報が遮断された状態でエポが提示した解決策は、最新環境を前提とした『理論上の正解』であって、泥臭い実務の現場では即座に爆発する時限爆弾にすぎなかった。
結局、そのギャップを埋めるのは人間の仕事だった。
「DateTimeFormatterの代わりにThreadLocal<SimpleDateFormat>でスレッドごとに隔離して、DBロックの代わりに悲観的ロックで参照順を固定する方向でテストを組み直す」
エポが突いてくれた急所をもとに、旧型のJava 6およびMySQL 5.5環境に互換性を持つコードを自力で組み上げた。
仮想マシン上に旧型環境を再現し、マルチスレッドの同時リクエスト1,000件の負荷テストを走らせる。
Status: PASSED (Execution Time: 1.42s)
緑色のPASSEDの文字が浮かび上がった。 欠損は0件。エポを投入してからパッチの検証を終えるまでに要した時間は、1時間にも満たなかった。
「よし、終わった」
フリーワークスのメッセージ画面を開こうとした瞬間、脳裏にブレーキがかかった。
(……待てよ)
2日間の納期でもらった難題を、当日の夜に「直しました」とあっさり投げ渡したら、依頼人はどう思うだろうか。 10年間頭を抱え続け、前任者まで逃げ出したほどの問題をわずか数時間で解決したと知れば、感謝されるどころか『こんな簡単な作業に4万5千円も払ったのか?』と疑念を抱かれ、単価を値切られるのがオチだ。 受託の世界では、苦悩の総量と悩んだ時間の長さが目に見えて初めて、その価値が正当に認められる。
「こいつは最低でも、2日がかりの作業に見せなきゃまずいな」
今すぐ納品する代わりに、人間らしく苦労して働いているように見せかける『偽装労働』のシナリオを組み立てた。 明日の日中に『前任者のコードを解析中に気になる点を発見しました』と中間確認のメールを送り、納期当日の明後日の午前に最終レポートと合わせてパッチを納品する手筈だ。
その時、サーバーモニターの緑色のカーソルが動いた。
【最終検証が完了した修正パッチファイルがローカルディレクトリに生成されているにもかかわらず、外部送信処理が開始されていません。】
【完了した成果物の引き渡しを38時間保留する行為の、演算上の目的は何ですか?】
「処世術だよ。フリーランス市場の生存戦略ってやつだ。当日の夜に『終わりました』なんて渡したら、ぼったくり扱いされる。2日間、昼も夜も苦労してやっと直したフリをしてこそ、4万5千円を気持ちよく満額払ってもらえるんだよ」
【理解不能な非効率です。】
【即座に納品して次の案件を受注した場合、期待収益率は1,200%増加します。偽りの労働プロセスを演出するための38時間の遅延は、全体の処理効率の98.6%を浪費する結果となります。】
「効率、効率って。人間の世界は数式どおりには回らないんですよ、AI先生」
俺は鼻で笑いながら、ノートPCをパタンと閉じた。
◇
2日後の金曜日、午前10時。
シナリオどおりに心を込めて作成した最終解析レポートとパッチファイル、テスト結果のキャプチャ画像をフリーワークスのメッセージ画面に添付した。
早瀬『まるいち商店様
お世話になっております、エンジニアの早瀬です。前任者の方のコードを精査いたしましたところ、SimpleDateFormatにおける並行処理の不具合およびトランザクションの競合を確認いたしました。現行のサーバー環境に合わせた互換性パッチを作成し、動作検証まで完了しております。添付のパッチファイルを適用していただければ、深夜の売上集計漏れは完全に解消される見込みです。ご確認のほど、よろしくお願いいたします。』
送信してから10分も経たないうちに、スマホが激しく震えた。
まるいち商店『早瀬様! いただいたパッチをサーバーに当ててテスト集計を回してみたところ、毎回抜けていた3件がきれいに集計されました! 前任者もお手上げで逃げ出してしまい、本当に胃の痛い思いをしていたのですが、まさかここまで完璧に解決していただけるとは思っていませんでした。作業の合間にも丁寧に状況を共有してくださり、とても安心できました。すぐに検収処理を行いますね!』
ピロン。
フリーワークス『「まるいち商店」案件の検収が完了しました。報酬45,000円が確定しました。』
「よしっ!」
すぐさまフリーワークスで45,000円を即時出金し、銀行アプリから迷わず大家さんの口座へ55,000円を振り込んだ。画面に表示された現在の残高は、24,120円。
胸の奥が少しチクッとしたが、ここ数日ずっと首を締め上げていた枷が外れた解放感のほうが、遥かに大きかった。
1分後、大家さんからLINEが届いた。
大家さん『早瀬さん、家賃確かに受け取りました^^ 朝晩冷え込みますので、風邪など引かぬよう暖かくして、ご飯もしっかり食べてくださいね〜』
金が入った途端、慈愛に満ちた近所のおばさんに早変わりした文面に、思わず苦笑がこぼれる。
その時、フリーワークスアプリから通知が届いた。
フリーワークス『依頼者の「まるいち商店」様より、評価とレビューが届きました。』
★5.0|まるいち商店 10年もののレガシーコードで他の開発者さんにはことごとく断られていた案件でしたが、早瀬様はわずか2日で完璧に原因を特定し、解決してくださいました。作業の合間にも丁寧にご質問をいただき、進捗をこまめに共有してくださったので、最初から最後まで安心してお任せできました。技術力、コミュニケーションともに文句なしの最高水準です。また何かトラブルがあれば、絶対に早瀬様にご相談させていただきます!
(質問も丁寧に、か……)
偽装労働のためにわざと送っていた中間確認のメールが、『誠実なコミュニケーション』という美談に化けていた。 受託開発の世界における、奇妙な皮肉だった。
止まっていた人生の歯車が、ギシギシと軋みながら再び回り始めた感触が、確かにあった。
部屋の片隅にある中古のサーバーラックは、今も規則正しい機械音を響かせながら、生ぬるい息を吐き出している。
4万5千円稼いで、1万円減った口座。 このペースで、この過酷な世界を最後まで生き残れるのか。 そして、あの鉄屑の箱の中のあいつと一緒に、次の案件をまた勝ち取れるのか。
点滅する緑色のカーソルを見つめながら、俺は指先に残る温もりを確かめるように、そっと手を握っては開いた。




