Комментарии 23

Люто ненавижу тех, кто использует финальные классы в публичных библиотеках-фреймворках.


Потому что чистота кода и прочее абстрактное бла-бла-бла это хорошо, но в жизни все бывает несколько сложнее. И часто бывает так, что представление автора какой-нибудь библиотеки о том, как ее должны использовать, не совпадает с тем, как ее нужно использовать тебе. Да, можно стотыщ раз произнести мантры про "нельзя давать доступ к внутренней реализации, ведь она может измениться без предупреждений и все сломать". Но на практике, когда у тебя стотыщ разгневанных клиентов, а нужный для их удовлетворения кусок функционала завязан на протектед метод/поле, то единственный выход тут — не трындеть о чистоте кода, а отнаследоваться от нужного класса и сделать публичный геттер для этого метода (антипаттерн "Паблик Морозов"). И тут — нна, получи по лицу, гад! — класс оказывается final.


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


И нет, вариант с "давайте обратимся к разработчику и он вынесет это в публичный метод" не работает. Разработчика может уже вообще не быть (проект заброшен), он может быть не согласен с открытием этой функциональности, или просто может очень долго реагировать на запросы. А у меня дедлайн вот прям завтра, и мне прям смерть как нужен именно этот кусок функционала в паблике.


В общем я лично не вижу никаких плюсов в финальных классах в реальном мире (а не в мире розовых пони и идеального кода), одни проблемы от них.

1. Всегда можно сделать форк нужной либы и в нем подфиксить самому, если автора нет\долго реагирует
2. Всегда можно заюзать рефлексию
3. не завязывайте свой код на чужие библиотеки, используйте адаптеры и будет вам счастье

С форками не хочется возиться, так как теряешь возможность обновлений и приходится этот форк поддерживать.


Ну вот рефлексию юзать и приходится. Там где она есть и доступна.

так как теряешь возможность обновлений
Патчи в помощь. Для данного конкретного случая (удаление final) они идеально подходят. В файлах меняется только одна строка, поэтому шанс получить Гит конфликт при обновлении зависимостей минимален.
1. Не все либы распространяются в виде исходников. Не все либы openSource. При этом ошибка внутри библиотеки которая могла бы решиться overrideом одного метода, и передачей внутрь своей версии класса которая например использует точно время с тайм-сервера вместо системного, превращается в обмазывание костылями, которые нужно будет еще оторвать когда либу починят.
2. Особенно можно в языках, где её нет или она в зачаточном уровне (кажется тут нужно передавать пример swift)
3. А лучше вообще пишите весь код сами. Особенно когда это код взаимодействия с amazon s3 например. Ну или в случае, если вы не можете решить проблему из-за того, что кто-то решил что её не может существовать, сразу меняйте инфраструктуру.

Полностью согласен JediPhilosopher — final в теории должен решать проблему неправильного написания кода неопытным разработчиком — но его строгая принудительность может создать куда больше проблем в итоге.

Куда разумнее было-бы — если бы final давал предупреждение — псс, ты что-то делаешь не так, подумай еще разок! А уж если ты ставишь supress(«я полностью осознаяю последствия») не создавал более проблем.

И тут скорее кажется нужно использовать договоренности об именовании а не совершенно негибкие языковые конструкции.
кусок функционала завязан на протектед метод/поле

Или приватном, тогда вообще туши свет.
А посмотрите-ка на эту ситуацию глазами автора библиотеки:
Вы решаете слегка отрефакторить код, не ломая обратную совместимость, и тут вам пишет разработчик, у которого ваши изменения в коде библиотеки ломают иерархию чего-то там — рефакторинг невозможен.
Вот и стелят соломку, чтобы по-максимому оградить себя от таких ситуаций, а не только из любви к идеальному коду.
Я думаю тут все просто, залез внутрь реализации — сам себе злобный буратино. Каждый кто это делает должен это понимать. И даже если он этого не понимает — это не проблема автора библиотеки.
Класс без final это точка расширения, которую мантейнер обязан поддерживать как минимум до следующей мажорной версии проекта (если он следует семвер). По сути поддержка сводится к полному отказу от рефакторинга таких классов. Нельзя добавлять/удалять методы и нельзя изменять сигнатуры существующих протектед методов и свойств. Иначе прилетят гневные багрепорты и жалобы на не соблюдение BC политики.
Люто ненавижу тех, кто использует финальные классы в публичных библиотеках-фреймворках.

Ну вот, собственно, эталонный пример, почему в публичных библиотеках/фрейморках предпочитают финалить. Человек пользуется, на халяву, результатами чужих трудов и люто ненавидит авторов за то, что они сделали не так, как удобно лично ему. Мне страшно представить, насколько сильнее будет эта ненависть, если в следующем релизе случайно поломается обратная совместимость к случайно не зафиналенному классу… Оно им надо, потоки вот такого выслушивать?

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

Потому что многие технологии, особенно основанные на AOP требуют, чтобы класс был открытым. Так работает большинство фреймворков и многие библиотеки для тестинга и мокирования. В Котлине последовали советам Фаулера и сделали все классы по дефолту final. Однако это очень сильно помешало тулзам из экосистемы Java. Пришлось придумывать костыли типа allopen.


Поощрение подхода «композиция вместо наследования»

Очень немногие ЯП реально поощряют данный подход. Если в языке нет extension methods или delegates, то любая композиция связана с кучей boilerplate-кода ввиде декораторов и самописных делегатов.

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

Это все хорошо, до тех пор пока вы не пишете библиотеки. А вот там уже final использовать надо максимально аккуратно или вообще не использовать.

Это все хорошо, до тех пор пока вы не поддерживаете обратную совместимость, вот там уже нефинал, надо максимально аккуратно и вообще не использовать.


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

Для этого есть правильная версионность, когда мажорные билды ломают совместимость а минорные поддерживают.


Главное ломать так, чтобы пользователь либы не смог собрать проект, тогда не страшно — ломайте. Это лучше, чем как выше уже писали, когда человек подлезет к вам через рефлексию и у него потом все сломается но он об этом не узнает, или когда он вообще не сможет исправить свою проблему, и вы будете делать превосходную библиотеку, которой никто пользоваться не сможет (а те у кого проблем не возникнет, им плевать на то final там или нет, они и не полезут внутрь).

Для этого есть правильная версионность, когда мажорные билды ломают совместимость а минорные поддерживают.

Это никак не меняет суть противоречия, просто вводит дополнительную индикацию в номере версии


Это лучше, чем как выше уже писали, когда человек подлезет к вам через рефлексию

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


когда он вообще не сможет исправить свою проблему, и вы будете делать превосходную библиотеку, которой никто пользоваться не сможет

Конечно лучше. Чем никто. А если кто-то может то может и не лучше?

А ещё в том, когда и как это делать правильно.

Не нашёл ответа на вопрос в статье. А вот ожидаемый холивар в комментах нашёл.

Композиция вместо наследования — это круто, но очень абстрактно. Я сам пишу на Python, но у нас такая же каша получается, поэтому вывел для себя несколько правил:
* избегать внуков и внучек в клиентском коде
* переопределять только те методы, которые рассчитаны на это
* ко всем остальным методам относиться как к финальным
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Информация

Дата основания
Местоположение
Кипр
Сайт
fun.co
Численность
51–100 человек
Дата регистрации

Блог на Хабре