У меня проблемы с тем, что я считаю слишком большой абстракцией в базе кода (или, по крайней мере, имею дело с ней). Большинство методов в кодовой базе были абстрагированы для получения самого высокого родителя A в кодовой базе, но у дочернего элемента B этого родителя есть новый атрибут, который влияет на логику некоторых из этих методов. Проблема в том, что эти атрибуты не могут быть проверены в этих методах, потому что входные данные абстрагированы от A, и, конечно, A не имеет этого атрибута. Если я пытаюсь создать новый метод для другой обработки B, он вызывается для дублирования кода. Предложение моего технического лидера состоит в том, чтобы создать общий метод, который принимает логические параметры, но проблема в том, что некоторые люди рассматривают это как «скрытый поток управления», где общий метод имеет логику, которая может быть не очевидна для будущих разработчиков , а также этот совместно используемый метод будет чрезмерно усложняться / извиваться один раз, если будущие атрибуты необходимо будет добавить, даже если он будет разбит на более мелкие совместно используемые методы. Это также увеличивает связь, снижает сплоченность и нарушает принцип единой ответственности, на который указал кто-то из моей команды.
По сути, большая часть абстракции в этой кодовой базе помогает уменьшить дублирование кода, но усложняет методы расширения / изменения, когда они созданы для получения максимальной абстракции. Что мне делать в такой ситуации? Я нахожусь в центре обвинений, хотя все остальные не могут договориться о том, что они считают хорошим, поэтому в конце концов мне больно.
Ответы:
Не все дубликаты кода созданы равными.
Допустим, у вас есть метод, который принимает два параметра и складывает их вместе
total()
. Скажем, у вас есть еще один называетсяadd()
. Их реализации выглядят полностью идентичными. Должны ли они быть объединены в один метод? НЕТ !!!Принцип « Не повторяйся сам» или « СУХОЙ» - это не повторение кода. Речь идет о распространении решения, идеи, так что, если вы когда-нибудь измените свою идею, вам придется переписывать везде, где вы распространяете эту идею. Blegh. Это ужасно. Не делай этого. Вместо этого используйте DRY, чтобы помочь вам принимать решения в одном месте .
Но DRY может быть испорчен в привычку сканировать код в поисках аналогичной реализации, которая кажется копией и вставкой где-то еще. Это мертвая форма мозга СУХОГО. Черт, вы можете сделать это с помощью инструмента статического анализа. Это не помогает, потому что игнорирует пункт СУХОЙ, который заключается в сохранении гибкости кода.
Если мои общие требования изменятся, мне, возможно, придется изменить мою
total
реализацию. Это не значит, что мне нужно изменить моюadd
реализацию. Если какой-нибудь придурок свел их вместе в один метод, то теперь мне нужна небольшая ненужная боль.Сколько боли? Конечно, я мог бы просто скопировать код и создать новый метод, когда мне это нужно. Так что нет ничего страшного, верно? Malarky! Если ничего другого, ты стоишь мне доброго имени! Хорошие имена трудно найти и плохо реагируют, когда вы играете с их значением. Хорошие имена, которые ясно дают понять намерения, важнее, чем риск того, что вы скопировали ошибку, которую, честно говоря, легче исправить, если ваш метод имеет правильное имя.
Поэтому мой совет - не допускать, чтобы коленные реакции на подобный код связывали вашу кодовую базу в узлы. Я не говорю, что вы можете игнорировать тот факт, что методы существуют, и вместо этого копировать и вставлять волей невольно. Нет, у каждого метода должно быть чертовски хорошее имя, которое поддерживает одну идею, о которой он говорит. Если его реализация совпадает с реализацией какой-то другой хорошей идеи, прямо сейчас, сегодня, кого это волнует?
С другой стороны, если у вас есть
sum()
метод, который имеет идентичную или даже отличную от реализации реализациюtotal()
, тем не менее, когда бы ни менялись ваши общие требования, вам придется менятьsum()
тогда есть большая вероятность, что это одна и та же идея под двумя разными именами. Мало того, что код будет более гибким, если они будут объединены, он будет менее запутанным в использовании.Что касается булевых параметров, да, это неприятный запах кода. Проблема не только в том, что поток управления создает проблему, но еще хуже в том, что вы вырезали абстракцию в плохой точке. Предполагается, что абстракции делают вещи проще, а не сложнее. Передача bools методу для управления его поведением подобна созданию секретного языка, который решает, какой метод вы действительно вызываете. Оу! Не делай этого со мной. Дайте каждому методу собственное имя, если только у вас нет честного полиморфизма .
Теперь вы, кажется, перегорели на абстракции. Это очень плохо, потому что абстракция - замечательная вещь, когда все сделано хорошо. Вы используете это много, не думая об этом. Каждый раз, когда вы водите автомобиль, не разбираясь в системе реечной передачи, каждый раз, когда вы используете команду печати, не думая о прерываниях ОС, и каждый раз, когда вы чистите зубы, не думая о каждой отдельной щетине.
Нет, проблема, с которой вы, похоже, столкнулись - плохая абстракция. Абстракция создана для того, чтобы служить другой цели, чем ваши потребности. Вам нужны простые интерфейсы в сложные объекты, которые позволяют вам требовать удовлетворения ваших потребностей, даже не разбираясь в этих объектах.
Когда вы пишете код клиента, который использует другой объект, вы знаете, что вам нужно и что вам нужно от этого объекта. Это не так. Вот почему клиентский код владеет интерфейсом. Когда вы клиент, ничто не может сказать вам, что вам нужно, кроме вас. Вы создаете интерфейс, который показывает ваши потребности и требует, чтобы все, что вам было вручено, отвечало этим потребностям.
Это абстракция. Как клиент, я даже не знаю, с чем говорю. Я просто знаю, что мне нужно от этого. Если это означает, что вы должны завернуть что-то, чтобы изменить его интерфейс, прежде чем передать его мне нормально. Мне все равно Просто делай то, что мне нужно. Хватит усложнять.
Если мне нужно заглянуть внутрь абстракции, чтобы понять, как ее использовать, абстракция потерпела неудачу. Мне не нужно знать, как это работает. Просто это работает. Дайте ему хорошее имя, и если я загляну внутрь, меня не удивит то, что я нахожу. Не заставляй меня продолжать смотреть внутрь, чтобы вспомнить, как его использовать.
Когда вы настаиваете, что абстракция работает таким образом, количество уровней за ней не имеет значения. Пока вы не смотрите за абстракцией. Вы настаиваете, что абстракция соответствует вашим потребностям, а не адаптируется к ней. Чтобы это работало, оно должно быть простым в использовании, иметь хорошее имя и не быть утечкой .
Это отношение породило инъекцию зависимостей (или просто отсылку, если вы, как я, в старой школе). Это хорошо работает с предпочтительным составом и делегированием по наследованию . Отношение идет под многими именами. Мой любимый - скажи, не спрашивай .
Я мог бы утопить тебя в принципах весь день. И, похоже, твои коллеги уже есть. Но вот в чем дело: в отличие от других областей техники этому программному обеспечению менее 100 лет. Мы все еще выясняем это. Так что не позволяйте кому-то с большим количеством устрашающего звучания книжного обучения запугивать вас писать трудный для чтения код. Слушайте их, но настаивайте, чтобы они имели смысл. Не принимай ничего на веру. Люди, которые каким-то образом пишут код только потому, что им сказали, что это так, даже не зная, зачем создавать самые большие беспорядки из всех.
источник
Обычное высказывание, которое мы все читаем здесь и там:
Ну, это не правда! Ваш пример показывает это. Поэтому я бы предложил слегка измененное утверждение (не стесняйтесь использовать повторно ;-)):
В вашем случае есть две разные проблемы:
Оба связаны:
Shape
может рассчитать егоsurface()
специализированным способом.Если вы абстрагируете какую-то операцию, где есть общий общий поведенческий паттерн, у вас есть два варианта:
Кроме того, этот подход может привести к эффекту абстрактной связи на уровне проекта. Каждый раз, когда вы хотите добавить какое-то новое специализированное поведение, вам придется абстрагировать его, изменить абстрактный родительский элемент и обновить все другие классы. Это не тот тип распространения изменений, который можно пожелать. И это не совсем в духе абстракций вне зависимости от специализации (по крайней мере, в дизайне).
Я не знаю ваш дизайн и не могу помочь больше. Возможно, это действительно очень сложная и абстрактная проблема, и лучшего пути нет. Но каковы шансы? Симптомы чрезмерной генерализации здесь. Может быть, пришло время снова взглянуть на это и рассмотреть композицию вместо обобщения ?
источник
Всякий раз, когда я вижу метод, в котором поведение включает тип его параметра, я сразу же задумываюсь, действительно ли этот метод принадлежит параметру метода. Например, вместо того, чтобы иметь такой метод:
Я бы сделал это:
Мы перемещаем поведение в место, которое знает, когда его использовать. Мы создаем реальную абстракцию, где вам не нужно знать типы или детали реализации. В вашей ситуации, возможно, имеет смысл переместить этот метод из исходного класса (который я буду называть
O
) для вводаA
и переопределения его в типеB
. Если метод вызываетсяdoIt
на какой - либо объект, перейтиdoIt
кA
и переопределение с различным поведением вB
. Если есть биты данных, откудаdoIt
он первоначально вызывается, или если метод используется в достаточном количестве мест, вы можете оставить исходный метод и делегировать:Мы можем погрузиться немного глубже, хотя. Давайте посмотрим на предложение использовать вместо этого логический параметр и посмотрим, что мы можем узнать о том, как думает ваш коллега. Его предложение сделать:
Это очень похоже на то, что
instanceof
я использовал в моем первом примере, за исключением того, что мы извлекаем эту проверку. Это означает, что мы должны назвать его одним из двух способов:или:
Во-первых, точка вызова не знает, какой у
A
нее тип . Следовательно, должны ли мы передавать логические значения полностью вниз? Это действительно шаблон, который мы хотим по всей базе кода? Что произойдет, если есть третий тип, который мы должны учитывать? Если это то, как метод вызывается, мы должны переместить его в тип и позволить системе полиморфно выбирать для нас реализацию.Во-вторых, мы уже должны знать тип
a
в точке вызова. Обычно это означает, что мы либо создаем экземпляр там, либо принимаем экземпляр этого типа в качестве параметра. Создание метода,O
который принимаетB
здесь будет работать. Компилятор будет знать, какой метод выбрать. Когда мы проводим такие изменения, дублирование лучше, чем создание неправильной абстракции , по крайней мере до тех пор, пока мы не выясним, куда мы действительно идем. Конечно, я предполагаю, что мы на самом деле не закончили, что бы мы ни изменили к этому моменту.Нам нужно более внимательно посмотреть на отношения между
A
иB
. Как правило, нам говорят, что мы должны отдавать предпочтение композиции, а не наследованию . Это не так в каждом случае, но это правда в удивительном количестве случаев, когда мы копаем.B
НаследуетA
, что означает, что мы считаем,B
что этоA
.B
следует использовать так же, какA
, за исключением того, что он работает немного по-другому. Но каковы эти различия? Можем ли мы дать различиям более конкретное название? Разве это неB
естьA
, но на самом делеA
есть,X
что может бытьA'
илиB'
? Как бы выглядел наш код, если бы мы это сделали?Если бы мы переместили метод
A
в соответствии с предложенным ранее, мы могли бы внедрить экземплярX
вA
и делегировать этот методX
:Мы можем реализовать
A'
иB'
, и избавиться отB
. Мы улучшили код, дав имя концепции, которая могла бы быть более неявной, и позволили себе устанавливать это поведение во время выполнения, а не во время компиляции.A
фактически стал менее абстрактным. Вместо расширенного отношения наследования он вызывает методы для делегированного объекта. Этот объект является абстрактным, но больше ориентирован только на различия в реализации.Есть еще одна вещь, на которую стоит обратить внимание. Давайте вернемся к предложению вашего коллеги. Если на всех сайтах вызовов мы явно знаем, какой
A
у нас тип , то мы должны совершать такие вызовы:Мы предполагали ранее, когда сочиняем, что
A
имеетX
либо,A'
либоB'
. Но, может быть, даже это предположение неверно. Это единственное место, где эта разница междуA
иB
имеет значение? Если это так, то, возможно, мы можем использовать немного другой подход. У нас все еще есть,X
что естьA'
илиB'
, но оно не принадлежитA
. ТолькоO.doIt
заботится об этом, так что давайте только передать этоO.doIt
:Теперь наш сайт звонков выглядит так:
Еще раз
B
исчезает, и абстракция переходит в более сфокусированнуюX
. На этот раз, однако,A
еще проще, зная меньше. Это еще менее абстрактно.Важно уменьшить дублирование в базе кода, но мы должны рассмотреть, почему дублирование происходит в первую очередь. Дублирование может быть признаком более глубоких абстракций, которые пытаются выбраться.
источник
Абстракция по наследству может стать довольно уродливой. Параллельные иерархии классов с типичными фабриками. Рефакторинг может стать головной болью. А также последующее развитие, место, где вы находитесь.
Существует альтернатива: точки расширения , строгих абстракций и многоуровневой настройки. Скажите одну настройку государственных заказчиков, основанную на этой настройке для конкретного города.
Предупреждение: к сожалению, это работает лучше всего, когда все (или большинство) классов сделаны на продлен. Нет варианта для вас, может быть, в маленьком.
Эта расширяемость работает благодаря тому, что базовый класс расширяемого объекта содержит расширения:
Внутренне существует ленивое отображение объекта на расширенные объекты по классу расширения.
Для классов и компонентов GUI одинаковая расширяемость, частично с наследованием. Добавление кнопок и тому подобное.
В вашем случае проверка должна посмотреть, расширена ли она, и проверить себя на соответствие расширениям. Введение точек расширения только для одного случая добавляет непонятный код, не очень хорошо.
Таким образом, нет никакого решения, кроме попытки работать в текущем контексте.
источник
«скрытое управление потоком» звучит слишком громоздко для меня.
Любая конструкция или элемент, вырванный из контекста, могут иметь эту характеристику.
Абстракции хороши. Я ограничиваю их двумя принципами:
Лучше не абстрагироваться слишком рано. Подождите, пока больше примеров шаблонов, прежде чем абстрагироваться. «Больше», конечно, субъективно и специфично для сложной ситуации.
Избегайте слишком большого количества уровней абстракции только потому, что абстракция это хорошо. Программист должен будет держать эти уровни в своей голове для нового или измененного кода, поскольку они устанавливают кодовую базу и углубляются на 12 уровней. Желание хорошо абстрагированного кода может привести к такому количеству уровней, что многим людям будет трудно им следовать. Это также приводит к тому, что кодовые базы поддерживаются только ниндзя.
В обоих случаях «больше» и «слишком много» не являются фиксированными числами. Это зависит. Вот что затрудняет.
Мне также нравится это рецензия от Санди Мец
https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
дублирование намного дешевле, чем неправильная абстракция,
и
предпочитайте дублирование неправильной абстракции
источник