gpt4 book ai didi

java - 如何验证所有自己抛出的运行时异常都包含在 Javadoc 中?

转载 作者:行者123 更新时间:2023-12-01 09:06:29 26 4
gpt4 key购买 nike

我在我的代码中抛出了一堆自定义运行时异常,我想确保在所有公共(public)方法中,我记录了可能抛出的运行时异常( 我自己 )以及原因。这将是非常有帮助的,因为我正在维护 library许多项目都在使用它,我希望它在抛出(运行时)异常方面是预先且可预测的。

是否有编译器选项、maven 插件、Intellij 插件或自定义工具可以帮助我找到遗漏的 throws条款?使用检查异常很容易,如果我错过了一个,编译器只会提示,但对于运行时异常,throws@throws不强制执行。

我想到的一件事是暂时让我自己的所有运行时异常检查异常(它们已经共享一个父类(super class)),但这将是一次性的练习。我想在每次进行更改时验证我的代码/文档,这样我就永远不会忘记记录我的运行时异常。

另一种方法可能是在整个代码中实际检查异常并仅在公共(public) api 中将它们转换为运行时:

class Foo {
// oops, throws not documented with @throws
public void publicMethod() {
try {
privateMethod1();
} catch (CheckedFooException e) {
throw new RuntimeFooException(e);
}
}

private void privateMethod1() throws CheckedFooException {
privateMethod2();
}

private void privateMethod2() throws CheckedFooException {
throw new CheckedFooException();
}
}

这种方法将迫使我在所有公共(public)方法中考虑 CheckedFooException。然后检查我是否错过了记录一个(即 @throws RuntimeFooException ),我只需在 catch.*CheckedFooException 上进行正则表达式搜索并检查丢失的 @throws条目。虽然过程相当笨拙(并且有很多公共(public) api 会充斥着 try...catch 语句)。

回答 :有一些关于你是否应该记录(你自己抛出的)运行时异常的讨论(到目前为止的总结:它取决于),但就我的问题的直接答案而言,接受的答案充分回答了它;只要付出一些时间和精力,我就可以采用这种方法,实现我的用例,甚至用它制作一个 maven 插件。我上传了 cleaned up start project为了这。

最佳答案

在理解了你的问题并研究了这个主题之后,我终于找到了我认为是完成这项工作的最佳工具之一。有了这个,你不仅可以找到你没有记录的每个 throws 实例,而且你还可以找到你没有抛出任何东西但不小心记录了 throw 值的地方。

这背后的想法是将代码解析为抽象语法树。然后查找方法并在方法中查找 throws 语句。如果方法有任何 throw 语句,请从这些语句中提取异常名称。然后获取该方法的 Javadoc。检查 Javadoc 中的所有 @throw 标记并获取记录的异常名称。之后,将异常抛出与记录的异常进行比较。最后,您必须根据您的使用情况自行解决。

我为此使用的工具是 JavaParser。你可以在 Github 上找到它们 https://github.com/javaparser/javaparser .我下载了他们的最新版本。他们的网站是 https://javaparser.org/ .他们写了一本关于这个主题的书,他们提到你可以为这本书支付 0 美元。但是,我没有读到,因为他们的程序也有 Javadoc 版本,可以在 https://www.javadoc.io/doc/com.github.javaparser/javaparser-core/3.15.1 找到。 .

我在下面写了一个演示代码。绝不意味着此代码是最终的。这只是一个例子。您必须对其进行修复以使其适用于您的情况。我没有考虑嵌套类、嵌套方法或方法中的类中的方法。此外,示例代码仅针对类而非接口(interface)编写。但是,很容易调整代码以更改能够处理接口(interface)。

为此,您需要下载 javaParser,构建它,并在您的类路径中拥有它们的 javaparser-core-3.15.1.jar 或任何版本。

演示代码如下,test.java 是我编写的项目中的一个文件,但您可以使用任何文件。我还在示例代码中包含了注释。

import com.github.javaparser.*;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.comments.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.javadoc.*;

import java.io.IOException;
import java.nio.file.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

class Main{
public static void main(String[] args) throws IOException {
// Set file path
Path path = Paths.get("test.java");

// Set configuration
ParserConfiguration parseConfig = new ParserConfiguration();
parseConfig.setCharacterEncoding(Charset.forName("UTF-8"));
parseConfig.setTabSize(4);
parseConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8);

// Get the parser
JavaParser jvParser = new JavaParser(parseConfig);

// Parse the result
ParseResult<CompilationUnit> parseResult = jvParser.parse(path);

// Check for problem
if ( !parseResult.isSuccessful() ) {
System.out.print("Parsing java code fail with the following problems:");
List<Problem> problems = parseResult.getProblems();
for ( Problem problem : problems ){
System.out.println(problem.getMessage());
}
return;
}

// Get the compilationUnit
// No optional checking for Optional<CompilationUnit> due to already check above.
CompilationUnit compilationUnit = parseResult.getResult().get();

// Get Classes
List<ClassOrInterfaceDeclaration> classes = compilationUnit.findAll(ClassOrInterfaceDeclaration.class).stream()
.filter(c -> !c.isInterface())
.collect(Collectors.toList());

// Traverse through each class to get method
for ( ClassOrInterfaceDeclaration c : classes ) {
// Get methods
List<MethodDeclaration> methods = c.getMethods();
for ( MethodDeclaration method : methods ) {
// Get the body statement
Optional <BlockStmt> body = method.getBody();
// if no body continue
if ( !body.isPresent() ) continue;
// After getting the body of the method code
// Search for the throw statements.
List<ThrowStmt> throwStatements = body.get().findAll(ThrowStmt.class);
// No throw statements, skip
if ( throwStatements.size() == 0 ) continue;

// Storing name of exceptions thrown into this list.
List<String> exceptionsThrown = new ArrayList<String>();

for ( ThrowStmt stmt : throwStatements ){
// Convert the throw expression to object creation expression and get the type.
String exceptionName = stmt.getExpression().asObjectCreationExpr().getType().toString();
if ( !exceptionsThrown.contains(exceptionName) ) exceptionsThrown.add(exceptionName);
}

/*
* Debug block for up to this point
System.out.println(method.getName());
System.out.println(exceptionsThrown);
System.out.println();
*
**/

// Get The Javadoc
Optional<Javadoc> javadoc = method.getJavadoc();
// To store the throws Tags
List<JavadocBlockTag> throwTags;
// A list of thrown exception that been documented.
List<String> exceptionsDocumented = new ArrayList<String>();

if ( javadoc.isPresent() ) {
throwTags = javadoc.get()
.getBlockTags()
.stream()
.filter(t -> t.getType() == JavadocBlockTag.Type.THROWS)
.collect(Collectors.toList());
for ( JavadocBlockTag tag : throwTags ) {
/*
* This may be buggy as
* the code assumed @throw exception
* to be on its own line. Therefore
* it will just take the first line as the exception name.
*/
String exceptionName = tag.getContent().toText()
.split("\n")[0]; // Use system line separator or change
// line accordingly.

if ( !exceptionsDocumented.contains(exceptionName) )
exceptionsDocumented.add(exceptionName);
}
}

// getBegin can extract the line out. But evaluating the optional would take some more code
// and is just for example so this was done like this without any checking.
System.out.println("Method: " + method.getName() + " at line " + method.getBegin());
System.out.println("Throws Exceptions: ");
System.out.println(exceptionsThrown);
System.out.println("Documented Exceptions:");
System.out.println(exceptionsDocumented);

System.out.println(System.lineSeparator() + System.lineSeparator());
}
}
}
}

test.java 内容:
package host.fai.lib.faiNumber;
/*
* Copyright 2019 Khang Hoang Nguyen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/
/**
* <p>The <code>Base2Util</code> class is a final class that provides
* static methods for converting base 2 numbering system values in
* string representation to a Java's Primitive Data Type.
*
* <p>Currently this class supports converting base 2 numbers values
* in string representation to integer int values and integer
* long values.
*
* <p>This class can parse unsigned base 2 numbers to a supported
* integer signed type as if the integer type is unsigned. However,
* some of the values must be interprete properly to get the correct
* result.
*
* <p>Example for interpreting signed value as unsigned value.
*
* <p>It is possible to store the value of 18446744073709551615L
* into a long(signed) value. However, if that value is stored into a
* signed long integer type and if we were to interprete the value
* normally, we would get a -1L value. However, if the -1L value is
* pass to LongUtil.toStringAsUnsigned, we would get
* 18446744073709551615 in string format.
*
* <p>The following example is to get to -1L. First, we assign a value
* of 9223372036854775807L to an interger long variable, multiply that
* variable to 2L, and add 1L to it.
* <pre>
* long a = 9223372036854775807L * 2L + 1L;
* System.out.println(a);
* System.out.println(LongUtil.toStringAsUnsigned(a));
* </pre>
*
* <p>Example methods for interprete signed type as unsigned type
* in a decimal strings value are
* {@link IntUtil#toStringAsUnsigned(int) IntUtil.toStringAsUnsigned}
* and {@link LongUtil#toStringAsUnsigned(long) LongUtil.toStringAsUnsigned}.
* </p>
*
* @author Khang Hoang Nguyen
*
* @since 1.0.0.f
**/
public final class Base2Util{
private Base2Util(){};
/**
* Parse the input string as signed base 2 digits representation
* into an integer int value.
*
* @param input
* A string to be parsed as signed base 2 number to an
* integer int value.
*
* @return An integer int value of the signed base 2 number
* {@code input} string.
*
* @throws NumberFormatException
* If the {@code input} string contains invalid signed
* base 2 digits, if the {@code input} string contains a
* value that is smaller than the value of Integer.MIN_VALUE(
* {@value java.lang.Integer#MIN_VALUE}),
* or if the {@code input} string contains a value that
* is larger than the value of Integer.MAX_VALUE(
* {@value java.lang.Integer#MAX_VALUE}).
*
* @throws EmptyStringException
* If the {@code input} string is empty.
*
* @since 1.0.0.f
**/
public static final int toInt(final String input){
final int length = input.length();
if ( length == 0 ) throw new EmptyStringException();
final char ch1 = input.charAt(0); int start;

if ( ch1 == '-' || ch1 == '+' ){
if ( length == 1 ) throw new NumberFormatException(input);
start = 1;
} else {
start = 0;
}

int out = 0, c;
while ( start < length && input.charAt(start) == '0' ) start++;
final int runlen = length - start;

if ( runlen > 31 ){
if ( runlen > 32 ) throw new NumberFormatException(input);
if ( ch1 != '-' ) throw new NumberFormatException(input);

if ( input.charAt(start++) != '1') throw new NumberFormatException(input);

for ( ; start < length; start++){
if ( input.charAt(start) != '0' ) throw new NumberFormatException(input);
}

return -2147483648;
}

for ( ; start < length; start++){
c = (input.charAt(start) ^ '0');
if ( c > 1 ) throw new NumberFormatException(input);
out = (out << 1) | c;
}

if ( ch1 == '-' ) return ~out + 1;
return out;
}

/**
* Parse the input string as unsigned base 2 number representation
* into an integer int value as if the integer int is an unsigned
* type. For values that need to be interpreted correctly, see the
* {@link IntUtil#toStringAsUnsigned(int) toStringAsUnsigned} method
* of the {@link IntUtil IntUtil} class.
*
* @param input
* A string to be parsed as unsigned base 2 number to an
* integer int value as if the integer int is an unsigned
* type.
*
* @return An int value that represents an unsigned integer int
* value of the unsigned base 2 number {@code input} string.
*
* @throws NumberFormatException
* If the {@code input} string contains invalid unsigned
* base 2 digits, if the {@code input} string contains a
* value that is beyond the capacity of the integer int
* data type.
*
* @throws EmptyStringException
* If the {@code input} string is empty.
*
* @since 1.0.0.f
**/
public static final int toIntAsUnsigned(final String input){
final int length = input.length();
if ( length == 0 ) throw new EmptyStringException();
int start = 0;

int out = 0, c;
while ( start < length && input.charAt(start) == '0' ) start++;
if ( length - start > 32 ) throw new NumberFormatException(input);

for ( ; start < length; start++){
c = (input.charAt(start) ^ '0');
if ( c > 1 ) throw new NumberFormatException(input);
out = (out << 1) | c;
}

return out;
}

/**
* Parse the input string as signed base 2 number representation
* into an integer long value.
*
* @param input
* A string to be parsed as signed base 2 number to an
* integer long value.
*
* @return An integer long value of the signed base 2 number
* {@code input} string.
*
* @throws NumberFormatException
* If the {@code input} string contains invalid signed
* base 2 digits, if the {@code input} string contains a
* value that is smaller than the value of Long.MIN_VALUE(
* {@value java.lang.Long#MIN_VALUE}), or if
* the {@code input} string contains a value that is larger
* than the value of Long.MAX_VALUE(
* {@value java.lang.Long#MAX_VALUE}).
*
* @throws EmptyStringException
* If the {@code input} string is empty.
*
* @since 1.0.0.f
**/
public static final long toLong(final String input){
final int length = input.length();
if ( length == 0 ) throw new EmptyStringException();
final char ch1 = input.charAt(0); int start = 0;

if ( ch1 == '-' || ch1 == '+' ){
if ( length == 1 ) throw new NumberFormatException(input);
start = 1;
}

long out = 0, c;
while ( start < length && input.charAt(start) == '0' ) start++;
final int runlen = length - start;

if ( runlen > 63 ){
if ( runlen > 64 ) throw new NumberFormatException(input);
if ( ch1 != '-' ) throw new NumberFormatException(input);

if ( input.charAt(start++) != '1') throw new NumberFormatException(input);

for ( ; start < length; start++){
if ( input.charAt(start) != '0' ) throw new NumberFormatException(input);
}

return -9223372036854775808L;
}

for ( ; start < length; start++){
c = (input.charAt(start) ^ '0');
if ( c > 1L ) throw new NumberFormatException(input);
out = (out << 1) | c;
}

if ( ch1 == '-' ) return ~out + 1L;
return out;
}

/**
* Parse the input string as unsigned base 2 number representation
* into an integer long value as if the integer long is an unsigned
* type. For values that need to be interpreted correctly, see the
* {@link LongUtil#toStringAsUnsigned(long) toStringAsUnsigned} method
* of the {@link LongUtil LongUtil} class.
*
* @param input
* A string to be parsed as unsigned base 2 number to an
* integer long value as if the integer long is an unsigned
* type.
*
* @return An integer long value represent the unsigned integer
* long value of the unsigned base 2 number {@code input}
* string.
*
* @throws NumberFormatException
* If the {@code input} string contains invalid unsigned
* base 2 digits, or if the {code input} string
* contains a value that is beyond the capacity of the
* long data type.
*
* @throws EmptyStringException
* If the {@code input} string is empty.
*
* @since 1.0.0.f
**/
public static final long toLongAsUnsigned(final String input){
final int length = input.length();
if ( length == 0 ) throw new EmptyStringException();
int start = 0;

long out = 0, c;
while ( start < length && input.charAt(start) == '0' ) start++;
if ( length - start > 64 ) throw new NumberFormatException(input);

for ( ; start < length; start++){
c = (input.charAt(start) ^ '0');
if ( c > 1L ) throw new NumberFormatException(input);
out = (out << 1) | c;
}

return out;
}
}

关于java - 如何验证所有自己抛出的运行时异常都包含在 Javadoc 中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56379496/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com