Model中有隨機值時候,該怎麼寫

在使用TDD實作資料庫存取界面時,常常會冒出:隨機值、時間值、id要怎麼比對的想法。我的做法是:不要在儲存的時候才給值。

model中有隨機值時候,該怎麼寫

在使用TDD實作資料庫存取界面時,常常會冒出:
「隨機值、時間值、id要怎麼比對」的想法。
我的做法是:不要在儲存的時候才給值。

在儲存的時候才給「建立時間」、「id」,
這種每次都不一樣的值,
會導致寫資料庫存取界面的時候,變得很難測試,
因為每次測試的值都不一樣,而且還是在儲存函式中給值的。

如果真的要測試,就得要定義隨機值產生器界面,用依賴注入。
在測試的時候,注入由測試控制的產生器;
在真正使用的時候,注入真實的隨機值產生器。
這個做法多了很多複雜的程式碼。

例如:
時間取得器界面

type CurrentTimeGetter interface{
  Get() time.Time
}

資料庫存取器還要注入這個界面

//UserRepository
func NewUserRepository(timeGetter CurrentTImeGetter, db *gorm.DB) *UserRepository{
  return &UserRepository{
    currentTimeGetter: timeGetter,
    db: db
  }
}

func(r *UserRepository)Create(ctx context.Context, user model.User)(string, error){
  user.CreatedAt = r.currentTimeGetter.Get()
  
  err := r.gorm.Create(&user).Error
  if err != nil{
    return "", fmt.Errorf("can't add user: %w", err)
  }

  return user.ID, nil
}

測試使用的界面實作

type StubTimeGetter struct{
  t time.Time
}

func (s *StubTimeGetter) Get()time.Time{
  return s.t
}

func (s *StubTimeGetter) Set(t time.Time){
  s.t = t
}

真正使用時注入的界面實作

type RealTimeGetter struct{}

func(r* RealTimeGetter) Get() time.Time{
  return time.Now()
}

而在物件建立的時候,直接給值,
反而簡化了資料庫存取界面的邏輯,
讓它專注在資料的存取、刪改。

//UserRepository
func NewUserRepository(db *gorm.DB) *UserRepository{
  return &UserRepository{
    db: db
  }
}

func(r *UserRepository)Create(ctx context.Context, user model.User)(string, error){
  err := r.gorm.Create(&user).Error
  if err != nil{
    return "", fmt.Errorf("can't add user: %w", err)
  }

  return user.ID, nil
}

這樣做,整個存取界面變得更好懂。
也避免使用時,需要再次確認
「哪些欄位不用給,哪些要給值」的問題。
因為全部都要給。

這個時候可能會想:「那調用的人沒照規矩怎麼辦?」
把這些欄位都設定成NOT NULL就可以了。

大部分的欄位都不應該是空的。
有空值,不止代表資料表設計有問題,也會導致,
資料庫的column histogram出現異常。

column histogram是用來計算輸入的SQL,
要用哪個index來初步篩選、哪些條件要用一筆一筆比對處理,
的算法。
所以columnn histogram異常,
就代表可能會執行到效率差的查詢方案。

所以我們自然可以用NOT NULL來要求欄位都要有值,
來預防沒給到值的情況。

comments powered by Disqus