вы можете автоматизировать поиск кандидатов на «завистливые» функции, но не финальное их определение
Смогу при таком условии (я снова повторяюсь, не пора ли плавно заканчивать спор?):
введения дополнительной не содержащейся в коде информации о предметной области
Эта дополнительная информация будет о важности определённых данных в определённых контекстах использования. Это довольно сложно и на практике вряд ли кто-то будет так делать, но теоретически — вполне осуществимо.
Не всякая функция, которая обрабатывает больше данных, чем ей доступно локально, является завистливой.
В данном случае функция была завистливой, давайте обойдёмся без софистики
Это плохой подход, потому что он нарушает SRP.
По вашему, SRP — это в «каждом классе максимум одна публичная функция»? Кстати, в земле нет непосредственно самого кода копания. Предлагаете всё-таки запихать код «на другую планету»?
Критерия, поддающегося автоматическому вычислению, ещё нет, но есть куча принципов проектирования, ведущих к простоте и понятности кода, критерии соответствия которым возможно автоматизировать с различным успехом и, возможно, с различной степенью необходимости введения дополнительной не содержащейся в коде информации о предметной области. Например, поиск завистливых функций довольно легко автоматизируется. Так что фраза «объективного критерия простоты все еще нет» — не оправдание для «всё неоднозначно, все принципы проектирования не объективны, что хочу то и творю». Если честно, не понимаю, для чего вы это сказали.
А теперь вы добавили круги (два параметра на объект). Будете дописывать логику в треугольники
как я говорил ранее (я снова повторяюсь, не пора ли плавно заканчивать спор?):
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы
Smell — это не обязательно сигнал к исправлению
Завистливая функция — это сигнал к исправлению, давайте обойдёмся без софистики
Еще полезно подумать о том, как вы будете реализовывать расчет пересечений для n фигур
Легко. Поскольку пересечение — тоже фигура (скорее всего какой нибудь скруглённый многоугольник), то пересечение 3х фигур находится так: фигура1.Пересечение(фигура2.Пересечение(фигура3)).
В каком из них должна быть функция расчета
Экскаватор копает землю. Вопрос: где будете искать код копки земли? Экскаватор копает землю или земля копается экскаватором? Если ответ не напрашивается исходя из предметной области, то один из хороших подходов — когда код найдётся и там и там. В экскаваторе непосредственно и в земле как Visitor: Земля.Копаться(IКопатель копатель){копатель.Копать(Я);}. Если код копания находится не в земле и не в экскаваторе, а «на другой планете» — это плохой подход, потому что код очень сложно найти. Тут всё аналогично, только вместо экскаватора и земли два разных типа фигур.
Дальше пограмист должен посмотреть на все оставшиеся классы (ну не на все, еще на пары другой было бы достаточно), что бы понять, что для каждой новой фигуры необходимо реализовать ее пересечение со всеми остальными
Программист обладает как минимум зачатками логики, и, зная что 1)есть треугольник, круг и трапеция, и 2)написав алгоритм расчёта пересечения треугольника и круга, догадается что расчёт пересечения треугольника и трапеции с неба не упадёт. А если не догадается, то посмотрит функцию вычисления пересечений и увидит что там помимо круга есть ещё и обработка остальных фигур (НеизвестнаяФигура.РассчитатьПересенияС(Я)). По-моему, проблема надуманная.
Можно сделать объявление функции поиска пересечений рядом с базовым классом для фигур принимающую в качестве параметров две ссылки(или указателя) на базовый класс.
Расположение кода в файлах служит второстепенным средством структурирование кода. Первостепенным (и более важным) является само устройство кода, т.е. расположение алгоритмов в методах и расположение данных и методов в классах.
в моем случае после этого нужно только добавить в список новый тип, а в вашем написать длинный блок if-else
Принцип KISS как раз таки и говорит, что «не нужно стрелять по воробьям из пушки». StaticDispatcher довольно сложен: он должен учитывать, что если трапеция является наследником многоугольника, то вызовется код расчёта именно трапеции (хотя фигура и является многоугольником), также он должен уметь переставлять местами аргументы функции (StaticDispatcher из книги это умеет). В конце концов, StaticDispatcher настолько сложен, что вы неправильно скопировали его куски из книги (он не компилируется по многим причинам), и никто этого не заметил. Кроме того, StaticDispatcher — протекающая абстракция, то есть чтобы его использовать, нужно знать его внутреннее устройство: при добавлении новой фигуры нужно изменять массив типов, так что игнорировать его сложность не удастся. Можно конечно написать инструкцию, но принципы проектирования направлены на создание понятного кода, а не кода, к которому требуются пояснения. В картинной галерее художник ведь не стоит возле картины с указкой и не говорит: «эта бесформенная клякса — собака, а эта непонятная загагулина — человек» (речь не идёт о всякого роде сюрреалистических картинах, где зритель получает удовольствие от осознания разгаданной загадки). Увидите вы кучу клякс, пожмёте плечами и пройдёте мимо. Только в случае с программой пройти мимо не получится… Сложность StaticDispatcher (пушки) может быть оправдана, только если блоков if-else будет очень много (гигантский воробей). Не думаю что их будет настолько много. Ну а если со временем вдруг количество фигур и увеличится, тогда можно будет и побороться с этой проблемой. Но в этом далеком будущем программа уже изменится, и лучшее решение в ней может отличаться от лучшего решения в настоящий момент. Кроме того, это будущее может так и не наступить.
Тут уже пошла тавтология. Я по этому поводу уже высказывался:
создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.
То есть вы повторили тоже самое что и я говорил, только другими словами.
вопрос: чем с точки зрения ООП это отличается от «правило расчета перечислений получило квадрат и круг, из каждого получило данные, построило пересечение»
В таком случае это процедурный подход, а не ООП. Здесь правило расчета перечислений — завистливая функция (Фаулер, «Рефакторинг»), это smell. Если функция использует больше данные постороннего класса, чем своего, то её нужно перенести в этот посторонний класс. Если использует данные нескольких классов — то в тот класс, из которого используется больше всего данных, или, как вариант, в тот класс, данные которого важнее в данном контексте использования.
Допустим есть класс человек, однажды у человека спросили: «мальчик, сколько тебе лет?». Тут то данные и утекли наружу класса. С тех пор мальчик сам решает, по детскому ему билету ехать или по взрослому. Кондуктора вскоре упразднили, потому что на вопрос «кто тут ещё не заплатил», все молчали, дабы не передавать никаких данных об объекте наружу объекта. :)
Вот ваши данные, нужные для расчета, и утекли наружу объекта.
Повторюсь, квадрат использовал свои данные для получения ответа. Не важно, что в момент поиска ответа он использовал какие то другие классы, помимо квадрата. Это не противоречит ООП. Если бы противоречило, то все программы состояли из одного единственного класса.
Давайте представим проект, в котором использовалось предложенное вами решение. Приходит на проект новый человек (ну или бывалый разработчик не открывал конкретно этот кусок кода с фигурами около полугода). Ему говорят: добавь-ка новую фигуру. Открывает он фигуру, и не видит никаких намёков на вычисление пересечения. Пересечение он не реализует. Юнит-теста не было написано, потому что, как вы говорите
можно взглядом оценить, что ничего не забыли
и
если есть необходимость писать юнит тест для проверки, что добавили все методы, то это уже не очень простой код
Так что через некоторое время выясняется что приложение иногда падает с ошибками при поиске пересечений… После этого программисту приходится изучать класс СтатическийДелец, который ничего общего не имеет ни с фигурами, ни с пересечениями, и задача решается.
Вариант развития событий 2. Понадобилось программисту вычислить пересечение 2х фигур. Зашёл он в класс фигуры и ничего не увидел. После этого он стал смотреть весь код, в которых используются фигуры. Пролистав много кода, нашёл он класс HatchingExecutor (Делатель штриховки). Однако в этом классе есть только алгоритмы пересечений для конкретных фигур, а не для абстрактной фигуры, поэтому программист написал свой switch-case для поиска пересечений 2х абстрактных фигур. Однако кто-то это заметил и сказал что вроде где-то видел механизм поиска пересечений. После этого он стал искать в проекте все места использования HatchingExecutor, благо было оно всего одно. Изучив StaticDispatcher, программист смог таки вызвать функцию поиска пересечений двух фигур. Много кода пролистано, несколько часов потрачено, нужная функция вызвана. Хэппи энд. Ну или может быть такой вариант: толстенная инструкция по особенностям проекта пролистана, 20 минут потрачено, нужная функция вызвана (но для такого финала нужно создать документацию и постоянно тратить силы на её поддержание).
Теперь рассмотрим ситуацию, если Фигура реализована так, как я предлагал:
Нужно добавить фигуру. Смотрим треугольник, делаем по аналогии, всё работает.
Нужно вызвать пересечение 2х фигур. Смотрим любую конкретную фигуру или абстрактную фигуру, вызываем нужную функцию, всё работает.
Критично, что у вас детали реализации (защита от бесконечной рекурсии) просочились в интерфейс.
Согласен, это проблема. Один из вариантов решения — не передавать значение через аргумент функции, а передавать через protected переменную. Однако вариант с тем чтобы просто ловить исключение по переполнению стека проще. Как пришёл домой, проверил — стек довольно быстро переполняется :) Тут вы были правы. Спасибо что помогли сделать простое решение ещё проще.
не критично, почему он кинулся, мне критично, что тест упал
то есть, по-вашему, добавление одной операции сравнения двух чисел — слишком большая цена, чтобы сделать тест более понятным и не адски тормозным, а также получить информацию почему тест упал? Призвание программиста — автоматизировать. Подход «принципиально не буду автоматизировать получение информации о причине падения теста, буду смотреть глазами» — сугубо не программисткий.
Во-первых, ООП — не единственная парадигма программирования.
И что с того??? Напомню, вы говорили терминами ООП: «Осталось вынести эти правила за пределы фигур».
А во-вторых, ваша задача изначально нарушает этот (ООП) подход: ваше действие производится над двумя объектами
То есть, по-вашему, ООП невозможно если какие-то операции производятся более чем над ним объектом?
Суть в том, что данные и код их использующий находятся в одной сущности. В примере квадрат не умеет сам рассчитывать пересечение с треугольником, и передаёт данные о себе треугольнику — квадрат использует свои данные для нахождения ответа. Делегирование ответственности и использование других классов для нахождение ответа — нормальное явление.
А в чем тогда смысл следовать KISS, если все проверки можно свалить на юнит тесты?
Наличие юнит-тестов не гарантируют что код простой и понятный (вы сами то поняли что спросили?).
Запуск юнит теста, только для проверки, что все варианты предусмотрели — дольше, чем просто пробежаться взглядом по десятку функций.
В реальных проектах считается хорошим тоном запускать все тесты перед отправкой кода в репозиторий. Особо ленивые не проверяют, т.к. тесты ещё и автоматически выполняются на сервере после отправки кода. При проверке глазами можно ошибиться, особенно если фигур много.
Если навигация по коду не удобна без средств предоставляемых IDE, стоит задуматься, а все ли хорошо сделано.
Можно и без помощи IDE найти два файлика. В любом случае просмотреть 2 маленьких файлика проще, чем один гигантский в 100 раз больше обоих. Ну если прям очень надо, можно сложить все алгоритмы нахождения пересечения в один класс, и вызывать его уже из фигур.
если код тяжело читается в простом текстовом редакторе без подсветки синтаксиса, то это плохой стиль кодирования
Вариант, который вы предлагаете, и с подсветкой, и без подсветки воспринимается тяжело.
а они и есть самое запутанное и опасное место, в котором можно допустить ошибки
вопрос был что в конкретно данном случае вам не нравится, а не почему в целом вам не нравятся проверки типов
Опять же расчет пресечения фигур не является неотемлемым свойством фигуры.
Это как посмотреть, всё зависит от конкретной ситуации. Я уже приводил пример, что вопрос, сама ли едет машина или её заставляют ехать законы физики, зависит от ситуации. Судя по постановке задачи, фигуры только то и нужны чтобы их пересечения искать, так что здесь расчет пресечения фигуры как раз и является основным и неотемлемым свойством фигуры. Опять таки, как я говорил, вынесение всех алгоритмов поиска пересечения в отдельный класс не противоречит моему решению, если использовать делегирование ответственности.
что для всех фигур предусмотрели расчет пресечения… поддерживать его сложнее
Почитайте повнимательнее, там юнит-тест это проверяет. К тому же не уверен что ваше решение это умеет проверять.
все это разбросанно по разным файлам и со временем может превратиться в кошмар
Код пересечения конкретной пары фигур легко находится. Почти во всех современных IDE есть горячие клавиши для быстрого перехода к нужному классу. Вам принципиально хранить все функции расчёта пересечений в одном файле? А если фигур миллион?
а это опять же проверки через динамическое приведение типов
Во-первых, что в конкретном данном случае вам в этом не нравится? Во-вторых, а в вашем примере динамических приведений как будто нету? Head* p2 = dynamic_cast<Head*>(&rhs) — а это тогда что?
Вы умеете предвидеть будущее и гарантировать что код фигур не изменится так, что исключение StackOverflow будет кидаться в тесте по другим причинам, помимо не написания алгоритма расчёта пересечения для некоторой пары фигур? И сколько у вас в проекте по времени тесты выполняются?
вынести эти правила за пределы фигур
Есть такой принцип, ООП называется, который говорит что неплохо бы данные и код который с ними работает в одной сущности хранить. Например есть класс машина, у машины есть функция ехать. Так вот, машина не сама едет, её законы физики заставляют ехать. Но создавать класс ЗаконыФизики только ради того чтобы координаты машины изменить — глупо. Точно также создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.
Это лишняя операция, которая никак не обусловлена собственно задачей.
Эта операция не лишняя, она нужна чтобы тест, проверяющий что вы указали правила расчёта пересечений для всех фигур упал, а не зациклился. Эта операция не нужна в релизной конфигурации. В принципе можно сделать тест и по-другому, например через рефлексию, тогда эта проверка будет не нужна. Но в С++ нет рефлексии.
У всех разные критерии «простоты».
Я в статье дал определение простоты, взятое из словаря Ушакова, даже ссылку дал. Прочитайте повнимательнее. Определение простоты в английском языке не сильно отличается.
Можно эту ситуацию решить в соответствии с возможностями языка. Проверку можно вынести в базовый класс — в Фигуру. Можно сделать проверку аспектом, и аспект джойнить на всех наследников фигуры. Если в языке для наследников наследуется и контракт, можно указать условие в контракте базового класса. Лично мне больше нравится вариант с вынесением проверки в базовый класс. Для того чтобы вызвать функцию РассчитатьПересечениеС никаких проверок делать не нужно. И ещё, НомерВызова лучше сделать параметром со значением по-умолчанию.
Этот ваш так называемый «простой» код с StaticDispatcher-ом просто ужасен. Чтобы рассчитать пересечение фигур нужно знать о классах HatchingExecutor и StaticDispatcher. При добавлении новой фигуры нужно модифицировать строку typedef StaticDispatcher<...>, а без знания StaticDispatcher-а до этого не догадаться. Проблема усугубляется тем, что в устройстве StaticDispatcher-а очень сложно разобраться, в том числе благодаря тому что имена переменных (p1, p2, lhs) не информативны. Простым это решение точно не назовёшь. Как бы я сделал: добавил бы в интерфейс Фигура виртуальную функцию РассчитатьПересечениеС(Фигруа ДругаяФигура). Пусть есть три фигуры: треугольник, квадрат и круг. В конкретную фигуру треугольник поместил бы код умеющий рассчитывать пересечение с квадратом и кругом, а если это не квадрат и круг то просим неизвестную фигуру саму рассчитать пересечение. Квадрат не умеет рассчитывать пересечение с треугольником, считает треугольник неизвестной фигурой и сразу просит у треугольника рассчитать пересечение. Добавил бы простенькую защиту от зацикливания изменив параметры в функции: РассчитатьПересечениеС(Фигруа ДругаяФигура, Число НомерВызова), если НомерВызова>2, кидаем исключение. Добавил бы юнит-тест проверяющий что все фигуры умеют пересекаться; в языках, поддерживающих рефлексию, в тесте бы искал всех наследников Фигуры через рефлексию, чтобы тест не нужно было менять при добавлении новой фигуры.
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы, чтобы найти код пересечения треугольника квадрата нужно посмотреть максимум 2 класса: квадрат и треугольник, а не устраивать расследование в стиле Шерлока Холмса. Этот код простой и интуитивно понятный, нет левых классов ДелательПересечений и СтатическийДелец.
Смогу при таком условии (я снова повторяюсь, не пора ли плавно заканчивать спор?):
Эта дополнительная информация будет о важности определённых данных в определённых контекстах использования. Это довольно сложно и на практике вряд ли кто-то будет так делать, но теоретически — вполне осуществимо.
В данном случае функция была завистливой, давайте обойдёмся без софистики
По вашему, SRP — это в «каждом классе максимум одна публичная функция»? Кстати, в земле нет непосредственно самого кода копания. Предлагаете всё-таки запихать код «на другую планету»?
Критерия, поддающегося автоматическому вычислению, ещё нет, но есть куча принципов проектирования, ведущих к простоте и понятности кода, критерии соответствия которым возможно автоматизировать с различным успехом и, возможно, с различной степенью необходимости введения дополнительной не содержащейся в коде информации о предметной области. Например, поиск завистливых функций довольно легко автоматизируется. Так что фраза «объективного критерия простоты все еще нет» — не оправдание для «всё неоднозначно, все принципы проектирования не объективны, что хочу то и творю». Если честно, не понимаю, для чего вы это сказали.
как я говорил ранее (я снова повторяюсь, не пора ли плавно заканчивать спор?):
Завистливая функция — это сигнал к исправлению, давайте обойдёмся без софистики
Легко. Поскольку пересечение — тоже фигура (скорее всего какой нибудь скруглённый многоугольник), то пересечение 3х фигур находится так: фигура1.Пересечение(фигура2.Пересечение(фигура3)).
Экскаватор копает землю. Вопрос: где будете искать код копки земли? Экскаватор копает землю или земля копается экскаватором? Если ответ не напрашивается исходя из предметной области, то один из хороших подходов — когда код найдётся и там и там. В экскаваторе непосредственно и в земле как Visitor: Земля.Копаться(IКопатель копатель){копатель.Копать(Я);}. Если код копания находится не в земле и не в экскаваторе, а «на другой планете» — это плохой подход, потому что код очень сложно найти. Тут всё аналогично, только вместо экскаватора и земли два разных типа фигур.
Довольно занимательная статья, которая поможет понять почему не стоит плодить сущности без необходимости.
Программист обладает как минимум зачатками логики, и, зная что 1)есть треугольник, круг и трапеция, и 2)написав алгоритм расчёта пересечения треугольника и круга, догадается что расчёт пересечения треугольника и трапеции с неба не упадёт. А если не догадается, то посмотрит функцию вычисления пересечений и увидит что там помимо круга есть ещё и обработка остальных фигур (НеизвестнаяФигура.РассчитатьПересенияС(Я)). По-моему, проблема надуманная.
Расположение кода в файлах служит второстепенным средством структурирование кода. Первостепенным (и более важным) является само устройство кода, т.е. расположение алгоритмов в методах и расположение данных и методов в классах.
Принцип KISS как раз таки и говорит, что «не нужно стрелять по воробьям из пушки». StaticDispatcher довольно сложен: он должен учитывать, что если трапеция является наследником многоугольника, то вызовется код расчёта именно трапеции (хотя фигура и является многоугольником), также он должен уметь переставлять местами аргументы функции (StaticDispatcher из книги это умеет). В конце концов, StaticDispatcher настолько сложен, что вы неправильно скопировали его куски из книги (он не компилируется по многим причинам), и никто этого не заметил. Кроме того, StaticDispatcher — протекающая абстракция, то есть чтобы его использовать, нужно знать его внутреннее устройство: при добавлении новой фигуры нужно изменять массив типов, так что игнорировать его сложность не удастся. Можно конечно написать инструкцию, но принципы проектирования направлены на создание понятного кода, а не кода, к которому требуются пояснения. В картинной галерее художник ведь не стоит возле картины с указкой и не говорит: «эта бесформенная клякса — собака, а эта непонятная загагулина — человек» (речь не идёт о всякого роде сюрреалистических картинах, где зритель получает удовольствие от осознания разгаданной загадки). Увидите вы кучу клякс, пожмёте плечами и пройдёте мимо. Только в случае с программой пройти мимо не получится… Сложность StaticDispatcher (пушки) может быть оправдана, только если блоков if-else будет очень много (гигантский воробей). Не думаю что их будет настолько много. Ну а если со временем вдруг количество фигур и увеличится, тогда можно будет и побороться с этой проблемой. Но в этом далеком будущем программа уже изменится, и лучшее решение в ней может отличаться от лучшего решения в настоящий момент. Кроме того, это будущее может так и не наступить.
То есть вы повторили тоже самое что и я говорил, только другими словами.
В таком случае это процедурный подход, а не ООП. Здесь правило расчета перечислений — завистливая функция (Фаулер, «Рефакторинг»), это smell. Если функция использует больше данные постороннего класса, чем своего, то её нужно перенести в этот посторонний класс. Если использует данные нескольких классов — то в тот класс, из которого используется больше всего данных, или, как вариант, в тот класс, данные которого важнее в данном контексте использования.
Повторюсь, квадрат использовал свои данные для получения ответа. Не важно, что в момент поиска ответа он использовал какие то другие классы, помимо квадрата. Это не противоречит ООП. Если бы противоречило, то все программы состояли из одного единственного класса.
Вариант развития событий 2. Понадобилось программисту вычислить пересечение 2х фигур. Зашёл он в класс фигуры и ничего не увидел. После этого он стал смотреть весь код, в которых используются фигуры. Пролистав много кода, нашёл он класс HatchingExecutor (Делатель штриховки). Однако в этом классе есть только алгоритмы пересечений для конкретных фигур, а не для абстрактной фигуры, поэтому программист написал свой switch-case для поиска пересечений 2х абстрактных фигур. Однако кто-то это заметил и сказал что вроде где-то видел механизм поиска пересечений. После этого он стал искать в проекте все места использования HatchingExecutor, благо было оно всего одно. Изучив StaticDispatcher, программист смог таки вызвать функцию поиска пересечений двух фигур. Много кода пролистано, несколько часов потрачено, нужная функция вызвана. Хэппи энд. Ну или может быть такой вариант: толстенная инструкция по особенностям проекта пролистана, 20 минут потрачено, нужная функция вызвана (но для такого финала нужно создать документацию и постоянно тратить силы на её поддержание).
Теперь рассмотрим ситуацию, если Фигура реализована так, как я предлагал:
Нужно добавить фигуру. Смотрим треугольник, делаем по аналогии, всё работает.
Нужно вызвать пересечение 2х фигур. Смотрим любую конкретную фигуру или абстрактную фигуру, вызываем нужную функцию, всё работает.
Согласен, это проблема. Один из вариантов решения — не передавать значение через аргумент функции, а передавать через protected переменную. Однако вариант с тем чтобы просто ловить исключение по переполнению стека проще. Как пришёл домой, проверил — стек довольно быстро переполняется :) Тут вы были правы. Спасибо что помогли сделать простое решение ещё проще.
Самого себя
то есть, по-вашему, добавление одной операции сравнения двух чисел — слишком большая цена, чтобы сделать тест более понятным и не адски тормозным, а также получить информацию почему тест упал? Призвание программиста — автоматизировать. Подход «принципиально не буду автоматизировать получение информации о причине падения теста, буду смотреть глазами» — сугубо не программисткий.
И что с того??? Напомню, вы говорили терминами ООП: «Осталось вынести эти правила за пределы фигур».
То есть, по-вашему, ООП невозможно если какие-то операции производятся более чем над ним объектом?
Суть в том, что данные и код их использующий находятся в одной сущности. В примере квадрат не умеет сам рассчитывать пересечение с треугольником, и передаёт данные о себе треугольнику — квадрат использует свои данные для нахождения ответа. Делегирование ответственности и использование других классов для нахождение ответа — нормальное явление.
Наличие юнит-тестов не гарантируют что код простой и понятный (вы сами то поняли что спросили?).
В реальных проектах считается хорошим тоном запускать все тесты перед отправкой кода в репозиторий. Особо ленивые не проверяют, т.к. тесты ещё и автоматически выполняются на сервере после отправки кода. При проверке глазами можно ошибиться, особенно если фигур много.
Можно и без помощи IDE найти два файлика. В любом случае просмотреть 2 маленьких файлика проще, чем один гигантский в 100 раз больше обоих. Ну если прям очень надо, можно сложить все алгоритмы нахождения пересечения в один класс, и вызывать его уже из фигур.
Вариант, который вы предлагаете, и с подсветкой, и без подсветки воспринимается тяжело.
вопрос был что в конкретно данном случае вам не нравится, а не почему в целом вам не нравятся проверки типов
Это как посмотреть, всё зависит от конкретной ситуации. Я уже приводил пример, что вопрос, сама ли едет машина или её заставляют ехать законы физики, зависит от ситуации. Судя по постановке задачи, фигуры только то и нужны чтобы их пересечения искать, так что здесь расчет пресечения фигуры как раз и является основным и неотемлемым свойством фигуры. Опять таки, как я говорил, вынесение всех алгоритмов поиска пересечения в отдельный класс не противоречит моему решению, если использовать делегирование ответственности.
Почитайте повнимательнее, там юнит-тест это проверяет. К тому же не уверен что ваше решение это умеет проверять.
Код пересечения конкретной пары фигур легко находится. Почти во всех современных IDE есть горячие клавиши для быстрого перехода к нужному классу. Вам принципиально хранить все функции расчёта пересечений в одном файле? А если фигур миллион?
Во-первых, что в конкретном данном случае вам в этом не нравится? Во-вторых, а в вашем примере динамических приведений как будто нету? Head* p2 = dynamic_cast<Head*>(&rhs) — а это тогда что?
Вы умеете предвидеть будущее и гарантировать что код фигур не изменится так, что исключение StackOverflow будет кидаться в тесте по другим причинам, помимо не написания алгоритма расчёта пересечения для некоторой пары фигур? И сколько у вас в проекте по времени тесты выполняются?
Есть такой принцип, ООП называется, который говорит что неплохо бы данные и код который с ними работает в одной сущности хранить. Например есть класс машина, у машины есть функция ехать. Так вот, машина не сама едет, её законы физики заставляют ехать. Но создавать класс ЗаконыФизики только ради того чтобы координаты машины изменить — глупо. Точно также создавать класс Пространство только ради того чтобы пересечение двух фигур посчитать — глупо. Для этого веские причины нужны, не стоит плодить лишние сущности. Вот если алгоритмы расчёта пересечений очень сложные, или если фигура очень много чего умеет помимо как пересекаться — тогда пожалуйста. Но даже тогда неплохо сделать чтобы фигура делегировала обращение расчёта пересечения пространству.Чаще удобнее машину просить поехать, а не законы физики или пространство.
Эта операция не лишняя, она нужна чтобы тест, проверяющий что вы указали правила расчёта пересечений для всех фигур упал, а не зациклился. Эта операция не нужна в релизной конфигурации. В принципе можно сделать тест и по-другому, например через рефлексию, тогда эта проверка будет не нужна. Но в С++ нет рефлексии.
Я в статье дал определение простоты, взятое из словаря Ушакова, даже ссылку дал. Прочитайте повнимательнее. Определение простоты в английском языке не сильно отличается.
Преимущество решения: при добавлении новой фигуры не нужно менять ранее написанные классы, чтобы найти код пересечения треугольника квадрата нужно посмотреть максимум 2 класса: квадрат и треугольник, а не устраивать расследование в стиле Шерлока Холмса. Этот код простой и интуитивно понятный, нет левых классов ДелательПересечений и СтатическийДелец.