Рефакторинг последовательных проверок в Mockito с помощью fluent-интерфейсов

    У статических методов есть одна мощная, но и в то же время весьма нежелательная особенность: их можно вызвать из любого места в коде, не особо имея возможность регламентировать порядок их вызова. Зачастую такой контроль очень важен, но иногда порядок не имеет очень большого смысла. Например, осуществлять проверки в юнит-тестах часто можно не в очень строгом порядке. И чтобы гарантировать, что в тестируемом юните выполенены все проверки, в Mockito существует всё тот же статический метод verifyNoMoreInteractions(...). Иногда можно по ошибке вызвать такой метод ещё до последнего verify(...) и потом с огорчением наблюдать "красный" тест. Но что если переложить заботу о порядке выполнения проверок на сам компилятор?



    Предположим, что некий тестируемый модуль располагает следующим тестом:


    public abstract class AbstractStructuredLoggingTest<T> {
    
        private final IStructuredLogger mockStructuredLogger = mock(IStructuredLogger.class);
    
        private T unit;
    
        @Nonnull
        protected abstract T createUnit(@Nonnull IStructuredLogger logger);
    
        protected final IStructuredLogger getMockStructuredLogger() {
            return mockStructuredLogger;
        }
    
        protected final T getUnit() {
            return unit;
        }
    
        @Before
        public void initializeMockStructuredLogger() {
            // Настройка мока. `selfAnswer()` -- ответ для мока, который возвращает сам мок для fluent-интерфейсов
            when(mockStructuredLogger.begin()).thenAnswer(selfAnswer());
            when(mockStructuredLogger.put(any(LogEntryKey.class), any(Object.class))).thenAnswer(selfAnswer());
            when(mockStructuredLogger.log(any(Scope.class), any(Severity.class), any(String.class))).thenAnswer(selfAnswer());
            when(mockStructuredLogger.end()).thenAnswer(selfAnswer());
            // Внедрение мока логгера в юнит (по крайней мере, формально). Каким образом оно устроено -- не имеет особого значения.
            unit = createUnit(mockStructuredLogger);
        }
    
        @After
        public void resetMockStructuredLogger() {
            try {
                // Проверка, не осталось ли непровернного взаимодействия с моком
                verifyNoMoreInteractions(mockStructuredLogger);
            } finally {
                // На всякий случай, сброс состояния мока, потому как ошибки падают каскадно...
                reset(mockStructuredLogger);
            }
        }
    
    }

    public final class AdministratorServiceStructuredLoggingTest
            extends AbstractStructuredLoggingTest<IAdministratorService> {
    
        private static final String USERNAME = "john.doe";
        private static final String PASSWORD = "opZK2lkXa";
        private static final String FIRST_NAME = "john";
        private static final String LAST_NAME = "doe";
        private static final String EMAIL = "john.doe@acme.com";
    
        @Nonnull
        protected IAdministratorService createUnit(@Nonnull final IStructuredLogger logger) {
            return createAdministratorService(logger);
        }
    
        @Test
        public void testCreate() {
            final T unit = getUnit();
            unit.create(USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL);
            final IStructuredLogger mockStructuredLogger = getMockStructuredLogger();
            verify(mockStructuredLogger).put(eq(OPERATION_CALLER_CLASS), any(IAdministratorService.class));
            verify(mockStructuredLogger).put(eq(OPERATION_CALLER_METHOD), any(Method.class));
            verify(mockStructuredLogger).put(eq(OPERATION_TYPE), eq(CREATE));
            verify(mockStructuredLogger).put(eq(OPERATION_OBJECT_TYPE), eq(ADMINISTRATOR));
            verify(mockStructuredLogger).put(eq(VALUE_ADMINISTRATOR_NAME), eq(USERNAME));
            verify(mockStructuredLogger).put(eq(VALUE_FIRST_NAME), eq(FIRST_NAME));
            verify(mockStructuredLogger).put(eq(VALUE_LAST_NAME), eq(LAST_NAME));
            verify(mockStructuredLogger).put(eq(VALUE_EMAIL), eq(EMAIL));
            verify(mockStructuredLogger).log(eq(APP_DEV), eq(INFO), any(String.class));
            verifyNoMoreInteractions(mockStructuredLogger);
        }
    
    }

    Догадаться, что именно проверяет этот тест несложно: он проверяет, вызвал ли тестируемый метод юнита все важные методы структурированного логгера. Поскольку в конце теста есть вызывается ещё и verifyNoMockInteractions(...), гарантируется, что у мока не осталось методов, для которых не было написано проверок. Кстати, интерфейс структурированного логгера предельно прост, но я приведу его здесь в несколько урезанном виде, потому как код взят из реального проекта.


    public interface IStructuredLogger {
    
        // В самом тесте не участвует, но имеет смысл, потому как позволяет демаркировать начало и конец сообщения для журналирования.
        @Nonnull
        IStructuredLogger begin()
                throws IllegalStateException;
    
        // Заполнение сообщения, которое попадёт в журнал.
        // key -- перечисление (enum) возможных ключей в сообщении (OPERATION_CALLER_CLASS, VALUE_FIRST_NAME и т.д.)
        // value -- произвольный аргумент
        @Nonnull
        IStructuredLogger put(@Nonnull LogEntryKey key, @Nullable Object value)
                throws IllegalStateException;
    
        // Запись сообщения в журнал.
        // scope -- перечисление типа журнала (например, APP_DEV -- запись одновременно и в пользовательский и в отладочный журналы)
        // severity -- снова перечисление для определения порога, которое должно преодолеть сообщение, чтобы попасть в журнал (ERROR, INFO и т.д.)
        // message -- произвольное сообщение, которое может легко прочитать человек
        @Nonnull
        IStructuredLogger log(@Nonnull Scope scope, @Nonnull Severity severity, @Nonnull String message)
                throws IllegalStateException;
    
        // Двойник для begin()
        @Nonnull
        IStructuredLogger end()
                throws IllegalStateException;
    
    }

    Как было сказано выше, статические методы, которыми усеян тест, не гарантируют, что проверки всех типов будут осуществлены. И, наверняка, такой тест завершится с ошибкой. Под типами проверок в этом тесте я подразумеваю возможность определить:


    • к какому классу привязано событие в журнале;
    • какое действие было совершено и над каким видом объектов;
    • какими аргументами для действия был заполнен журнал событий;
    • какие журналы физически были задействованы в процессе журналирования;
    • структурированный логгер в методе юнита больше ни для чего не применялся.

    По сути, имеется конечный набор требований, который бы можно было заставить выполнится в определённом порядке.
    Для решения этой задачи можно рассмотреть варианты с использованием шаблона стратегия, где бы был некий интерфейс с методами на каждый тип проверки, и каждый метод отвечал бы за свой аспект журналирования. Или, например, шаблонный метод. Но очевидно, что такие подходы были бы весьма громоздки, ненадёжны в плане гарантирования разделения аспектов по именно соответсвующим им методам. Да и читабельностью бы пришлось пожертвовать, чего уж точно не хотелось бы делать.


    Лет пять назад, помнится, я наткнулся в Интернете на статью, описывающей реализацию шаблона строитель, которая, используя некоторые не совсем очевидные техники, гарантировала, что создание сложного объекта будет осуществлено в правильном порядке. Имеется ввиду следующее: для некоего объекта-строителя сначала можно вызвать только метод setFoo(), и лишь потом — setBar() с последующим build(). И никак не в другом порядке, ведь за порядком следит компилятор!


    Похожий подход, но уже с другой реализацией, можно использовать и для упрощённого написания тестов по правилам, описанным выше строго в одном порядке, при этом без использования шаблонного метода. Учитывая кое-как формализированные особенности тестирования в этом случае, можно создать набор таких интерфейсов, которые и будут заниматься сцеплением таких переходов. И для удобства можно использовать текучий интерфейс, который позволит выстроить элегантную цепочку проверок.


    // Шаг, который проверяет, к какому юниту и его методу было ассоциировано сообщение
    @FunctionalInterface
    public interface IOperationCallerVerificationStep {
    
        // unitMatcherSupplier -- возвращает юнит
        // methodMatcherSupplier -- возвращает метод, из которого ожидается вызов журналирования
        // Метод осуществляет несколько проверок, и если не было ошибок -- создать объект для следующего шага
        @Nonnull
        IOperationTypeVerificationStep withOperationCaller(
                @Nonnull Supplier<?> unitMatcherSupplier,
                @Nonnull Supplier<Method> methodMatcherSupplier
        );
    
        // По умолчанию считаем, что нас не волнует метод юнита, с которого был вызван логгер. Это довольно
        // спорное утверждение, потому что это может дискредитировать сами юнит-тесты. Но для систем с автоматическим
        // журналированием (например, с помощью процессора аннотаций журнала во время исполнения, а не [APT](http://docs.oracle.com/javase/7/docs/technotes/guides/apt/)) это не
        // имеет большого значения. По крайней мере, автоматическая генерация журнала позволяет _удобно_
        // формировать объект типа Method, чего не скажешь про ручное формирование такого сообщения.
        @Nonnull
        default IOperationTypeVerificationStep withOperationCaller(
                @Nonnull final Supplier<?> unitMatcherSupplier
        ) {
            return withOperationCaller(unitMatcherSupplier, () -> any(Method.class));
        }
    
    }

    // Шаг, проверяющий операцию и тип объекта этой операции
    @FunctionalInterface
    public interface IOperationTypeVerificationStep {
    
        // operationTypeMatcherSupplier -- возвращает тип операции
        // objectTypeMatcherSupplier -- возвращает тип объекта, над которым осуществляется операция
        @Nonnull
        IValueVerificationStep withOperationType(
                @Nonnull Supplier<OperationType> operationTypeMatcherSupplier,
                @Nonnull Supplier<ObjectType> objectTypeMatcherSupplier
        );
    
    }

    // Шаг, проверяющий именно контекстные данные (т.е., те, которые были переданы в юнит)
    public interface IValueVerificationStep {
    
        // logEntryKeyMatcherSupplier -- возвращает тип ключа для структурированного сообщения
        // valueMatcherSupplier -- произвольный аргумент
        // Кстати, этот метод не возвращает следующий объект следующего шага, потому как этот метод является
        // вариадическим -- т.е., подразумевают вызов нескольких таких методов подряд, потому как количество таких
        // пар неизвестно заранее.
        @Nonnull
        IValueVerificationStep withValue(
                @Nonnull Supplier<LogEntryKey> logEntryKeyMatcherSupplier,
                @Nonnull Supplier<?> valueMatcherSupplier
        );
    
        // Весьма синтетический метод. Нужен только для того, чтобы осуществить переход на следующий шаг.
        @Nonnull
        ILogVerificationStep then();
    
    }

    // Финальный шаг, проверяющий, в какой журнал и с каким порогом будет записано сообщение
    @FunctionalInterface
    public interface ILogVerificationStep {
    
        // scopeMatcherSupplier -- тип журнала
        // severityMatcherSupplier -- порог для сообщения
        // messageMatcherSupplier -- произвольное сообщение
        // Это финальная проверка, поэтому этот метод не возвращает ничего
        void withLog(
                @Nonnull Supplier<Scope> scopeMatcherSupplier,
                @Nonnull Supplier<Severity> severityMatcherSupplier,
                @Nonnull Supplier<String> messageMatcherSupplier
        );
    
        // Здесь нас не волнует сообщение вообще
        default void withLog(
                @Nonnull final Supplier<Scope> scopeMatcherSupplier,
                @Nonnull final Supplier<Severity> severityMatcherSupplier
        ) {
            withLog(scopeMatcherSupplier, severityMatcherSupplier, () -> any(String.class));
        }
    
        // А здесь считаем, что по умолчанию сообщение пишется в два журнала одновременно (и APP, и DEV)
        default void withLog(
                @Nonnull final Supplier<Severity> severityMatcherSupplier
        ) {
            withLog(() -> eq(APP_DEV), severityMatcherSupplier, () -> any(String.class));
        }
    
    }

    Почти все интерфейсы получилось проаннотировать как @FunctionalInterface, хотя это и не является необходимостью. Тем не менее, "вариадический" интерфейс обладает двумя методами, поскольку нужно как-то сообщить о завершении проверки журналирования аргументов операции. Итак, оригинальный код теста теперь может принять следующий вид:


    public abstract class AbstractStructuredLoggingTest<T> {
    
        private final IStructuredLogger mockStructuredLogger = mock(IStructuredLogger.class);
    
        private T unit;
    
        @Nonnull
        protected abstract T createUnit(@Nonnull IStructuredLogger logger);
    
        // Сюрприз-сюрприз! Метод больше не нужен, потому что все проверки инкапсулируются именно в этом классе
        /*protected final IStructuredLogger getMockStructuredLogger() {
            return mockStructuredLogger;
        }*/
    
        protected final T getUnit() {
            return unit;
        }
    
        @Before
        public void initializeMockStructuredLogger() {
            when(mockStructuredLogger.begin()).thenAnswer(selfAnswer());
            when(mockStructuredLogger.put(any(LogEntryKey.class), any(Object.class))).thenAnswer(selfAnswer());
            when(mockStructuredLogger.log(any(Scope.class), any(Severity.class), any(String.class))).thenAnswer(selfAnswer());
            when(mockStructuredLogger.end()).thenAnswer(selfAnswer());
            unit = createUnit(mockStructuredLogger);
        }
    
        @After
        public void resetMockStructuredLogger() {
            try {
                verifyNoMoreInteractions(mockStructuredLogger);
            } finally {
                reset(mockStructuredLogger);
            }
        }
    
        // Метод, создающий первый шаг для проверки журналирования. Именно в нём и сосредоточены все проверки.
        // Код выглядит довольно неуклюже, но он хорошо решает свою задачу. Ситуация несколько упрощается
        // наличием лямбда-выражений. До того как перейти на следующий шаг, вызываются методы verify(...),
        // которые и были полностью инкапсулированы. В последнем шаге verifyNoMoreInteractions не вызывается,
        // поскольку этот метод вызывается после каждого теста автоматически.
        protected final IOperationCallerVerificationStep verifyLog() {
            return (unitMatcherSupplier, methodMatcherSupplier) -> {
                verify(mockStructuredLogger).put(eq(OPERATION_CALLER_CLASS), unitMatcherSupplier.get());
                verify(mockStructuredLogger).put(eq(OPERATION_CALLER_METHOD), methodMatcherSupplier.get());
                return (IOperationTypeVerificationStep) (operationTypeMatcherSupplier, objectTypeMatcherSupplier) -> {
                    verify(mockStructuredLogger).put(eq(OPERATION_TYPE), operationTypeMatcherSupplier.get());
                    verify(mockStructuredLogger).put(eq(OPERATION_OBJECT_TYPE), objectTypeMatcherSupplier.get());
                    return new IValueVerificationStep() {
                        @Nonnull
                        @Override
                        public IValueVerificationStep withValue(@Nonnull final Supplier<LogEntryKey> logEntryKeyMatcherSupplier,
                                @Nonnull final Supplier<?> valueMatcherSupplier) {
                            verify(mockStructuredLogger).put(logEntryKeyMatcherSupplier.get(), valueMatcherSupplier.get());
                            return this;
                        }
    
                        @Nonnull
                        @Override
                        public ILogVerificationStep then() {
                            return (scopeMatcherSupplier, severityMatcherSupplier, messageMatcherSupplier) -> verify(mockStructuredLogger).log(scopeMatcherSupplier.get(), severityMatcherSupplier.get(), messageMatcherSupplier.get());
                        }
                    };
                };
            };
        }
    
    }

    Ну и, собственно, само упрощение, ради которого было усложнено базовый функционал тестов:


    public final class AdministratorServiceStructuredLoggingTest
            extends AbstractStructuredLoggingTest {
    
        private static final String USERNAME = "usr";
        private static final String PASSWORD = "qwerty";
        private static final String FIRST_NAME = "john";
        private static final String LAST_NAME = "doe";
        private static final String EMAIL = "usr@mail.com";
    
        @Nonnull
        protected IAdministratorService createUnit(@Nonnull final IStructuredLogger logger) {
            return createAdministratorService(logger);
        }
    
        @Test
        public void testCreate() {
            final T unit = getUnit();
            unit.create(USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL);
            verifyLog()
                    .withOperationCaller(() -> any(IAdministratorService.class))
                    .withOperationType(() -> eq(CREATE), () -> eq(ADMINISTRATOR))
                    .withValue(() -> eq(VALUE_ADMINISTRATOR_NAME), () -> eq(USERNAME))
                    .withValue(() -> eq(VALUE_FIRST_NAME), () -> eq(FIRST_NAME))
                    .withValue(() -> eq(VALUE_LAST_NAME), () -> eq(LAST_NAME))
                    .withValue(() -> eq(VALUE_EMAIL), () -> eq(EMAIL))
                    .then()
                    .withLog(() -> eq(INFO));
        }
    
    }

    Как по мне, код стал надёжнее и весьма красивее. Да и удобнее тоже. И главное — любая умная IDE при нажатии на точку сама подсказывает, каким должен быть следующий шаг. Таким образом, и компилятор и IDE добавляют ешё немного уверенности в том, насколько хорошо написан тест. Кстати, почему используются Supplier-ы и лямбда-выражения? Дело в том, что Mockito проверяет, передаются ли стабы напрямую в моки, и если нет — бросает исключение. На самом деле, здесь, насколько мне известно, правила немного сложнее, и, например, Mockito игнорирует анонимные классы. И ввиду этого факта есть небольшая лазейка: Mockito не отслеживает передачу матчеров через return, что открывает дорогу к использованию лямбд. Это немного усложняет код и читабельность, но лямбды достаточно неплохо с этим справляются.


    В итоге получился следующий результат:


    • однотипные тесты;
    • каждый следующий шаг в тесте формально описывает свой следующий шаг, что прекрасно дополняется поддержкой со стороны компилятора и IDE, что недостижимо в случае использования статических методов (по крайней мере, в начальном варианте теста);
    • инициализация тестов и их последующая проверка осуществляется абстрактным тестом, а конкретный тест просто описывает проверки, даже по сути прямо не взаимодействуя с оригинальным юнитом.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 8

      0
      Mockito утверждает, что это вообще-то антипаттерн https://webcache.googleusercontent.com/search?q=cache:6sm46ByiAY0J:https://monkeyisland.pl/2008/07/12/should-i-worry-about-the-unexpected/+&cd=1&hl=en&ct=clnk&gl=nl&lr=lang_en%7Clang_ru

      Лучше тестировать состояние. В вашем примере можно сделать состояние логгера доступным для теста и проверять его с помощью assertThat.

      (И еще сильно режут глаза интерфейсы, начинающиеся с I — вы до этого на Delphi писали?)
        +1
        Mockito утверждает, что это вообще-то антипаттерн

        Да, это также упоминается в javadoc-е Mockito.verifyNoMoreInteractions(Object...). Но я считаю, что для систем, от которых ожидается чётко регламентированное поведение (как, например, строгое журналироване в тексте статьи), такая практика вполне даже применима и может быть использована именно во благо, а не во вред, о чём говорят разработчики Mockito, ссылаясь на возможное злоупотребление этой возможностью. Например, если метод тестируемого юнита обращается к какому-либо другому юниту и это влияет на состояние сообщения, которое планируется отправить в журнал — тест может указать на возможные проблемы во взаимодействии компонент между собой. И если это не считать проблемами, то тест может фактически выступать в роли документа, формально описывающего ожидаемые результаты взаимодействия нескольких компонент (если считать таковыми тесты, конечно). Намеренное игнорирование Mockito.verifyNoMoreInteractions(Object...) можно сравнить с намеренным подавлением предупреждений компилятора или других инструментов анализа.


        Лучше тестировать состояние. В вашем примере можно сделать состояние логгера доступным для теста и проверять его с помощью assertThat.

        Возможно, но мне такое утверждение кажется весьма спорным: здесь логгер выступает в роли компонента с write-only семантикой, и я бы не хотел видеть, как он предоставляет доступ к своему временному состоянию (имеется ввиду время жизни с begin() до log(...)) даже с помощью простейшего .contains(LogEntryKey key, Object value). Кроме того, тест с помощью Mockito позволяет гарантировать, что логгер собрал не только данные для следующей записи в журнале (т.е., состояние), а также и отослал эти данные куда-то (log(...)). Можно возразить, что и log(...) можно реализовать так, чтобы он выставлял некоторое состояние, описывающее факт "отосланного сообщения", но тогда наверняка пришлось бы пожертвовать или чистотой интерфейса, добавив в него что-то типа isLogged(), или в тестах завязываться на конкретную реализацию и каким-то образом узнавать о таком флажке (пусть даже приватном). Подход с Mockito, я считаю, более естественнен.


        (И еще сильно режут глаза интерфейсы, начинающиеся с I — вы до этого на Delphi писали?)

        Не полностью по Java, да. На самом деле это прямо позаимствовано из C#/.NET (я, честно говоря, с Delphi только TFoo помню). Мне кажется, это немного елегантнее, чем FooImpl, BarImpl и BazImpl, которые реализуют один и тот же интерфейс. Плюс, с практической точки зрения, такие имена удобнее читать за компьютерами коллег, которые принебрегают возможностями подстветки, или в системах, в которых такая возможность отсутствует вообще.

          0
          логгер выступает в роли компонента с write-only семантикой

          Не обязательно использовать Mockito для этого. Если логгер — это интерфейс, то можно написать свою имплементацию, которая собирает вызовы в журнал, который торчит наружу. И можно просто проверять через assertThat(logger.getActions(), has(<list of stuff>))
          Мне кажется, так будет короче и проще, чем у вас. У вас очень похоже на overengineering.

          Не полностью по Java, да

          Дело вкуса, однако я за всю карьеру ни разу не сталкивался с IInterface на Java.
          Я согласен вот с этими доводами https://stackoverflow.com/questions/541912/interface-naming-in-java
            0
            Если логгер — это интерфейс, то можно написать свою имплементацию, которая собирает вызовы в журнал, который торчит наружу. Мне кажется, так будет короче и проще, чем у вас.

            Вот как раз этого я и пытаюсь избежать, поэтому предпочитаю тестирование поведения в таких случях. Мне бы не хотелось видеть состояние наружу только ради тестирования, даже если бы реализацей такого логгера стал бы внутренний класс, а сам интерфейс бы не регламентировал передачу внутреннего состояния наружу. В таком случае Mockito берёт всю возню с состоянием на себя и, я уверен, делает это великолепно.

              0
              Мне бы не хотелось видеть состояние наружу только ради тестирования, даже если бы реализацей такого логгера стал бы внутренний класс, а сам интерфейс бы не регламентировал передачу внутреннего состояния наружу

              Реализацией был бы класс в директории src/test
              То есть это был бы класс только для тестов. Мне кажется, так было бы проще/чище.

              Mockito берёт всю возню с состоянием на себя

              Я бы согласился, если бы не простыня кода, чтобы заставить ваш подход работать.
              Обычно нет нужды писать какой-то сложный код, чтобы работать с Mockito.
              Если же вам пришлось это сделать, то, скорее всего, вы используете Mockito не по назначению.
                +1

                Мы, возможно, говорим о немного разных вещах. Я ставил целью статьи не показать преимущество тестирования поведения над тестированием состояния или наооборот, а в том, как с помощью средств языка и немножко — инструмента тестирования — создать некое формальное описание строгого порядка выполнения проверок в виде некого подобия DSL. Не более того. Это и повлекло за собой создание той "простыни", ведь без неё никак в обеих случаях. Т.е., упор именно на переходы между проверками:


                • сначала обязательная проверка на operation caller;
                • потом обязательная проверка на operation type;
                • и лишь потом — проверка аргументов value.

                Единственное, что меня действительно огорчает — пришлось засыпать код лямбдами, потому что Mockito так работает. В случае использования только чистых JUnit/TestNG[+Hamcrest], в них (в лямбдах), конечно, не было бы нужды. И даже если я бы сделал упор на тестировании состояния, следуя вашей рекоммендации, у меня бы всё-равно в базовом абстрактном тесте был бы базовый метод, verifyLog(), который знал бы о состоянии, а производные тесты бы просто описывали конкретные правила, например:


                verifyLog()
                           .withOperationCaller(any(IAdministratorService.class))
                           .withOperationType(eq(CREATE), eq(ADMINISTRATOR))
                           .withValue(eq(VALUE_ADMINISTRATOR_NAME), eq(USERNAME))
                           .withValue(eq(VALUE_FIRST_NAME), eq(FIRST_NAME))
                           .withValue(eq(VALUE_LAST_NAME), eq(LAST_NAME))
                           .withValue(eq(VALUE_EMAIL), eq(EMAIL))
                           .then()
                           ... // здесь не уверен

                что по смыслу тождественно прямой проверке через has/contains или их аналоги, которые полностью инкапсулированы в базовом verifyLog().

              0
              naming conventions были сформулированы для того чтоб упростить жизнь девелоперам, однако это не означает что слепое следование решит все проблемы. К тому же Sun часто сами их нарушали (на счет oracle не в курсе).

              Использовать наименования классов в виде IXService, IXComponent, IXDao — очень удобно т.к. простая подстановка "*" вместо конкретного названия сущности позволяет вам найти список всех компонент которые относятся к конкретному слою приложения. Необходимость в таком поиске часто возникает в долго играющих проэктах.

              Откуда корни ростут… незнаю как у ТС а у меня из исходников java: префиксы Abstract, Base используются для маркировки абстрактных классов, однако в enterprise и так хватает длинных названий, поэтому намного удобнее использовать AXService вместо AbstractXService. А если можно для абстрактных классов то можно и для интерфейсов. В целом позволяет избавится от многих не всегда полезных приставок в названиях классов, если интерфейс называется AccountService скорее за все имплементация будет иметь имя: DefaultAccountService, AccountServiceImpl, RestAccountService или WebAccountService, которые полезной нагрузки практически не несут

              p.s. сейчас не вспомню, но точно видел нейминг IInterface в какой-то достаточно популярной либе
            0
            Интерфейсы начинающиеся с «I» в Java — это чаще всего Eclipse Project Naming Conventions.

          Only users with full accounts can post comments. Log in, please.