Comments 40
А почему вы решили что KISS и SOLID это разное? Можно писать простой код, соблюдая SOLID.
Понятно что есть учебные примеры на которых можно противопоставить эти принципы, но в реальности это не так. Тот же спагетти код и KISS это разное. Писать лапшу вовсе не значит делать проще.
Например? проще править класс, чем делать его закрытым для изменения и открытым для расширения. Или не во все места стоит вставлять инвертированные зависимости.
Вот принцип подстановки нужно соблюдать неукоснительно, ибо когда наследник нарушает контракт, это фу-фу-фу и код становится сложнее.
Вообще, хорошо зная принципы разработки, можно обосновать любой код.
KISS не запрещает править существующие классы. Люди часто его не правильно понимают и ассоциируют с подходом «вжух вжух и в продакшн». Это разные вещи. KISS это про то чтобы не заводить ненужные абстракции пока они не понадобятся.
Это да, но аббревиатура KISS расшифровывается: Keep It Simple Stupid. Последнее слово как-раз имеется ввиду, что ты можешь сделать решение более топорное, возможно даже немного накостылять и захардкодить
Вы все неправильно поняли. Последнее слово - это обращение.
"Делай проще, тупица".
Данный принцип говорит лишь о том, что не нужно переусложнять и писать максимально понятный и простой код, а принципы SOLID должны в этом помогать.
Я думаю, Gromilo имел ввиду, что править класс это просто и удобно(т.е. соответствует KISS), но противоречит Open/Close принципу из SOLID. Выходит, что не всегда KISS и SOLID дружат.
С моей точки зрения все немного иначе.
Что означает принцип Open/Close? Наверно везде, где я читал, имеется ввиду - не меняй исходный код класса, а сделай наследника и расширяй его. Но я думаю что на самом деле имеется ввиду - не изменяй интерфейс(т.е. контракт, который класс предоставляет), а расширяй его. А что касается имплементации - это инкапсулировано внутри класса и (в лучшем случае) на всю остальную систему это не повлияет. Хорошо, если код покрыт тестами.
Да, именно это я и имел ввиду.
Тут немного другой критерий. Не знаю, что имели в виду авторы принципа, но на практике это неплохо работает.
Делать наследника нужно только если оба варианта используются в рабочем коде. Это означает, что не нужно делать наследников при изменении требований в бизнес-логике, так как бизнес-логика обычно одна. Также не нужно таким образом фиксить баги, версия с багом скорее всего вам не нужна.
В идеале так и нужно писать простой код, соблюдая SOLID. В статье речь идет о том, что можно уйти в крайности:
Простая система, может превратиться в God Object
Супер гибкая система, может превратиться в спагетти.
Поэтому в статье объясняется, что нужно держать баланс. Нельзя просто следовать SOLID. В тоже время нужно контролировать, чтобы Stupid классы не превращались в God Object'ы.
Простая система, может превратиться в God Object
И тогда код будет противоречить KISS. Не, может в теории для человека это и будет простым кодом. но на практике обычно система из божественных объектов приводит к головной боли
keep it это значит что код всегда должен оставаться простым, god object'ы же делает код трудным
God object в некоторых случаях может все ещё оставаться сравнительно простым и понятным. Сложным будет тестирование его и зависимы компонентов, а так-же внесение изменений в зависимо коде.
Есть два вида - те что знают все и те что могут все. Первое - результат отстуствия развязок в зависимостях, а вот второе - противоречит как SOLIDтак и KISS. God object не обязательно противоречит SOLID если несет в себе абстрактную функционал, но этапе компиляции требует определения всего неоходимого.
Вопрос: как найти баланс?
Ответ: использовать здравый смысл
Пожалуйста ;)
делай раз: пишем код, который будет работать;
делай два: рефакторим код, соблюдая баланс SOLID / KISS.
А сразу написать чистый код, с соблюдением всех принципов?
Такое сработает, если разработчик уже решал похожую задачу ранее. Если для специалиста задача новая, то легко угодить в ловушку двух зайцев. Думать одновременно о том, чтобы код и работал и был причесан, может очень сильно тормозить процесс разработки (по себе знаю). В результате будет потрачено больше времени, сил и энергии. Поэтому рабочий код и чистый код — это разного рода задачи, и лучше рефакторить после проверки работоспособности кода.
Писать какой-то код, а потом рефакторить - ещё большая ошибка. Вы лишь перекладываете необходимую работу на будущего себя (или других разработчиков, которые будут разбирать в вашем коде, после того как вы выгорите и уйдете в закат). Беда в том, что плохой код порождает ещё больше плохого кода и в конечном счёте придется потратить ещё больше времени и сил. Но что ещё более вероятно, никто ничего не будет делать. Все будут "плакать, колоться но есть кактус". А потом релиз. И в итоге мы получаем очередного убийцу <вставить_свой_вариант> от российских разработчиков.
Не пренебрегать архитектурным планированием и изучать различные практики и их мотивацию. Тот же CppCoreGuidelines порой кажется избыточным, пока не набьёшь собственных шишек.
Я нашел баланс путем перехода на процедурный стиль без использования классов. Классы - интеграционная вещь, на голом Си - функциональная. В одном модуле можно спокойно разложить по функциям то, что спокойно заняло бы 3 уровня иерархии.
А какая сфера? Я например веб сервисы пишу нарпах и не умею в процедурный стиль.
В Си эти 3 уровня иерархии вы будете имитировать префиксами в названиях функций, и будет примерно то же самое.
нет. у меня мышление не ООПшное изначально, и подход к разработке аппаратно-зависимый, т.к. аппаратура связана между собой и требует тесного межмодульного взаимодействия(некоторые вещи инкапсулировать не получится, а если имплиментацию зафрендить - то будет совсем дремучий лес). в ООП выносится уже совсем высокоуровневый слой, но если взять по количеству исходных текстов, то это процентов 30-40.
Можете привести пример? Я не вижу принципиальной разницы между обращением к глобальным переменным и к переменным, проброшенным в this через конструктор. Если в свободных функциях нет 3 уровней иерархии, то их не будет и если сгруппировать их в классы. В крайнем случае просто оборачиваем все функции в class ... { ... }
.
Как пример(из последнего с чем работал), есть EGL, DRM, X11 и некая аппаратура, которая делает из RGB/RGBA h.264, при этом наиболее комфортным вариантом использования этого было бы собрать всё в некое GUI дав управляющий фассад + полную свободу использования OpenGL не оборачивая его ни во что. С другой стороны крайне интересна работа самой аппаратуры сжатия, т.к. в режимах DRM и X11 там используются принципиально разные подходы, и это никак не приводится к общему знаменателю (можно конечно, но здесь придется очень пожертвовать производительностью ради красовости кода, либо создать псевдо-аналог Qt, где под капотом имплементоры будут очень тесно связаны друг с другом, и оверхед от использования классов будет чудовищным, и внутреннее взаимодействие зафренденых классов будет настолько сложным, что перевод всего на обычный процедурный стиль значительно упростит как разработку, так и дальнейший анализ написанного). Но это справедливо для Embedded, где решает не проц, а аппаратура. На ПЭВМ с этим проще(там как раз решает проц) - но она и дороже на порядок. Что мы имеем на выходе: некий прединициализированный UI с фассадным интерфейсом, набор ВУ-логики, где активно применяется MVC/MVP, SOLID и т.д. и набор процедур, которые всё это поднимают и работают под капотом, написанных в стиле близких к ядру Linux (т.е. С-интерфейсы без ООП вообще)
Ок, но непонятно, как это связано с уровнями иерархии. Если с классами нужно 3 уровня иерархии, то и с C-функциями эти уровни будут обозначаться префиксами. Если с C-функциями нет 3 уровней иерархии, то и с классами можно сделать без них.
Только личный опыт. Я видел как подобные вещи люди пытались утрамбовать в классы, в итоге больше половины времени убивали на организацию ООП, от чего страдала реализация, и в последствии структура классов признавалась неудачной. Один из модулей за год пережил 4 варианта - причем крайний был признан руководителем менее удачным по сравнению с предыдущим.
Тут мораль такова, что ООП - это шаблон мышления, и если он прибит гвоздями - то пиши пропало. А в остальных случаях он является лишь инструментом, и разработчик сам выбирает, использовать его или нет.
Ну так я поэтому и спросил, можете привести пример кода? ООП не налагает никаких ограничений на реализацию, как хотите так и делаете. Я же привел пример, оборачиваем все ваши функции в класс, и получаем ООП, который выглядит и работает так же, как ваш код. Почему вы думаете, что нельзя так сделать, и с ООП надо обязательно делать какую-то "структуру классов"?
Когда люди считают, что этого недостаточно, и начинают дробить на классы больше чем нужно, то это не ООП, а оверинжиниринг и неправильное применение. Другой вариант неправильного применения, это когда помещают ответственность не туда, куда нужно. Например делают circle.Draw(deviceContext)
, когда правильно сделать drawService.Draw(deviceContext, circle)
.
Не везде реализовано ООП, если брать мои проекты - то тогда надо всё переводить на C++, а это налагает доп.требования, т.к. используются инструменты статического анализа. Это оборачивание в данном случае не даёт ничего кроме понятного ООПшникам исходного текста и лишних трудозатрат на удовлетворение анализаторов (а без них смысла применять C++ нет, т.к. на нём прострелить себе ногу в разы проще, чем на голом Си). Я кстати очень резкий противник оберточного программирования, и пресекаю любые попытки команды в него скатиться.
Когда ООП нет, это понятно, но вы сказали, что с ООП код будет хуже, чем просто с функциями. Вот я и говорю, что это не так. Если он получается хуже, это неправильное использование. Тут 2 противоположных проявления, или дробят одну ответственность на несколько классов, или помещают несколько в один. Вы вот тоже судя по всему не видите сущность "модуль X" или "сервис X", и называете ее выделение оберточным программированием, хотя используете ее в C. Просто в C++ она будет в явном виде.
потому что вы никогда не знаете на 100%, в каких местах ваш проект будет развиваться
Сразу видно жертву Agile. "Только темная сторона мыслит абсолютами." Есть целый ряд долгоиграющих проектов и ОКР, где это "примерно" известо, а гибкое управление категорически не приветствуется
Принципы SOLID vs KISS. Как найти баланс?