Category Archives: .NET

Проблемы с зависимостями в библиотеках

Written by elwood

Все мы пишем библиотеки и библиотечки. Туда складывается реюзабельный код, который сам по себе использует самые разные зависимости, начиная со стандартных библиотек и библиотек логирования и заканчивая навороченными зависимостями, реализующими редко используемую функциональность. В результате “модуль” обрастает кучей зависимостей, и просто так взять и использовать его в разных проектах становится не так уж и удобно. Приходится разделять такой “модуль” на несколько, каждый из которых зависит от своих библиотек. А тут еще и проблемы с версионностью (в одном проекте поменяли – в другом тоже нужно обновлять). И пусть даже с этой проблемой можно справиться (с использованием DVCS, поддерживающих subrepositories), но в любом случае в конечном итоге всё это становится сложно поддерживаемым.

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

Немного о фреймворках

Written by elwood

Обычно считается, что фреймворки для разработки полезны тем, что предоставляют возможность программисту пользоваться некой готовой инфраструктурой. То есть основной плюс – в том, что ДОБАВЛЯЮТСЯ ВОЗМОЖНОСТИ использовать готовое. Но мне в последнее время кажется, что главное преимущество даже не в этом, а в том, что фреймворки, как правило, помимо дополнительных возможностей также накладывают существенные ограничения на код, который будет в рамках него работать.

Пример – Spring MVC предлагает только несколько стандартных способов написать обработчик запроса. Хочешь обработать запрос – пропиши маппинг и добавь метод в контроллер. Хочешь датабиндинг – используй @ModelAttribute. Почему это хорошо ? Потому что разработчику нужно меньше шевелить мозгами, раздумывая над тем, как бы лучше запрограммировать этот кусочек. Больше времени останется на продумывание более важных вещей. Плюс к этому, все, кто знакомы со Spring MVC, будут с первого взгляда понимать то, что хотел сказать программист Вася, написавший этот код года два назад. Значит, добавление ограничений положительно влияет на процесс разработки ? Получается, так.

Действительно, имея широкий набор возможностей, тяжело научиться их правильно использовать. Причем, как правило, сначала учишься использовать правильно, набивая шишки, и попутно придумываешь правила сам. Некий кодстайл, паттерны для того, чтобы писать корректный код, не думая о деталях. Так я учился в своё время языку С++. Читая описание того, что такое header-файл, я нигде не мог найти аргументированных правил по его использованию. Сейчас такая литература появилась в изобилии, но в то время у меня еще не было интернета. И я долгое время не знал, как оформлять h-файлы и как их инклудить в cpp – что именно должно быть в h-файле, а что – в cpp, можно ли инклудить h-файл в другой h-файл; если cpp-файл использует h-файл, зависящий от другого, то нужно ли в cpp-файл включать эту зависимость итд. Поэтому сидел и морщил ум на тему, как же сделать правильно. А всё потому, что С++ предоставляет множество способов сделать это НЕправильно, а очевидности, очевидные для опытных программистов, далеко не так очевидны для новичков. Аналогичные шишки приходилось набивать и на других платформах. C#, к примеру, заставил меня призадуматься о стратегии обработки исключений и особенно о корректной реализации IDisposable в иерархии классов.

Поэтому хороший фреймворк должен не только предоставлять пачку возможностей, но и набор несложных правил о том, как, собственно, писать код. Чтобы лезть в декомпилятор/исходники/дебагер приходилось как можно реже. Codestyle & FAQ – идеальные форматы для такой информации.

NBox и проблема с App.config

Written by elwood

Как известно, CLR при запуске .NET-приложения пытается прочитать config-файл с именем запускаемой сборки + расширение “.config”. Но ведь если запускаемый exe-файл переименовать, то config-файл не подгрузится – и приложение, возможно, перестанет работать. С этим частенько сталкиваются разработчики при работе над деплоингом приложения. И, к сожалению, .NET не содержит способов, позволяющих самому приложению задать имя config-файла во время некой процедуры “прединициализации”. Это возможно лишь для опций, которые читаются классом Settings путем написания собственного SettingsProvider’a, реализующего интерфейс IApplicationSettingsProvider (см Application Settings Architecture для более подробной информации).

Но мы можем сделать самостоятельно из нашего приложения некий “микрозагрузчик”, который создает AppDomain, конфигурирует его и запускает сам себя, используя сконфигурированный AppDomain. Это действительно работает, причем в самом примитивном случае код очень компактен :

 
 AppDomainSetup setupInfo = new AppDomainSetup();
 string entryAssemblyPath = Assembly.GetEntryAssembly().Location;
 string entryAssemblyDir = Path.GetDirectoryName(entryAssemblyPath);
 setupInfo.ApplicationBase = Path.GetDirectoryName(entryAssemblyPath);
 setupInfo.ConfigurationFile = Path.Combine(entryAssemblyDir, "MyApp.exe.config");
 
 AppDomain forkedDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, setupInfo);
 forkedDomain.ExecuteAssembly(Assembly.GetEntryAssembly().Location);
 AppDomain.Unload(forkedDomain);

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

А теперь подумаем, что произойдет, если у приложения, обработанного с помощью NBox (а это, как правило, один-единственный исполняемый файл), изменить имя файла. Если приложение не использовало config-файлы, то все будет ок. Но в противном случае – при запуске – CLR просто не подцепит старый конфиг, и приложение, скорее всего, работать не будет. Но, используя вышеприведенные приемы, мы можем заставить NBox распознать config-файл, и перезапустить самого себя, предварительно сконфигурировав AppDomain нужным config-файлом. А чтобы работа по разворачиванию файлов и сборок не производилась дважды, NBox может извлечь файлы во время начального запуска, и, перезапустившись, уже не извлекать файлы, работая со сборками. Таким образом можно минимизировать потери производительности, одновременно получая еще одну степень свободы в том, как сконфигурировать запускаемое приложение.

В принципе, теперь даже не обязательно извлекать config-файл рядом с exe-файлом упакованного приложения. NBox может закешировать извлекаемые config-файлы во временной директории, извлекая их заново в случае необходимости. Для пользователя это означает минус один файл, создаваемый в каталоге приложения, а для разработчика – уверенность в том, что его конфигурация будет применена независимо от названия exe-файла.

PS. Интересно, что метод AppDomainSetup.SetConfigurationBytes, который бы мог избавить нас от использования файлов в таких случаях, похоже, просто не работает 🙂 Здесь тред об этом : AppDomainSetup.SetConfigurationBytes has no visible effect on configuration…