行外人看不見的債務-技術債務 中的 WeekDay 如果比著筆者去做,會點樣寫?

筆者認為呢一個 WeekDay Enum 寫法都仲可以接受到,因為佢真係淨係 WeekDay,唔會再加上去。

若你細心啲諗,我寫 Test Case 要寫哂 7 個 Test case 去滿足 7 個 if case,先可以達到 100% coverage,7 個 if 都係做緊同類嘅野,所以係有地方可以改善;一般測試 parse 呢類 Method 理應只有兩類 Test case - Valid/Invalid test case。

其實除左 Testability 比較差之外,該寫法並不 Scalable,例如加多一個 Type 叫 UNKNOWN去處理啲唔係 WeekDay 嘅 Type ,難道我又要加多一個 if ?

原始的 Code

public enum WeekDay{
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY,
  SUNDAY;

  public static WeekDay parse(String str){
    WeekDay weekDay = null;
    if("monday".equalsIgnoreCase(str) || "1".equals(str)){
      weekDay = WeekDay.MONDAY;
    } else if("tuesday".equalsIgnoreCase(str) || "2".equals(str)){
      weekDay = WeekDay.TUESDAY;
    } else if("wednesday".equalsIgnoreCase(str) || "3".equals(str)){
      weekDay = WeekDay.WEDNESDAY;
    } else if("thursday".equalsIgnoreCase(str) || "4".equals(str)){
      weekDay = WeekDay.THURSDAY;
    } else if("friday".equalsIgnoreCase(str) || "5".equals(str)){
      weekDay = WeekDay.FRIDAY;
    } else if("saturday".equalsIgnoreCase(str) || "6".equals(str)){
      weekDay = WeekDay.SATURDAY;
    } else if("sunday".equalsIgnoreCase(str) || "7".equals(str)){
      weekDay = WeekDay.SUNDAY;
    }
    return weekDay;
  }
}

筆者的 Refactor

筆者答案一 (Scalable,理論上最快速,多用咗 Memory)

Concept: Application 行之前將整個 Enum Array 放入 Static HashMap 內,其後每次運行 RefactoredWeekDay.parse 為 O(1)

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public enum RefactoredWeekDay {
  MONDAY(1),
  TUESDAY(2),
  WEDNESDAY(3)
  THURSDAY(4),
  FRIDAY(5),
  SATURDAY(6),
  SUNDAY(7);

  Integer weekDayInt;

  private static Map<String,RefactoredWeekDay> strMap = new HashMap<>();
  private static Map<Integer,RefactoredWeekDay> intMap = new HashMap<>();
  
  static {
    strMap = Stream.of(RefactoredWeekDay.values())
    .collect(Collectors.toMap(
        RefactoredWeekDay::name,
        (wd) -> wd
    ));

    intMap = Stream.of(RefactoredWeekDay.values())
    .collect(Collectors.toMap(
        RefactoredWeekDay::getWeekDayInt,
        (wd) -> wd
    ));
  }

  RefactoredWeekDay(Integer weekDayInt){
    this.weekDayInt = weekDayInt;
  }

  public Integer getWeekDayInt(){
    return this.weekDayInt;
  }

  public static Optional<RefactoredWeekDay> parse(String str){
    Objects.requireNonNull(str);

    if(str.matches("[0-9]+")){
      return Optional.ofNullable(intMap.get(Integer.parseInt(str)));
    }
    return Optional.ofNullable(strMap.get(str.toUpperCase()));
  }
}

筆者答案二 (慳 Memory 多用啲 CPU)

Concept: 每次都會 Iterate 整個 Enum Array 運行RefactoredWeekDay.parse 為 O(n)

import java.util.Optional;
import java.util.stream.Stream;

public enum RefactoredWeekDay {
  MONDAY(1),
  TUESDAY(2),
  WEDNESDAY(3),
  THURSDAY(4),
  FRIDAY(5),
  SATURDAY(6),
  SUNDAY(7);

  Integer weekDayInt;

  RefactoredWeekDay(Integer weekDayInt){
    this.weekDayInt = weekDayInt;
  }

  public Integer getWeekDayInt(){
    return this.weekDayInt;
  }

  private boolean isEqual(String weekDayStr){
    return this.name().equalsIgnoreCase(weekDayStr)
        || String.valueOf(this.getWeekDayInt()).equals(weekDayStr);
  }

  public static Optional<RefactoredWeekDay> parse(String str){
    return Stream.of(RefactoredWeekDay.values())
        .filter(wd -> wd.isEqual(str))
        .findFirst();
  }
}

總結

如果 Application 內經常需要運行 RefactoredWeekDay.parse,答案一比較合適(利用 Memory 加 HashMap 加速)。相反,則為答案二比較適合。

但其實 JVM 有 Adaptive Optimizer,相信答案二的方法在實際環境不會比答案一相差得遠。實際環境邊個好啲,真係要跑過 Micro Benchmark 先至知!

歡迎各位將你嘅答案 Inbox 比筆者 :)
筆者會不斷更新此 Post,比大家睇下各個不同 Programmer 嘅風格~