LINUX.ORG.RU

Модульность или куда катится мир

 


0

3

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

Для интерфейса и реализации отдельно задаётся импорт, т.е. перечень ссылок.

Интерфейсы ссылаются друг на друга, образуя дерево. Реализации могут ссылаться друг на друга, образуя произвольный граф.

И - вуаля! Нет проблемы циклических ссылок. Система сама понинает, что в каком порядке собирать, функции из разных модулей могут вызывать друг друга.

Примерно то же самое, но более мощное, и более грязное, мы видим в С. Роль интерфейса играет заголовочный файл. Заголовочные файлы включаются друг в друга, образуя дерево. В теле файла *.c можно писать что угодно. Так реализации могут циклически ссылаться друг на друга.

Линкер решает проблему ссылок, собирая циклический граф вызовов функций.

В CL, Python и Golang я не вижу этого механизма. Точно могу сказать за CL, что этого механизма нет. Нужно вручную выделять часть, образующую цикл и выносить её в отдельный пакет. В Питоне пишут про какие-то «хаки». В Go предлагают объединить весь код в один модуль или вынести общий интерфейс вверх по иерархии. То, что раньше делал компьютер, нужно делать руками.

Производительность труда разработчика снизилась.

В ИТ отрасли произошла деавтоматизация. А казалось бы, компьютер придуман, чтобы автоматизировать труд, в т.ч. труд разработчика.

Что не так со мной? Или не со мной?

★★★★★

В ИТ отрасли произошла деавтоматизация

ППКС. Щито поделать. Компьютер теперь не инструмент, а виртуальный мирок.

anonymous
()
Ответ на: комментарий от Debasher

Парсер, написанный методом рекурсивного спуска.

parse_expr вызывает parse_plus и обратно.

Отладка принтами:

В strcat может вставить вызов debug_print, а debug_print может быть реализован через strcat (естественно, при входе в реализацию debug_print логгирование временно отключается).

Да, в общем-то, любое место, где функции взаимно рекурсивны и их достаточно много, чтобы было неудобно держать их в одном модуле.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)

Мир катится в сторону выдавливания одиночек и мелких контор.

«И - вуаля!» в паскале порадовало, сами придумали проблему саму решили.

ilovewindows ★★★★★
()
Ответ на: комментарий от ilovewindows

«И - вуаля!» в паскале порадовало, сами придумали проблему саму решили.

И в С тоже? Такой маргинальный, незначимый язычок. Действительно, давайте упраздним файлы *.h и оставим только *.c. Неудобно ведь всё время лазить по двум файлам, правда?

den73 ★★★★★
() автор топика
Ответ на: комментарий от ilovewindows

А, оказывается заголовочные файлы в C, оказывается, придумали, чтобы тебе было удобно по ним лазить! Наконец-то всё выяснилось :)

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Ну да, для чего же еще. Думаешь для того, чтобы ты порассуждал по циклические связи?

ilovewindows ★★★★★
()
Последнее исправление: ilovewindows (всего исправлений: 1)
Ответ на: комментарий от den73

А, оказывается заголовочные файлы в C, оказывается, придумали,

Не заголовочные файлы а #include

включай *.c файлы в любую часть другого с-файла, никто не запрещает.

anonymous
()

Нужно вручную выделять часть, образующую цикл и выносить её в отдельный пакет.

В этом и есть смысл модульности. В той же сишке модульность как раз-таки совершенно никакая.

anonymous
()
Ответ на: комментарий от den73

Да, в общем-то, любое место, где функции взаимно рекурсивны и их достаточно много, чтобы было неудобно держать их в одном модуле.

Если функции взаиморекурсивны до они ДОЛЖНЫ быть в одном модуле. Иначе у тебя что-то очень плохое с архитектурой. Какой разделять ф-и по модулям если она друг без друга работать не могут?

anonymous
()
Ответ на: комментарий от den73

хотя кажись так нет, чот он не разрешает
хотя вообще задача так-то решаема, берёшь какой-нить ссылочный тип, заюзываешь его везде где надо, а потом уже присваиваешь ему функцию

Debasher ★★★★★
()
Ответ на: комментарий от Debasher

хотя вообще задача так-то решаема, берёшь какой-нить ссылочный тип, заюзываешь его везде где надо, а потом уже присваиваешь ему функцию

Да, ещё можно интерфейс определить выше по дереву иерархии, а реализовать ниже (в другом модуле). Но это крайне неудобно и выглядит насилием над природой вещей. Особенно проблематична ситуация, когда есть модуль с сильной связанностью, типа парсера. В С и Паскале я его могу разбить хоть как-то. Части останутся сильно связанными по вызовам, но они будут лежать в разных файлах и тем самым всё-таки разбиение сложной задачи на простые в какой-то степени произойдёт. А современные языки этого не позволяют.

Вот я и хочу понять, это прогресс зашёл слишком далеко или это действительно так должно быть?

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

В С и Паскале я его могу разбить хоть как-то. Части останутся сильно связанными по вызовам, но они будут лежать в разных файлах

А смысл в этом какой? В чем проблема один модуль в несколько файлов рассовать?

anonymous
()
Ответ на: комментарий от den73

можно интерфейс определить выше по дереву иерархии

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

кстати, что мешает заюзать несколько файлов для одного неймспейса?

Debasher ★★★★★
()
Ответ на: комментарий от Debasher

мне кажется это вполне адекватное решение

Только оно усложняет анализ. Самое простое, что есть - это функция. У неё есть одна-единственная точка, где эта функция определена. А пришёл тебе интерфейс. И вдруг что-то не так с ним. И что дальше? Где исходник того, что тебе пришло?

И ещё интерфейс - это сущность виртуальная. Она снижает производительность. Это не всегда уместно.

То есть, если принято решение, что мы всегда должны решать проблемы связанности через интерфейс, то на это должны быть очень серьёзные причины.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

parse_expr вызывает parse_plus и обратно.

Это пишется в одном модуле. Декомпозиция задачи никак не связана с размером модуля.

В strcat может вставить вызов debug_print, а debug_print может быть реализован через strcat (естественно, при входе в реализацию debug_print логгирование временно отключается).

Вся суть костылей.

Да, в общем-то, любое место, где функции взаимно рекурсивны и их достаточно много, чтобы было неудобно держать их в одном модуле.

Круто ты придумал. Поной еще, что сложно сделать взаимно рекурсивные функции, реализованные в разных программах.

C, #include и все-все-все

В С заголовочные файлы вообще придуманы не для взаимно рекурсивных функций. Да, язык позволяет это сделать, но ты делаешь фигню, и все нытье сводится к тому, что не дают делать неправильно.

staseg ★★★★★
()

IMHO тебе понравится Ocaml.

anonymous
()
Ответ на: комментарий от staseg

parse_expr вызывает parse_plus и обратно.

Это пишется в одном модуле.

Ты, конечно, можешь обосновать, _почему_ это должно писаться в одном модуле?

den73 ★★★★★
() автор топика
Ответ на: комментарий от Mahmood

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

200 строк - тривиально!

500 - поднажмём

1000 строк - вздохну и поморщусь, полазаю по интернету, потуплю, а потом всё же примусь разгребать

4000 строк - ну, я попал

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от Mahmood

Во-первых, парсеры сложны и легко вылетают за разумные пределы размеров. Во-вторых, почему я должен делать это руками, если в C и Pascal это мог сделать за меня компилятор? Кто мне за это заплатит?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Код не станет проще от того что ты размажешь высокоуровневую логику кода по нескольким файлам.

KblCb ★★★★★
()
Ответ на: комментарий от KblCb

Код не станет проще от того что ты размажешь высокоуровневую логику кода по нескольким файлам

А я говорю, что станет, если я не размажу, а разложу в соответствии с каким-то принципом. Например, операторы в файл operator.x, выражение - в файл expression.x . Надо обосновывать, чем это проще, или сам догадаешься?

den73 ★★★★★
() автор топика

Да, модули в новых языках стали несколько странными. Раньше необходимость разделения интерфейса модуля от его реализации считалось очевидным. И не только в C и Pascal, но и в трушном ML, например, где есть разделение на сигнатуры и структуры.

anonymous
()
Ответ на: комментарий от den73

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

staseg ★★★★★
()
Последнее исправление: staseg (всего исправлений: 1)
Ответ на: комментарий от anonymous

Если функции взаиморекурсивны до они ДОЛЖНЫ быть в одном модуле. Иначе у тебя что-то очень плохое с архитектурой. Какой разделять ф-и по модулям если она друг без друга работать не могут?

ППКС

feofan ★★★★★
()
Ответ на: комментарий от feofan

ППКС

Обоснования не вижу. У тебя программа без любого одного модуля не соберётся. Модули вообще не нужны?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Модуль должен быть самодостаточным. Циклические зависимости между модулями - зло, которое усложняет понимание.

feofan ★★★★★
()
Ответ на: комментарий от feofan

Модуль должен быть самодостаточным. Циклические зависимости между модулями

Ты сам видишь, что два эти утверждения не являются логическим дополнением друг друга? #include <stdio.h>

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

по нескольким файлам

Ну, кстати, про Go - внутри модуля дели функции по файлам как тебе угодно.

feofan ★★★★★
()
Ответ на: комментарий от den73

Здравый смысл. Если модуль не является логической единицей, то это кусок, хм, кода, а не модуль.

feofan ★★★★★
()
Ответ на: комментарий от den73

Один модуль может зависеть от другого. Это нормальная ситуация. Если два модуля зависят друг от друга - это очень плохая архитектура. Обычно это означает, что человек, который её придумал абсолютно не знает чего хочет.

feofan ★★★★★
()
Последнее исправление: feofan (всего исправлений: 1)
Ответ на: комментарий от den73

Не лучше. Я первый спросил.

Слышал про правильно заданный вопрос? Так вот первым и отвечай – обосновывай необходимость столь тесной интеграции функций разных модулей. Пока твоя аргументация сводится к тому, что ты написал большую кучу говна и хочешь разложить ее по пакетикам.

staseg ★★★★★
()
Ответ на: комментарий от feofan

Если два модуля зависят друг от друга - это очень плохая архитектура.

Кто это сказал? В какой книге это написано, в каком стандарте?

den73 ★★★★★
() автор топика
Ответ на: комментарий от staseg

Слышал про правильно заданный вопрос?

До свидания.

den73 ★★★★★
() автор топика

Паскаль конечно интересный язычок, но как академическое средство.

Интерфейсы не решают проблемы зависимостей. Они хорошо. Да. Но корень проблем ссылок «неосиляторство» и отсутствие ясности. Недопонимание. Тупняк.

Подсказка: есть взаимоисключающие зависимости и прочие ссылки. Решаются так-же как и решение конфликтов при мерджах в [D]VCS. Автоматически можно разрулить, когда учитывается время возникновения ссылок.

И не надо про C и заголовочные файлы - там не все так прозрачно, как может показаться. Частокол из проверок #ifdef #ifndef #def никто не отменял... Работать без спецодежды рискованно.

anonymous
()
Ответ на: комментарий от feofan

Не буду читать. Книга написана в 2005 году. В 2005 году всё уже испортилось. Книга могла быть профинансирована Ораклом или Саном (в Яве ведь вроде бы тоже нет рекурсивно зависимых модулей) или просто отражать воззрения автора на тот момент.

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

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Не буду читать.

Зря.

В двух словах трудно объяснить. Имеешь представление что такое связность и что такое связанность?

feofan ★★★★★
()
Ответ на: комментарий от anonymous

Да, модули в новых языках стали несколько странными. Раньше необходимость разделения интерфейса модуля от его реализации считалось очевидным. И не только в C и Pascal, но и в трушном ML, например, где есть разделение на сигнатуры и структуры.

Считалось очевидным, потому что люди писали код в текстовых редакторах и им нужно было быстро окинуть взглядом интерфейс, не отвлекаясь на реализацию. Сейчас эта проблема решается с помощью IDE. Можно открыть класс, открыть окошко Outline в Idea и увидеть все методы, поля и тд в удобном древовидном виде. Или даже просто нажать автодополнение в нужном месте.

Legioner ★★★★★
()
Ответ на: комментарий от feofan

Имеешь представление что такое связность и что такое связанность?

Определение прочитал.

При проектировании систем необходимо стремиться, чтобы модули имели минимальную зависимость друг от друга, т.е. были минимально «сцеплены» между собой (отсюда и термин «сцепление» или связанность).

Необходимо стремиться - это хорошо. Но есть такое понятие как «компромиссы в разработке систем». Т.е., стремится нужно сразу ко множеству противоречащих друг другу вещей. И ещё не факт что в данном конкретном случае победит именно стремление к минимальной зависимости. Что тогда делать? Почему парсер сложен объективно, а я должен дополнительно страдать?

den73 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.