“Ручная” локализация

Категории: Разработка ПО
Тэги: , ,

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

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

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

Начнём с инструментации кода. Создадим класс, который будет нашим менеджером ресурсов. Далее создадим в нём функцию загрузки ресурсов из файла:

// Создаем корневое имя ресурса
string resourcesBase = "MyApplication";
 
// Создаем путь к папке с ресурсами
string location = Assembly.GetExecutingAssembly().Location;
location = Path.GetDirectoryName(location);
DirectoryInfo dInfo = Directory.GetParent(location);
 
// Читаем из реестра текущую локаль и добавляем ее в путь
resourcesPath += GetMainProgramLocale();
 
// Создаем сам ResourceManager
m_ResManager = ResourceManager.CreateFileBasedResourceManager(resourcesBase, dInfo.FullName + resourcesPath, null);

Для этого вопспользуемся CreateFileBasedResourceManager, которая подгрузит из папки указанный набор ресурсов и вернет ResourceManager.

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

public string GetString(string strID)
{
    try
    {
        // В самом простом виде, например, так
        return m_ResManager.GetString(strID);
    }
    catch (MissingManifestResourceException e)
    {
        // Обработка исключительной ситуации
    }
    catch (InvalidOperationException e)
    {
        // Обработка исключительной ситуации
    }
    // и т.д.
}

Далее, для удобства билда, создадим bat-файл, который будет упаковывать .resx фалы в бинарные .resource, с помощью утилиты resgen и помещать их в нужный каталог.

call "%VS80COMNTOOLS%..\..\VC\bin\vcvars32.bat"
rem На вский случай вызываем vcvars для корректного вызова resgen
 
resgen MyAppl\Localization\en\MyAppl.en-US.resx MainProgram\loc\en\MyAppl.resources
resgen MyAppl\Localization\ru\MyAppl.ru-RU.resx MainProgram\loc\ru\MyAppl.resources

А запуск этого bat-файла поместим в Post Build Event.

Вот и все. Скрипт отвечающий за сборку менять не надо – у нас есть Post Build Event, а собственный менеджер ресурсов вернет нужный ресурс, подгружая необходимый набор в момент запуска приложения.

Patrick Cauldewell. Политика обработки исключительных ситуаций.

Категории: Игры, Разработка ПО
Тэги: , ,

Перевёл в меру художественных способностей замечательную статью Patrick Cauldewell – Exception handling policy:

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

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

  • Имена методов образуются от глаголов. Сейчас я увлечен применением глаголов и существительных в исходных текстах (что, как я недавно узнал, делает меня приверженцем Домен-ориентированного подхода к проектированию)
    • Если метод подразумевает перевод денег с одного банковского счета на другой, то он должен называться Transfer.
    • Если метод не может выполнить обязательства, наложенные на него отглагольным именем, то он должен возбудить исключение. Т.е. если метод Transfer не может перевести деньги он должен возбудить исключение. Точка.
  • Перехватывай только те исключения, которые ты знаешь как обработать. Конкретизируй.
    • Не перехватывай System.Exception
      • Если ты не знаешь, какого исключения ждать, значит ты не знаешь как его обработать. Отправь его выше.
      • Перехват System.Exception, суть попытка разрешить настоящую исключительную ситуацию, которую ты не предвидел.
  • Реализуй набор исключительных ситуаций специфичный для твоего приложения, не скупись.
    • Разрабатывай исключения настолько точно описывающие ситуацию, насколько это возможно.
      • Если я пишу функцию Transfer, при этом на счете-отправителе недостаточно средств для проведения операции, порождай InsufficientFundsExceprion. Не ArgumentException, ApplicationException или InvalidOperationException.
    • Скрывай исключения описывающие реализацию приложения, оборачивая их в исключения ориентированные на пользователя.
      • Если ты реализуешь конфигурирование системы, и процесс инициализации читает данные из XML-файла настроек, а самого файла при этом не оказалось – не порождай FileNotFoundException. Оберни его в ConfigurationInitializationException.
        • Пользователю наплевать на твои конфигурационные файлы, его волнует ошибка инициализации приложения.
        • При использовании твоего модуля пользователь должен перехватать ConfigurationInitializationException для отслеживания корректности инициализации твоего модуля, а никак не FileNotFoundException.
        • Если ты решил поменять способ инициализации приложения с XML файла на данные из ДБ, не заставляй пользователя знать твою внутреннюю кухню и заботиться о перехвате DbNotFoundException. Он должен по прежнему отслеживать появление ConfigurationInitializationException.
    • Навяжи катосмизованные исключения пользователю
      • В Java ты можешь проверять это во время компиляции
      • В .NET ты можешь добавить описание исключений в XML-документацию снабдив их элементом . Это не сделает их применение обязательным, но так они хотя бы будут задокументированны.
    • Убедись, в верности реализации конструкторов твоих исключений и в возможности их сериализации
      • В VS 2005 существует шаблон для генерации готового класса, реализующего исключение
    • На самом деле может быть очень специфические причины, по которым необходимо перехватывать System.Exception (хот я в это и не верю)
      • Ты никогда не должен перехватывать Exception, порождая взамен что-то другое.
        • Если ты не заботишься о стабильности твоего кода, зачем ты его пишешь? Возможно, это указывает на проблемы проектирования.
      • Если ты застукал себя за тем, что пытаешься найти аргументы, почему необходимо перехватить Exception, скорее всего у тебя проблемы с проектированием.
        • Если существуют реальные причины, по которым твое приложение должно «упасть». Если никто не знает, как обработать такую исключительную ситуацию, должно ли твое приложение продолжать работать? Или стоит оставить все свои проблемы и закрыть приложение?
        • Если твое приложение читает из базы данных, а базы данных нет, должен ли ты продолжать работу и вываливать в лог ошибки или просто закрыть приложение? (Понятно, что это зависит от того насколько долго база данных будет недоступна и .т., но я начал углубляться, суть то вы уяснили, так? )

YMMV, вперед.

Очень правильная статья. Согласен по всем пунктам.