Final Or Not Final? That Is The Questuion.

Вурдалак

Продвинутый новичок
Final не мокается, иногда надо.
Это признак какой-то проблемы: либо ты должен мокать интерфейс, либо в случае всяких value object'ов типа DateTime ты должен создавать инстанс класса напрямую.

http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Есть правда некоторые возможные проблемы с Doctrine, например, но это частности.
 

Ulen

Новичок
Спасибо за примеры! Буду читать книги и наверно погляжу внутренности каких нибудь фреймворков.
 

fixxxer

К.О.
Партнер клуба
Это признак какой-то проблемы: либо ты должен мокать интерфейс, либо в случае всяких value object'ов типа DateTime ты должен создавать инстанс класса напрямую.
В юнит-тестах - разумеется. Я ж говорю - иногда. :) Бывает, скажем, надо по-быстрому написать тест на баг, прям по репродюс кейсу.

Не то чтобы прям аргумент против final, так, между прочим.
 

Вурдалак

Продвинутый новичок
Не то чтобы прям аргумент против final, так, между прочим.
Да просто я уже по 100 раз слышу одни и те же аргументы: мокать нельзя, якобы нарушает OCP, нельзя перекрыть баг во внешней библиотеке и т.д. У меня уже есть запасённые контраргументы. :)
 

Adelf

Administrator
Команда форума
Нельзя делать final! Мокать нельзя, Нарушает OCP, нельзя перекрыть баг во внешней библиотеке! Жду контраргументов :)
 

Вурдалак

Продвинутый новичок
Не нужно.

Не нарушает.

нельзя перекрыть баг во внешней библиотеке
Копируй весь класс с багом, фикси и используй его, как реализацию. :)
 

whirlwind

TDD infected, paranoid
Копируй весь класс с багом, фикси и используй его, как реализацию. :)
Я правильно понял, ты предлагаешь создать новую физическую копию класса вместе со всеми его выявленными и невыявленными недостатками и называешь это более корректным подходом чем расширение на базе наследования?
 

Absinthe

жожо
Как и было - процедуры, обернутые в классы.

Методом тыка тут не научишься, читай литературу*.

* Посоветуйте ему кто-нибудь, я не знаю подходящих книг, легких для "вхождения" в тему
А я бы наоборот посоветовал с практики заниматься. Только не методом тыка, а методом работы с хорошим кодом.
Литературу хорошую не видел, видел только капитанские книги, которые могут объяснить только тому, кто понимает, а непонимающему знаний не добавят.
 

Вурдалак

Продвинутый новичок
называешь это более корректным подходом
Нет. И то, и другое — это вынужденный хак до фикса на стороне vendor'а. Мой пойнт в том, что отказываться от final с мотивацией «чтобы можно было перекрыть баг, если он есть» не стоит того, т.е. и без наследования есть аж 2 варианта:
  1. копирование (подразумевается, естественно, в свой неймспейс, там где адаптер к либе лежит);
  2. декорирование (если доступа к коду нет или он большой по объёму, но не всегда может получится).
 

Вурдалак

Продвинутый новичок
Понятно. Просто я бы не стал так категорично заявлять насчет final. Как минимум часто protected фабричный метод.
Точка расширения через интерфейс фабрики в конструкторе будет куда лучше.

Работая в закрытом проекте, я бы вполне возможно не стал бы делать protected-метод вообще: всегда можно улучшить API класса, добавив эту точку расширения, если такое вдруг потребуется, т.к. это наш локальный код.

А если я бы писал OSS, то скорее всего не поленился бы сразу сделать интерфейс в конструкторе вместо protected-метода.

Да и потом, фабричных методов что-то я у себя не припоминаю, кроме как в виде named constructor. Всё в основном инжектится, а если не инжектится, то это и есть класс-фабрика.
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
Все это круто. Я надеюсь ты про бритву Оккама не забыл. Пока программы пишут люди, все эти тепломягкие споры упираютсяв некий предел. Предел когда идеальный код оправдан, а когда нет, с практической точки зрения в случае написания и последующего сопровождения человеками. При экстремальной степени декомпозиции (которая определенно возникнет при твоем подходе - это только вопрос сложности проекта) возникают совсем другие проблемы. По этому final, а особенно при работе в команде, его как бы на практике часто проще убрать, чем другими способами проблему решить.
 

fixxxer

К.О.
Партнер клуба
Я правильно понял, ты предлагаешь создать новую физическую копию класса вместе со всеми его выявленными и невыявленными недостатками и называешь это более корректным подходом чем расширение на базе наследования?
Физическая копия в виде форка репозитория - вполне корректный подход.
 

Вурдалак

Продвинутый новичок
По этому final, а особенно при работе в команде, его как бы на практике часто проще убрать, чем другими способами проблему решить.
Проще убрать final, чем добавить новый аргумент в конструктор или, на крайняк, сделать сеттер? Возможно в ближайшей перспективе это проще, но как и с прочими «простыми» решениями это может создать проблемы в будущем.
 

whirlwind

TDD infected, paranoid
Проще убрать final, чем добавить новый аргумент в конструктор или, на крайняк, сделать сеттер? Возможно в ближайшей перспективе это проще, но как и с прочими «простыми» решениями это может создать проблемы в будущем.
Куда добавить, какие простые решения и какие проблемы? Ты хочешь изменить поведение используя внутренний полиморфизм типа - путем подмены реализации одного метода, который используется внутри этого же класса. С файналом тебе всю реализацию заново переписывать придется, потенциально добавляя туда косяков и глюков. Файнал действительно нарушает OCP, как тут было подмечено.

Вот еще один пример где от final+декоратор больше гемора, чем пользы. Допустим, есть проект с активным event-driven общением подсистем. Вместе с событием передается инстанс отправителя, что бы потребители могли в обработчиках работать сразу с конкретным поставщиком. Ясен пень, что набор событий предусмотрен API, то есть некой базовой реализацией. Если делать специфику через декоратор, то получаем несоотвествие инстансов отправителя и поставщика. И это очень хреновая ситуация, потому что модульными тестами этот косяк выцепляется слабо.

PS. Вот, не истина в последней инстанции, но тем не менее из википедии
должны быть открыты для расширения, но закрыты для изменения»;[1] это означает, что такие сущности могут позволять менять свое поведение без изменения их исходного кода
Файнал как и статика убивает полиморфизм (в данном случае внутренний). Нет полиморфизма, нет возможности изменить поведение. Отсутствие этой возможности и есть закрытость класса для расширения. Никто же не говорит что в принципе изменения недоступны. Этот принцип относится именно к классу.
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Файнал действительно нарушает OCP, как тут было подмечено.
Это заблуждение. http://blog.8thlight.com/uncle-bob/2013/03/08/AnOpenAndClosedCase.html
It should be easy to change the behavior of a module without changing the source code of that module. This doesn't mean you will never change the source code, this doesn't mean you can stop using your version control systems (sheesh!). What it means is that you should strive to get your code into a position such that, when behavior changes in expected ways, you don't have to make sweeping changes to all the modules of the system. Ideally, you will be able to add the new behavior by adding new code, and changing little or no old code.
Тащемта, в каком-то смысле это «истина в последней инстанции».

Ты можешь изменить класс, если тебе требуется улучшить API, т.е. добавить нужную точку расширения. Смысл OCP в том, что ты не должен каждый раз менять класс, когда тебе потребуется что-то новое (дописывать туда и т.д.).

Наследование вообще является лишь частным случаем расширения и класс с нужными точками расширения никак не будет нарушать OCP.

OCP не [только] про наследование.
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
У меня складывается впечатление, что ты пытаешься объяснить нам об антипаттерне big ball of mud. Или может быть ты думаешь, что я имею в виду сохранность первоначальной версии? Это полное заблуждение. Я буду менять класс (именно его код) когда захочу и как захочу. И я буду делать это совершенно безопасно, потому что у меня есть модульные тесты. Все что нужно, что бы такая ситуация сохранялась, это прекратить думать, что ты сегодня знаешь лучше, что нужно будет завтра, писать kiss (бритва Оккама как минимум) и всегда оставлять возможность перекрывать любые методы (то есть всегда оставлять шанс для полиморфизма). Для криворуких молодых программистов, которые могут случайно не там написать, существует такая процедура как code review. OCP от кривого дизайна абсолютно никак не спасает.
 

Вурдалак

Продвинутый новичок
Всё что я хочу, указав final, — это сказать, что этот класс наследовать не нужно и стоит найти другой способ. Если тебе по какой-то причине нужно перекрыть конкретный метод, то возникает вопрос: почему именно этот метод, нельзя ли это выделить в отдельную сущность (стратегию и т.д.). Иными словами, в основном, по-хорошему, может быть либо final, либо abstract.

Кстати, снятие final можно отлеживать и автоматически отправлять на ревью, это может быть хорошим маркером проблемы. «extends» же отслеживать труднее, т.к. могут встречаться корректные случаи.
 
Сверху