Try   HackMD

工作日記 #010 : 本週復盤 0505~0509 | 單一職責 SRP | 錯誤碼體驗 issue | Class 常數在 JVM 佔用的空間

久違更新一下工作的日記,說是日記但實際上是把這整週工作上遇到的一些議題做個整理,之後固定做個當週的復盤,紀錄每週的成長,而這週其實相較前兩週沒那麼有強度,但還是可從當中衍生出幾個我想紀錄下來的觀念和學習,分別是:

  • 初次在工作中聽到大名鼎鼎的「單一職責原則」。
  • 我認為在工程上合理的流程,會有用戶體驗上的問題。
  • Class 裡多新增的常數會存放在 JVM 的哪個空間。

單一職責原則

「單一職責原則」這個詞應該可以說是軟體工程師的聖經之一,大家都知道它很重要,但不一定每個人都有確實實踐,也不一定每個情境下都必須實踐,還是需要根據需求去做一些彈性調整。

需求

而我這週遇到的問題是,忘記密碼的 API 重置成功後會自動登入,但如果遇到有開啟 Captcha 功能的網站,會因為登入時需要輸入驗證碼而報錯,所以必須優化成「重置後兩分鐘內可以跳過 Captcha 驗證」。

這個需求牽涉到三個模塊:

  1. 首先登入的主流程是在 Gateway 系統。
  2. Captcha 驗證會 call 另一個 A 系統。
  3. Geetest 驗證會 call 主要串接三方服務的 B 系統。

我一開始的作法是:

  1. 重置密碼成功後會 async 發送請求給 A 系統,寫進一筆紀錄進 Redis 資料庫,TTL=2min。
  2. Captcha 跟 Geetest 驗證時先判斷有無密碼重置的紀錄,有紀錄的話就不往下繼續驗證的邏輯。

問題與解決方案

但這邊遇到的問題是,A 跟 B 兩個系統主要的職責是執行驗證的邏輯,判斷要不要執行則是 Gateway 這邊決定的,Gateway 擁有控制權,如果是到子系統才判斷登入要不要避開驗證,相當於把控制權從 Gateway 這邊交出去,這樣職責就錯亂了,所以應該是:

  1. 重置密碼成功後會 async 發送請求給 A 系統,寫進一筆紀錄進 Redis 資料庫,TTL=2min(這邊保持不變)。
  2. Gateway 登入時先去 call A 系統查詢是否有重置密碼的紀錄,結果返回給 Gateway 後,根據有無紀錄來判斷要不要再發請求給驗證 Captcha 跟 Geetest 的系統。

如此一來控制登入流程的這個職責就完全是 Gateway 這邊攬下了。所以在遇到多個微服務之間的改動時,要先去思考哪個微服務做哪些事、他們各自的職責為何?才不會後續不好維護或後面的人不好接手,工作起來的順暢度也會提升。

錯誤碼體驗問題

跟上面同一張 Ticket 的問題,在登入流程中 Gateway 去 Call A 系統取得重置密碼紀錄時,假設 Redis 的節點故障,可能會拋出 Exception。

原本做法是讓它直接往上傳給用戶,因為我想說就算這邊不拋,到時自動登入也會拋出驗證碼錯誤的例外,那為何不先拋?

但我得到主管的說法是,拋出驗證碼錯誤比拋出系統錯誤的用戶體驗好一點,雖然我是不知道到底是怎麼個好法,但確實我只用工程的角度去思考合理的做法,而不是從用戶的角度去思考,這個觀點滿有趣的,下次要更多設想到 UX 的層面來做設計。

常數在 JVM 裡佔用的空間

這也算是我在上面那張 Captcha 的單衍生出的問題,就是我在一個 Class 裡面定義了一個常數,而這個常數實際上會不會佔用到 JVM 的空間,而它又會被存放在 JVM 的哪個位置,什麼時候才會被拿出來使用?

private static final String DEFAULT_VAL = "0";

Metaspace

這個 DEFAULT_VAL 的常數變數會在 JVM 運行時存放在本地內存裡,Metaspace 的 Runtime Constant Pool

Metaspace 是在 JVM 裡用來存放 Class 相關資訊的空間區域,JVM 在執行中如遇到類加載的情況時,會把 Class 相關的 Metadata,如 Class 內的靜態變數、常數、方法等資訊存入 Metaspace。在 Java 8 以前這個區域被稱為永久代 (PermGen),Java 8 以後被替換成 Metaspace,也從 JVM 堆內存移到本地內存。

JVM 在執行過程中遇到需要使用某個類時,會進行以下步驟:

  1. 通過類加載器加載類的 .class 文件
  2. 解析類文件的二進制數據
  3. 將類的元數據信息存入 Metaspace
  4. 為靜態變數分配空間(在 Metaspace 中)
  5. 執行類的靜態初始化代碼(static 塊)

Metaspace 的特點是,它使用的是本地內存(Native Memory),而非 Java 堆內存
默認情況下,它可以動態擴展(直到系統內存上限)可以通過 -XX:MetaspaceSize-XX:MaxMetaspaceSize 參數限制其大小,不受 Java 垃圾回收器直接管理,但類卸載時會釋放對應空間

Heap

引用 DEFAULT_VAL 這個變數的字串值 "0",會存放在 JVM Heap Memory 裡的 String Constant Pool。JVM 裡的 Heap Memory 就是用來存放所有對象實例的空間區域,包括引用類型變數的實例、字串變數引用的值等等。