LINUX.ORG.RU

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

 


0

3

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

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

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

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

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

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

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

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

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

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

★★★★★

Ответ на: комментарий от den73
// A.java
package test;

public class A {
    public B b;

    public int f(int x) {
        return b.f(x);
    }
}

// B.java
package test;

public class B {
    public A a;

    public int f(int x) {
        return x == 0 ? 1 : x * a.f(x - 1);
    }
}

// Test.java
package test;

public class Test {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
        System.out.println(a.f(5));
    }
}
Legioner ★★★★★
()
Ответ на: комментарий от anonymous

Частокол из проверок #ifdef #ifndef #def никто не отменял...

Во-первых, есть #pragma once, во-вторых, они не имеют прямого отношения к разговору, в-третьих, судя по тому, что ты написал #def вместо #define ты не пишешь на С и С++=)

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

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

Модули изменились не только по оформлению, но и по смыслу. Дублирование служит не только для чтения, но и для обезпечения одинаковости интерфейса во всех использованных модулях. И как раз для решения проблемы рекурсивных ссылок.

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

Считалось очевидным, потому что люди писали код в текстовых редакторах

Люди и сейчас пишут в текстовых редакторах. Правда на некоторых языках, с засилием public static void bla, это не очень удобно, но таки пишут. И язык не должен затачиваться под то, как будут писать на нем код.

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

Я так понимаю, package - это и есть модуль? Тогда это пример как раз решения циклических ссылок в рамках одного модуля. Интерфейс состоит из трёх классов A,B,Test. А когда из станет не 3, а 303, будет несколько неудобно работать с таким пакетом. Вот я и хочу разбить 303 на 150 и 153. 150 в парсере оператора и 153 в парсере выражения.

Интерфейсы их друг на друга не ссылаются. А реализации - ссылаются. То, что делается в Pascal и пока я не понял, делается ли в Java.

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

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

feofan ★★★★★
()

Я в питоне делал так:

module = None

def foo():
    global module
    if module is None:
        import module
Да, руками. Говно, короче, безусловно.

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

Я так понимаю, package - это и есть модуль?

Модуль это файл. package это логически связанные модули. Ну и импорты можно не писать, если классы в одном пакете.

Тогда это пример как раз решения циклических ссылок в рамках одного модуля. Интерфейс состоит из трёх классов A,B,Test. А когда из станет не 3, а 303, будет несколько неудобно работать с таким пакетом. Вот я и хочу разбить 303 на 150 и 153. 150 в парсере оператора и 153 в парсере выражения.

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

// a/A.java
package a;

import b.B;

public class A {
    public B b;

    public int f(int x) {
        return b.f(x);
    }
}

// b/B.java
package b;

import a.A;

public class B {
    public A a;

    public int f(int x) {
        return x == 0 ? 1 : x * a.f(x - 1);
    }
}

// test/Main.java
package test;

import a.A;
import b.B;

public class Test {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
        System.out.println(a.f(5));
    }
}
Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от feofan

Уменьшение связанности и увеличение связности упрощает понимание.

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

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

Если это точно будет работать, то можно сделать вывод, что проблема именно в криворуких разработчиках современных языков и на этом закрывать тему. Ты уверен, что это будет работать?

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

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

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

Я обновил сообщение. Этот код компилируется и работает так, как ожидается. Я не вижу никакой технической причины, по которой рекурсивные зависимости могли бы не работать.

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

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

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

Возможно. Я столкнулся всего один раз с подобной ситуацией и, честно говоря, особо не разбирался, сделал, чтобы работало.

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

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

Спасибо, но мне это незачем. Меня устраивает двусторонняя. Если она по сути двусторонняя, то я только насилием сделаю её односторонней. Она останется двусторонней по сути (топологию не изменишь), просто появятся лишние сущности, которые будут нужны, чтобы выразить её на одностороннем языке. И эти сущности затруднят понимание, тестирование и т.п. Легко ли подсчитать, что будет в конечном итоге за цикл поддержки больше: выигрыш от одностороннести или проигрыш от лишних сущностей? Я не знаю. А вот сроки сдачи версии это явно затянет.

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

Если два модуля не могут быть использованы друг без друга, то на самом деле это один модуль.

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

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

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

Спасибо, ты настоящий товарищ! Ты решил мой вопрос.

Я не вижу никакой технической причины, по которой рекурсивные зависимости могли бы не работать.

Её и нет. Я просто подумал, что раз от этого ушли, то это могло быть чем-то объективным вызвано и что есть неубиваемая идеологическая причина.

Единственное, требуется два прохода по файлу или что-то эквивалентное этому, поскольку нужно сначала проверить все интерфейсы, а потом уже все реализации. Но это не мешает тому же Дельфи компилироваться со страшной скоростью.

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

Ещё один возможный нюанс - сложности с тем, что автоматически определяется процесс загрузки модулей. В том же Дельфи есть initialization и finalization. Иногда бывают проблемы из-за порядка вызова. Здесь как раз понадобилась бы иерархическая структура, в которой было бы чётко ясно, кто за кем инициализируется. Впрочем, при направленной зависимости интерфейсов это достигается. В Java это, видимо, не достигается, для меня это перебор с гибкостью.

Вывод: рекурсивно зависимые модули - это хорошо и нужно. Ставлю галочку. Всем спасибо за участие!

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

Такой маргинальный, незначимый язычок.

к сожалению, значимый

Действительно, давайте упраздним файлы *.h и оставим только *.c.

Кстати, несколько лет назад для плюсов предлагали концепцию модулей, уж не знаю, как оно там щас

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

Если она по сути двусторонняя, то я только насилием сделаю её односторонней.

По сути у тебя один модуль. И ты насилием пытаешься сделать из него два и получаешь закономерные проблемы.

feofan ★★★★★
()

Единственная причина, по которой в Си файлы делятся на .c и .h заключается в том, что так глобальная область видимости не загрязняется структурами, функциями и переменными, которые используются для реализации интерфейса, но при этом не являются необходимыми для его описания. Введение namespace-ов в каком-либо виде искореняет эту проблему.
P.S. Циклические ссылки не нужны.

userd
()

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

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

Мужики, я что-то не понял, автор понимает вообще, что он пишет? Что там линкер собирает, и как связать линкер и заголовочники?

Мне кажется,это новый анонимус

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

Зарегистрируйся.

Аргумент уровня «ты — хуй». Не пойти ли бы тебе попилить sbcl под андроид, а?

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

К слову: на уровне языка проблемы нет, но модули-проекты есть и у сборщика — мавена.

Проекты собираются в артефакты-jar-ки поодиночке, так что взаимнорекурсивных проектов делать нельзя: никто не сможет первым скомпилироваться. В репозитории нет его визави, и поэтому сам не может попасть в хранилище.

anonymous
()
Ответ на: комментарий от I-Love-Microsoft

В C++ такая проблема есть? class B и всё - можешь его юзать из A.

Если ты пишешь реализацию в .h файле (например это шаблонный класс), вроде не получится так сделать. Покажи, как сделать аналогичный моему код на C++, где A и B это классы с шаблонным параметром.

На практике — да, большой проблемы нет. Явное разделение на интерфейс и реализацию убирает все проблемы с рекурсией.

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

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

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

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

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

den73 с другого компа

Господа. Я прошу прощения. Я свой вопрос решил, у меня нет ресурсов дальше дискутировать. Но я от вас что-то получил, надо бы что-то отдать. Постараюсь изо всех сил.

Мнение многих здесь состоит в том, что рекурсивные ссылки не нужны.

Тогда вам домашнее задание. Посмотрите, сколько кода написано на С++ и Яве, а сколько на Го, Питоне и Расте вместе взятых. Вот когда отношение будет в пользу трёх последних языков, можно будет сказать, что они отвечают требованиям реальности. Пока об этом говорить рано.

Да и на Дельфи не надо смотреть свысока. Это вполне прагматичный язык и на нём тоже написано много полезного кода.

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

Вы лучше сами подумайте для себя, что запрещает модулям быть рекурсивно связанными. Даже если мы рассмотрим требование слабой связанности, то «слабая» и «взаимно рекурсивная» - это разное. Что такое «слабость» связей? Прежде всего, это малое количество и малая сложность связей. Взаимная рекурсия не обязательно сложна. А однонаправленная связь может быть настолько сложной, что нельзя будет говорить о «слабости».

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

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

Объясните себе (не мне), почему наличие взаимной рекурсии является препятствием для декомпозиции. Оно не является. Я пытался приводить примеры. Могу ещё один пример привести. Клиент-сервер. Во многих клиент-серверных технологиях есть понятие «обратный вызов». Если отбросить детали реализации, то обратный вызов - это взаимная рекурсия между системами, которые могут быть расположены на совершенно разных планетах. Объясните теперь себе (не мне), почему это один модуль.

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

И всегда помните: книга - это не только источник знаний, а ещё и инструмент власти над миром. Два примера: Библия и Капитал.

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

Думайте своей головой и да пребудет с вами Бог!

anonymous
()
Ответ на: den73 с другого компа от anonymous

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

ОПРЕДЕЛЕНИЕ.

anonymous
()
Ответ на: den73 с другого компа от anonymous

Объясните себе (не мне), почему наличие взаимной рекурсии является препятствием для декомпозиции. Оно не является. Я пытался приводить примеры. Могу ещё один пример привести. Клиент-сервер. Во многих клиент-серверных технологиях есть понятие «обратный вызов». Если отбросить детали реализации, то обратный вызов - это взаимная рекурсия между системами, которые могут быть расположены на совершенно разных планетах.

Во всех нормальных архитектурах сервер и клиент вообще не связаны никак и ничего друг о друге не знает. За взаимодействие отвечает отдельный элемент, в отдельном модуле, да. И никакой рекурсии.

Теперь ответь сам для себя - какой смысл не выделять функционал в отдельный модуль, если можно выделить? Какой смысл не уменьшать связность, если можно уменьшить?

anonymous
()

[Slowpoke]

Вопрос, на самом деле, поднят хороший. Но ТС явно не пытался разобраться в коде, функции которого равномерно разнесены по N файлам, и периодически дёргают друг друга, завися каким-то образом от контекста файла, в котором определены :(

vzzo ★★★
()
Ответ на: den73 с другого компа от anonymous

OMG, сколько патетики. Особенно радует позиция «не читал, но осуждаю».

Посмотрите, сколько кода написано на С++ и Яве, а сколько на Го, Питоне и Расте вместе взятых.

А теперь посмотрите на возраст всех этих инструментов.

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

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

ТС - герой-одиночка. У него нет такой цели «облегчить чтение своего кода».

feofan ★★★★★
()

Циклические зависимости свидетельствуют об одном - это бардак в голове разработчика!

AF ★★★
()

Разделение интерфейса модуля и реализации - полезно(и жаль, что многие новые языки делают это плохо).

Циклические зависимости - это почти всегда плохо (при чтении и при его модификации ты наешься). И почти всегда этого можно избежать с пользой для архитектуры.

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

Прогресс зашел слишком далеко. Хотя для луа (другие я со временем перестал юзать) проблемы не существует, т.к. там нет понятия модуля(ц)(тм) из коробки, а есть однократный загрузчик из набора путей, и окружения, которые легко расшарить по произвольному ключу, например. Немного переписав дефолтный setfenv все становится тривиальным.

Тоже всегда раздражало, что файл стал какой-то значимой единицей в понятии модуля.

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

Вот тут:

Почему парсер сложен объективно

позволю с тобой не согласиться.

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

Вдумчивая декомпозиция полностью самопального парсера приведет тебя к конструкции типа паттерна «интерпретатор», которая довольно интуитивна, модульна и тд и тп.

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

Я так понимаю, ты делаешь простейший вариант разбора - top-down, который сводится к написанию 100500 процедур типа Expr_N(input, context) Ну так ничего не мешает организовать твой код в соответствии с паттерном интерпретации, который реализуем в хуевой туче ЯП.

impfp
()

den73, если бы вы привели пример программы на Паскале, у обсуждения появился бы шанс стать предметней. Какая-нибудь самая простая программа, которая берет что-нибудь из stdin и возвращает что-нибудь в stdout, и при этом решает задачу, которую на Паскале можно правильно решить, а на современных языках — нет.

Без кода ваше исходное сообщение выглядит просто как провокация флейма.

Anatolik ★★
()

Если ты так упарываешься по старым идеям, то SRP как бы никто не отменял https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html , а по нему выходит что циклические ссылки — очевидное говно.

У меня сейчас прям перед глазами большой питонячий проект с рекурсивными зависимостями и все упирается в то что авторы были мудаками и не понимали что делали, выделение общих более мелких модулей как раз решит проблему.

A1
()
Ответ на: комментарий от arturpub

Я с этим и не спорил. В go модуль может состоять из многих файлов, я об этом уже тут писал.

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

Вдумчивая декомпозиция полностью самопального парсера приведет тебя к конструкции типа паттерна «интерпретатор», которая довольно интуитивна, модульна и тд и тп.

Извини, но это ни о чём. Что такое «конструкции типа паттерна «интерпретатор»», я должен телепатически постичь?

http://www.codelab.ru/p/interpreter/#key сообщает нам: «Паттерн интерпретатор не поясняет, как создавать дерево, то есть разбор выражения не входит в его задачу.»

Вдумчивая декомпозиция полностью самопального парсера

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

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

если бы вы привели пример программы на Паскале

Я привёл примеры ситуаций из жизни.

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

Оно уже прошло, прекрасное предметное обсуждение.

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