跳到主要內容
實戰紀錄4 分鐘閱讀

我寫了姓名拆解,兩個月後把它從註冊流程刪掉

為了讓註冊姓名支援原住民的名字,我寫了一段拆解規則。記錄這些規則的問題,以及團隊討論後決定不拆、改存單一欄位的過程。

這次的需求是:註冊時的姓名要能支援原住民的名字。原住民的名字不一定能分成一個字的姓和後面的名,有些還會用間隔號 · 連接,所以表單改成一個「姓名」欄位,但後端的資料還是要 first_name 和 last_name 兩個值。

我的第一個想法很直覺:寫一個函式,把使用者輸入的姓名拆開。兩個月後,我把註冊流程裡的這段邏輯刪了。

當時的拆解規則

我依照常見的姓名格式寫了幾條規則:

  1. 有間隔號 · 或空白,就從第一個符號切開。
  2. 開頭是中文,第一個字當姓,其他當名。
  3. 其他情況,整串放進同一個欄位。

寫成程式大概是這樣:

js
function splitName(name) {
  const trimmed = name.trim()
  const index = trimmed.search(/[·\s]/)

  if (index !== -1) {
    return {
      surname: trimmed.slice(0, index),
      givenName: trimmed.slice(index + 1),
    }
  }

  if (/^[一-龥]/.test(trimmed)) {
    return { surname: trimmed[0], givenName: trimmed.slice(1) }
  }

  return { surname: trimmed, givenName: '' }
}

測試時輸入「王小明」,結果完全正確,看起來沒有問題。

問題在於規則永遠寫不完

姓名不是一種能用幾條規則描述的格式:

輸入拆解結果問題
歐陽小明歐 / 陽小明複姓被切開
范姜小明范 / 姜小明複姓被切開
張李小明張 / 李小明無法判斷是否為雙姓
瓦旦·達路瓦旦 / 達路順序不一定是「姓在前」
John SmithJohn / Smith英文是名在前
Mary Ann SmithMary / Ann Smith中間名怎麼算

當然可以繼續加規則,例如放一份複姓清單,但「張李」這種情況,光看字串根本無法判斷。程式只是在猜,而猜錯的是使用者的名字。

最後的修法:團隊討論後決定不拆

原本的方向就是把姓名拆開存。後來團隊內部討論,對照資料庫的欄位後發現其實沒有必要拆分:後端收到姓和名之後,會直接接成一個完整姓名儲存。前端辛苦拆開,後端又接回去,拆分根本沒有帶來價值。

所以註冊時改成使用者打什麼,就存什麼:

js
// 之前
const { surname, givenName } = splitName(form.name)

// 之後
const payload = {
  last_name: form.name,
  first_name: '',
}

這次改動刪掉了 40 多行,只留下兩行。資料庫的欄位沒有調整,所以完整姓名放在 last_name,first_name 留空。

這不是最漂亮的資料結構,但系統不用再猜哪個部分是姓,原住民的名字也能完整保存。

什麼時候才需要拆

資料還是存完整姓名,真的需要分開時才拆。例如要顯示「王先生您好」,就在使用的當下,依照調整後的規則自動拆分:

  • 有間隔號 · 或空白時,從第一個符號切開。
  • 沒有符號的中文姓名,以第一個字當姓。
  • 判斷不了的情況,就顯示完整姓名。

和原本最大的差別是,拆分結果只用來顯示,不寫回資料庫。規則就算猜錯,完整姓名也還在,之後調整規則就能修正,不會留下拆錯的資料。

如果姓和名會影響重要用途,例如要和證件資料比對,就不適合靠規則判斷,應該讓使用者自己分開填寫。

這次學到的事

  • 測試資料全部通過,不代表規則是對的。我只想到了「王小明」。
  • 碰到人名、地址這類真實世界的資料,要先懷疑自己的假設。
  • 當規則需要一直補例外時,先問這段程式是不是根本不該存在。
  • 動手前先和團隊確認資料實際怎麼使用,討論之後常常會發現不用做那麼多。
  • 刪程式碼也是一種修法,而且常常是最好的那種。