Comments 17
«модуль» рассматривается скорее как единица распределения ответственности, нежели как подпрограмма
В принципе, да, только, «ответственности» кого перед кем?
Возьмем простейший случай – оконную программу, на C++, в Windows, поддерживающей различного рода дочерние компоненты.
Для их слаженной работы используется программная логика, объединяющая все эти компоненты и их контейнер в единое целое. Именно эта целостная логика не позволяет однозначно помещать заданные компоненты в независимые либо зависимые, только на уровне интерфейсов, модули.
Рассмотрим, например, стилизацию. Каждый компонент имеет множество параметров для настройки своего отображения и методов работы (скажем, стандартный редактор CEdit может быть однострочным и многострочным, позволять модификацию данных либо быть только для чтения, отрисовываться самостоятельно либо пользователем и т.п.). При этом, компоненты одного типа, могут находиться в разных состояниях (те же, однострочный и многострочный редакторы, одного класса CEdit).
Кроме того, компоненты должны как-то располагаться в главном окне приложения и менять свои координаты и размеры, при изменении размеров главного окна. Кстати, многие, по этой причине любят диалоги, там размеры и координаты компонентов фиксированы. Хотя, потом изобретают «велосипеды», вроде «ресайзэйблных» диалогов.
Так вот, для управления только стилем компонентов, нужно, где-то, определять структуры данных для этого. Либо на стороне «сервера», в данном случае, главного окна, либо на стороне «клиента», то бишь, внутри класса компонента (обычно наследующего класс стандартного компонента).
Иногда, это удобно делать на «сервере», а, иногда, на «клиенте». Бывают, нередко, более предпочтительны смешанные случаи.
Вот вам и «модулярная модулизация». Правы будут и одни и другие и третьи. Фактически все зависит от конкретного проекта.
И это мы еще не рассматривали логику работы компонентов, обмен данными между ними и т.д. Именно, по этой причине я себе чуть мозги не сломал, организуя взаимодействие различных, формально независимых, классов графического интерфейса пользователя (GUI), при работе с одними и теми же данными, в своей обучающей программе «L'école», с её шестью режимами работы и разными вариантами их использования (через меню). Для конкретики, можете посмотреть ее последнюю версию в моей статье: «Роль данных при изучении иностранного языка» – https://habr.com/ru/articles/930868/ .
Таким образом, модульность на уровне классов – есть, а на уровне программной логики – нет. По крайней мере, я её воспринимаю как единое целое, пусть и не запредельно сложное, после её фактической реализации.
и, всё-таки, в чём конкретно Вы видите "модульность"?
и, всё-таки, в чём конкретно Вы видите "модульность"?
На уровне проектов, на C++ / WTL, с которыми я имею дело, в своих пет-проектах, под модульностью, я понимаю:
1. Изолированный код, который легко добавлять и удалять из проекта.
2. Иерархию вида:
Функции;
Классы;
Namespaces;
Файлы;
Каталоги;
Проекты;
Решения.
3. Шаблоны проектирования (для каждой итерации проекта):
Проект «Main», с номером итерации, скажем, последняя итерация моего последнего пет-проекта ( https://habr.com/ru/articles/955838/ ), была – 24;
Точка входа: «Main.cpp», c функцией «WinMain()» – одинакова во всех проектах, которая создаёт и вызывает экземпляр класса «CApp» и ничего более;
В классе «CApp» создается экземпляр класса «CMainWindow», инициализируются необходимые библиотеки, вроде, «GDIplus», класс (менеджер) потоков, при необходимости, цикл, либо менеджер событий и класс (менеджер) видов (дочерних окон, занимающих всю клиентскую область главного окна);
В классе «CMainWindow» создаются и инициализируются компоненты главного окна и функция их отображения при изменении размеров окна приложения;
Каждый используемый компонент (класс) размещается в отдельной паре файлов: *.cpp и *.h;
Для большей структуризации проекта могут использоваться отдельные каталоги. Если подгружаемый код не подлежит частой модификации, например, опенсорсный код консольного проигрывателя, переделываемый под оконный интерфейс, то он может быть общим для всех итераций проекта.
Единственным слабым местом, для меня, здесь является проектирование общей программной логики. Именно на ней уходит больше всего времени, не считая поисков ответов, типа, «ноу-хау» (как именно сделать то, сё, пятое, десятое). Здесь хорошо помогают ИИ-сервисы, но, бывает, решение они дают и оно работает, но, как именно и почему – непонятно. Что сильно усложняет модификацию этого кода. Поэтому, владеть темой, безусловно, необходимо и не жалеть на это времени. Тогда легче и «правильные» вопросы задавать ИИ-ям и, вообще, вести с ними диалог.
Как-то так. Ответов на все вопросы это не дает, но, я, практически, использую именно этот метод «итерационно-модульного» программирования, на C++ / WTL. Думаю, что это лучше, чем ничего.
Позволю конкретизировать свой вопрос. Что же такое модульность на уровне программной логики? Пусть у нас есть два модуля — модуль А и модуль Б. Можно ли. например, выделить модуль (А) для обслуживания загрузки/выгрузки приложения и модуль (Б) для печати? Можно ли выделить модуль (А) для работы с пользовательским интерфейсом и модуль (Б) для работы с базами данных? Можно ли выделить модуль (А) для работы с заказами и модуль (Б) для работы с накладными? И всё это — на уровне программной логики?
Трудно говорить о модульной логике без привязки к системе разработки. Если мы программируем конфигурацию для «1С», то там вся пользовательская логика – локальна, в рамках модели используемой платформы. Поэтому, там легко все делиться, ибо «всё уже разделено до нас».
Другое дело приложения уровня С++, с легкими фреймворками, вроде WinAPI или WTL. Там вся логика сосредоточена в менеджере потоков, менеджере событий и менеджере интерфейса (либо, более общо, GUI), которые программист делает сам, индивидуально, для каждого проекта.
В частном случае, у вас есть события (пользовательские и системные), они имеют собственные обработчики, в которых и фиксируется основная программная логика. Вы можете делегировать дочерним элементам управления часть своих властных полномочий, тем же компонентами по работе с базами данных, отчетами, печатью и т.п. Хотя, всё равно, концептуальный контроль остаётся за вами, точнее, за главным классом приложения.
Вот эта иерархическая балансировка «ответственности», о чём упоминалось в статье, и есть искомая логическая модульность. Другое дело, что она плохо формализуема. То, что хорошо для одного проекта, может быть плохо для другого. Я, в таких случаях говорю, что главный принцип программирования – это метод «здравого смысла». По крайней мере, в самых сложных случаях, в моей практике, именно он меня выручал, а не новомодные принципы программирования.
Однако, если вы перейдете на «тяжёлые» фреймворки, вроде, MFC, wxWidgets, Win32++ или Qt, то там уже надо «вписываться» в их модель разработки, подчинять их программной логике. Иногда, это сильно облегчает жизнь, как в случае «1С» (особенно, «семерки»), иногда, наоборот, страшно ограничивает.
Короче говоря, программная логика принципиально завязана ни решаемые задачи и инструменты, используемые для этого. Поэтому, общий подход здесь вряд ли будет слишком технологичен. Только самые общие принципы программирования, скажем, минимальность кода, его уникальность, оптимальное структурирование и хорошее оформление, комментирование и документирование. А самое главное – здравый смысл.
Раз уж подняли эту тему, а никого никогда не смущает, что в более-менее традиционной слоистой архитектуре приложений, любое изменение структуры данных требует изменений почти на всех слоях?
Пример: допустим, есть какая-нибудь CRM. Бизнес захотел, чтобы в карточке клиента появился аттрибут "персональный менеджер". Для этого нам нужно:
1. Добавить поле в БД, написать миграцию для этого.
2. Добавить работу с этим полем в 3 SQL-запроса (insert, update, select) или в аналогичные выражения ORM
3. Добавить атрибут в объект данных (entity), возможно не в один
4. В зависимости от архитектуры, изменить интерфейсы и добавить новую логику в несколько слоев бэкенда: репозиторий, сервис, контроллер, допилить валидацию
5. Расширить API бэкенда
6. Дополнить еще пару слоёв во Фронтенде
7. И только теперь добавить в UI новый элемент.
8. Дописать тесты
А ведь бизнес с такими простыми запросами приходит каждую неделю.
Если CRM не на WEB то все гораздо проще. Иногда WEB это либо дань моде или даже человеческий фактор, программисты не умеють писать не на WEB. Отсюда ненужные трехзвенки, PHP и пр. пр. пр.
Сугубо десктопные CRM - это какая доля рынка? 1% хотя бы есть?
Отсюда ненужные трехзвенки
А как вы представляете архитектуру CRM для хотя бы двух пользователей? А для 1000, в разных городах, странах, языках и часовых поясах?
Просто Web подходит не для всего. Некоторые задачи удобнее решать с помощью desktop. Это на текущем уровне развития технологий конечно.
Решение вроде как есть - модуляризация на основе сущностей предметной области (Клиенты, Менеджеры, Назначения). Но внутри-то тоже надо как-то управлять сложностью, в итоге тоже будет некоторый аналог слоистой архитектуры, с множеством абстракций, дублированной логики из соседних доментов и проч.
Как решение, это переход к другой парадигме разработки ПО, уход от традиционных способов управления сложностью. Возможно какие-то универсальные системы (платформы, не ЯП), которые смогут работать с доменами предметной области любой сложности. Внутри динамические системы, которые автоматически определяют параметры хранения данных, логику обработки и способ отображения данных. Что-то типа такого на уровне хранения.
Есть такой давнишний архитектурный концепт как Аспектно-ориентированное программирование, но в реальных проектах я за 20 лет его почти не встречал. В своих личных небольших проектах частично использовал - отличная концепция, особенно для бизнес-приложений и информационных систем, где сущности плодятся и разрастаются со страшной скоростью
Пример: допустим, есть какая-нибудь CRM. Бизнес захотел, чтобы в карточке клиента появился аттрибут "персональный менеджер".
Если рассматривать Ваш пример не как учебный, а как реальный, то получается, что на этапе составления требований упустили очевидное бизнес-правило: когда у каждого клиента должен быть свой персональный менеджер.
Для этого нам нужно:1. Добавить поле в БД, написать миграцию для этого.
А что, если содержимое карточки описывается подчинённой таблицей, где каждая строка соответствует отдельному полю карточки? Вместо того, чтобы добавлять новое поле в существующую таблицу, Вы в специальном редакторе полей карточки добавляете новое.
2. Добавить работу с этим полем в 3 SQL-запроса (insert, update, select) или в аналогичные выражения ORM
Если у нас есть таблица полей (из предыдущего пункта), то мы может всегда использовать один и то же запрос с группировкой. Добавив новое поле в карточку, мы просто увеличим на одну позицию длину списка для каждой группы. При этом логика работы не изменилась.
3. Добавить атрибут в объект данных (entity), возможно не в один
А что это такое?
4. В зависимости от архитектуры, изменить интерфейсы и добавить новую логику в несколько слоев бэкенда: репозиторий, сервис, контроллер, допилить валидацию
Неужели трудно в подавляющих случаях строить интерфейс на лету. Вернёмся к списку полей карточки клиента. Что мешает один описать всю логику работы с данным полем (персональный менеджер) в одном месте?
5. Расширить API бэкенда
Интересно, что Вы имеете здесь в виду? Возможность организации запросов вида "Все действующие (персональные) менеджеры", "История работы персонального менеджера" и "История взаимоотношения с клиентом (через посредство работы с одним или несколькими персональными менеджерами)"?
6. Дополнить еще пару слоёв во Фронтенде
Уже было сказано.
7. И только теперь добавить в UI новый элемент.
Уже было сказано.
8. Дописать тесты
Дописывать тесты приходится всегда, когда появляется новая функция. А какие тест-кейсы Вы здесь ожидаете?
на этапе составления требований упустили очевидное бизнес-правило: когда у каждого клиента должен быть свой персональный менеджер.
Вы так говорите, как будто системы проектируют один раз на берегу, и они потом живут годами долго и счастливо без изменений. Так не бывает. В конкретном примере может быть так, что при первичном внедрении понятие "персональный менеджер" просто отсутствовало в бизнесе. А потом появилось. Реальный живой бизнес динамичен, он меняется и подстраивается под рынок, клиента, владельцев. IT-системы должны быть не препятствием в этом, а помощником.
А что, если содержимое карточки описывается подчинённой таблицей, где каждая строка соответствует отдельному полю карточки?
А если нет? И одной подчиненной таблицей вы не обойдетесь, т.к. поля могут быть разного типа данных (в том числе ссылочные, следовательно надо обеспечить ссылочную целостность). Можно конечно сделать "широкую" таблицу со всевозможными типами. Или хранить всё в строках, или в JSONах. Но с такими подходами рано или поздно вас ждет инженерный ад - посмотрите на Битрикс (недавно была статья), там как раз так сделано.
> Расширить API бэкенда
Интересно, что Вы имеете здесь в виду?
Фронтенд (веб-морда) как-то общается с бэк-эндом (сервером), верно? Это называется API. Чтобы фронт получил/записал данные нового поля, в API оно должно появиться, верно?
Неужели трудно в подавляющих случаях строить интерфейс на лету.
Хорошо бы. Но в команде есть UI/UX специалист, который думает иначе, и проектирует интерфейсы исходя из удобства пользвателя а не удобства программиста.
А какие тест-кейсы Вы здесь ожидаете?
Банальный CRUD с новым полем и валидацию, хотя бы.
В моем комплексе (больше 5 млн строк, больше 50 приложений) основное правило такое: размер модуля не должен превышать 1000 строк. Если превышает, то такой модуль кандидат на разбиение. Таких модулей 95%. Есть и исключения по 2-4 тыс строк. Естественно, модули группируются с учетом назначения и архитектурного слоя. Но в одном модуле может быть несколько связанных или однородных классов. Наша особенность - модули могут использоваться повторно в разных приложениях и сочетаться в них в разных комбинациях. Как показал опыт, такой подход оптимальный для поддержки, рефакторинга и уменьшения зависимостей.
Раньше в легаси у нас были модули по 10-15 тыс строк, поддерживать такое был ад.
Допускаю , что в других кейсах, например какая-нибудь стабильная неделимая библиотека с сильно связанными классами, поставляемая пользователям в исходниках, могут быть другие критерии
В моем комплексе (больше 5 млн строк, больше 50 приложений) основное правило такое: размер модуля не должен превышать 1000 строк
Довольно прямолинейный подход. Здесь пригодилась бы какая-нибудь подходящая ортогонализация. Как в математике. кто предложит?
размер модуля не должен превышать 1000 строк
Не многовато ли? Почему именно 1000? Меня лично начинают напрягать модули больше 200 строк.
Намечен альтернативный подход к реаизации, лишенный этого недостатка
Проверьте текст на ошибки
Дэвид Л. Парнас «О критериях для разбиения систем на модули»