如何禁用Sonar的警告:隐藏实用类构造函数?

138

我在Sonar上看到这个警告:

隐藏实用类构造函数:

实用类不应该有公共或默认构造函数

我的类:

public class FilePathHelper {
    private static String resourcesPath;
    public static String getFilePath(HttpServletRequest request) {
        if(resourcesPath == null) {
            String serverpath = request.getSession()
                                       .getServletContext()
                                       .getRealPath("");
            resourcesPath = serverpath + "/WEB-INF/classes/";   
        }
        return resourcesPath;       
    }
}

我希望有解决方法能够移除在 Sonar Qube 上出现的这个警告


2
最好禁用这个无用的Sonar警告。私有构造函数只会增加代码噪音,只是为了满足一些任意的Sonar规则而已。请参考此处如何禁用规则:https://sqa.stackexchange.com/questions/24734/how-to-deactivate-a-rule-in-sonarqube - john16384
12个回答

246
如果这个类仅仅是一个工具类,那么你应该将该类声明为final并定义一个私有构造函数:
public final class FilePathHelper {
   private FilePathHelper() {
      //not called
   }
}

这将防止默认的无参构造函数在您的代码中的其他地方被使用。

此外,您可以将类声明为final,以便它不能在子类中扩展,这是实用类的最佳实践。由于您仅声明了一个私有构造函数,其他类也无法扩展它,但将类标记为final仍然是最佳实践。


11
“final”实际上是多余的:如果一个类只有私有构造函数,那么它已经是有效的final。标记为final虽然没有伤害。 - Marko Topolnik
20
为了使 Sonar 真正考虑到违规问题已解决,您必须将类标记为 final。仅添加私有构造函数无法清除违规问题。 - Cebence
6
实际上,你应该如何测试/覆盖这样的“无法到达”的代码? - dokaspar
14
所以我们应该在我们的类中添加不必要的代码,只是为了让一些分析工具感到满意?有人可以扩展或实例化这个类。那又怎样?这有多重要?会引起一些错误吗?或者为什么我甚至要使用像PowerMock这样的工具来测试一个从未使用过的私有构造函数,只是为了满足Sonar和我的代码覆盖率工具的要求? - Sergej Werfel
4
@SergejWerfel 之前的帖子,但我同意。我在想,实际上为什么要将它定义为final或添加私有构造函数呢?如果有人想要实例化我的工具类,那就随便吧。我并不在意。除了别人说这是最佳实践,我没有看到任何理由证明它应该被认为是“最佳实践”。如果有人能够真正给出一个有效的原因,说明为什么这是最佳实践,我愿意改变我的想法。 - Christopher Schneider
显示剩余7条评论

22

我不了解 Sonar,但我猜测它正在寻找一个私有构造函数:

private FilePathHelper() {
    // No-op; won't be called
}

否则Java编译器将提供一个公共无参构造函数,这并不是你想要的。(你还应该将类声明为final,尽管由于它只有一个私有构造函数,其他类也无法继承它。)

12

我使用一个没有实例的枚举

public enum MyUtils { 
    ; // no instances
    // class is final and the constructor is private

    public static int myUtilityMethod(int x) {
        return x * x;
    }
}

您可以使用以下方式进行调用

int y = MyUtils.myUtilityMethod(5); // returns 25.

你能否展示更多关于这个解决方案的代码吗?关于如何使用 enum 作为实用类? - Jin Kwon
@JinKwon 我已经添加了一个例子。你有具体的疑问吗? - Peter Lawrey
1
不,我只是想知道一个例子。谢谢。我从来没有想过这种方式。 - Jin Kwon
我通常使用枚举类作为实用工具类,但是针对JDK8的checkstyle 7.0与提供的示例不兼容... - Marcello DeSales
@MarcellodeSales 多年以来我都没有尝试过checkstyle。我更喜欢那些不仅可以发现问题,还可以自动修复问题的工具。;) - Peter Lawrey

10
最佳实践是在构造类时抛出错误。 示例:
/**
 * The Class FooUtilityService.
 */
final class FooUtilityService{

/**
* Instantiates a new FooUtilityService. Private to prevent instantiation
*/
private FooUtilityService() {

    // Throw an exception if this ever *is* called
    throw new AssertionError("Instantiating utility class.");
}

3
为什么这是最佳实践?我们在这里获得了什么?虽然各个层面都存在愚蠢,但是仅仅为了预防实例化(在这种情况下相当无害)而添加这些代码行似乎有些过头了,因为这会给一个本来只包含常量的清晰类引入半打代码行的噪音。 - john16384
@john16384 这是满足现代静态代码分析器的"最佳实践",语言创建者会同意这是正确的。但这是否适用于你在工作中的"最佳实践",取决于你的组织如何看待这种事情。如果你的老板高度重视静态代码分析和警告甚至"信息"的消除,那么你的论点应该针对这一点。在一个大型团队环境中,假设每个人都知道不要实例化任何给定类可能并不好。这可能属于"防御性编码"范畴。 - Thomas Carlisle
3
@ThomasCarlisle语言的创建者并没有创造这些代码分析器;无用的警告是可以关闭的(比如当你在项目中没有使用序列化时,臭名昭著的缺失serialVersionId警告)-- 相反,这种工具应该在使用该类来获取常量的时候在使用现场发出警告;人们抱怨Java冗长,而满足由编码分析器中粗略调整的“默认”规则推动的任意未经证实的规则并不会有所帮助。 - john16384

9

您可以使用Lombok注解来避免不必要的初始化。

使用以下方式将@NoArgsConstructorAccessLevel.PRIVATE一起使用:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FilePathHelper {
   // your code 
}

4

我建议在Sonar中禁用这个规则,引入私有构造函数并没有真正的好处,只会增加代码库中其他人需要阅读和计算机需要存储和处理的冗余字符。


有一个好处。它将防止意外创建冗余的类实例。 - Stephen C
这就是我所说的“真正的好处”。我知道这个规则背后的“推理”。但这是一个真正的问题吗?这不可能发生——有人应该编写代码来创建类,因此这不是偶然的。如果有人创建了他们不需要的实例,那是他们的问题。简而言之,在我看来,这不值得担心,也不能证明你的代码库中额外字符的存在是合理的。 - user431640
这比那更糟糕。u.method()看起来像一个实例方法。但是如果uUtilityClass的实例,而(自然)method是静态的,则我们有一些误导性的代码。(我看到过它?是的!)private构造函数的真正用途是绝对防止这种情况发生。(谁的问题?使用您的类的程序员。)并且通过推广,您...因为您设计了可实例化的实用类。 - Stephen C
Sonar 不仅仅是为了编写代码的程序员自身利益而存在,它也是为了其他需要使用第一位程序员所写代码的程序员们的利益而存在。 - Stephen C
3
如果你喜欢这个功能,启用声纳规则并坚持使用它。我个人不想在我的代码中有那些额外的字符。而且不用担心,那个创建Utility类实例的人无法通过私有构造函数来阻止他制造问题。他总是可以引入无限循环、内存泄漏、将代码因为难以维护而变得混乱或者干脆烧掉办公室。 - user431640

3

使用Lombok的另一种方法是使用@UtilityClass注解。

@UtilityClass在Lombok v1.16.2中作为实验性功能引入:

如果一个类被注解了@UtilityClass,则会对它进行以下操作:

  • 它被标记为final。
  • 如果其中声明了任何构造函数,则会生成错误:
    • 否则,会生成一个私有无参构造函数;它会抛出一个UnsupportedOperationException异常。
  • 该类中的所有方法、内部类和字段都被标记为static

概述:

实用程序类是仅用于函数命名空间的类。它的实例不存在,并且它的所有成员都是静态的。例如,java.lang.Mathjava.util.Collections是众所周知的实用程序类。

此注解会自动将带注释的类转换为实用程序类。

实用程序类不能被实例化。

通过使用@UtilityClass标记您的类,Lombok将自动生成一个抛出异常的私有构造函数,将您添加的任何显式构造函数标记为错误,并将类标记为final。

如果该类是内部类,则该类也将被标记为static

实用程序类的所有成员都会自动标记为静态的。包括字段和内部类。

示例:

import lombok.experimental.UtilityClass;

@UtilityClass
public class FilePathHelper {

    private static String resourcesPath;

    public static String getFilePath(HttpServletRequest request) {
        if(resourcesPath == null) {
            ServletContext context = request.getSession().getServletContext();
            String serverpath = context.getRealPath("");               
            resourcesPath = serverpath + "/WEB-INF/classes/";   
        }
        return resourcesPath;       
    }
}

官方文档参考:

该页面介绍了Lombok实验性特性 - Utility Class的使用方法和示例。

0

虽然使用@UtilityClass注释会在sonarCube上显示问题。 因此,基本问题是类的“Java提供默认的无参数公共构造函数”。现在我们有两个解决方案 -

  1. 删除@UtilityClass并将其设置为具有私有构造函数的static final类。
  2. 不要将其用作类,而是将其用作枚举。

但是 - 当在sonarQube中出现问题时,请使用 - @SuppressWarnings("java:###")
"###"规则编号。


这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Michael Katt

-1
添加私有构造函数:
private FilePathHelper(){
    super();
}

10
欢迎来到StackOverflow!不幸的是,这个答案并没有提供任何新的信息,因为已经有其他人给出了类似的答案。而且,更好的做法是详细阐述代码如何解决问题以及为什么这样做可以解决问题。 - René Vogt

-1
public class LmsEmpWfhUtils {    
    private LmsEmpWfhUtils() 
    { 
    // prevents access default paramater-less constructor
    }
}

这可以防止默认的无参构造函数在代码中的其他地方被使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接