Как определить основной класс приложения Java? - PullRequest
0 голосов
/ 01 мая 2018

Мы разрабатываем платформу, где многие разработчики будут писать свои собственные приложения 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?

В идеале я хочу запустить тест, используя Спока.

В качестве альтернативы, есть ли лучший способ достичь этой цели? Боюсь, что проверки кода в организации такого размера среди множества отдельных команд без какого-либо центрального контроля / иерархии просто не помогут.

РЕДАКТИРОВАТЬ, ЧТОБЫ ОТРАЖАТЬ ВХОД ОТ КОММЕНТАРИЙ:

По сути, это проблема людей. Тем не менее, число людей в их 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");

  }
}

1 Ответ

0 голосов
/ 02 мая 2018

Вы и другие отделы можете скомпилировать весь код с помощью компилятора AspectJ, либо вручную из командной строки, через пакетные файлы, через IDE, настроенную для AspectJ (например, Eclipse, IDEA), либо через Maven. Я создал Maven для вас на GitHub, просто клонируйте проект . Извините, он не использует ваши классы 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, но это ничего не навязывает, только сообщит о проблеме) .

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";
}

Компилировать с Maven

При запуске mvn clean compile (см. Проект GitHub для POM) вы увидите этот вывод (немного укороченный):

[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

В Eclipse с AJDT (AspectJ Development Tools) это выглядит так:

Eclipse showing custom AspectJ compiler error

Просто переименуйте метод main в UnwantedApplication во что-то еще, например mainX, и ошибка исчезнет.

...