Pull to refresh
23
0
Николай Григорьев @HexGrimm

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

Send message
Недокументированные интерфейсы

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

Пустил слезу…
Апогей недокументированных интерфейсов — отсутствие интерфейсов
Движок совершенно не тянется. Во всех технологиях такого рода есть stripping, в билд попадает то что используется. И железо тоже совершенно не причем (если не задействованы новейшие технологии, от графического апи например). Если вы говорите про PC платформу, то тем более. Вы в комментарии привели логическую связь между движком UNITY и приложением, которое тормозит. Я считаю что связи такой непосредственно нет. И на UNITY и на UE есть отличные микро-проекты для самых слабых платформ.
Вот так и поддерживаются стереотипы о производительности движков. Забавно такое видеть особенно в комментариях в стиме.
Ведь дело только лишь в том как готовить этот движок, и очевидно что криво написанных игр на юнити гораздо больше чем на многих других движках, так как налепить чего нибудь работающего легче. И UnityTechnologies винить тут совершенно не в чем (если не считать узко технических вопросов).
А вы подключали профайлер к неоптимизированному варианту? Интересно, производительность падала из-за оверхеда вызова Update для большого количества классов, или сам по себе кадр с огромным рулоном из инструкций (Update каждого экземпляра) выполнялся долго.
Я в своей команде подчиненным руки отрывал за синглтоны и статику.
На мой взгляд, статья пестрит вредными советами:
— Дополнительное наследование от BounceElement не есть хорошо, да и получение ссылок таким образом такие же минусы влечет за собой как и Service Locator.
— Зачем держать в сцене классы логики как MonoBehaviour, если можно создать их нормально.
— При росте проекта либо увеличится количество классов контроллера, и появится много связей многие ко многим, так как о всех знают все, либо контроллеры разрастутся и придется перечитывать главу про SOLID.
Для примера того как можно писать, конечно подходит.
Я бы добавил еще одну ключевую разницу между «Наблюдатель (Observer)» и «Посредник (Mediator)». Паттерн «Наблюдатель» не применим если для подписчиков важен порядок прихода сообщений, а паттерн «Посредник» как раз может это регламентировать.
А я вижу в этом хороший плюс для open-source проектов по аннонимизации. Ведь Tor, например, преследует защиту человеческих прав на свободу информации независимо от государства в котором человек находится. Так и крауд-инвестиции привлекутся быстрее, и больше, сеть станет производительнее.
Касательно непосредственно кода:
Приведенный выше код не так уж и удобно читать. В некоторых случаях есть переносы строк и отступы, в некоторых нет. Тоже самое по поводу открывающихся фигурных скобок. По поводу кода на GitHub я предложу исправления, но в статье рекомендую пользоваться чем нибудь общепринятым (Как вариант).
А за статью — спасибо!
А как же быть в ситуации, если человек не раскрывает своего имени в жалобе на контрольно-наздорные органы из-за того что боится других контрольно-наздорных органов?
Проблема хорошо разбирается в:
Refactoring to Patterns (Addison-Wesley Signature Series)
Joshua Kerievsky
Глава 6 (на сколько я помню)
Там написано куда понятнее, чем я бы смог сформулировать.
Если коротко: Для рефакторинга каждого синглтона в проекте вам придется так же модифицировать все классы, которые использовали его. И вместо того чтобы рефакторить класс как черный ящик, или модуль с устаканившимся апи, вам придется менять логику во множестве классов сразу. Один из неприятнейших моментов при этом — если есть синглтоны, значит нет тестов — значит при изменении класса ни кто не гарантирует, что все будет работать — чем больше классов подверглись изменениям, тем больше багов потенциально получится. В добавок, ситуацию усложнит получение данных через вложенные аксессоры. Чем больше вложенность, тем быстрее в команде выявить программиста для увольнения. Если у вас в коде несколько аксессоров, первый из которых статический, то вам придется решить сложную архитектурную задачу уже для множества классов, а не для 2х. Как пример:
var ref = SoundManager.Instance.LastAudioProcessor.DoSmth();

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

В этом я и вижу основной минус паттерна и его ключевую оссобенность. Я не могу сказать что вы сравниваете термины одного уровня. Глобальная точка доступа и есть его недостаток. Хуже становится не Sound Manager'у, а классу, которому приходится содержать в себе указание типа. Попробуйте написать на него тест.
Это оправдано тем, что в вашем проекте DI используется повсеместно, и этот Sound Manager просто удобно вклинить в уже поднятую систему?

Если вы просто передаёте ссылки вниз по дереву зависимостей через конструкторы, это тоже DI, и Sound manager будет работать без контейнера (Если передавать только через конструктор, то у вас в любом случае получится дерево, без связей «многие ко многим») В нашем случае да, мы стараемся не держать конструкторы громоздкими и часть ссылок проставляем контейнером. Множество других модулей используются у нас в том же ключе, и это решается у нас на этапе планирования архитектуры. У нас проектов много, и такая мобильность «модулей» для нас — дополнительное преимущество.
Потому что в случае отдельного заимствования вашего Sound Manager из гитхаба, придётся тащить все интерфейсы и «поднимать» DI в проекте, где его может не быть, либо ради простоты делать ту же глобальную точку доступа.

Интерфейсы Sound Manager'а — это часть модуля, апи для работы с ним. Они и должны храниться в том же namespace, и передаваться в субмодуле. Если вам не хочется использовать DI, но вы хотите сохранить слабую связанность, то тогда хотя бы используйте для получения ссылки на интерфейс Service Locator. Это рекомендация, и конечно, IMHO.
По моему мнению, у вас получился не столько пример Sound Manager, сколько пример использования DI в Unity проектах на примере Sound Manager.

Согласен, я сделал в статье упор именно на рефакторинг, и эти рекомендации применимы и к другим частям приложений. Я считаю правильным, написать о том что способ из реф статьи так же влечет за собой пассивное ухудшение качества кода проекта, и предложил способ как этого избежать. Я сам лично видел раз 5 код проектов, которые выросли из домашней разработки в нечто ценное, и затем тонули в бесконечном рефакторинге при первой же смене требований к продукту, из-за такого использования Singleton, по большей части.
Касательно Singleton lifestyle — в каком случае, по вашему мнению, вы сможете использовать два или несколько разных компоновщиков в вашей игре и чем это может быть оправдано? (компоновщик — это Container, если я правильно понял по контексту?)

Да, компоновщик — это контейнер. В области разработки игр мне ни разу не приходилось использовать более одного. Возможно, имеет смысл разгружать класс, в котором перечислены бинды, тк он должен знать обо всех namespace, классы которых используются и прятать часть биндов ниже по графу ссылок. Но из-за того, что придется создавать дополнительную зависимость от конкретного контейнера в нескольких местах вместо одного, я такого стараюсь не практиковать. Наша история перехода по контейнерам была такая: Strange — Zenject — Ninject.

в статье вы несколько раз приводите доводы касательно удобства тестирования отдельных модулей. Как в вашем случае происходит процесс тестирования? Что конкретно и каким образом вы тестируете в «модуле» Sound Manager?

Для тестирования мы используем набор: Editor Test Runner + NUnit + NSubstitude. Тесты к модулю лежат в директории модуля по доп пути Модуль/Tests/Editor/. Тесты пишем на то, что проверить вручную долго: Сохранение уровней звука и музыки + вкл\выкл между сессиями (сохранение в Prefs), тест значений по умолчанию, корректное использование данных из файла конфигурации, Чистка gameObjects после себя. В случае провала теста все равно чистимся с помощью атрибута с ITestAction из NUnit, если нужно. Вообще, в случае когда у вас и сверху и с низу интерфейсы у «модуля», то потенциально возможно покрытие 100%. CI у нас тесты не учитывает, Unity Cloud Build недавно вынес этот функционал и беты (если вывел), запускаем в ручную до сливания feature и после.
Тесты не делаем для классов, которые уже непосредственно работают с MonoBehaviour или ScriptableObject, так как тесты могут выполнятся долго и стараемся эту прослойку между кодом и юнити делать как можно тоньше.
Некоторые модули пишем по TDD, но для нас это скорее треннинг, чем парадигма.
А риск есть всегда, в вашем случае — полное отсутствие контроля за количеством параллельных звуков и возможность спаунить подсистемы звука, хотя они должны быть единственными

Защититься от дублирования AudioListener в моем случае, не так сложно, и это так же можно инкапсулировать внутри модуля. (Как пример: добавив не публичную статику.) Это уже опционально, так как при регистрации в контейнере этот кейс уже исключается. Действительно, каждый пишет как хочет и это рождает полезные дискуссии.
Под lazy Optimization мы имеем в виду одно и тоже, это так — Отложенная инициализация. И в вашем и в моем случае объект инициализируется непосредственно перед первым обращением к нему. Однако в статье я использую другой термин.
В области действия отдельного компоновщика компонент с жизненным стилем
Singleton ведет себя подобно паттерну проектирования Singleton, но структурно
ситуация отличается. Всякий раз, когда потребитель запрашивает компонент, по-
дается один и тот же экземпляр.
Но на этом сходство заканчивается. Потребитель не может получить через ста-
тический член доступ к зависимости, находящейся в области видимости Singleton,
и если мы запросим экземпляры у двух разных компоновщиков, мы получим два
разных экземпляра
(Внедрение зависимостей в .NET Марк Симан, стр 321, пункт 8.3.1)
Так какой смысл городить весь этот огород, если все в итоге сводится к синглтону?

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

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

А речь идет именно про перенос этого модуля из проекта Unity3D в проект Unity3D. Смена набора технологий это уже совсем другой разговор и статья к нему не относится.

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

Я понимаю, что у вас возможно было такое требование к продукту и мой коментарий именно к вашему проекту не применим. Но вы же согласитесь, что при написании такого апи для работы с SoundController риск человеческого фактора остался, пусть вы и переложили его на дизайнера? (Например: на ранней стадии проекта дизайнер реализует звук для кнопок, а через 2 месяца, забыв про это, делает звук перехода между меню в том же канале.)

И в геймдев пытается пролезть этот ынтерпрайз, печально...

Какое значение имеет эта строка поясните?

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity