В какой момент YAGNI должен иметь преимущество перед хорошими методами кодирования и наоборот? Я работаю над проектом на работе и хочу постепенно внедрить хорошие стандарты кода для моих коллег (в настоящее время их нет, и все просто взломано без всякой рифмы или причины), но после создания серии классов (мы не делайте TDD, или, к сожалению, никакого вида юнит-тестирования) Я сделал шаг назад и подумал, что это нарушает YAGNI, потому что я точно знаю, что нам не нужно расширять некоторые из этих классов.
Вот конкретный пример того, что я имею в виду: у меня есть слой доступа к данным, обертывающий набор хранимых процедур, который использует элементарный шаблон в стиле репозитория с основными функциями CRUD. Поскольку существует несколько методов, которые нужны всем моим классам репозитория, я создал общий интерфейс для своих репозиториев, который называется IRepository
. Однако затем я создал «маркерный» интерфейс (т. Е. Интерфейс, который не добавляет никакой новой функциональности) для каждого типа репозитория (например ICustomerRepository
), и конкретный класс реализует это. Я сделал то же самое с реализацией Factory для построения бизнес-объектов из DataReaders / DataSets, возвращаемых хранимой процедурой; подпись моего класса репозитория имеет тенденцию выглядеть примерно так:
public class CustomerRepository : ICustomerRepository
{
ICustomerFactory factory = null;
public CustomerRepository() : this(new CustomerFactory() { }
public CustomerRepository(ICustomerFactory factory) {
this.factory = factory;
}
public Customer Find(int customerID)
{
// data access stuff here
return factory.Build(ds.Tables[0].Rows[0]);
}
}
Меня беспокоит то, что я нарушаю YAGNI, потому что я знаю с 99% уверенностью, что никогда не будет причины давать что-то, кроме бетона, CustomerFactory
этому хранилищу; так как у нас нет модульных тестов, мне не нужны MockCustomerFactory
или подобные вещи, а наличие такого большого количества интерфейсов может запутать моих коллег. С другой стороны, использование конкретной реализации фабрики кажется дизайнерским запахом.
Есть ли хороший способ прийти к компромиссу между правильным дизайном программного обеспечения и не чрезмерной архитектурой решения? Я задаюсь вопросом, нужно ли мне иметь все «единственные интерфейсы реализации» или я мог бы пожертвовать небольшим количеством хорошего дизайна и просто иметь, например, базовый интерфейс и затем один конкретный, и не беспокоиться о программировании для интерфейс, если реализация будет когда-либо использоваться.
источник
Ответы:
YAGNI.
Ложное предположение.
Это не «жертва». То есть хороший дизайн.
источник
В большинстве случаев отказ от кода, который вам не нужен, приводит к лучшему дизайну. Наиболее поддерживаемым и ориентированным на будущее проектом является тот, который использует наименьшее количество хорошо названного, простого кода, который удовлетворяет требованиям.
Самые простые проекты - самые легкие для развития. Ничто так не убивает ремонтопригодность, как бесполезные, сверхпрочные слои абстракции.
источник
YAGNI и SOLID (или любая другая методология проектирования) не являются взаимоисключающими. Однако они являются приполярными противоположностями. Вы не должны придерживаться 100% либо, но будут некоторые компромиссы; чем больше вы смотрите на шаблон с высокой абстракцией, используемый одним классом в одном месте, говорите YAGNI и упрощаете его, тем меньше становится твёрдым дизайн. Обратное также может быть правдой; много раз в разработке, дизайн реализован ТВЕРДЫМ «по вере»; Вы не видите, как вам это понадобится, но у вас есть предчувствие. Это может быть правдой (и все больше и больше правдоподобно, если вы приобретете больше опыта), но это также может привести к тому, что у вас будет столько же технического долга, сколько и пощечины «сделай это легко»; вместо DIL-кода "спагетти-код" вы можете получить "код лазаньи", Имея так много слоев, что простое добавление метода или нового поля данных превращается в многодневный процесс перехода через служебные прокси-серверы и слабосвязанные зависимости только с одной реализацией. Или вы можете получить «код равиоли», который состоит из таких маленьких кусочков, что при перемещении вверх, вниз, влево или вправо в архитектуре вы пройдете через 50 методов по 3 строки в каждом.
Я сказал это в других ответах, но вот оно: На первом проходе заставь это работать. На втором проходе сделайте это элегантно. На третьем проходе сделайте его твердым.
Разбивая это:
Когда вы впервые пишете строку кода, он просто должен работать. На данный момент, насколько вы знаете, это одноразовый. Таким образом, вы не получаете никаких стилевых баллов за построение «башни из слоновой кости», чтобы добавить 2 и 2. Делайте то, что вам нужно, и предполагайте, что вы никогда больше этого не увидите.
В следующий раз, когда ваш курсор войдет в эту строку кода, вы опровергли свою гипотезу с того момента, когда впервые ее написали. Вы пересматриваете этот код, вероятно, либо для его расширения, либо для использования в другом месте, поэтому он не является одноразовым. Теперь должны быть реализованы некоторые базовые принципы, такие как DRY (не повторяйся) и другие простые правила для разработки кода; извлекать методы и / или циклы формы для повторяющегося кода, извлекать переменные для общих литералов или выражений, возможно, добавлять комментарии, но в целом ваш код должен самодокументироваться. Теперь ваш код хорошо организован, хотя, возможно, все еще тесно связан, и любой, кто смотрит на него, может легко узнать, что вы делаете, читая код, вместо того, чтобы отслеживать его построчно.
В третий раз, когда ваш курсор вводит этот код, это, вероятно, очень важно; вы либо расширяете его еще раз, либо он становится полезным по крайней мере в трех других местах кодовой базы. На данный момент это ключевой, если не основной, элемент вашей системы, и он должен быть спроектирован как таковой. На этом этапе у вас обычно также есть знания о том, как он использовался до сих пор, что позволит вам принимать правильные проектные решения относительно того, как проектировать архитектуру, чтобы упростить эти способы использования и любые новые. Теперь правила SOLID должны войти в уравнение; извлекать классы, содержащие код с конкретными целями, определять общие интерфейсы для любых классов, которые имеют сходные цели или функциональные возможности, устанавливать слабосвязанные зависимости между классами и проектировать зависимости таким образом, чтобы их можно было легко добавлять, удалять или заменять.
С этого момента, если вам нужно будет расширить, переопределить или повторно использовать этот код, он все хорошо упакован и абстрагирован в формате «черного ящика», который мы все знаем и любим; подключите его везде, где вам это нужно, или добавьте новый вариант темы в качестве новой реализации интерфейса без необходимости изменения использования указанного интерфейса.
источник
Вместо любого из них я предпочитаю WTSTWCDTUAWCROT?
(Что самое простое, что мы можем сделать, это полезно, и мы можем выпустить в четверг?)
В моем списке вещей есть простые сокращения, но они не являются приоритетными.
источник
YAGNI и хороший дизайн не противоречат друг другу. YAGNI о (не) поддержке будущих потребностей. Хороший дизайн - это прозрачность того, что ваша программа делает прямо сейчас, и как она это делает.
Сделает ли введение фабрики ваш существующий код проще? Если нет, не добавляйте его. Если это так, например, когда вы добавляете тесты (что вы должны делать!), Добавьте его.
YAGNI - это не сложность для поддержки будущих функций.
Хороший дизайн - это устранение сложности при поддержке всех текущих функций.
источник
Они не в конфликте, ваши цели неверны.
Что вы пытаетесь достичь?
Вы хотите писать качественное программное обеспечение, и для этого вы хотите, чтобы ваша база кода была небольшой и не имела проблем.
Теперь мы достигли конфликта, как мы покрываем все случаи, если мы не пишем случаи, которые мы не собираемся использовать?
Вот как выглядит ваша проблема.
(кому интересно, это называется испаряющимися облаками )
Итак, что движет этим?
Какой из них мы можем решить? Что ж, похоже, не нужно тратить время, а раздувание кода - это великая цель, и она имеет смысл. Как насчет этого первого? Можем ли мы выяснить, что нам нужно для кодирования?
Давайте перефразируем все это
Вам не нужен компромисс, вам нужен кто-то, кто руководит командой, кто компетентен и имеет видение всего проекта. Вам нужен кто-то, кто может спланировать то, что вам нужно, вместо того, чтобы каждый из вас бросал вещи, которые вам НЕ нужны, потому что вы так неуверенны в будущем, потому что ... почему? Я скажу вам почему, потому что ни у кого нет ни одного чертового плана среди всех вас. Вы пытаетесь ввести стандарты кода, чтобы решить совершенно отдельную проблему. Ваша основная проблема, которую вам нужно решить, - это четкая дорожная карта и проект. Получив это, вы можете сказать: «Стандарты кода помогают нам более эффективно достигать этой цели как команда», что является абсолютной истиной, но выходит за рамки этого вопроса.
Найдите менеджера проекта / команды, который может сделать это. Если у вас есть, вам нужно попросить у них карту и объяснить проблему YAGNI, которую представляет отсутствие карты. Если они крайне некомпетентны, напишите план самостоятельно и скажите: «Вот мой доклад для вас о том, что нам нужно, просмотрите его и сообщите нам ваше решение».
источник
Учитывая ваш код , который будет расширено с модульными тестами никогда не будет подпадать под YAGNI, потому что вы будете в ней нуждаются. Однако я не уверен, что ваши изменения в дизайне интерфейса с одной реализацией фактически повышают тестируемость кода, потому что CustomerFactory уже наследуется от интерфейса и в любом случае может быть заменен на MockCustomerFactory в любое время.
источник
Вопрос ставит ложную дилемму. Правильное применение принципа ЯГНИ не является чем-то несвязанным. Это один из аспектов хорошего дизайна. Каждый из принципов SOLID также является аспектом хорошего дизайна. Вы не можете всегда полностью применять каждый принцип в любой дисциплине. Реальные проблемы накладывают много сил на ваш код, и некоторые из них выдвигают противоположные направления. Принципы дизайна должны учитывать все это, но ни один из принципов не подходит для всех ситуаций.
Теперь давайте посмотрим на каждый принцип с пониманием того, что, хотя они могут иногда тянуться в разных направлениях, они никоим образом не конфликтуют друг с другом.
YAGNI был задуман для того, чтобы помочь разработчикам избежать какой-то особой переделки: то, что происходит от создания неправильной вещи. Это достигается за счет того, что мы избегаем принимать ошибочные решения слишком рано, основываясь на предположениях или прогнозах относительно того, что, по нашему мнению, изменится или потребуется в будущем. Коллективный опыт говорит нам, что когда мы делаем это, мы обычно ошибаемся. Например, YAGNI бы сказать вам , чтобы не создавать интерфейс с целью повторного использования , если вы не знаете сейчас , что вам нужно несколько исполнителей. Точно так же YAGNI бы сказать , не создают «ScreenManager» для управления единой формы в приложении , если вы не знаете , прямо сейчас , что вы будете иметь более одного экрана.
Вопреки тому, что думают многие, SOLID - это не возможность повторного использования, универсальность или даже абстракция. SOLID предназначен для того, чтобы помочь вам написать код, подготовленный к изменениям , не говоря о том, что это за конкретное изменение. Пять принципов SOLID создают стратегию для построения кода, который является гибким, но не слишком универсальным, и простым, но не наивным. Правильное применение кода SOLID создает небольшие, сфокусированные классы с четко определенными ролями и границами. Практический результат состоит в том, что для любого необходимого изменения требований, необходимо затронуть минимальное количество классов. И точно так же, для любого изменения кода, существует минимальное количество «пульсаций» для других классов.
Рассматривая пример ситуации, которую вы имеете, давайте посмотрим, что скажут YAGNI и SOLID. Вы рассматриваете общий интерфейс хранилища из-за того, что все хранилища выглядят одинаково снаружи. Но ценность общего, универсального интерфейса - это возможность использовать любой из реализаторов, не зная, какой именно он является. Если в вашем приложении нет места, где это было бы необходимо или полезно, YAGNI говорит, что не делайте этого.
Есть 5 твердых принципов, чтобы смотреть на. S является единственной ответственностью. Это ничего не говорит об интерфейсе, но может сказать что-то о ваших конкретных классах. Можно утверждать, что обработку самого доступа к данным лучше всего возложить на один или несколько других классов, в то время как ответственность репозиториев заключается в переводе из неявного контекста (CustomerRepository неявно является хранилищем для сущностей Customer) в явные обращения к Обобщенный API доступа к данным с указанием типа объекта Customer.
О является открытым-закрытым. Это в основном о наследовании. Это применимо, если вы пытаетесь извлечь свои репозитории из общей базы, реализующей общие функциональные возможности, или если вы ожидаете получить дополнительную информацию из разных репозиториев. Но вы не, так что это не так.
L является лисковской заменяемостью. Это применимо, если вы намеревались использовать репозитории через общий интерфейс репозитория. Он накладывает ограничения на интерфейс и реализации, чтобы обеспечить согласованность и избежать специальной обработки для различных имплементаторов. Причина этого заключается в том, что такая специальная обработка подрывает назначение интерфейса. Возможно, будет полезно рассмотреть этот принцип, поскольку он может предостеречь вас от использования общего интерфейса репозитория. Это совпадает с руководством YAGNI.
Я является интерфейсом сегрегации. Это может применяться, если вы начинаете добавлять различные операции запроса в свои репозитории. Разделение интерфейса применяется в тех случаях, когда вы можете разделить члены класса на два подмножества, где одно будет использоваться определенными потребителями, а другое другими, но ни один потребитель не будет использовать оба подмножества. Руководство состоит в том, чтобы создать два отдельных интерфейса, а не один общий. В вашем случае маловероятно, что выборка и сохранение отдельных экземпляров будут использоваться одним и тем же кодом, который будет выполнять общие запросы, поэтому может быть полезно разделить их на два интерфейса.
D - инъекция зависимостей. Здесь мы возвращаемся к той же точке, что и S. Если вы разделили использование API доступа к данным на отдельный объект, этот принцип говорит, что вместо того, чтобы просто обновлять экземпляр этого объекта, вы должны передать его при создании хранилище. Это облегчает управление временем жизни компонента доступа к данным, открывая возможность делиться ссылками на него между вашими репозиториями, не прибегая к созданию единого пакета.
Важно отметить, что большинство принципов SOLID не обязательно применяются на данном конкретном этапе разработки вашего приложения. Например, следует ли вам прервать доступ к данным, зависит от того, насколько это сложно, и хотите ли вы проверить свою логику хранилища, не обращаясь к базе данных. Похоже, это маловероятно (к сожалению, на мой взгляд), поэтому, вероятно, в этом нет необходимости.
Таким образом, после всего этого мы находим, что YAGNI и SOLID действительно предоставляют один общий надежный совет, имеющий непосредственное отношение: вероятно, нет необходимости создавать общий универсальный интерфейс хранилища.
Вся эта осторожная мысль чрезвычайно полезна в качестве учебного упражнения. Как вы учитесь, это отнимает много времени, но со временем вы развиваете интуицию и становитесь очень быстрыми. Вы будете знать, что нужно делать, но вам не нужно думать обо всех этих словах, если кто-то не попросит вас объяснить, почему.
источник
Кажется, вы верите, что «хороший дизайн» означает следование некоторой идеологии и формальному набору правил, которые всегда должны применяться, даже когда они бесполезны.
ИМО это плохой дизайн. YAGNI - это составляющая хорошего дизайна, никогда не противоречащая ему.
источник
В вашем примере я бы сказал, что YAGNI должен победить. Это не будет стоить вам так много, если вам нужно добавить интерфейсы позже. Кстати, действительно ли это хороший дизайн - иметь один интерфейс на класс, если он вообще не служит цели?
Еще одна мысль, может быть, иногда вам нужен не хороший дизайн, а достаточный дизайн. Вот очень интересная последовательность постов по теме:
источник
Некоторые люди утверждают, что имена интерфейсов не должны начинаться с I. Конкретно, одна из причин заключается в том, что вы фактически теряете зависимость от того, является ли данный тип классом или интерфейсом.
Что запрещает вам
CustomerFactory
сначала быть классом, а потом превращать его в интерфейс, который будет реализованDefaultCustormerFactory
илиUberMegaHappyCustomerPowerFactory3000
? Единственное, что вам нужно изменить, это место, где реализация получает экземпляры. И если у вас более менее хороший дизайн, то это всего несколько мест.Рефакторинг является частью разработки. Лучше иметь небольшой код, который легко реорганизовать, чем иметь интерфейс и класс, объявленный для каждого отдельного класса, что заставляет вас менять каждое имя метода как минимум в двух местах одновременно.
Реальная цель использования интерфейсов - достижение модульности, что, возможно, является наиболее важной основой хорошего дизайна. Однако обратите внимание, что модуль определяется не только его отделением от внешнего мира (хотя мы так его воспринимаем с внешней точки зрения), но в равной степени своей внутренней работой.
Я хочу сказать, что разъединение вещей, которые по своей природе связаны друг с другом, не имеет особого смысла. В некотором смысле это похоже на наличие шкафа с отдельной полкой на чашку.
Важность заключается в том, чтобы разбить большую сложную проблему на более мелкие и простые подзадачи. И вы должны остановиться на том месте, где они станут достаточно простыми без дополнительных подразделений, иначе они на самом деле станут более сложными. Это можно рассматривать как следствие ЯГНИ. И это определенно означает хороший дизайн.
Цель не состоит в том, чтобы каким-то образом решить локальную проблему с помощью одного хранилища и одной фабрики. Цель состоит в том, чтобы это решение не повлияло на остальную часть вашего заявления. Вот что такое модульность.
Вы хотите, чтобы ваши коллеги посмотрели на ваш модуль, увидели фасад с горсткой не требующих объяснений звонков и почувствовали себя уверенными, что они могут их использовать, не беспокоясь о всех потенциально сложных внутренних водопроводах.
источник
Вы создаете
interface
многократные реализации в будущем. С таким же успехом вы можете иметьI<class>
для каждого класса в вашей кодовой базе. Не.Просто используйте один конкретный класс в соответствии с YAGNI. Если вы обнаружите, что вам нужен «фиктивный» объект для целей тестирования, превратите исходный класс в абстрактный класс с двумя реализациями, одна с исходным конкретным классом, а другая с фиктивной реализацией.
Вам, очевидно, придется обновить все экземпляры исходного класса, чтобы создать новый конкретный класс. Вы можете обойти это, используя статический конструктор.
YAGNI говорит не писать код, прежде чем он будет написан.
Хороший дизайн говорит об использовании абстракции.
Вы можете иметь оба. Класс - это абстракция.
источник
Почему маркер взаимодействует? Меня поражает, что он делает только пометки. С каким "тегом" для каждого типа фабрики, какой смысл?
Цель интерфейса состоит в том, чтобы дать классу «действия как» поведение, чтобы дать им, так сказать, «способность хранилища». Таким образом, если все ваши конкретные типы репозитория ведут себя как один и тот же IRepository (все они реализуют IRepository), то все они могут обрабатываться одинаково другим кодом - точно таким же кодом. На данный момент ваш дизайн является расширяемым. Добавление более конкретных типов репозитория, все они обрабатываются как универсальные IRepository (s) - тот же код обрабатывает все конкретные типы как «универсальные» репозитории.
Интерфейсы предназначены для управления вещами на основе общности. Но пользовательские интерфейсы маркеров а) не добавляют поведения. и б) заставить вас справиться с их уникальностью.
В той степени, в которой вы разрабатываете полезные интерфейсы, вы получаете выгоду от необходимости писать специализированный код для обработки специализированных классов, типов или пользовательских интерфейсов маркеров, которые имеют соотношение 1: 1 к конкретным классам. Это бессмысленная избыточность.
Я могу видеть интерфейс маркера, если, например, вам нужна строго типизированная коллекция множества разных классов. В коллекции все они "ImarkerInterface", но когда вы вытаскиваете их, вам приходится приводить их к соответствующим типам.
источник
Можете ли вы прямо сейчас записать неопределенно разумный репозиторий ICustomer? Например, (действительно показательно, вероятно, плохой пример), в прошлом ваши клиенты всегда использовали PayPal? Или все в компании гудят в связи с Alibaba? Если это так, вы можете использовать более сложный дизайн сейчас и показаться дальновидным своим боссам. :-)
В противном случае, подождите. Гадание на интерфейсе до того, как у вас есть одна или две фактические реализации, обычно терпит неудачу. Другими словами, не обобщайте / абстрагируйте / не используйте причудливый шаблон проектирования, пока у вас не будет пары примеров для обобщения.
источник