淩晨一點,林夕租住的單身公寓裏隻有鍵盤敲擊聲。
螢幕的光映在他臉上,SVN的提交記錄影一條無限延伸的時間軸。周凱昨晚那個“修複測試環境資料庫連線超時引數”的提交,確實修改了一個不存在的引數——準確說,是在一個早已廢棄的配置檔案裏,新增了一行毫無意義的配置。
“投石問路。”林夕輕聲說。
這是《孫子兵法·虛實篇》的道理:故策之而知得失之計,作之而知動靜之理。對方在試探,想看看自己這個新人會不會盯著程式碼變動,會有什麽反應。
他沒有反應。
隻是默默在自己的本地倉庫更新了程式碼,然後繼續做自己的事情——繪製係統架構圖。
這是林夕選擇的切入點。不動程式碼,不碰業務邏輯,隻是把現有的、混亂的現狀用圖表梳理清楚。這活兒枯燥費時,但絕對安全,而且一旦完成,價值不言而喻。
他用了Visio、draw.io和最簡單的PPT,從三個維度開始梳理:
第一層是物理架構——伺服器、資料庫、快取、訊息佇列的部署關係。這需要翻找運維檔案、伺服器配置,甚至從日誌裏反推呼叫鏈。
第二層是程式碼架構——包結構、類依賴、介麵定義。這是最混亂的部分,同一個功能在不同模組裏重複實現了三次,而關鍵的積分計算核心邏輯,卻散落在七個不同的Service類裏。
第三層是業務架構——這是最難的。沒有檔案,他隻能從資料庫表結構、頁麵UI和埋點日誌裏,一點一點還原這個係統到底要做什麽。
工作到淩晨三點,他趴在桌上睡著了。夢裏全是交錯的箭頭和破碎的類圖。
接下來的三天,林夕保持著同樣的節奏:白天在公司看程式碼、問問題、參加會議,晚上回去繼續畫圖。他問問題很有技巧——不問“為什麽這麽設計”,而是問“這個功能是怎麽用的”“使用者在這裏點了之後會發生什麽”。
週四下午,技術部周會。
周凱主持會議,各部門輪流匯報進度。輪到林夕時,周凱笑了笑:“小林剛來,還在熟悉環境,這周就先不用報了。”
“凱哥,我正好有些發現想分享一下。”林夕舉起手,聲音不大但清晰。
會議室裏安靜了一瞬。幾個老程式設計師交換了眼神——新人第一週就主動發言,少見。
周凱的眼鏡片反射著燈光:“哦?你說。”
林夕連線投影,螢幕上出現一張簡潔的架構總覽圖。“這幾天我在學習會員積分係統,為了理解得更透徹,我嚐試梳理了它的整體架構。這是我初步整理的圖表,可能有很多錯誤,想請大家指正。”
圖上用不同顏色標注了核心模組、資料流向和已知的問題點。最醒目的是幾個用紅色標注的“單點故障風險”——包括那個沒有集群、沒有備份的核心積分資料庫。
會議室裏響起低低的議論聲。
“這個資料庫……”一個戴黑框眼鏡的中年男人皺眉開口,他是運維組的李工,“我記得不是做過主從複製嗎?”
“我查了部署檔案和伺服器列表,確實隻有單例項。”林夕調出另一頁,“這是我從跳板機登入伺服器查到的,3306隻在10.23.16.47這一台機器上開放。”
李工的臉色變了:“不對,三年前我親手搭的主從。老張,你記得不?”他看向旁邊的人。
被叫作老張的程式設計師摸了摸下巴:“好像是有這麽回事……但後來有一次遷移,是不是……”
“我想起來了。”坐在角落一直沉默的測試組負責人王姐忽然開口,“三年前的‘雙十一’前,積分係統壓力測試沒過,當時緊急擴容。是不是那次把從庫提成主庫用了,但後來沒補新的從庫?”
會議室裏陷入短暫的沉默。三年前的技術債務,像一具被遺忘的屍骨,突然被挖了出來。
周凱清了清嗓子:“好,這個問題很重要。小林發現得及時,李工,你們運維組跟進一下,盡快評估風險。”
他的語氣平穩,但林夕注意到,周凱放在桌上的右手食指,輕輕敲了兩下桌麵。
散會後,林夕在茶水間遇到趙磊。這個黑眼圈程式設計師遞給他一罐紅牛:“哥們兒,可以啊。第一週就挖出這麽個大雷。”
“隻是運氣好,正好看到了。”林夕拉開拉環。
“運氣?”趙磊湊近些,聲音壓低,“你知道三年前那次事故,最後是誰背鍋的嗎?”
林夕搖頭。
“當時的技術負責人,叫陳啟明,我學校早幾屆的學長,技術很強。事故後他就離職了,聽說去了國外。”趙磊頓了頓,“有傳言說,那次擴容方案本來有問題,但沒人聽他的。”
“周凱當時在嗎?”
“在啊,他那會兒是陳啟明的副手。”趙磊喝了一口咖啡,意味深長地看了林夕一眼,“後來陳啟明走了,他就上位了。”
回到工位,林夕看著螢幕上那幅架構圖。紅色的“單點故障”標注格外刺眼。
這不是運氣。這是必然——任何一座“屎山”程式碼背後,都埋著曆史、人事和妥協。他隻是恰好掀開了第一層土。
下班前,周凱的微信來了:“來我辦公室一趟。”
辦公室的玻璃門關著,隔音很好。周凱坐在辦公桌後,窗外是漸暗的天色和初亮的霓虹。
“坐。”他指了指對麵的椅子,“架構圖做得不錯,很細致。沒想到你效率這麽高。”
“隻是基礎工作。”林夕說。
“基礎工作才見真功夫。”周凱笑了笑,從抽屜裏拿出一份列印的需求檔案,“既然你已經熟悉了積分係統,正好,有個緊急需求。市場部要做個促銷活動,需要在積分兌換頁麵加個彈窗廣告,引導使用者去新的活動頁麵。很簡單,就改個前端元件,加個跳轉。”
他把檔案推過來:“明天上午就要上線。你是新人,這個需求不大,正好練練手。程式碼在積分係統的frontend分支裏,你應該已經看到了。”
林夕接過檔案。需求確實簡單:在使用者點選“兌換”按鈕後,先彈出廣告,確認後才進入原流程。
“今晚加班能搞定嗎?”周凱問,“這是個很好的機會,讓你實際提交一次程式碼,走完整的上線流程。”
“我盡量。”林夕說。
“不是盡量,是必須。”周凱身體前傾,語氣溫和但不容置疑,“市場部的需求,耽誤了影響很大。你是管培生,這種能體現執行力的事,一定要做好。”
回到工位時已經晚上七點。林夕沒有立刻開始寫程式碼,而是先做三件事:
第一,找到frontend模組的完整程式碼,通讀一遍。
第二,在本地完整執行前後端,確認現有功能正常。
第三,搜尋類似的彈窗功能在公司其他係統裏是怎麽實現的。
前兩步很順利。但第三步讓他發現了問題——公司有統一的UI元件庫,彈窗應該使用`CommonModal`元件,但這個積分係統的前端,是三年前用React舊版本寫的,裏麵混用了至少三種不同的彈窗實現方式,而且有的有記憶體泄漏問題。
他開啟需求檔案,又仔細看了一遍。忽然注意到一個細節:需求裏要求“使用者點選兌換按鈕後立即彈出”,但現有的程式碼裏,兌換按鈕的點選事件會直接觸發一個非同步請求,然後跳轉頁麵。
如果在這中間強行插入一個同步彈窗,會阻塞非同步請求,可能導致請求超時或狀態混亂。
更微妙的是:這個兌換功能,恰好呼叫的是那個有單點故障風險的資料。
林夕靠在椅背上,閉上眼睛。
《孫子兵法·九變篇》:“故用兵之法,無恃其不來,恃吾有以待也;無恃其不攻,恃吾有所不可攻也。”
不要指望敵人不來,而要依靠自己做好準備;不要指望敵人不進攻,而要依靠自己擁有讓敵人無法進攻的防禦。
他睜開眼,開始寫程式碼。但不是直接修改原按鈕的點選事件,而是:
1. 先寫一個完整的、獨立的彈窗元件,嚴格遵循現有程式碼風格。
2. 為這個元件編寫單元測試,覆蓋顯示、確認、取消三種場景。
3. 在本地反複測試彈窗與原有非同步請求的銜接,確保不會阻塞。
4. 準備了一個降級方案:如果彈窗元件載入失敗,自動跳過,不影響原流程。
做完這些,已經是深夜十一點。他提交程式碼,撰寫清晰的技術方案說明,並特意在注釋裏標注:“為避免阻塞原有非同步請求,采用Promise鏈式呼叫,確保彈窗確認後再觸發兌換。”
提交前,他停頓了幾秒。
然後,他複製了整個修改前後的程式碼差異,以及測試用例,打包發給了周凱,抄送了測試組的王姐。
郵件標題:“【會員積分係統】彈窗需求技術方案與測試報告,請審閱。”
郵件正文隻有一句話:“凱哥,功能已開發完成並在本地測試通過。詳細方案見附件,如有問題請指出,我將隨時修改。”
點選傳送。
十分鍾後,周凱回複了,隻有一個字:“好。”
但林夕注意到,郵件顯示已讀的時間,是五分鍾後。
周凱看了五分鍾的附件。
窗外,城市的燈火依然璀璨。林夕關掉電腦,揹包時,那本《孫子兵法》滑到了包底。
他走出大廈,夜風吹在臉上。手機震動,是趙磊發來的微信:“哥們兒,你改的那個前端模組,我剛剛聽說……三年前陳啟明離職前,最後一次提交就是改那裏。他留了個注釋,說那個按鈕的非同步邏輯有個隱藏bug,在極端並發下會丟資料。但後來沒人敢動。”
林夕停在路燈下,影子被拉得很長。
他慢慢打字回複:“那個bug,修複了嗎?”
趙磊的回複很快:“不知道。陳啟明的程式碼被回滾過一部分,後來是別人重寫的。現在到底有沒有bug,可能隻有鬼知道。”
林夕抬頭看著大廈。三樓的某個視窗還亮著燈。
他突然明白,自己白天挖出的那個“單點故障”資料庫,和今晚接手的這個“簡單需求”,可能從來都不是兩個獨立的問題。
它們之間,連著一條看不見的線。
而這條線,在三年前,就有人已經走過。