我们正在开发一个平台,许多开发人员将编写自己的ETL应用程序,这些应用程序使用供应商的API,然后将其提交到平台上执行。我们希望在编写Main类(通常只使用供应商的API)时将开发人员从他们自己的东西限制,以便促进一些强烈持有的约定。 (大)组织有一种人们做自己的事情的文化,多年来导致一些非常讨厌的架构,所以我们想强加一些可以通过CI / CD实施的最佳实践惯例,这将有助于培养代码共享。另一种选择是对所有人免费回归规范,我们迫切希望避免这种规范。
我们如何确定应用程序的主要类是什么?我们怎样才能测试呢?我们想要定义开发人员使用的抽象类或接口,它将定义开发人员必须遵守的一些前期承诺(否则测试将失败)。我们无法修改供应商的代码。
因此,更具体地说,目前我们有:
public class MyNastyFreeForAll {
public static void main(String[] ) {
//...
}
}
有没有办法检测/执行类似的内容:
public class MyConventionEnforcingClass implements/extends MyConventions {
public static void main(String[] ) {
//...
}
}
即。测试应用程序的主类使用从MyConventions
派生的东西?
理想情况下,我想使用Spock运行测试。
或者,是否有更好的方法来实现这一目标?我担心,在一个没有中央控制/层级的大量独立团队中这样规模的组织中的代码审查不会削减它。
编辑反思意见中的输入:
从本质上讲,这是一个人的问题。然而,1000多岁的人口和文化变迁不会在一夜之间发生。它不会仅仅通过教育,记录和影响,从而希望人们做正确的事情。我正在寻找一种技术解决方案,可以轻轻地引导我们的开发人员做正确的事情 - 如果他们愿意,他们总是可以颠覆这一点,但我想要他们如果他们想要这样做,他们会尽力去做。这是因为我正在寻找一个我在SO上发布的技术解决方案,而不是寻求如何在另一个网站上推动文化变革的指导。
编辑提供MCVE:
这是使用抽象类的示例。验证(在编译或测试时)主类派生自MyConventions
会很好。如果一个用户想要主动颠覆这一点,那么就这样吧 - 你可以把马带到水中 - 但是我试图让最终用户更容易做正确的事而不是做正确的事情。简单地给他们一个为他们做样板的课程是不够的,因为这些用户喜欢做他们自己的事情并且可能会忽略你,所以应该有某种形式的轻触技术执行。没有尝试在doProcessing()
上强加惯例,但可以使用相同的原则来添加前后方法等来实现这一目标。
如果有另一种实现这一目标的方式,那么我对这些想法非常感兴趣。
// MyNastyFreeForAll.java
// written by end-user
public class MyNastyFreeForAll {
public static void main(String[] args) {
MyNastyFreeForAll entrypoint = new MyNastyFreeForAll();
entrypoint.doBoilerplate();
entrypoint.doProcessing();
}
private void doBoilerplate() {
// lot of setup stuff here, where the user can go astray
// would like to provide this in a class, perhaps
// but we need to be able to enforce that the user uses this class
// and doesn't simply try to roll their own.
System.out.println("Doing boilerplate my own way");
}
private void doProcessing() {
// more things that the user can misuse
System.out.println("Doing doProcessing my way");
}
}
// MyConventions.java
// written by team that knows how to set things up well/correctly
public abstract class MyConventions {
public void doBoilerplate() {
System.out.println("Doing boilerplate the correct way");
}
public abstract void doProcessing();
}
// MyConventionsImpl.java
// written by end-user
public class MyConventionsImpl extends MyConventions {
public static void main(String[] args) {
MyConventions entrypoint = new MyConventionsImpl();
entrypoint.doBoilerplate();
entrypoint.doProcessing();
}
public void doProcessing() {
System.out.println("Doing doProcessing my way");
}
}
答案 0 :(得分:1)
您和其他部门可以使用AspectJ编译器编译所有代码,可以从命令行手动,通过批处理文件,通过为AspectJ配置的IDE(例如Eclipse,IDEA)或通过Maven编译。我在GitHub上为您创建了Maven设置,just clone the project。对不起,它没有使用你的MCVE课程,因为我看到它们太晚了,不想重新开始。
现在让我们假设所有符合要求的应用程序都需要实现接口:
package de.scrum_master.base;
public interface BasicInterface {
void doSomething(String name);
String convert(int number);
}
package de.scrum_master.app;
import de.scrum_master.base.BasicInterface;
public class ApplicationOne implements BasicInterface {
@Override
public void doSomething(String name) {
System.out.println("Doing something with " + name);
}
@Override
public String convert(int number) {
return new Integer(number).toString();
}
public static void main(String[] args) {
System.out.println("BasicInterface implementation");
ApplicationOne application = new ApplicationOne();
application.doSomething("Joe");
System.out.println("Converted number = " + application.convert(11));
}
}
或者,有一个基类应用程序必须扩展:
package de.scrum_master.base;
public abstract class ApplicationBase {
public abstract void doSomething(String name);
public String convert(int number) {
return ((Integer) number).toString();
}
}
package de.scrum_master.app;
import de.scrum_master.base.ApplicationBase;
public class ApplicationTwo extends ApplicationBase {
@Override
public void doSomething(String name) {
System.out.println("Doing something with " + name);
}
public static void main(String[] args) {
System.out.println("ApplicationBase subclass");
ApplicationTwo application = new ApplicationTwo();
application.doSomething("Joe");
System.out.println("Converted number = " + application.convert(11));
}
}
现在我们有一个应用程序可以自己做,既不实现接口也不扩展基类:
package de.scrum_master.app;
public class UnwantedApplication {
public void sayHello(String name) {
System.out.println("Hello " + name);
}
public String transform(int number) {
return new Integer(number).toString();
}
public static void main(String[] args) {
System.out.println("Unwanted application");
UnwantedApplication application = new UnwantedApplication();
application.sayHello("Joe");
System.out.println("Transformed number = " + application.transform(11));
}
}
现在让我们编写一个AspectJ方面,它通过declare error
产生编译错误(也可以通过declare warning
发出警告,但这不会强制执行任何操作,只报告问题)。 / p>
package de.scrum_master.aspect;
import de.scrum_master.base.BasicInterface;
import de.scrum_master.base.ApplicationBase;
public aspect ApplicationContractEnforcer {
declare error :
within(de.scrum_master..*) &&
execution(public static void main(String[])) &&
!within(BasicInterface+) &&
!within(ApplicationBase+)
: "Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}
此代码的含义是:在de.scrum_master
或任何子包中查找包含主要方法的所有类,但不实现BasicInterface
而不是扩展ApplicationBase
。实际上,您当然只会选择后两个标准中的一个。我在这里做两件事给你一个选择。如果找到任何此类,则会显示带有指定错误消息的编译器错误。
无论出于何种原因,有些人不喜欢精彩表达的AspectJ本地语言(Java语法的超集),但更喜欢编写丑陋的注释风格方面,将所有方面切入点打包成字符串常量。这是相同的方面,只是在另一种语法中。选择任何。 (在GitHub项目中,我通过搜索不存在的包xde.scrum_master
来停用原生方面,以避免双重编译错误。)
package de.scrum_master.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;
@Aspect
public class ApplicationContractEnforcer2 {
@DeclareError(
"within(de.scrum_master..*) && " +
"execution(public static void main(String[])) && " +
"!within(de.scrum_master.base.BasicInterface+) && " +
"!within(de.scrum_master.base.ApplicationBase+)"
)
static final String errorMessage =
"Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}
运行mvn clean compile
时(参见POM的GitHub项目),您将看到此输出(稍微缩短):
[INFO] ------------------------------------------------------------------------
[INFO] Building AspectJ sample with declare error 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- aspectj-maven-plugin:1.10:compile (default) @ aspectj-application-contract-enforcer ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[ERROR] "Applications with main methods have to implement BasicInterface or extend ApplicationBase"
C:\Users\alexa\Documents\java-src\SO_AJ_EnforceMainClassImplementingInterface\src\main\java\de\scrum_master\app\UnwantedApplication.java:12
public static void main(String[] args) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
在Eclipse中使用AJDT(AspectJ开发工具),它看起来像这样:
只需将main
中的UnwantedApplication
方法重命名为mainX
之类的其他内容即可,错误就会消失。