SonarLint 规则 Java:S3252("static" 基础 class 成员不应通过派生类型访问)

SonarLint rule Java:S3252 ("static" base class members should not be accessed via derived types)

前几天安装了STS的SonarLint,想请教一个关于规则的问题Java:S3252 ("static" base class members should not be accessed via derived types)

在我的代码中,出于某种原因(我不是公司的架构师),有一些接口包含在许多程序中使用的 i18n 键。这些接口之一称为 RegieI18n 并扩展了 CommonI18n。当我需要来自 CommonI18n 的密钥时,如果 class 涉及“Regie”,例如 RegieI18n.KEY_ACTION_EXECUTE,我会使用 RegieI18n 而不是 CommonI18n.KEY_ACTION_EXECUTE。在这种情况下不遵守规则 S3252。

在我看来,使用 RegieI18n(child)有助于防止开发人员在某天 re-write 某些代码时决定覆盖 KEY_ACTION_EXECUTE RegieI18n 的另一个值。该规则也可以应用于方法(实际上是所有可访问的成员),请不要关注我们示例中使用 i18n 键的事实。

按照我的想法为什么要尊重S3252?

这是危险地接近 'opinion',这超出了 SO 的范围。因此,在这个答案中没有太多意见 - 只是 S3252 所基于的 objective 基础。

Java 通常称为 'Object Oriented',因此,它的大部分概念都是根据 OO 来解释的 - 并且大概您的普通 java 编码员的直觉在于 OO 方向.

在这种情况下这是个问题:static 东西 不“做” OO。 AT ALL. - 例如,动态调度(如果你调用 someAnimalRef.makeNoise() 实际上调用 Dog class 的 makeNoise() 方法,如果 ref 结果是引用Dog 的实例,即使变量 someAnimalRef 的类型为 Animal) - 根本不适用于 static.

换句话说,鉴于这是静态的东西,javac 决定使用哪个常量(并且对于 encore,如果所述常量是编译时常量 - 即字符串或数字文字,或者可能已经仅使用 baked-in 仅涉及文字的数学(无库)导出 - 然后对该变量的任何引用都直接替换为常量)。

这可能会造成混淆。按照您编写它的方式,您仍然会得到 'inheritance' 的排序 - 但它是编译时继承。具体来说,如果您有(将这 3 个存储在单独的文件中):

interface CommonI18n {
  String KEY_ACTION_EXECUTE = "Execute";
}

interface RegieI18N extends  CommonI18n {
}

class Test {
  public static void main(String[] args) {
    System.out.println(RegieI18N.KEY_ACTION_EXECUTE);
  }
}

和运行:

javac *.java; java Test

它显然会打印 Execute。如果您随后编辑 RegieI18N.java 并重新编译 就是这样:

interface RegieI18N extends CommonI18n {
  String KEY_ACTION_EXECUTE = "Uitvoeren";
}

// and then on the command line:

javac RegieI18N.java
java Test

仍然 打印 Execute,即使您的意图显然是它现在应该打印 Uitvoeren。有两个不同的原因(其中一个就足够了):[A] 静态内容由 javac 100% 解析,所以除非你重新编译 Test.java,否则它永远不会工作,并且[B] 像这样的常量是内联的。

这就是警告背后的基本思想:这令人困惑,即使没有,这也会妨碍您未来的灵活性。您 不能 introduce/change 任何 i18n 内容,除非您重新编译 整个应用程序 。如果 Test.classTest.java 更新,一些 'incremental compilation' 工具将不会重新编译测试,这很重要。

所以要避免这种情况?

简单 - 不要使用常量。如果您想要 'override in a subtype',请不要使用 static。 Java 内置了 i18n 内容,您可以将数据放入属性文件中,然后可以将这些文件与 class 文件一起发送(如果需要,可以放在 jar 中)。这种基于接口的策略似乎基本上没有优势(我猜从技术上讲它的性能更高,但我怀疑你是否能够衡量它,并且考虑到 i18n 有点暗示 'strings that are meant for human eyeballs',而 'meant for human eyeballs' 有点暗示:CPU 所以不会成为瓶颈,你可以完全忽略它),性能上升似乎是 疯狂的疯狂 对观点的误解。为此担心性能是完全错误的。很有可能 - 当然,每条规则都有例外。

如果你不想使用java的东西,一个好的开始是调用一个方法(如果你真的需要它是静态的)来获取这些字符串;你不想写 RegieI18N.KEY_ACTION_EXECUTE,你想写 RegieI18N.getKeyActionExecute(),甚至 RegieI18n.KEY_ACTION_EXECUTE()(注意末尾的括号)。如果需要,您可以使用 code-gen 创建这些方法。您现在可以灵活地在其中编写您喜欢的任何内容,并且 JVM 永远不会内联或以其他方式消除此类调用。如果稍后您决定将该方法用于 return 其他东西,并且您希望通过重新编译 just RegieI18n 来部署它,那么现在您可以了。

当然,你只是在糟糕地重新发明轮子,但这消除了 S3252 试图解决的主要问题。