@Hint

Кто должен логировать? Функция или тот, кто её вызывает?

Есть сайт с пользователями, есть модераторы. Есть журнал действий (субъект, объект, действие, описание, время и пр.), есть функция добавления записи в журнал. Допустим, нужно заносить в журнал все действия по блокировке и разблокировке пользователей (пользователей могут блокировать как модераторы, так и какие-то автоматические процедуры). Есть функция блокировки. Есть около десяти вызовов этой функции. Вопрос. Кто должен вызывать функцию логирования: функция блокировки или та функция, которая её вызывает? Если сама функция, то кода меньше (не надо много раз дублировать код), но добавляются лишние параметры, она начинает заниматься не совсем своим делом и пр. Если тот, кто вызывает функцию, то это очень много одинаковых строк кода. Плюс еще можно вызвать функцию блокировки и забыть добавить код логирования. Как правильно?
  • Вопрос задан
  • 291 просмотр
Решения вопроса 1
artemgapchenko
@artemgapchenko
То, что вы описали, называется в англоязычной литературе cross-cutting concerns - это такие области кода, которые решают не саму бизнес-задачу, а являются как бы перпендикулярными к ней, и относятся скорее к функционированию системы. Вы правы насчёт того, что добавлять логирование в саму функцию - это, наверное, не совсем правильный выбор, так как во-первых, функция начинает решать две задачи, что сразу же уменьшает время на понимание её работы, а во-вторых, возможно будут варианты вызова функции, при которых её выполнение логировать не обязательно.
Я бы, возможно, попробовал зайти к этой задаче со стороны паттерна Декоратор (дальше пишу на Java, так как этот язык для меня основной, но принципы должны быть понятны).

Определяем интерфейс:

interface ClientHandler {
	void blockUser(User user);
	void unblockUser(User user);
}

Дальше пишем реализацию, которая будет заниматься блокированием/разблокированием пользователя:

public final class ClientHandlerImpl implements ClientHandler {
	public void blockUser(User user) {
		// Логика блокирования пользователя
	}

	public void unblockUser(User user) {
		// Логика разблокирования пользователя
	}
}

А теперь ход конём: пишем декоратор, который будет оборачивать собой написанную нами имплементацию (и в аргументе конструктора пробрасываем ClientHandlerImpl):

public final class ClientHandlerLoggingDecorator implements ClientHandler {
	private final ClientHandler handler;

	public ClientHandlerLoggingDecorator(final ClientHandler handler) {
		this.handler = handler;
	}

	public void blockUser(User user) {
		Log.d("User " + user.getName() + " blocked!")
		handler.blockUser(user);
	}

	public void unblockUser(User user) {
		Log.d("User " + user.getName() + " unblocked!")
		handler.unblockUser(user);
	}
}


Дальше можно будет создать, например, фабрику ClientHandler'ов, которая по запросу будет возвращать нам новый инстанс ClientHandler'а:

public final class ClientHandlerFactory {
	public static ClientHandler getClientHandler() {
		return new ClientHandlerLoggingDecorator(new ClientHandler());
	}
}

Чего мы добились:
1. Код логирования вынесен из реализации ClientHandler, если вам нужно будет изучить реализацию блокирования/разблокирования, вы просто открываете ClientHandlerImpl, и изучаете её.
2. (связано с предыдущим пунктом) Реализацию блокирования/разблокирования и логирования теперь можно менять независимо друг от друга.
3. Логирующее поведение становится скрытым для пользователей ClientHandler - они просто получают новый инстанс при обращении к фабрике, и используют его. Хотите отключить логирование? Меняете реализацию фабрики, и она начинает возвращать ClientHandlerImpl. Хотите сделать это поведение настраиваемым? Пишете дополнительный код, который на старте читает конфигурацию, и начинает использовать либо ту реализацию фабрики, которая возвращает ClientHandler, покрытый декоратором, либо реализацию, которая возвращает голый ClientHandler. Либо же зашиваете этот выбор внутрь самой фабрики.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы