Блог Томаша Фиялковского о программировании

«Проблемы возникают не из‑за того, чего вы не знаете. Проблема в том, что вы твердо уверены в том, что неверно». Старые основы, которые могут быть незнакомы старшим разработчикам.

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

Рассмотрим класс LocalDateTime. Название этого класса наводит на мысль, что он хранит локальную дату и время. Такое представление часто слышишь на собеседованиях (если собеседующий вообще помнит про API). Однако эксперимент показывает: у LocalDateTime мало общего с понятием «локального времени» в том смысле, в котором это понимают многие.

Настройка компьютера, например, на польский часовой пояс позволяет сформулировать простое утверждение: если LocalDateTime действительно сохраняет дату и время в локальном часовом поясе, то для получения текущей даты в другом часовом поясе достаточно создать () и затем применить atZone((» «)). На деле это не работает так, как принято ожидать.

Проблема в том, что метод () возвращает системную дату и время без привязки к часовому поясу, а метод atZone просто «приклеивает» указанный часовой пояс к уже имеющемуся значению без корректного переконвертирования. В результате выражение ().atZone((«UTC»)) фактически даёт «сейчас», сдвинутое на разницу между системным часовым поясом и UTC, а не «текущее время в UTC» в привычном смысле.

Это поведение подтверждается документированием класса: LocalDateTime — это «дата и время без часового пояса в системе календаря ISO‑8601», например 2007‑12‑03T10:15:30. Тем не менее многие полагаются на интуитивное значение имени класса и не читают документацию, что приводит к тонким ошибкам.

Подобная путаница наблюдается и с классом LocalDate. Ошибки такого рода непросто обнаружить, особенно если создание экземпляра LocalDateTime и последующее преобразование в ZonedDateTime происходят в разных местах программы, например в разных файлах.

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

Более удачное название могло бы подчёркивать отсутствие часового пояса, например ZonelessDateTime, NoZoneDateTime или NotZonedDateTime, но вообще имя класса в идеале должно указывать на ответственность объекта, а не на то, чего у него нет.

Тем, кто использует Kotlin, доступна возможность создать псевдонимы типов (type aliases), чтобы применять более выразительные имена в своём коде. Лучшее практическое решение, по мнению автора, — по возможности не использовать LocalDateTime. Понимание нюансов работы с типами даты и времени и внимательное отношение к именам классов помогает избежать сложных и трудноотлавливаемых багов.

Блог Programming Chi (или Qi) посвящён скромным идеям, ошибкам и неудачам, с которыми автор сталкивается ежедневно. К счастью, помимо неудач, встречается множество удачных и элегантных решений, о которых тоже стоит рассказывать. Если у читателя есть лучшее название для LocalDateTime или мысли по этой теме, автор приглашает поделиться ими.