npm共有包和私有包_错误跟踪报告–第3部分–策略和软件包私有

npm共有包和私有包

IMG_0742 这是本系列中的第三个博客,它宽松地着眼于跟踪应用程序错误。 在本系列中,我正在编写一个轻量级但具有工业实力的应用程序,该应用程序定期扫描应用程序日志文件,查找错误,如果发现错误,则生成并发布报告。

如果您阅读了该系列的第一个博客 ,您可能会记得我最初说过我需要一个Report类,并且“如果您查看代码,您将找不到名为Report的类,它将被重命名为Results并进行了重构。创建Formatter接口,TextFormatter和HtmlFormatter类以及Publisher接口和EmailPublisher类”。 该博客介绍了设计过程,重点介绍了重构背后的原因以及如何实现最终实现。

如果继续阅读,您可能会认为下面给出的设计逻辑有些人为。 那是因为。 从Report类到Results类, FormatterPublisher接口以及其实现的实际过程可能只花了几秒钟的时间才能完成。 但是,将其全部写下来需要一些时间。 设计故事就是这样……

如果您有一个名为Report的类,那么您如何定义其职责? 您可以这样说:“ Report类负责生成错误报告。 这似乎符合“ 单一职责负责人”的原则 ,所以我们应该没事……还是? 说一个报告负责生成一个报告是重言式的。 这就像说一个表负责成为一个表一样,它什么也没有告诉我们。 我们需要进一步分解。 “生成报告”是什么意思? 涉及哪些步骤? 考虑一下,要生成报告,我们需要:

  1. 整理错误数据。
  2. 将错误数据格式化为可读文档。
  3. 将报告发布到已知的目的地。

如果将所有这些都包含在Report类的职责定义中,则会得到以下内容:

Report类负责整理错误数据并将数据格式化为可读文档, 然后将报告发布到已知目标。”

显然,这违反了“单一责任主体”,因为Report类具有三项职责,而不是一项; 您可以通过使用“和”一词来分辨。 这实际上意味着我们有三个类:一个用于处理结果,一个用于格式化报告,一个用于发布报告,这三个松散耦合的类必须协作才能交付该报告。

如果回头看原始要求,第6点和第7点表示:

6. When all the files have been checked, format the report ready for publishing.
7 . Publish the report using email or some other technique.

要求6非常直截了当且具体,我们知道我们必须格式化报告。 在真实的项目中,您要么必须自己提出格式,要么询问客户他们希望在报表中看到什么。

要求7有点问题。 第一部分是可以的,它说“使用电子邮件发布报告”,Spring没问题。 第二种写得很糟:还有哪一种技术? 这是第一个发行版本所必需的吗? 如果这是一个现实世界的项目,是您正在谋生的项目,那么这里就是您需要问几个问题的地方-必要时大声地问。 那是因为无法量化的要求会影响时间表,这也可能使您看起来很糟糕。

当要成为一名优秀的开发人员时,质疑定义不明确的需求或故事是一项关键技能。 如果需求是错误或含糊的,只要您自己整理并以自己的方式解释,就没有人会感谢您。 如何表达您的问题是另一回事……对这个问题“专业化”并说出类似的话通常是个好主意:“不好意思,您有五分钟的时间向我解释这个故事吗,我听不懂”。 您只会得到几个答案,通常是:

  1. “现在不要打扰我,以后再来……”
  2. “哦,是的,这是要求中的错误–感谢您发现它,我将对其进行整理。”
  3. “最终用户在这里确实含糊不清,我将与他们联系并阐明它们的含义。”
  4. “我不知道-猜一猜。”
  5. “此要求意味着您需要执行X,Y,Y…”

…并记住记下您的未解决需求问题并加以追究:其他人的不活动可能会威胁您的截止日期。

在这种特殊情况下,需要澄清的是,我将在以后的博客中添加其他发布方法,并且我希望代码被设计为可扩展的,这简而言之意味着使用接口。

屏幕截图2014-03-09 at 15.35.14

上图显示了Report类的最初思想已分为三个部分: ResultsFormatterPublisher 。 任何熟悉设计模式的人都会注意到,我已经使用了策略模式FormatterPublisher实现注入到Results类中。 这使我可以告诉 Results类要generate()报告,而Results类却不知道有关该报告,其构造或去向的任何信息。

@Service 
public class Results { 

  private static final Logger logger = LoggerFactory.getLogger(Results.class); 

  private final Map<String, List<ErrorResult>> results = new HashMap<String, List<ErrorResult>>(); 

  /** 
   * Add the next file found in the folder. 
   * 
   * @param filePath 
   *            the path + name of the file 
   */ 
  public void addFile(String filePath) { 

    Validate.notNull(filePath); 
    Validate.notBlank(filePath, "Invalid file/path"); 

    logger.debug("Adding file {}", filePath); 
    List<ErrorResult> list = new ArrayList<ErrorResult>(); 
    results.put(filePath, list); 
  } 

  /** 
   * Add some error details to the report. 
   * 
   * @param path 
   *            the file that contains the error 
   * @param lineNumber 
   *            The line number of the error in the file 
   * @param lines 
   *            The group of lines that contain the error 
   */ 
  public void addResult(String path, int lineNumber, List<String> lines) { 

    Validate.notBlank(path, "Invalid file/path"); 
    Validate.notEmpty(lines); 
    Validate.isTrue(lineNumber > 0, "line numbers must be positive"); 

    List<ErrorResult> list = results.get(path); 
    if (isNull(list)) { 
      addFile(path); 
      list = results.get(path); 
    } 

    ErrorResult errorResult = new ErrorResult(lineNumber, lines); 
    list.add(errorResult); 
    logger.debug("Adding Result: {}", errorResult); 
  } 

  private boolean isNull(Object obj) { 
    return obj == null; 
  } 

  public void clear() { 
    results.clear(); 
  } 

  Map<String, List<ErrorResult>> getRawResults() { 
    return Collections.unmodifiableMap(results); 
  } 

  /** 
   * Generate a report 
   * 
   * @return The report as a String 
   */ 
  public <T> void generate(Formatter formatter, Publisher publisher) { 

    T report = formatter.format(this); 
    if (!publisher.publish(report)) { 
      logger.error("Failed to publish report"); 
    } 
  } 

  public class ErrorResult { 

    private final int lineNumber; 
    private final List<String> lines; 

    ErrorResult(int lineNumber, List<String> lines) { 
      this.lineNumber = lineNumber; 
      this.lines = lines; 
    } 

    public int getLineNumber() { 
      return lineNumber; 
    } 

    public List<String> getLines() { 
      return lines; 
    } 

    @Override 
    public String toString() { 
      return "LineNumber: " + lineNumber + "\nLines:\n" + lines; 
    } 
  } 
}

首先使用“ Results代码,您可以看到有四个公共方法。 三个负责编组结果数据,另一个负责生成报告:

  • addFile(…)
  • addResults(…)
  • clear(…)
  • generate(…)

上面的前三个方法管理Result的内部Map<String, List<ErrorResult>> results哈希映射。 此映射中的键是FileLocator类找到的任何日志文件的名称,而值是ErrorResult bean的ListErrorResult bean是一个简单的内部bean类,用于将找到的任何错误的详细信息分组在一起。

addFile()是一个简单的方法,用于向Results类注册文件。 它在results图中生成一个条目,并创建一个空列表。 如果它保持为空,那么我们可以说该文件没有错误。 调用此方法是可选的。

addResult()是将新的错误结果添加到地图的方法。 使用org.apache.commons.lang3.Validate验证输入参数后,它将测试此文件是否已在结果映射中。 如果不是,它将在最终创建新的ErrorResult bean并将其添加到Map适当List之前创建一个新条目。

clear()方法非常简单:它将清除results图的当前内容。

其余的公共方法generate(…)负责生成最终的错误报告。 这是我们的策略模式实现,带有两个参数: Formatter实现和Publisher实现。 该代码非常简单,因为只需考虑三行。 第一行调用Formatter实现以格式化报告,第二行发布报告,如果报告生成失败,第三行记录任何错误。 请注意,这是一个通用方法(如方法签名所附的<T>所示)。 在这种情况下,唯一需要注意的“ Gotcha”是,对于两个
Formatter实现和Publisher实现。 如果不是这样,整个事情将会崩溃。

public interface Formatter { 

  public <T> T format(Results report); 
}

Formatter是具有单个方法的接口: public <T> T format(Results report) 。 此方法将Report类作为参数,并以您喜欢的任何类型返回格式化的报告

@Service 
public class TextFormatter implements Formatter { 

  private static final String RULE = "\n==================================================================================================================\n"; 

  @SuppressWarnings("unchecked") 
  @Override 
  public <T> T format(Results results) { 

    StringBuilder sb = new StringBuilder(dateFormat()); 
    sb.append(RULE); 

    Set<Entry<String, List<ErrorResult>>> entries = results.getRawResults().entrySet(); 
    for (Entry<String, List<ErrorResult>> entry : entries) { 
      appendFileName(sb, entry.getKey()); 
      appendErrors(sb, entry.getValue()); 
    } 

    return (T) sb.toString(); 
  } 

  private String dateFormat() { 

    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 
    return df.format(Calendar.getInstance().getTime()); 
  } 

  private void appendFileName(StringBuilder sb, String fileName) { 
    sb.append("File:  "); 
    sb.append(fileName); 
    sb.append("\n"); 
  } 

  private void appendErrors(StringBuilder sb, List<ErrorResult> errorResults) { 

    for (ErrorResult errorResult : errorResults) { 
      appendErrorResult(sb, errorResult); 
    } 

  } 

  private void appendErrorResult(StringBuilder sb, ErrorResult errorResult) { 
    addLineNumber(sb, errorResult.getLineNumber()); 
    addDetails(sb, errorResult.getLines()); 
    sb.append(RULE); 
  } 

  private void addLineNumber(StringBuilder sb, int lineNumber) { 
    sb.append("Error found at line: "); 
    sb.append(lineNumber); 
    sb.append("\n"); 
  } 

  private void addDetails(StringBuilder sb, List<String> lines) { 

    for (String line : lines) { 
      sb.append(line); 
      // sb.append("\n"); 
    } 
  } 
}

这真是无聊的代码。 它所做的就是使用StringBuilder创建报告,仔细添加文本,直到报告完成。 只有兴趣点在format(…)方法的第三行代码中:

Set<Entry<String, List<ErrorResult>>> entries = results.getRawResults().entrySet();

这是一本教科书,讲述了Java很少使用的包可见性的全部内容。 Results类和TextFormatter类必须协作才能生成报告。 为此, TextFormatter代码需要访问Results类的数据。 但是,该数据是Result类内部工作的一部分,并且不应公开获得。 因此,使数据通过package private方法可访问是有意义的,这意味着只有那些需要数据承担其分配职责的类才能获得它。

生成报告的最后一部分是格式化结果的发布。 再次使用策略模式来完成; Report类的generate(…)方法的第二个参数是Publisher接口的实现:

public interface Publisher { 

  public <T> boolean publish(T report); 
}

它还包含一个方法: public <T> boolean publish(T report); 。 此泛型方法采用类型为'T'的report参数,如果成功发布了报告,则返回true。

实施情况如何? 这个班的? 第一个实现使用Spring的电子邮件类,并将成为我的下一个博客的主题,该博客将很快发布...

如果您想查看本系列中的其他博客,请在这里看……

  1. 使用Spring跟踪应用程序异常
  2. 使用Spring跟踪异常–第2部分–委托模式

翻译自: https://www.javacodegeeks.com/2014/03/error-tracking-reports-part-3-strategy-and-package-private.html

npm共有包和私有包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值