【问题标题】:How to go about formatting code without hurting the DRY principle in java + PDFBox?如何在不损害 java + PDFBox 中的 DRY 原则的情况下格式化代码?
【发布时间】:2023-03-10 02:35:02
【问题描述】:

请原谅这个问题的阴暗面,请允许我解释一下。

我使用 PDFBox 创建了一个 PDFGenerator。 PDF 大约有 9 页,实际的 PDFGenerator.java 是一个拥有近 4k 行代码的怪物,大部分代码是 PDF 内文本的恒定像素定位。

当前版本 (v1) 包括 2 个主要变量,一个供应和一个排气。因此,对于某物的每一行,都有一个供应值和一个排放值。

整个事情完美运行,我对整个生成过程非常满意。然而,现在 v2 已经到来,客户希望能够创建供应或排放,或两者兼而有之

这就是我对 DRY 原则的问题所在。理论上,两者的代码都已经存在。当它是一个或另一个时,唯一改变的是文本的位置,现在它在两列之间居中。

示例:两者(当前正在生成)

属性............供应......排气

高度 ....................................5 .5

宽度 ....................................5 ..5

示例:一个或另一个

属性............供应......

高度 ....................................5....... ......

宽度 ....................................5........ .......

这是一个生成一行的块:

    pdContentStream.beginText();
    pdContentStream.setFont(boldFont, BOLD_FONT_SIZE);
    pdContentStream.newLineAtOffset(TEXT_BEGIN, currentYCoord);
    pdContentStream.showText(messageSource.getMessage("pdf.value.TDVentilator", null, this.locale));
    pdContentStream.endText();

    pdContentStream.beginText();
    pdContentStream.setFont(boldFont, BOLD_FONT_SIZE);
    pdContentStream.newLineAtOffset(SUPPLY_BEGIN, currentYCoord);
    pdContentStream.showText(messageSource.getMessage("pdf.supply", null, this.locale));
    pdContentStream.endText();

    pdContentStream.beginText();
    pdContentStream.setFont(boldFont, BOLD_FONT_SIZE);
    pdContentStream.newLineAtOffset(EXHAUST_BEGIN, currentYCoord);
    pdContentStream.showText(messageSource.getMessage("pdf.exhaust", null, this.locale));
    pdContentStream.endText();

请记住,这类块会在整个生成过程中重复出现。现在是我的 DRY 问题出现的地方。

我想到的第一件事是将当前代码导出到仅用于生成两者的函数中,并创建(复制/粘贴)用于其中一个或另一个的第二个函数。但是大部分代码会重复这样做(少了一个块,因为我们只有一个输出变量而不是两个)。

我能想到的另一种方法是在每个代码块之前创建一个 if() ,如果这种情况则采用该代码块,如果这种情况采用该代码块。再一次,DRYness 不存在(因为相同的 if 必须出现在每个代码块之前)。

我的问题是:通常最好的方法是什么?我不介意怪物是否再次从 4k 行代码增长到 8k 行代码,但如果有更简单(更好)的方法来做到这一点,我会全力以赴。

干杯:)

【问题讨论】:

  • 你不必每次都设置字体,只要它改变了。而且您不必每次都执行 beginText / endText,但请注意传递给 newLineAtOffset 的偏移值是相对于前一个偏移量的。 (第一个是 0,0)。如果输出较少,则结果 PDF 文件会更小,如果您将它们存储或从 http 行发送,这是一件好事。
  • 对于您的单元测试(您希望进行单元测试),我建议您对 PDF 进行渲染并比较图像结果。这样,您可以确保重构后的 PDF 保持不变。请参阅 TestPDFToImage.java 以获得灵感。
  • 是的,伙计,我当然会进行单元测试咳咳 :) 有趣的事情要注意(也许它会在某个时候帮助某人)。我运行了一个经典的文件比较测试,以查看 PDF 的硬拷贝是否与生成的 PDF 相同,并且即使它们完全相同,它也会不断抛出错误。原来PDF每次都设置一个新ID,这个ID可以通过“pdfDocument.setDocumentId(1);”静态设置之后,测试没有问题通过。当然,静态 id 只是为了测试而设置的,对于部署,它是正常生成的。

标签: java pdfbox dry


【解决方案1】:

查看您的代码:

pdContentStream.beginText();
pdContentStream.setFont(boldFont, BOLD_FONT_SIZE);
pdContentStream.newLineAtOffset(EXHAUST_BEGIN, currentYCoord);
pdContentStream.showText(messageSource.getMessage("pdf.exhaust", null, this.locale));
pdContentStream.endText();

我认为不同的唯一部分是给messageSource.getMessage()的第一个参数。

所以你的重构可能会从引入:

public void prepareContent(Whatever pdContentStream, String message) {
    pdContentStream.beginText();
    pdContentStream.setFont(boldFont, BOLD_FONT_SIZE);
    pdContentStream.newLineAtOffset(EXHAUST_BEGIN, currentYCoord);
    pdContentStream.showText(messageSource.getMessage(message, null, this.locale));
    pdContentStream.endText();
}

然后你的主要代码归结为:

prepareContent(pdContenStream, "pdf.value.TDVentilator");
prepareContent(pdContenStream, "...

等等。然后:您可能会将这些东西放在它自己的类中,在其中创建 pdContentStream 一个字段;摆脱每次调用都需要该参数。

之后应该更好地“组织”这些字符串。写下来没有意义:

foo("bla");
foo("blub");

相反,您将诸如“pdf.value.TDVentilator”之类的值推送到列表中;然后暗示迭代列表/集合/任何东西,从那里获取所需的信息。

长话短说:你不会长出怪物。你甚至不允许它们存在。您显示的代码已经是对 DRY 的严重违规,绝对应该被容忍!

【讨论】:

  • 谢谢伙计,使用列表(或更准确地说,是地图)的想法非常完美。我将文本打包到地图中,并通过迭代轻松获得从 20 行代码生成的整个页面。我还设法将怪物从超过 4k 行减少到大约 1k 行 :) 我不知道当我编写原始代码时脑子里发生了什么,但我现在绝对知道不要再犯这样的错误了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
  • 2016-11-12
  • 1970-01-01
相关资源
最近更新 更多