Подтвердить что ты не робот

Как понять общую картину в связном приложении?

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

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

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

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

Здесь строка кода из введенного пользователем класса обслуживания: -

  // myExpiryCutoffDateService was injected, 
  Date cutoff = myExpiryCutoffDateService.get();

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

Вот что это могло бы выглядеть в более сложном приложении.

  ExpiryDateService = new ExpiryDateService();
  Date cutoff = getCutoffDate( databaseConnection, paymentInstrument );

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

Я нахожу код первого стиля сложнее, чем код второго стиля.

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

Кто-нибудь еще испытывает эту проблему? Какие у вас решения? Это что-то, к чему можно приспособиться? Существуют ли какие-либо инструменты для визуализации того, как классы связаны друг с другом? Должен ли я делать классы более крупными или более связанными?

(Умышленно оставил этот вопрос контейнером-агностиком, поскольку меня интересуют ответы для любого).

4b9b3361

Ответ 1

Пока я не знаю, как ответить на этот вопрос в одном абзаце, я попытался ответить на него в блоге вместо: http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx

Подводя итог, я считаю, что наиболее важные моменты:

  • Для понимания слабосвязанной базы кода требуется другое мышление. Хотя труднее "переходить к сотрудникам", оно также должно быть более или менее неуместным.
  • Свободная связь - это понимание части без понимания всего. Вам редко нужно понимать все это одновременно.
  • При обнулении ошибки, вы должны полагаться на трассировки стека, а не на статическую структуру кода, чтобы узнать о коллабораторах.
  • Ответственность разработчиков заключается в написании кода, чтобы убедиться, что его легко понять - это не ответственность разработчика, читающего код.

Ответ 2

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

Я использую Visual Studio и пользовательскую структуру, поэтому описанная вами проблема - это моя жизнь. В Visual Studio SHIFT + F12 - мой друг. Он показывает все ссылки на символ под курсором. Через некоторое время вы привыкаете к обязательно нелинейной навигации через свой код, и становится во-вторых, думать о терминах "какой класс реализует этот интерфейс" и "где находится сайт для инъекций/конфигурации, чтобы я мог видеть, какой класс используется для удовлетворения этой зависимости интерфейса".

Существуют также расширения, доступные для VS, которые предоставляют усовершенствования пользовательского интерфейса, чтобы помочь с этим, например Производительность Power Tools. Например, вы можете навести курсор мыши на интерфейс, появится информационное окно, и вы можете нажать "Реализован", чтобы увидеть все классы в вашем решении, реализующие этот интерфейс. Вы можете дважды щелкнуть, чтобы перейти к определению любого из этих классов. (Я все равно обычно использую SHIFT + F12 в любом случае).

Ответ 3

У меня просто была внутренняя дискуссия об этом, и в итоге я написал эту пьесу, которая, я думаю, слишком хороша, чтобы не делиться. Я копирую его здесь (почти) неотредактированным, но, несмотря на то, что он является частью более крупного внутреннего обсуждения, я думаю, что большая часть его может стоять в одиночестве.

Обсуждается введение пользовательского интерфейса под названием IPurchaseReceiptService и следует ли его заменять с помощью IObserver<T>.


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

public interface IPurchaseReceiptService
{
    void SendReceipt(string transactionId, string userGuid);
}

Если мы сохраним его как Header Interface, в данный момент он имеет только один метод SendReceipt. Это круто.

Что не так классно, так это то, что вам пришлось придумать имя для интерфейса и другое имя для этого метода. Там немного перекрывается между двумя: слово Receipt появляется дважды. IME, иногда это перекрытие может быть еще более выраженным.

Кроме того, имя интерфейса IPurchaseReceiptService, что также не очень полезно. Суффикс службы, по сути, является новым Менеджером и является IMO, дизайнерским запахом.

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

public EvoNotifyController(
    ICreditCardService creditCardService,
    IPurchaseReceiptService purchaseReceiptService,
    EvoCipher cipher
)

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

Теперь сравним это с использованием известного интерфейса, такого как IObserver<T>:

public EvoNotifyController(
    ICreditCardService creditCardService,
    IObserver<TransactionInfo> purchaseReceiptService,
    EvoCipher cipher
)

Это позволяет вам избавиться от бюрократии и уменьшить ее суть. У вас все еще есть намерение, показывающее имена - вы просто смещаете дизайн с Тип подсказки роли типа на Аргумент имени Роль Подсказка.


Когда дело доходит до обсуждения "несвязанности", я не подозреваю, что использование IObserver<T> волшебным образом устранит эту проблему, но у меня есть другая теория об этом.

Моя теория заключается в том, что причина, по которой многие программисты находят программирование для интерфейсов настолько сложными, - это именно то, что они используются в Visual Studio. Перейти к функции определения (кстати, это еще один пример того, как инструмент гниет ум). Эти программисты постоянно находятся в состоянии разума, где им нужно знать, что "с другой стороны интерфейса". Почему это? Может быть, потому, что абстракция плохая?

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

Однако, если вы примените RAP, я надеюсь, что медленно, программисты узнают, что за конкретным интерфейсом может существовать какая-либо реализация этого интерфейса, и их клиентский код должен иметь возможность обрабатывать любую реализацию этого интерфейса без изменения правильность системы. Если эта теория верна, мы только что внесли "Принцип замещения Лискова" в базу кода, не пугая кого-либо с понятиями с высокими бровями, которые они не понимают:)

Ответ 4

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

Это неточно. Для каждого класса вы точно знаете, с какими объектами зависит класс, чтобы обеспечить его функциональность во время выполнения.
Вы знаете их, так как вы знаете, какие объекты должны быть введены.

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

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

Вы также можете использовать средства, предоставляемые вашей IDE.
Поскольку вы ссылаетесь на Eclipse, тогда Spring имеет плагин для него, а также визуальную вкладку, отображающую настроенный beans. Вы это проверили? Разве это не то, что вы ищете?

Также ознакомьтесь с тем же обсуждением в Spring Forum

UPDATE:
Повторяя ваш вопрос, я не думаю, что это реальный вопрос.
Я имею в виду это следующим образом. Как и все вещи loose coupling не является панацеей и имеет свои собственные недостатки как таковые.
Большинство из них, как правило, сосредоточено на преимуществах, но, как и любое решение, оно имеет свои недостатки.

То, что вы делаете в своем вопросе, описывает один из его основных недостатков, который заключается в том, что на самом деле не так просто увидеть большую картину, поскольку у вас есть все, настраиваемое и подключенное к /STRONG > .
Существуют и другие недостатки, которые можно было бы жалобы, например. что он медленнее, чем приложения с жесткой связью, и по-прежнему остается верным.

В любом случае повторная итерация, то, что вы описываете в своем вопросе, не является проблемой, на которую вы наступили, и можете найти стандартное решение (или любое другое для этого).

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

Это похоже на вопрос:
Эй, я использую этот шаблон с именем Singleton. Он отлично работает, но я не могу создать новые объекты! Как я могу получить эту проблему? Ну, вы не можете; но если вам нужно, возможно, одноэлемент не для вас....

Ответ 5

Одна вещь, которая помогла мне, заключается в размещении нескольких тесно связанных классов в одном файле. Я знаю, что это идет вразрез с общим советом (с 1 классом на файл), и я, как правило, согласен с этим, но в моей архитектуре приложения он работает очень хорошо. Ниже я попытаюсь объяснить, в каком случае это.

Архитектура моего бизнес-уровня разработана вокруг концепции бизнес-команд. Классы команд (простые DTO с только данными и без поведения) определены, и для каждой команды есть "обработчик команд", который содержит бизнес-логику для выполнения этой команды. Каждый обработчик команд реализует общий интерфейс ICommandHandler<TCommand>, где TCommand является фактической бизнес-командой.

Потребители берут зависимость от ICommandHandler<TCommand> и создают новые экземпляры команд и используют обработчик впрыска для выполнения этих команд. Это выглядит так:

public class Consumer
{
    private ICommandHandler<CustomerMovedCommand> handler;

    public Consumer(ICommandHandler<CustomerMovedCommand> h)
    {
        this.handler = h;
    }

    public void MoveCustomer(int customerId, Address address)
    {
        var command = new CustomerMovedCommand();

        command.CustomerId = customerId;
        command.NewAddress = address;

        this.handler.Handle(command);
    }
}

Теперь потребители зависят только от конкретного ICommandHandler<TCommand> и не имеют представления о фактической реализации (как и должно быть). Однако, хотя Consumer не должен ничего знать о реализации, во время разработки я (как разработчик) очень заинтересован в фактической бизнес-логике, которая выполняется, просто потому, что разработка выполняется в вертикальных срезах; что я часто работаю как с пользовательским интерфейсом, так и с бизнес-логикой простой функции. Это означает, что я часто переключаюсь между бизнес-логикой и логикой пользовательского интерфейса.

Так что я сделал, это поставить команду (в этом примере CustomerMovedCommand и реализацию ICommandHandler<CustomerMovedCommand>) в том же файле с первой командой. Поскольку сама команда является конкретной (поскольку ее DTO нет причин ее абстрагировать), прыжок в класс легко (F12 в Visual Studio). Поместив обработчик рядом с командой, переключение на команду означает также переход к бизнес-логике.

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

Конечно, это всего лишь 45% моего бизнес-уровня. Еще один большой мир (скажем, 45%) - это запросы, и они аналогичным образом разрабатываются с использованием класса запросов и обработчика запросов. Эти два класса также помещаются в один и тот же файл, который -again-позволяет мне быстро перейти к бизнес-логике.

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

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

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

Ответ 6

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

Это принцип Intention-Revealing Interface, как указано в Проект с поддержкой домена (http://domaindrivendesign.org/node/113).

Вы можете переименовать метод get:

// intention revealing name
Date cutoff = myExpiryCutoffDateService.calculateFromPayment();

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

Ответ 7

Я нашел The Brain, который будет полезен при разработке как инструмент отображения node. Если вы пишете некоторые скрипты для анализа вашего источника в XML, то Brain принимает, вы можете легко просматривать свою систему.

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

Ответ 8

В зависимости от того, сколько разработчиков работает над проектами, и хотите ли вы повторно использовать некоторые его части в разных проектах, свободная связь может вам очень помочь. Если ваша команда большая, и проект должен охватывать несколько лет, наличие свободной связи может помочь, так как работа может быть назначена различным группам разработчиков более легко. Я использую Spring/Java с большим количеством DI и Eclipse предлагает некоторые графики для отображения зависимостей. Использование F3 для открытия класса под курсором помогает. Как указано в предыдущих сообщениях, вам помогут знания ярлыков для вашего инструмента.

Еще одна вещь, которую следует учитывать, это создание пользовательских классов или оболочек, поскольку их легче отслеживать, чем обычные классы, которые у вас уже есть (например, Date).

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

Ответ 9

Документация!

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

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

Документация по API
APIDoc с хорошей функцией поиска. Каждый файл и - почти - каждый метод имеет четкое описание.

Документация "Большая фотография"
Я думаю, что хорошо иметь вики, которые объясняют общую картину. Боб создал прокси-систему? Как это работает? Выполняет ли он проверку подлинности? Какой компонент будет использовать его? Не полный учебник, а просто место, где вы можете прочитать 5 минут, выяснить, какие компоненты задействованы и как они связаны друг с другом.

Я согласен со всеми пунктами ответа Марка Seemann, но, когда вы впервые попадаете в проект (ы), даже если вы хорошо понимаете принципы, нарушающие принципы развязки, вам нужно будет много угадать, или какую-то помощь, чтобы выяснить, где реализовать конкретную функцию, которую вы хотите разработать.

... Снова: APIDoc и небольшая разработка Wiki.

Ответ 10

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

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

Вы думаете, что вам нужно перемещаться по всем зависимостям от вашей среды разработки? Забудь об этом! Это та же ситуация, что и в случае компиляции и времени выполнения. Во время компиляции можно обнаружить какую-либо ошибку, вы не можете быть уверены, работает ли она, если вы ее не протестируете, а это значит ее выполнить. Хотите узнать, что стоит за интерфейсом? Поместите точку останова и запустите проклятое приложение.

Аминь.

... обновлено после комментария...

Не уверен, что он будет служить вам, но в Eclipse есть нечто, называемое представлением иерархии. Он показывает вам все реализации интерфейса в вашем проекте (не уверен, что и рабочее пространство). Вы можете просто перейти к интерфейсу и нажать F4. Затем он покажет вам все конкретные и абстрактные классы, реализующие интерфейс.

The hierarchy view in Eclipse after pressing F4