Читаю статью по диагонали наоборот )) В секции "Liskov Substitution Principle" вообще LOL. Если разговор про SOLID, так почему у вас "чтец" из файла занимается его расшифровкой, т.к.
могут быть разные алгоритмы расшифровки
разные источники данных: поток, строка, файл, тестовые данные
разные способы получения данных: синхронно, асинхронно, блоками и т.п.
разный формат файлов
хочется проверять каждый функционал в отдельности, иначе у вас тестов будет миллион
Лучше сразу подумать о всем SRP, ISP, DIP, LSP, OCP и отделить мухи от котлет:
Чтение данных
Расшифровка данных
Итог:
абстракция чтения данных
абстракция алгоритма расшифровки
абстракция самих данных
реализация чтения
реализация алгоритма расшифровки
тесты алгоритма расшифровки поверх абстракции данных
тесты чтения данных
Ваш итог:
В данном случае строгое следование LSP привело к:
Увеличению количества кода
Дублированию логики чтения файла
Усложнению структуры проекта
Мой итог:
простые и надежные реализации < 10 строк
без дублирования кода
дешево в поддержке (можно мозг вообще не включать)
минимум необходимости в последующих модификациях (OCP)
возможность использовать как кирпичики (ISP, LSP, DIP)
С чего вы взяли что я не читал? В книге вам приводят пример того что может быть абстрактным слоем на примере кода приложения. Чаще всего абстракции в языках программирования для модулей типов - это интерфейсы, там где они есть. Так всем понятней. Но совсем необязательно использовать именно интерфейсы. Основная мысль там "что и от чего должно зависеть, что бы зависеть поменьше". DIP не только про модули типов, а вообще про зависимости. Использование интерфейсов не дает ни какой гарантии что вы придерживаетесь DIP и наоборот. Например у вас есть 100500 nuget/maven пакетов - это тоже модули. Если грамотно разбить библиотеки по пакетам, то DIP позволит их использовать и поддерживать более эффективно. Тоже самое и про компоненты приложения, про плагины, про микро-сервисы и т.п.
Я рекомендую вам читая книгу, больше вникать в смысл прочитанного.
Начать использовать интерфейсы, когда уже серьёзно припрёт? Где та тонкая грань, когда можно переходить на нормальный дизайн?
У сожалению когда "вирмиешеподобный" код достигает определенной критической массы. Его уже невозможно или сложно поддерживать: исправлять там баги, добавлять функционал. Часто, рефакторинг займет больше времени чем просто выкинуть и переписать с нуля. Но не так все просто, так как вокруг еще сотня таких "классов", которые знают о всех остальных - по сути приложение из 1 большого класса распиханного по файлам с разными названиями. Классы 10K линий, пол сотни методов, пяток вложенных классов, и поток погоняет 10-ком других потоков с кучей локов - друзей дедлоков)).
Я вижу тут несколько проблем. 1. Часто в разработку идут люди без специального образования. 2. В ВУЗах РФ дают много математики, алгоритмов, и всего чего угодно, но не учат делать ПО.
Я знаю, что он писал на английском, а еще его книгу переводили на другие языки. Вы просто взяли несколько фраз, без учета контекста.
A somewhat more naive, yet still very powerful, interpretation of the DIP is the simple heuristic: “Depend onabstractions.” Simply stated, this heuristic recommends that you should not depend on a concrete class—that allrelationships in a program should terminate on an abstract class or an interface.
According to this heuristic,
• No variable should hold a pointer or reference to a concrete class.
• No class should derive from a concrete class.
• No method should override an implemented method of any of its base classes.
А тут вам привели пример "наивной" интерпретации DIP, когда есть некий абстрактный слой, который чаще всего выглядит как набор интерфейсов - это базовый сценарий. Задача DIP, как и других принципов SOILD уменьшить связанность кода. Можно обойтись и без интерфейсов, что бы выделить некий API (более высокого уровня абстракции) и уменьшить зависимость между слоями. Просто интерфейсы используются чаще всего для этого в языках где они есть. Но с другой стороны, если нет понимания зачем это все вообще нужно, можно наделать много интерфейсов, которые будут раскрывать кучу деталей и ни как не будут абстракциями "более высокого уровня".
DIP про уровни абстракции. Скажем, есть 2 дивизии. Ими управляют 2 генерала. Если генералу дивизии 1 что то нужно от дивизии 2 он не пойдёт напрямую к солдату из дивизии 2, он пойдёт к генералу дивизии 2 (или к маршалу, который управляет дивизиями), так как это его уровень. Здесь про уровни подчинения, но с уровнем абстракции похоже. Да часто абстракции определены интерфейсами или другими абстрактными типами или же DTO или же просто другими типами.
DIP про связи между модулями, а модули это не только типы, но и проекты, сборки, пакеты и т.п. Это советы, а не требования.
Согласен, что интерфейсы сами по себе не особо полезны. Но какая связь DIP и интерфейсов? Может вы, как и автор статьи, спутали с Dependency Injection? В последнем действительно рекомендуется (но не обязательно) использовать интерфейсы для внедрения зависимостей.
Я понимаю, легко ошибиться так как акронимы совпадают. Dependency Injection это одна из реализации идеи IoC.
Да интерфейсы страшная вещь и не нужная)) Мой вопрос не касается DIP, а как вы пишете модульные тесты? Либо вы их не пишите, либо они у вас не модульные. И то и другое ок, просто интересно
DIP часто превращается в догму "всегда используйте интерфейсы", что приводит к созданию ненужных абстракций. И вот уже весь код набит интерфейсами, которые имплементируются ровно один раз в одном классе и только затрудняют навигацию. "На всякий случай"
Какая догма? DIP это совет о том "что от чего должно зависеть" чтобы ваш код был максимально простым и его было максимально дёшево поддерживать. Абстракция там не обязательно про интерфейс. Там про уровень абстракции. Так почти везде у вас
Нет смысла объяснять что то опытном человеку)) Это сложно.
Автор плохо понимает то о чем пишет(( Статья изобилует кучей выводов из воздуха, основанных на очем.
SOLID это советы, следовать которым нужно с головой. Появились они когда люди находились по граблями ООП. Когда ООП был дикий. По сути это best practics для ООП.
Основания их идея - сделать код простым за счёт уменьшение его связанности.
Вы совершенно правы. В плане производительности классические библиотеки DI хорошо справляются со своими задачами. И действительно, при разрешении зависимостей бывает нужно идти на компромиссы только в горячих местах. Pure.DI помогогает избежать большинства даже этих компромиссов. Я использовал Pure.DI при написании библиотек, где приходилось выжимать всю производительность до капли и там этот генератор показывал себя замечательно и не надо было думать где использовать DI, а где кто-то выкручиваться иначе. Ещё в плане производительности Pure.DI сильно выигрывает при первом разрешении объектов, так как время на подготовку ему не нужно.
А вот что касается ошибок времени выполнения, по своему опыту я часто наталкиваюсь на них в повседневной работе с классическим DI, скорее всего я менее внимательный чем вы. И для меня проверки на этапе компиляции особо полезны. Эти ошибки исправляются быстро, это да, но у меня эти ошибки пролазили в сборки приложений/сервисов, особенно когда упор делался на модульные тесты, а интеграционных было мало.
В сумме мне кажется, Pure.DI мог бы быть очень полезен для написания библиотек/фреймворков. Он и на производительность совсем не повлияет и зависимостей ни каких не добавляет и работатет в любых условиях/платформах/девайсах. Также в геймдеве мог бы помочь.
Вот список ключевых преимуществ. Если в 2-х словах, то это не библиотека, а помощник, который пишет налету код для построения объектов, у которых есть свои зависимости, у которых есть свои зависимости … и т.д., т.е. код композиции объектов. Если что-то идет не так-то будут ошибки компиляции, а не исключения во время выполнения. Сгенерированный код не бросает исключений, не использует отражение типов, работает очень быстро и не тратит больше памяти, так как это обычный код, который вы бы написали руками.
Представьте, что у вас нет библиотек DI. Вы пишете свой код в парадигме DI (все зависимости внедряете, а не создаете внутри, программируете на основе абстракций и т.п.). Ту часть вашего кода, которая собирает все объекты воедино Pure.DI напишет за вас.
И используйте его. При этом если Owned из API Pure.DI не будет использован (не будет внедрен где-то), то и накапливать он ничего не будет и даже не будет создан. А будет работать только ваш.
на этапе компиляции будет предупреждение от том что привязка была перегружена, например:
Composition.cs(23,51): Clock.ViewModels.IClockViewModel has been overridden.
т.е. забыли Bind для IClockViewModel
Программа не упадет на запуске (если специально не включить флаг для игнора невозможности определить зависимость для внедрения). Сначала будет проверена корректность графа зависимостей, если что то не так, то мы увидим проблему на этапе компиляции. Если даже что то пойдет не так, например ошибка в генераторе, то код сгенериться не верно и его проверит компилятор. И проверку плохой код не пройдет и опять остановится на этапе компиляции. До запуска бинарника программы просто не дойдет, потому что не будет этого бинарника. Или я вас не правильно понял ))
Например, закомментируйте эту строку. Ошибка компиляции будет такой:
FormMain.cs(11, 39): Unable to resolve "Clock.ViewModels.IClockViewModel" in FormMain(Clock.ViewModels.IClockViewModel clockViewModel<--Clock.ViewModels.IClockViewModel)).
Если в IDE перейти по ошибке то он укажет на параметр IClockViewModel clockViewModel в конструкторе класса FormMain. Т.е. программа конечно не скомпилируется и бинарника, который можно запустить у вас не будет.
Кстати думаю что в ваш проект можно analyzer добавить - который бы отлавивал стандартные ошибки использования вашего продукта, анализатор от генератора почти не отличается, а удобства использования повысит.
Да стандартные ошибки отлавливаются и указывается место и рекомендации к исправлению. Самая главная идея - все ошибки на этапе компиляции, на этапе выполнения все должно работать как задумано - ни каких исключений. Если ошибки находятся, то код не скомпилировать
Не много не в тему, но интресно узнать, а какая у вас мотивация заниматься open source проектом подобным этому?
Opensource - для меня возможность изучать инструменты, с которыми работаю. А если они приносят кому-то пользу, то в двойне приятно)) Конкретно это проект мне нравится, так как на мой взгляд DI это основа основ надежных и дешевых в поддержке и доработке приложений.
Глядя на функциональность Incremental Generators, выглядит так что было бы вполне легко реализовать что-то типа greasemonkey для браузеров.
Такой подход будет сложно реализовать, так как анализируется не только исходный код, но и все бинарные зависимости. Плюс слишком много событий когда нужно будет перестраивать код –будет не удобно. Я не уверен, что до конца понял вашу мысль про greasemonkey.
Хотел одно время написать, но с приходом этой движухи с ИИ не понятно, нужно ли вообще суетиться.
Думал в направлении подключить какой-нибудь ИИ, но у меня не много в этом опыта и кажется производительность анализа графа может быть не высокой, а результат сложно прогнозировать.
Да, это генератор кода на основе .NET Incremental Generators и идея Pure.DI похожа на Jab, но много различий в деталях.
Jab – это все вокруг IServiceProvider, Pure.DI не привязан ни к чему
Jab – использует атрибуты для метаданных, в Pure.DI использует и атрибуты, но самое главное основной API Pure.DI похож на обычные DI контейнеры
В Jab на сколько я понял каждую зависимость нужно регистрировать атрибутом, что не очень удобно, в Pure.DI нужно определить только корни или привязки абстракций и все работает само
В Jab при построении композиции объектов везде используются вызовы GetService<T>(), это не хорошо по производительности
В связи с подходом из предыдущего пункта нет эффективного способа реализовать такие времена жизни PerResolve или PerBlock, которые очень удобны
Непонятно одно: если в какой-то момент не нужны будут программисты (и доводы разумны, исчезли же когда-то фонарщики или конюхи), то сколько времени пройдёт до того момента, когда не нужны будут архитекторы, менеджеры, инженеры, бизнес аналитики, руководители и другие люди, которые занимаются умственные трудом? Если сгинут программисты, то згинет множество др специльностей. В какой-то момент дойдёт до рабочих и солдат, а потом до художников, дизайнеров, режиссёров и композиторов. И последний вопрос зачем люди? ))
Читаю статью по диагонали наоборот )) В секции "Liskov Substitution Principle" вообще LOL. Если разговор про SOLID, так почему у вас "чтец" из файла занимается его расшифровкой, т.к.
могут быть разные алгоритмы расшифровки
разные источники данных: поток, строка, файл, тестовые данные
разные способы получения данных: синхронно, асинхронно, блоками и т.п.
разный формат файлов
хочется проверять каждый функционал в отдельности, иначе у вас тестов будет миллион
Лучше сразу подумать о всем SRP, ISP, DIP, LSP, OCP и отделить мухи от котлет:
Чтение данных
Расшифровка данных
Итог:
абстракция чтения данных
абстракция алгоритма расшифровки
абстракция самих данных
реализация чтения
реализация алгоритма расшифровки
тесты алгоритма расшифровки поверх абстракции данных
тесты чтения данных
Ваш итог:
Мой итог:
простые и надежные реализации < 10 строк
без дублирования кода
дешево в поддержке (можно мозг вообще не включать)
минимум необходимости в последующих модификациях (OCP)
возможность использовать как кирпичики (ISP, LSP, DIP)
С чего вы взяли что я не читал? В книге вам приводят пример того что может быть абстрактным слоем на примере кода приложения. Чаще всего абстракции в языках программирования для модулей типов - это интерфейсы, там где они есть. Так всем понятней. Но совсем необязательно использовать именно интерфейсы. Основная мысль там "что и от чего должно зависеть, что бы зависеть поменьше". DIP не только про модули типов, а вообще про зависимости. Использование интерфейсов не дает ни какой гарантии что вы придерживаетесь DIP и наоборот. Например у вас есть 100500 nuget/maven пакетов - это тоже модули. Если грамотно разбить библиотеки по пакетам, то DIP позволит их использовать и поддерживать более эффективно. Тоже самое и про компоненты приложения, про плагины, про микро-сервисы и т.п.
Я рекомендую вам читая книгу, больше вникать в смысл прочитанного.
У сожалению когда "вирмиешеподобный" код достигает определенной критической массы. Его уже невозможно или сложно поддерживать: исправлять там баги, добавлять функционал. Часто, рефакторинг займет больше времени чем просто выкинуть и переписать с нуля. Но не так все просто, так как вокруг еще сотня таких "классов", которые знают о всех остальных - по сути приложение из 1 большого класса распиханного по файлам с разными названиями. Классы 10K линий, пол сотни методов, пяток вложенных классов, и поток погоняет 10-ком других потоков с кучей локов - друзей дедлоков)).
Я вижу тут несколько проблем. 1. Часто в разработку идут люди без специального образования. 2. В ВУЗах РФ дают много математики, алгоритмов, и всего чего угодно, но не учат делать ПО.
Я знаю, что он писал на английском, а еще его книгу переводили на другие языки. Вы просто взяли несколько фраз, без учета контекста.
А тут вам привели пример "наивной" интерпретации DIP, когда есть некий абстрактный слой, который чаще всего выглядит как набор интерфейсов - это базовый сценарий. Задача DIP, как и других принципов SOILD уменьшить связанность кода. Можно обойтись и без интерфейсов, что бы выделить некий API (более высокого уровня абстракции) и уменьшить зависимость между слоями. Просто интерфейсы используются чаще всего для этого в языках где они есть. Но с другой стороны, если нет понимания зачем это все вообще нужно, можно наделать много интерфейсов, которые будут раскрывать кучу деталей и ни как не будут абстракциями "более высокого уровня".
Что это? Мысль, написанная на английском языке должна быть более убедительной? Типа как мысль, произнесенная во время протирания очков?))
DIP про уровни абстракции. Скажем, есть 2 дивизии. Ими управляют 2 генерала. Если генералу дивизии 1 что то нужно от дивизии 2 он не пойдёт напрямую к солдату из дивизии 2, он пойдёт к генералу дивизии 2 (или к маршалу, который управляет дивизиями), так как это его уровень. Здесь про уровни подчинения, но с уровнем абстракции похоже. Да часто абстракции определены интерфейсами или другими абстрактными типами или же DTO или же просто другими типами.
DIP про связи между модулями, а модули это не только типы, но и проекты, сборки, пакеты и т.п. Это советы, а не требования.
Согласен, что интерфейсы сами по себе не особо полезны. Но какая связь DIP и интерфейсов? Может вы, как и автор статьи, спутали с Dependency Injection? В последнем действительно рекомендуется (но не обязательно) использовать интерфейсы для внедрения зависимостей.
Я понимаю, легко ошибиться так как акронимы совпадают. Dependency Injection это одна из реализации идеи IoC.
Да интерфейсы страшная вещь и не нужная)) Мой вопрос не касается DIP, а как вы пишете модульные тесты? Либо вы их не пишите, либо они у вас не модульные. И то и другое ок, просто интересно
Какая догма? DIP это совет о том "что от чего должно зависеть" чтобы ваш код был максимально простым и его было максимально дёшево поддерживать. Абстракция там не обязательно про интерфейс. Там про уровень абстракции. Так почти везде у вас
Нет смысла объяснять что то опытном человеку)) Это сложно.
Автор плохо понимает то о чем пишет(( Статья изобилует кучей выводов из воздуха, основанных на очем.
SOLID это советы, следовать которым нужно с головой. Появились они когда люди находились по граблями ООП. Когда ООП был дикий. По сути это best practics для ООП.
Основания их идея - сделать код простым за счёт уменьшение его связанности.
Спасибо за интерес!
Вы совершенно правы. В плане производительности классические библиотеки DI хорошо справляются со своими задачами. И действительно, при разрешении зависимостей бывает нужно идти на компромиссы только в горячих местах. Pure.DI помогогает избежать большинства даже этих компромиссов. Я использовал Pure.DI при написании библиотек, где приходилось выжимать всю производительность до капли и там этот генератор показывал себя замечательно и не надо было думать где использовать DI, а где кто-то выкручиваться иначе. Ещё в плане производительности Pure.DI сильно выигрывает при первом разрешении объектов, так как время на подготовку ему не нужно.
А вот что касается ошибок времени выполнения, по своему опыту я часто наталкиваюсь на них в повседневной работе с классическим DI, скорее всего я менее внимательный чем вы. И для меня проверки на этапе компиляции особо полезны. Эти ошибки исправляются быстро, это да, но у меня эти ошибки пролазили в сборки приложений/сервисов, особенно когда упор делался на модульные тесты, а интеграционных было мало.
В сумме мне кажется, Pure.DI мог бы быть очень полезен для написания библиотек/фреймворков. Он и на производительность совсем не повлияет и зависимостей ни каких не добавляет и работатет в любых условиях/платформах/девайсах. Также в геймдеве мог бы помочь.
Вот список ключевых преимуществ. Если в 2-х словах, то это не библиотека, а помощник, который пишет налету код для построения объектов, у которых есть свои зависимости, у которых есть свои зависимости … и т.д., т.е. код композиции объектов. Если что-то идет не так-то будут ошибки компиляции, а не исключения во время выполнения. Сгенерированный код не бросает исключений, не использует отражение типов, работает очень быстро и не тратит больше памяти, так как это обычный код, который вы бы написали руками.
Представьте, что у вас нет библиотек DI. Вы пишете свой код в парадигме DI (все зависимости внедряете, а не создаете внутри, программируете на основе абстракций и т.п.). Ту часть вашего кода, которая собирает все объекты воедино Pure.DI напишет за вас.
Реализация
Ownedвыглядит примерно так:Его производительность почти полностью зависит от производительности
List<T>.Я замерял, но под рукой цифр нет - довольно быстрый))Предполагается, что утилизируемых объектов не будет слишком много:
так как дорогих ресурсов не должно быть много
если накапливать много, то могу быть проблемы с памятью
Вы всегда можете заменить его на свой. Для этого зарегистрируйте свою реализацию накопителя, например
MyOwned:И используйте его. При этом если
Ownedиз API Pure.DI не будет использован (не будет внедрен где-то), то и накапливать он ничего не будет и даже не будет создан. А будет работать только ваш.В этом случае
на этапе компиляции будет предупреждение от том что привязка была перегружена, например:
Composition.cs(23,51): Clock.ViewModels.IClockViewModel has been overridden.
Программа не упадет на запуске (если специально не включить флаг для игнора невозможности определить зависимость для внедрения). Сначала будет проверена корректность графа зависимостей, если что то не так, то мы увидим проблему на этапе компиляции. Если даже что то пойдет не так, например ошибка в генераторе, то код сгенериться не верно и его проверит компилятор. И проверку плохой код не пройдет и опять остановится на этапе компиляции. До запуска бинарника программы просто не дойдет, потому что не будет этого бинарника. Или я вас не правильно понял ))
Например, закомментируйте эту строку. Ошибка компиляции будет такой:
FormMain.cs(11, 39): Unable to resolve "Clock.ViewModels.IClockViewModel" in FormMain(Clock.ViewModels.IClockViewModel clockViewModel<--Clock.ViewModels.IClockViewModel)).
Если в IDE перейти по ошибке то он укажет на параметр IClockViewModel clockViewModel в конструкторе класса FormMain. Т.е. программа конечно не скомпилируется и бинарника, который можно запустить у вас не будет.
Нужны и все зависимости
Да стандартные ошибки отлавливаются и указывается место и рекомендации к исправлению. Самая главная идея - все ошибки на этапе компиляции, на этапе выполнения все должно работать как задумано - ни каких исключений. Если ошибки находятся, то код не скомпилировать
Opensource - для меня возможность изучать инструменты, с которыми работаю. А если они приносят кому-то пользу, то в двойне приятно)) Конкретно это проект мне нравится, так как на мой взгляд DI это основа основ надежных и дешевых в поддержке и доработке приложений.
Такой подход будет сложно реализовать, так как анализируется не только исходный код, но и все бинарные зависимости. Плюс слишком много событий когда нужно будет перестраивать код –будет не удобно. Я не уверен, что до конца понял вашу мысль про greasemonkey.
Думал в направлении подключить какой-нибудь ИИ, но у меня не много в этом опыта и кажется производительность анализа графа может быть не высокой, а результат сложно прогнозировать.
Спасибо за интерес))
Да, это генератор кода на основе .NET Incremental Generators и идея Pure.DI похожа на Jab, но много различий в деталях.
Jab – это все вокруг IServiceProvider, Pure.DI не привязан ни к чему
Jab – использует атрибуты для метаданных, в Pure.DI использует и атрибуты, но самое главное основной API Pure.DI похож на обычные DI контейнеры
В Jab на сколько я понял каждую зависимость нужно регистрировать атрибутом, что не очень удобно, в Pure.DI нужно определить только корни или привязки абстракций и все работает само
В Jab при построении композиции объектов везде используются вызовы GetService<T>(), это не хорошо по производительности
В связи с подходом из предыдущего пункта нет эффективного способа реализовать такие времена жизни PerResolve или PerBlock, которые очень удобны
В Jab нет эффективных фабрик как такое
Те фабрики что есть - не очень эффективны, например вот такие фокусы сделать невозможно
В Jab плохая работа с обобщёнными типами
Он не потокобезопасен, на сколько я понял
Нет множества полезны фич такие как аргументы композиции/корней
Нет возможности сделать лямбды с параметрами как такое
Нет настроек генерации кода как такие подсказки
Я не понял как там делать перехват как это или декоратор как этот
Я не понял, как там внедрять реализации по ключу - может быть не доделано еще
Там не поддерживаются BCL (Lazy, IList ...) и другие типы из коробки, я уже и не говорю про создание Span на стеке))
Нет возможности из коробки внедрять Func<> что бы создавать столько инстансов сколько нужно
и много другого
Jab - это генератор DI "на минималках", но многим его возможностей вполне достаточно, мне нет))
Непонятно одно: если в какой-то момент не нужны будут программисты (и доводы разумны, исчезли же когда-то фонарщики или конюхи), то сколько времени пройдёт до того момента, когда не нужны будут архитекторы, менеджеры, инженеры, бизнес аналитики, руководители и другие люди, которые занимаются умственные трудом? Если сгинут программисты, то згинет множество др специльностей. В какой-то момент дойдёт до рабочих и солдат, а потом до художников, дизайнеров, режиссёров и композиторов. И последний вопрос зачем люди? ))