《设计模式之美》实战二(下):重构ID生成器项目中各函数的异常处理代码

王争《设计模式之美》学习笔记

重构 generate() 函数

  • ID 由三部分构成:本机名、时间戳和随机数。
  • 时间戳和随机数的生成函数不会出错,唯独主机名有可能获取失败。
  • 对于 generate() 函数,如果本机名获取失败,函数返回什么?这样的返回值是否合理?
    • 如果主机名获取失败,substrOfHostName 为 NULL,那 generate() 函数会返回类似“null-6723733647-83Ab3uK6”这样的数据。
    • 如果主机名获取失败,substrOfHostName 为空字符串,那 generate() 函数会返回类似“-6723733647-83Ab3uK6”这样的数据。
  • 作者认为以上处理都不理想,更倾向于明确地将异常告知调用者。所以,这里最好是抛出受检异常,而非特殊值。

重构 getLastFieldOfHostName() 函数

  • getLastFieldOfHostName() 函数获取主机名失败会影响后续逻辑的处理,并不是我们期望的,所以,它是一种异常行为。这里最好是抛出异常,而非返回 NULL 值。
  • getLastFieldOfHostName() 函数用来获取主机名的最后一个字段,UnknownHostException 异常表示主机名获取失败,两者算是业务相关,所以可以直接将 UnknownHostException 抛出,不需要重新包裹成新的异常。
  • getLastFieldOfHostName() 函数修改之后,generate() 函数也要做相应的修改。我们需要在 generate() 函数中,捕获 getLastFieldOfHostName() 抛出的 UnknownHostException 异常。
  • 在generate() 函数中,我们需要捕获 UnknownHostException 异常,并重新包裹成新的异常 IdGenerationFailureException 往上抛出。原因有三:
    • 调用者在使用 generate() 函数的时候,只需要知道它生成的是随机唯一 ID,并不关心 ID 是如何生成的。这是依赖抽象而非实现编程。
    • 从代码封装的角度来讲,我们不希望将 UnknownHostException 这个比较底层的异常,暴露给更上层的代码,也就是调用 generate() 函数的代码。而且,调用者拿到这个异常的时候,并不能理解这个异常到底代表了什么,也不知道该如何处理。
    • UnknownHostException 异常跟 generate() 函数,在业务概念上没有相关性。

重构 getLastSubstrSplittedByDot() 函数

  • 对于 getLastSubstrSplittedByDot(String hostName) 函数,如果 hostName 为 NULL 或者空字符串,这个函数应该返回什么?
    • 我们可以不在 private 函数中做 NULL 值或空字符就可以了。
    • 如果函数是 public 的,你无法掌控会被谁调用以及如何调 ,为了尽可能提高代码的健壮性,我们最好是在 public 函数中做 NULL 值或空字符串的判断。
    • 实际为了方便写单元测试,getLastSubstrSplittedByDot() 是 protected 的。这里我们最好也加上 NULL 值或空字符串的判断逻辑。虽然加上有些冗余,但多加些检验总归不会错的。

重构 generateRandomAlphameric() 函数

  • 对于 generateRandomAlphameric(int length) 函数:
    • 如果 length < 0,生成一个长度为负值的随机字符串是不符合常规逻辑的,是一种异常行为,我们抛出 IllegalArgumentException 异常。
    • 如果 length = 0:
      • 我们既可以把它定义为一种异常行为,抛出 IllegalArgumentException 异常。
      • 也可以把它定义为一种正常行为,让函数在入参 length = 0 的情况下,直接返回空字符串。
      • 最关键的一点是,要在函数注释中,明确告知 length = 0 的情况下,回返回什么样的数据。

重构之后的 RandomIdGenerator 代码

public class RandomIdGenerator implements IdGenerator {
  private static final Logger logger = LoggerFactory.getLogger(RandomIdGenerator.class);

  @Override
  public String generate() throws IdGenerationFailureException {
    String substrOfHostName = null;
    try {
      substrOfHostName = getLastFieldOfHostName();
    } catch (UnknownHostException e) {
      throw new IdGenerationFailureException("...", e);
    }
    long currentTimeMillis = System.currentTimeMillis();
    String randomString = generateRandomAlphameric(8);
    String id = String.format("%s-%d-%s",
            substrOfHostName, currentTimeMillis, randomString);
    return id;
  }

  private String getLastFieldOfHostName() throws UnknownHostException{
    String substrOfHostName = null;
    String hostName = InetAddress.getLocalHost().getHostName();
    if (hostName == null || hostName.isEmpty()) {
      throw new UnknownHostException("...");
    }
    substrOfHostName = getLastSubstrSplittedByDot(hostName);
    return substrOfHostName;
  }

  @VisibleForTesting
  protected String getLastSubstrSplittedByDot(String hostName) {
    if (hostName == null || hostName.isEmpty()) {
      throw new IllegalArgumentException("...");
    }

    String[] tokens = hostName.split("\\.");
    String substrOfHostName = tokens[tokens.length - 1];
    return substrOfHostName;
  }

  @VisibleForTesting
  protected String generateRandomAlphameric(int length) {
    if (length <= 0) {
      throw new IllegalArgumentException("...");
    }

    char[] randomChars = new char[length];
    int count = 0;
    Random random = new Random();
    while (count < length) {
      int maxAscii = 'z';
      int randomAscii = random.nextInt(maxAscii);
      boolean isDigit= randomAscii >= '0' && randomAscii <= '9';
      boolean isUppercase= randomAscii >= 'A' && randomAscii <= 'Z';
      boolean isLowercase= randomAscii >= 'a' && randomAscii <= 'z';
      if (isDigit|| isUppercase || isLowercase) {
        randomChars[count] = (char) (randomAscii);
        ++count;
      }
    }
    return new String(randomChars);
  }
}

你可能感兴趣的:(课程学习笔记,设计模式,异常)