А что, если вам сказали бы, что класс без final — это ошибка проектирования? Что возможность наследования должна быть исключением, а не правилом? В новом переводе от команды Spring АйО автор предлагает радикальную идею: все классы должны быть final по умолчанию.
⚠️ Статья была написана ещё до появления Kotlin`а. Цель перевода данной статьи — поднять запылившийся на полке вопрос в дизайне API: «Нужно ли оставлять API открытым по‑умолчанию для внешнего расширения?»
На днях у нас состоялась интересная дискуссия, в ходе которой всплыла тема final-классов. По какой-то причине меня вдруг осенило: все классы должны быть final по умолчанию. То есть классы должны быть неявно final, без необходимости явного указания. К примеру, следующий код, на мой взгляд, должен считаться недопустимым:
class Parent { ... }
class Child extends Parent { ... } // недопустимо: родитель неявно finalВместо final можно было бы ввести другой модификатор, скажем, open. Это позволило бы нам расширять классы следующим образом:
open class Parent { ... }
class Child extends Parent { ... } // допустимо: родитель явно открытТеперь возникает вопрос: почему я считаю, что final должен быть значением по умолчанию? Это не связано с производительностью. Подсказку дают следующие цитаты из великолепного выступления Джоша Блоха о проектировании API (см. [1][2][3]):
«When in doubt — leave it out»
«You can always add, but you can never remove»
Что это означает? Если вы не уверены, стоит ли включать ту или иную функцию в API, то лучше её не включать. Потому что как только вы добавите функцию в публичный API, пользователи начнут на неё полагаться, и вам придётся её поддерживать. Если она плохо спроектирована, вы можете столкнуться с проблемами. Конечно, можно попробовать её пометить как deprecated — но, скорее всего, она останется навсегда.
Комментарий от Михаила Поливахи
В разработке программных продуктов уже долгие существует закон, который популяризовал Google в своей книжке Software Engineering in Google. Закон этот
Имея достаточное количество пользователей вашего API, это совершенно не важно, что вы обещаете конечным пользователям в контракте, а что — нет. Все наблюдаемые side-эффекты Вашего API будут кем-либо проэксплуатированы.
Вот тут про него можно почитать детальнее: https://www.hyrumslaw.com/
Как это всё связано с final-классами? Всё просто: не-final класс можно расширять! Любые его public или protected методы можно переопределить, а protected поля — читать и изменять. Более того, вы не можете отменить это решение — то есть, если класс однажды стал публичным и не-final, то таким он и останется. В то же время, если использовать final как значение по умолчанию, то это решение можно изменить — то есть, класс всегда можно «открыть», но нельзя будет «закрыть» обратно.
Это наблюдение — вовсе не открытие! Джош Блох в своей (тоже отличной) книге Effective Java утверждает: «старайтесь делать каждый класс или член максимально недоступным». Например, всегда следует отдавать предпочтение private перед protected полями. Однако, по какой-то причине, он не распространяет этот принцип на выбор между final и не-final классами.
Тем не менее, похоже, что в мире есть единомышленники. И, конечно же, есть те, кто придерживается иного мнения…
Комментарий от Ильи Сазонова
Статья написана в далёком 2011 году. С тех пор много поменялось. На вопрос, надо ли писать final везде, где только можно, индустрия дала однозначный ответ - надо!
Оксиморон неизменяемая переменная прочно вошёл в нашу жизнь.
У нас появились records, в Kotlin уже давно есть val. И даже в Java этот самый val доступен через Lombok.
В Rust вообще нужно специально писать mut, для того, чтобы появилась возможность вписать в переменную новое значение.
Но это что касается переменных. Что касается классов, то, хотя просто так сделать их по умолчанию final нельзя, в Java появились sealed classes, которые делают примерно то, о чём рассуждает автор статьи.
Многие этого не заметили, но уже сейчас разработчикам часто приходится доказывать обратную позицию, что если это улучшает читаемость кода, то можно делать переменные изменяемыми, а классы открывать для наследования.
Будущее наступило!
Комментарий от Евгения Сулейманова
Идея final-классов по умолчанию здравая, но в «проде» она сразу сталкивается с экосистемой прокси и ленивых прокладок.
Минимум 2 примера:
1. AOP/прокси в Spring. CGLIB не любит final классы/методы.
2. JPA/Hibernate (ленивая загрузка). Прокси-сабклассы не сгенерятся поверх final.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
 
           
 
karrakoliko
не монимаю откуда эта шизофрения пошла, но делать нужно с точностью до наоборот.
надо дизайнить так, чтобы оставлять возможность расширения и наследования.
если вы пишете на ооп языке, и ваш дизайн ограничивает типовые механизмы ооп по умолчанию, а не в каких-то исключительных случаях - вам требуется переобучение.
"а что если кто то отнаследуется и все сломает?" - это не причина связывать руки тем, кто не сломает.
мы хотим переиспользовать так много кода, как можем.
это какой то всратый коммунизм с уравниловкой по низу (я не смог отнаследовать не сломав, и мой сосед не смог - значит все вокруг не могут и мы для всх это запретим ради общего блага). я сам, черт возьми, беру на себя риск, переиспользуя твой код, и сам несу за это ответственность.
и что теперь, целый форк заводить чтобы твой чертов final убрать?
ну и не включайте, причем здесь ограничение наследования? то шутка какая-то, эти вещи вообще не связаны
приходит очередной тимлид и начинается заново возня final vs не final, protected vs private, господи, как же достало