ТВЕРДЫЕ против ЯГНИ
Один из самых частых аргументов, которые я слышу за то, что не придерживался принципов SOLID в объектно-ориентированном проектировании, - это YAGNI (хотя аргумент часто так не называет):
"Это нормально, что я поместил и функцию X, и функцию Y в один и тот же класс. Это так просто, зачем добавлять новый класс (т.е. сложность)".
"Да, я могу поместить всю свою бизнес-логику непосредственно в код GUI, это намного проще и быстрее. Это всегда будет единственный GUI, и очень маловероятно, что когда-нибудь появятся новые важные требования".
"Если в маловероятном случае появления новых требований мой код станет слишком загроможденным, я все равно смогу выполнить рефакторинг для нового требования. Поэтому ваш аргумент" Что делать, если вам позже понадобится… "не считается".
Каковы были бы ваши самые убедительные аргументы против такой практики? Как я могу действительно показать, что это дорогостоящая практика, особенно для тех, кто не имеет большого опыта в разработке программного обеспечения.
10 ответов
Дизайн - это управление и баланс компромиссов. YAGNI и SOLID не противоречат друг другу: первый говорит, когда добавлять функции, второй говорит как, но оба они управляют процессом проектирования. В моих ответах ниже на каждую из ваших конкретных цитат используются принципы как YAGNI, так и SOLID.
- Компоненты многократного использования сложнее, чем одноразовые.
- Повторно используемый компонент должен быть опробован в трех различных приложениях, прежде чем он станет достаточно общим для использования в библиотеке повторного использования.
- " Правила трех Роберта Гласса: факты и ошибки в разработке программного обеспечения"
Рефакторинг в повторно используемые компоненты имеет ключевой элемент: сначала найти одну и ту же цель в нескольких местах, а затем переместить ее. В этом контексте YAGNI применяет, вставляя эту цель, где это необходимо, не беспокоясь о возможном дублировании, вместо добавления общих или повторно используемых функций (классов и функций).
В первоначальном проекте лучший способ показать, когда YAGNI не применяется, - это определить конкретные требования. Другими словами, сделайте рефакторинг перед написанием кода, чтобы показать, что дублирование не просто возможно, но уже существует: это оправдывает дополнительные усилия.
Да, я могу поместить всю свою бизнес-логику прямо в код GUI, это намного проще и быстрее. Это всегда будет единственный графический интерфейс, и маловероятно, что когда-нибудь появятся новые важные требования.
Это действительно единственный пользовательский интерфейс? Планируется ли фоновый пакетный режим? Будет ли когда-нибудь веб-интерфейс?
Каков ваш план тестирования, и будете ли вы тестировать бэкэнд-функциональность без графического интерфейса? Что облегчит вам тестирование графического интерфейса, поскольку вы обычно не хотите тестировать внешний код (например, элементы управления графического интерфейса платформы) и вместо этого концентрироваться на своем проекте.
Это нормально, что я поместил функцию X и функцию Y в один класс. Это так просто, зачем добавлять новый класс (т.е. сложность).
Можете ли вы указать на распространенную ошибку, которую следует избегать? Некоторые вещи достаточно просты, такие как квадрат числа (x * x
против squared(x)
) для слишком простого примера, но если вы можете указать на конкретную ошибку, сделанную кем-то, особенно в вашем проекте или в вашей команде, - вы можете показать, как общий класс или функция будут избегать этого в будущем.
Если в маловероятном случае новых требований мой код станет слишком загроможденным, я все равно смогу выполнить рефакторинг для нового требования. Таким образом, ваш аргумент "Что если вам позже понадобится..." не считается.
Проблема здесь в предположении "маловероятно". Вы согласны, что это маловероятно? Если это так, вы согласны с этим человеком. Если нет, то ваша идея дизайна не совпадает с идеей этого человека - решение этой проблемы поможет решить проблему или, по крайней мере, покажет вам, куда идти дальше.:)
Мне нравится думать о YAGNI в терминах "наполовину, а не наполовину", чтобы заимствовать фразу из 37signals ( https://gettingreal.37signals.com/ch05_Half_Not_Half_Assed.php). Речь идет об ограничении ваших возможностей, чтобы вы могли сосредоточиться на том, чтобы хорошо делать самые важные вещи. Это не повод, чтобы стать небрежным.
Бизнес-логика в GUI кажется мне недооцененной. Если ваша система не является тривиальной, я был бы удивлен, если бы ваша бизнес-логика и графический интерфейс не изменились независимо, несколько раз. Поэтому вы должны следовать SRP ("S" в SOLID) и рефакторинга - YAGNI не применяется, потому что он вам уже нужен.
Аргумент о YAGNI и ненужной сложности абсолютно применим, если вы сегодня выполняете дополнительную работу, чтобы учесть гипотетические будущие требования. Когда эти сценарии "что, если позже нам нужно..." не осуществятся, вы застрянете с более высокими затратами на обслуживание из-за абстракций, которые теперь мешают тем изменениям, которые у вас действительно есть. В данном случае мы говорим об упрощении дизайна путем ограничения области действия - делания наполовину, а не наполовину.
Похоже, вы спорите с кирпичной стеной. Я большой поклонник YAGNI, но в то же время я также ожидаю, что мой код всегда будет использоваться по крайней мере в двух местах: в приложении и в тестах. Вот почему такие вещи, как бизнес-логика в коде пользовательского интерфейса, не работают; Вы не можете тестировать бизнес-логику отдельно от кода пользовательского интерфейса при таких обстоятельствах.
Однако из ответов, которые вы описываете, кажется, что человек просто не заинтересован в том, чтобы делать лучшую работу. На этом этапе никакой принцип не поможет им; они хотят сделать только минимально возможное. Я бы даже сказал, что это не ЯГНИ, управляющий их действиями, а лень, и вы одни не победите лень (почти ничего не может, кроме угрожающего менеджера или потери работы).
Ответа нет, или, скорее, нет ответа, который может понравиться ни вам, ни вашему собеседнику: и YAGNI, и SOLID могут быть неправильными подходами.
Попытка перейти на SOLID с неопытной командой или командой с жесткими целями доставки в значительной степени гарантирует, что вы в конечном итоге получите дорогой, чрезмерно спроектированный набор кода... который НЕ будет SOLID, просто чрезмерно спроектирован (иначе в реальном мире).
Попытка перейти на YAGNI для долгосрочного проекта и надеяться, что вы сможете провести рефакторинг позже, работает только до определенной степени (также приветствуется в реальном мире). YAGNI преуспевает в проверке концепций и демонстрации, получая рынок / контракт, а затем сможет инвестировать в нечто более твердое.
Вам нужно и то, и другое в разные моменты времени.
Принципы SOLID позволяют программному обеспечению адаптироваться к изменениям - как в требованиях, так и в технических изменениях (новые компоненты и т. Д.), Два из ваших аргументов в пользу неизменных требований:
- "Маловероятно, что когда-нибудь появятся новые важные требования".
- "Если в маловероятном случае возникнут новые требования"
Может ли это быть правдой?
Ничто не заменит опыта, когда речь идет о различных затратах на разработку. Для многих практикующих, я думаю, что делать что-то паршиво, сложно поддерживать, никогда не приводило к проблемам для них (эй! Безопасность работы). В долгосрочной перспективе я думаю, что эти расходы станут ясными, но что-то делать с ними раньше времени - чужая работа.
Здесь есть и другие отличные ответы.
Правильное применение этих принципов часто не очень очевидно и во многом зависит от опыта. Что трудно получить, если вы сами этого не сделали. Каждый программист должен был иметь опыт последствий неправильной работы, но, конечно, это всегда должен быть "не мой" проект.
Объясните им, в чем проблема, если они не слушают, а вы не в состоянии заставить их слушать, пусть они делают ошибки. Если вам слишком часто приходится решать проблему, вы должны отшлифовать свое резюме.
Юнит-тесты качества, и я имею в виду юнит-тесты, а не интеграционные тесты, нуждаются в коде, который соответствует SOLID. Не обязательно на 100%, на самом деле редко так, но в вашем примере объединение двух функций в один класс усложнит модульное тестирование, нарушит принцип единой ответственности и значительно усложнит обслуживание кода новичками команды (так как его будет сложнее понять).,
С помощью модульных тестов (при условии хорошего покрытия кода) вы сможете безопасно и надежно рефакторировать функцию 1, вы не нарушите функцию 2, но без модульных тестов и с функциями того же класса (просто быть ленивым в вашем примере) рефакторинг в лучшем случае рискован, в лучшем случае губителен.
Итог: следуйте принципу KIS (сделайте это простым), или для интеллектуала принцип KISS (kis глупо). Возьмите каждый случай по заслугам, глобального ответа нет, но всегда учитывайте, нужно ли другим кодерам читать / поддерживать код в будущем, а также преимущества модульных тестов в каждом сценарии.
По моему опыту, это всегда суждение. Да, вам не нужно беспокоиться о каждой мелочи вашей реализации, и иногда вставление метода в существующий класс является приемлемым, хотя и некрасивым решением.
Это правда, что вы можете рефакторинг позже. Важным моментом является на самом деле сделать рефакторинг. Поэтому я считаю, что настоящая проблема не в случайном компромиссе проекта, а в том, чтобы отложить рефакторинг, когда станет ясно, что есть проблема. На самом деле, пройти через это трудная часть (как и во многих вещах в жизни...).
Что касается ваших индивидуальных очков:
Это нормально, что я поместил функцию X и функцию Y в один класс. Это так просто, зачем добавлять новый класс (т.е. сложность).
Я хотел бы отметить, что иметь все в одном классе сложнее (потому что отношения между методами более близки и сложнее для понимания). Наличие множества маленьких классов не сложно. Если вы чувствуете, что список становится длинным, просто организуйте их в пакеты, и все будет в порядке:-). Лично я обнаружил, что простое деление класса на два или три класса может очень помочь с удобочитаемостью без каких-либо дальнейших изменений.
Не бойтесь маленьких классов, они не кусаются;-).
Да, я могу поместить всю свою бизнес-логику прямо в код GUI, это намного проще и быстрее. Это всегда будет единственный графический интерфейс, и маловероятно, что когда-либо появятся новые важные требования.
Если кто-то скажет: "Маловероятно, что появятся новые важные требования". с невозмутимым лицом, я считаю, что этот человек действительно нуждается в проверке реальности. Будь тупым, но нежным...
Если в маловероятном случае новых требований мой код станет слишком загроможденным, я все равно смогу выполнить рефакторинг для нового требования. Таким образом, ваш аргумент "Что если вам позже понадобится..." не считается
Это имеет некоторые достоинства, но только если они действительно сделают рефакторинг позже. Так что примите это и держите их за обещание:-).
Понятное, гибкое и способное к исправлениям и улучшениям - это всегда то, что вам нужно. В самом деле, YAGNI предполагает, что вы можете вернуться и добавить новые функции, когда они окажутся необходимыми с относительной легкостью, потому что никто не собирается делать что-то сумасшедшее, например, неуместную функциональность в классе (YAGNI в этом классе!) Или использование бизнес-логики в логике пользовательского интерфейса.,
Могут быть случаи, когда то, что сейчас кажется сумасшедшим, было разумным в прошлом - иногда границы пользовательского интерфейса против бизнеса или между различными наборами обязанностей, которые должны быть в другом классе, не так ясны или даже не меняются. Могут быть моменты, когда 3 часа работы абсолютно необходимы в течение 2 часов. Есть моменты, когда люди просто не делают правильный звонок. По этим причинам могут произойти случайные перерывы в этом отношении, но они будут мешать использованию принципа ЯГНИ, а не быть его причиной.
tldr;
SOLID предполагает, что вы понимаете (хотя бы немного) будущие изменения в коде, за исключением SRP. Я скажу, что оптимистично настроен прогнозировать. YAGNI, с другой стороны, предполагает, что в большинстве случаев вы не знаете направления будущих изменений, что пессимистично в отношении способности прогнозировать.
Отсюда следует, что SOLID/SRP просит вас сформировать классы для кода так, чтобы он имел единственную причину для изменения. Например, небольшое изменение графического интерфейса или изменение ServiceCall.
YAGNI говорит (если вы хотите принудительно применить его в этом сценарии), так как вы не знаете, ЧТО изменится, и если изменение GUI вызовет изменение GUI+ServiceCall (аналогично, изменение бэкенда, вызывающее изменение GUI+SeviceCall) Просто поместите весь этот код в один класс.
Длинный ответ:
Прочитайте книгу "Гибкая разработка программного обеспечения, принципы, шаблоны и практики"
Я помещаю короткий отрывок из него о SOLID/SRP: "Если [...] приложение не изменяется таким образом, что две обязанности меняются в разное время, нет необходимости разделять их. Действительно, разделяя их пахнет ненужной сложностью.
Здесь есть следствие. Ось изменения - это ось изменения, только если изменения происходят. Неразумно применять SRP - или любой другой принцип, если на то пошло - если нет симптомов ".