Статьи

Лучший способ войти в систему для безопасного, проверенного кода

Весь код для этого примера можно найти в GitHub .

Один из самых неприятных запахов для меня при переходе на новую кодовую базу — распространение журналирования по всему приложению. Я видел некоторые классы, где каждая другая строка кода может регистрироваться. Обычное оправдание этому — «Это на всякий случай» или «это помогает нам отлаживать!». Обычно это код для того факта, что код не был написан с использованием TDD или проверен каким-либо реальным способом. Это рецепт катастрофы.

Одной из других проблем с этим является то, что регистрация может быть опасной. Слишком многие разработчики просто предполагают, что Log4J / SLF4J / независимо от того, что новый фреймворк будет * просто работать *. Как скажет любой разработчик с унцией опыта, если это может пойти не так, то, вероятно, так и будет.

Это означает, что каждые 3-6 месяцев будут возникать проблемы в UAT или производственной среде, вызванные неправильным ведением журнала. Я видел, что критически важные для бизнеса системы с малой задержкой наносили урон из-за дополнительных журналов, которые были добавлены, чтобы помочь отладить что-то. Мой фаворит — Heisenbug, который исправлен, добавляя регистрацию, но появляется снова, убирая это. Прекрасный.

Это явно свидетельствует о более широкой проблеме приложения с производительностью и тестированием приложения, и некоторые ошибки просто отказываются воспроизводить себя в тестовой среде. Но в идеальном мире, как мы должны войти в наш код? Какой золотой стандарт?

Представляем ApplicationEvents

Ведение журнала — это не то, что следует делать «на всякий случай». Это должно быть с конкретной, направленной целью. Обычно это указывает на событие жизненного цикла, такое как начало или конец запущенного процесса. Это может быть раскрытие определенной статистики, если вы еще не создали для этого подходящий инструмент. Ключевым моментом является то, что строка журнала должна определенно исходить из требования; какая-то потребность вашего бизнеса или клиента. Если линия не появляется, когда должна, то это должно быть проблемой с красными тревожными звонками.

Как требование является четким, тестируемым блоком. Мы должны быть в состоянии проверить нашу регистрацию. Приложение регистрируется в правильное время? Это записывает правильную строку? Все это должно быть легко проверяемым.

В действительности тестирование кода регистрации не так просто. Большинство каркасов журналирования имеют некоторую статическую инициализацию (Yay! Global State!), Что затрудняет тестирование; вы можете взломать, прочитав выходной файл журнала, но это не особенно чисто и быстро. Вместо этого мы можем использовать класс ApplicationEvents.

Интерфейс ApplicationEvents имеет метод для каждого важного события в вашем приложении, которое необходимо зарегистрировать; думаю, что методы, такие как beginStartOfDay () или databaseConnected (). Я работал над крупными корпоративными приложениями, в которых было менее 30 различных методов, но вполне возможно разделить класс на несколько ApplicationEvents, если он станет непривлекательным. Затем это передается в классы, которые в этом нуждаются (внедрение зависимости AKA). Это означает, что его можно легко смоделировать для тестов и создать различные реализации для интересных целей.

Конкретный пример

public static void main(String[] args) {
        StartOfDayListener startOfDayListeners = new CompositeStartOfDayListener(
                new SomeStartOfDayProcess(),
                new SomeStartOfDayProcess(),
                new SomeStartOfDayProcess()
        );

        get("/beginStartOfDay", new SoDHandler(startOfDayListeners));
    }


    private static class SoDHandler implements Route {
        public static Logger LOGGER = LoggerFactory.getLogger(SoDHandler.class);

        private StartOfDayListener startOfDayListeners;

        private SoDHandler(StartOfDayListener startOfDayListeners) {
            this.startOfDayListeners = startOfDayListeners;
        }

        public Object handle(Request request, Response response) throws Exception {
            LOGGER.info("SOD Requested, handling...");
            startOfDayListeners.onStartOfDay();
            LOGGER.info("SOD Finished");
            return "Start of Day complete";
        }
    }

В этом примере у нас есть очень простой веб-сервер, который принимает один запрос, чтобы начать процесс начала дня (SOD). При вызове onStartOfDay () будет вызываться для всего, что подписано на уведомление о событии.

У нас есть 2 соответствующие строки журнала; один, чтобы сказать, что SOD был запрошен, и один, чтобы сказать, что он закончил. В настоящее время очень сложно протестировать эти строки; обычно мы игнорируем их и подтверждаем функциональность, проверяя, что вызывается метод onStartOfDay. Но это такие логи, которые часто имеют решающее значение для мониторинга, и даже в этом надуманном примере они достаточно важны для тестирования.

Самый простой способ проверить это — заменить строки журнала на ApplicationEventsLogger (интерфейс, который я создал).

 private static class SoDHandler implements Route {
        public static Logger LOGGER = LoggerFactory.getLogger(SoDHandler.class);

        private StartOfDayListener startOfDayListeners;
        private ApplicationEvents applicationEvents;

        private SoDHandler(StartOfDayListener startOfDayListeners, ApplicationEvents applicationEvents) {
            this.startOfDayListeners = startOfDayListeners;
            this.applicationEvents = applicationEvents;
        }

        public Object handle(Request request, Response response) throws Exception {
            applicationEvents.onStartOfDay();

            startOfDayListeners.onStartOfDay();

            applicationEvents.startOfDayEnds();

            return "Start of Day complete";
        }

    }

public interface ApplicationEvents extends StartOfDayListener {
    void onStartOfDay();
    void startOfDayEnds();
}

Теперь мы можем создать поддельную версию ApplicationEvents и утверждать, что начинаются и заканчиваются строки журнала. Одна из причин, по которой TDD хорош, заключается в том, что он заставляет задуматься о дизайне. Должна ли вызываться строка конца журнала, если в слушателях есть исключение? Это то, что сейчас прямо указано в тестах.

ApplicationEvents должен существовать один раз для каждого приложения. Добавьте новые методы в интерфейс по мере необходимости. Это должно сознательно заставить вас подумать «мне нужна эта строка журнала?». Надеемся, что это предотвратит ложную регистрацию.

Чуть дальше

Вы заметите дублированное имя метода для onStartOfDay в ApplicationEventsLogger и StartOfDayListener. Теперь мы можем пойти дальше и заставить наши ApplicationEvents расширять StartOfDayListener и включать его в качестве прослушивателя в начале. Ведение журнала не загромождает базу кода. Если вы широко используете шаблон слушателя в своей кодовой базе, вы должны обнаружить, что ведение журнала становится намного более прозрачным, а ваша кодовая база намного чище и проще в обращении.

static ApplicationEvents applicationEvents = new ApplicationEventsLogger();

    public static void main(String[] args) {
        StartOfDayListener startOfDayListeners = new CompositeStartOfDayListener(
                applicationEvents,
                new SomeStartOfDayProcess(),
                new SomeStartOfDayProcess(),
                new SomeStartOfDayProcess()
        );

        get("/beginStartOfDay", new SoDHandler(startOfDayListeners, applicationEvents));
    }

В заключении

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