本文是 TRIAL&RetailAI Advent Calendar 2025 第 18 天的文章。
昨天是@zushi_ryota 的「1.26次元的圖形?! ~嘗試計算分形圖形的維度~」這篇文章。文中用 D 語言生成了在雪花中可以見到的分形圖形,真心推薦大家閱讀!
那麼,作為一名工程師已經工作了兩年。
目前,我正在參與每秒有大量數據流入的即時處理系統的開發。這兩年來,經歷了許多失敗後,常常會想「我早該知道這些」。
在本文中,我將分享我親身學到的教訓。
「這個規範已確定」是謊言。
剛開始的時候,我很自信地按照確定的規範進行「完美的設計」。但現實並不如人願。在發佈前,會突然收到「還想再加這個項目」或「想改變計算邏輯」的請求。
// 對變更敏感的設計:Service 直接依賴 PostgreSQL
class OrderService(
private val postgresRepository: PostgresOrderRepository
) {
fun save(order: Order) {
postgresRepository.insert(order) // 直接對 PostgreSQL 進行 INSERT
}
}
當聽到「請改成 Google Cloud 儲存」的那一刻...
// 對變更不敏感的設計:抽象化訪問層
interface OrderRepository {
fun save(order: Order)
}
class OrderService(
private val repository: OrderRepository // 依賴於介面
) {
fun save(order: Order) {
repository.save(order) // 不知道保存到哪裡
}
}
「改成 Google Cloud」→ 只需替換實現類
class PostgresOrderRepository : OrderRepository { ... }class GoogleCloudOrderRepository : OrderRepository { ... } // 只需新增這一行要堅持設計對變更友好
有關設計的更多資訊,參考@kakine_juri的
「如果只是按功能劃分,是不行的。寫作軟體這塊,還需要學習。」他對於基於變動性進行劃分的設計方法「Righting Software」做了清晰的解釋。
奇怪的數據總是會來。
「在規範上不可能出現的數據」,在現實中卻很常見。
實際遭遇的例子:
// 不可隨意相信
fun process(data: ExternalData) {
val amount = data.amount // 可能為 null、負值,或超過 Int 的最大值
}
// 在入口階段驗證
fun process(data: ExternalData) {
val amount = requireNotNull(data.amount) { "amount is required" }
require(amount > 0) { "amount must be positive" }
}
對所有來自外部的數據都要保持懷疑態度
在處理大量數據的系統中,未涵蓋的情況更容易出現錯誤。
如果只因為「正常情況都能運行」而安心,那麼在生產環境中,你會為只有在特定條件組合下才會發生的錯誤感到懊惱。
// 容易忽略的測試案例
@Test
fun `即使是空列表也能正常運作`() { }
@Test
fun `處理大量數據(10 萬條)不會超時`() { }
@Test
fun `相同鍵的數據連續到達仍然保持一致性`() { }
@Test
fun `處理過程中即使隊列數據中斷也能恢復`() { }
測試是未來自己的一份保障
曾經一段時間,每次從 develop 分支切出 feature 分支後,就無止境地進行提交。
結果會如何:
Git 是開發的生命線
關於提交信息,參考@fujithuro 的「讓未來的自己不會哭的提交信息」。我也曾經因為懶惰,做了很多像是 [fix]修正 這類毫無意義的提交,忍不住想揍過去的自己...
「三天可以完成」→ 實際需要一周。
預估不準確的原因:
預估要算 1.5 到 2 倍
尤其是對於經驗尚淺的工程師,即使預估三倍也不會被罵(多半是這樣)。
數據庫也有其極限。
實際遇到的問題:
// 緩慢
items.forEach { repository.save(it) }
// 快速
repository.bulkInsert(items)
了解數據庫的特性並善加利用
「能動就好!」是最危險的想法。
複製粘貼就能運行的代碼。不經意間添加的註解。完全不理解的庫。這些情況在特定情況下都可能成為災難。
實際經歷過的可怕故事:
@Transactional 的行為,導致數據不一致不懂「為什麼會動」的代碼就不要寫
當故障發生時,沒有日誌就無法查明原因。
擔心「日誌輸出過多會讓系統變慢」而有所保留,最終在生產環境中目睹發生了什麼卻一無所知的地獄。
// 不好的例子:什麼都不知道
fun process(data: Data) {
try {
service.execute(data)
} catch (e: Exception) {
throw e // 失敗的數據是什麼?
}
}
// 好的例子:可追蹤
fun process(data: Data) {
logger.info { "Processing started: id=${data.id}" }
try {
service.execute(data)
logger.info { "Processing completed: id=${data.id}" }
} catch (e: Exception) {
logger.error(e) { "Processing failed: id=${data.id}, payload=$data" }
throw e
}
}
日誌是未來自己的一封信
@WithSpan 來正確設置分段(opentelemetry)關於 opentelemetry 的使用,參考@kyojinnaapyon 的「log.debug() 只有看不到的世界,OpenTelemetry 給我們展示」也是不錯的資源。利用 opentelemetry 讓我能夠更快地定位 bug!
用一句話總結我兩年來的學習,
「總之,工程師是真正有趣的工作。」
讓我們朝著不被 AI 淘汰的怪物工程師努力吧。
明天的手作日曆將由@ikeda_takato 為大家帶來「ATT&CK T1059 深入探討:攻擊者為什麼如此熱愛 CLI」。敬請期待!
RetailAI 與 TRIAL 正在招聘工程師。
有興趣的人請隨時聯繫我們!