Pull to refresh

Comments 40

А почему вы решили что KISS и SOLID это разное? Можно писать простой код, соблюдая SOLID.

Понятно что есть учебные примеры на которых можно противопоставить эти принципы, но в реальности это не так. Тот же спагетти код и KISS это разное. Писать лапшу вовсе не значит делать проще.

Например? проще править класс, чем делать его закрытым для изменения и открытым для расширения. Или не во все места стоит вставлять инвертированные зависимости.

Вот принцип подстановки нужно соблюдать неукоснительно, ибо когда наследник нарушает контракт, это фу-фу-фу и код становится сложнее.

Вообще, хорошо зная принципы разработки, можно обосновать любой код.

KISS не запрещает править существующие классы. Люди часто его не правильно понимают и ассоциируют с подходом «вжух вжух и в продакшн». Это разные вещи. KISS это про то чтобы не заводить ненужные абстракции пока они не понадобятся.

Это да, но аббревиатура KISS расшифровывается: Keep It Simple Stupid. Последнее слово как-раз имеется ввиду, что ты можешь сделать решение более топорное, возможно даже немного накостылять и захардкодить

Вы все неправильно поняли. Последнее слово - это обращение.

"Делай проще, тупица".

Данный принцип говорит лишь о том, что не нужно переусложнять и писать максимально понятный и простой код, а принципы SOLID должны в этом помогать.

И так и так читают, это пришло же из армии. "keep it super simple", "keep it simple, silly", "keep it short and simple", "keep it short and sweet", "keep it simple and straightforward", "keep it simple and stupid". В программировании это скорее рекомендация, а не правило.

Я думаю, Gromilo имел ввиду, что править класс это просто и удобно(т.е. соответствует KISS), но противоречит Open/Close принципу из SOLID. Выходит, что не всегда KISS и SOLID дружат.

С моей точки зрения все немного иначе.

Что означает принцип Open/Close? Наверно везде, где я читал, имеется ввиду - не меняй исходный код класса, а сделай наследника и расширяй его. Но я думаю что на самом деле имеется ввиду - не изменяй интерфейс(т.е. контракт, который класс предоставляет), а расширяй его. А что касается имплементации - это инкапсулировано внутри класса и (в лучшем случае) на всю остальную систему это не повлияет. Хорошо, если код покрыт тестами.

Да, именно это я и имел ввиду.

Тут немного другой критерий. Не знаю, что имели в виду авторы принципа, но на практике это неплохо работает.

Делать наследника нужно только если оба варианта используются в рабочем коде. Это означает, что не нужно делать наследников при изменении требований в бизнес-логике, так как бизнес-логика обычно одна. Также не нужно таким образом фиксить баги, версия с багом скорее всего вам не нужна.

В идеале так и нужно писать простой код, соблюдая SOLID. В статье речь идет о том, что можно уйти в крайности:

  • Простая система, может превратиться в God Object

  • Супер гибкая система, может превратиться в спагетти.

Поэтому в статье объясняется, что нужно держать баланс. Нельзя просто следовать SOLID. В тоже время нужно контролировать, чтобы Stupid классы не превращались в God Object'ы.

Не сочтите за рекламу, я к сайту не имею никакого отношения. Но часто даю его почитать коллегам, которые злоупотребляют быстрокодингом: тыц

Тыц не работает. У них какой-то битый сервер, на http3 делает хз что

Простая система, может превратиться в 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++ она будет в явном виде.

Хуже-лучше, это очень субьективно. зависит больше от писателя, чем от читателя. Бывает изучаешь какой-нибудь проект на гитхабе и думаешь: о господи, лучше бы тут классов не было, когда на входе в класс у тебя 5-7 строк - френдзона.

потому что вы никогда не знаете на 100%, в каких местах ваш проект будет развиваться

Сразу видно жертву Agile. "Только темная сторона мыслит абсолютами." Есть целый ряд долгоиграющих проектов и ОКР, где это "примерно" известо, а гибкое управление категорически не приветствуется

к сожалению, в бизнесе это очень оправданно, т.к. с одной стороны заказчик внятно не может объяснить, чего он хочет в долгосрочной перспективе, а с другой - команде разработки надо его грамотно "подоить". Agile - как раз для этого удачный инструмент :)

Sign up to leave a comment.

Articles