Denis Gladkikh
Russian   |  English

Config Transformation Tool: Теперь поддерживаются параметры

Пару недель назад я писал про небольшую утилиту Config Transformation Tool, которую я создал на базе задачи трансформирования web.config файла. В тот момент у меня сразу же возникла идея, что было бы неплохо еще иметь возможность указывать места в файле-трансформере, вместо которых можно было бы подставлять значения при помощи этой утилиты. И вот, сегодня я готов объявить, что мне удалось решить эту задачу. Сначала хотелось бы поблагодарить AlexBar, за то, что он порекомендовал посмотреть глубже при помощи .Net Reflector в недры библиотеки Microsoft.WebApplication.Build.Tasks.Dll, и отыскать там класс Microsoft.Web.Publishing.Tasks.XmlTransformation, который умеет выполнять XML-Document-Transform для строк. Мне это очень сильно упростило реализацию. Чтобы утилита смогла поддерживать параметры мне предстояло решить две задачи: (а) уметь пробегаться по файлу и подставлять значения вместо параметров, (б) уметь парсить командную строку на предмет передачи параметров со значениями.

ParametersTask

Итак, сначала опишу, как я решал первую задачу. Синтаксис для параметров я решил сделать таким: {Имя_параметра:значение_по_умолчанию}, где значение по умолчанию это необязательный параметр. Правила для замены следующие:

  1. если указан параметр со значением, то использовать это значение;
  2. если параметр не указан, но указано значение по умолчанию, то подставлять его;
  3. если параметр не указан, и значение по умолчанию так же не указано, то оставить все как есть.

Перед тем как решать данную задачу я так же подумал о том, что RegEx или простыми string.Replace тут лучше не увлекаться, так как, если параметров будет много, то такая задача может выполняться очень долго. Потому я решил обойтись одним проходом по строке-трансформере, и, соответственно, за этот один проход подставить значения параметров. Так же я подумал о том, что в строке-трансформере могут применяться символы ‘{‘, ‘}’ не только для моих параметров, а для того, чтобы моя утилита их игнорировала нужно использовать комбинации “\}”, “\{“, ну и, соответственно, для самого символа ‘\’ так же используем комбинацию “\\”. Итак, класс ParametersTask имеет одно поле _parameters с типом IDictionary<string, string>, где ключи – это имена параметров, а значения – значения этих параметров. Основной метод ApplyParameters:

public string ApplyParameters(string sourceString)
{
    StringBuilder result = new StringBuilder();
 
    int index = 0;
 
    char[] source = sourceString.ToCharArray();
 
    bool fParameterRead = false;
 
    StringBuilder parameter = new StringBuilder();
 
    while (index < source.Length)
    {
        // If parameter read, read it and replace it
        if (fParameterRead && source[index] == '}')
        {
            var s = parameter.ToString();
            int colonIndex = parameter.ToString().IndexOf(':');
 
            var parameterName = colonIndex > 0 ? s.Substring(0, colonIndex) : s;
            var parameterDefaultValue = colonIndex > 0 ? s.Substring(colonIndex + 1, s.Length - colonIndex - 1) : null;
 
            string parameterValue = null;
            if (_parameters != null && _parameters.ContainsKey(parameterName))
                parameterValue = _parameters[parameterName];
 
            // Put "value" or "default value" or "string which was here"
            result.Append(parameterValue ?? parameterDefaultValue ?? "{" + parameter + "}");
 
            fParameterRead = false;
            index++;
            continue; 
        }
        
        if (source[index] == '{')
        {
            fParameterRead = true;
            parameter = new StringBuilder();
            index++;
        }
        // Check is this escape \{ \} \\
        else if (source[index] == '\\')
        {
            var nextIndex = index + 1;
            if (nextIndex < source.Length)
            {
                var nextChar = source[nextIndex];
                if (nextChar == '}' || nextChar == '{' || nextChar == '\\')
                {
                    index++;
                }
            }
        }
 
        if (fParameterRead)
            parameter.Append(source[index]);
        else
            result.Append(source[index]);
 
        index++;
    }
 
    return  result.ToString();
}

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

[Test]
public void ApplyParameters_Sample()
{
    const string ExpectedResult =
        @"
<value key=""Value CustomParameter1"" value=""False"" />
<value key=""Test2"" value=""Value CustomParameter2"" />
<value key=""Test3"" value=""False"" />";
 
    const string Source =
        @"
<value key=""{CustomParameter1:Default value}"" value=""{TrueValueParameter:True}"" />
<value key=""Test2"" value=""{CustomParameter2:Default value of CustomParameter2}"" />
<value key=""Test3"" value=""{TrueValueParameter:True}"" />";
 
    ParametersTask task = new ParametersTask();
 
    task.AddParameters(new Dictionary<string, string>
                        {
                            {"CustomParameter1", "Value CustomParameter1"},
                            {"TrueValueParameter", "False"},
                            {"CustomParameter2", "Value CustomParameter2"}
                        });
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void WithoutParameters()
{
    const string Source =
        @"
<value key=""{CustomParameter1}"" value=""{TrueValueParameter}"" />
<value key=""Test2"" value=""{CustomParameter2}"" />
<value key=""Test3"" value=""{TrueValueParameter}"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(Source, result);
}
 
[Test]
public void WithoutParameters_But_With_Default_Values()
{
    const string ExpectedResult =
        @"
<value key=""Default value"" value=""True"" />
<value key=""Test2"" value=""Default value of CustomParameter2"" />
<value key=""Test3"" value=""False"" />";
 
    const string Source =
        @"
<value key=""{CustomParameter1:Default value}"" value=""{TrueValueParameter:True}"" />
<value key=""Test2"" value=""{CustomParameter2:Default value of CustomParameter2}"" />
<value key=""Test3"" value=""{TrueValueParameter:False}"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void Apply_With_Double_Colon_In_Definition()
{
    const string ExpectedResult =
        @"
<value key=""Default:value"" value=""Val"" />";
 
    const string Source =
        @"
<value key=""{Parameter1:Default:value}"" value=""Val"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void Apply_With_Escaped_Brackets()
{
    const string ExpectedResult =
        @"
<value key=""Default:value"" value=""{TestParameter:Test}"" />";
 
    const string Source =
        @"
<value key=""{Parameter1:Default:value}"" value=""\{TestParameter:Test\}"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void Apply_With_Escaped_Brackets_In_Default_Value()
{
    const string ExpectedResult =
        @"
<value key=""Defa{ultva}lue"" value=""{TestParameter:Test}"" />";
 
    const string Source =
        @"
<value key=""{Parameter1:Defa\{ultva\}lue}"" value=""\{TestParameter:Test\}"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void Apply_With_Parameter_At_End_Of_String()
{
    const string ExpectedResult =
        @"
<value key=""Defa{ultva}lue"" value=""Test";
 
    const string Source =
        @"
<value key=""{Parameter1:Defa\{ultva\}lue}"" value=""{TestParameter:Test}";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}
 
[Test]
public void Apply_With_Parameter_At_Start_Of_String()
{
    const string ExpectedResult =
        @"Defa{ultva}lue"" value=""{TestParameter:Test}"" />";
 
    const string Source =
        @"{Parameter1:Defa\{ultva\}lue}"" value=""\{TestParameter:Test\}"" />";
 
    ParametersTask task = new ParametersTask();
    var result = task.ApplyParameters(Source);
    Assert.AreEqual(ExpectedResult, result);
}

ParametersParser

Вторая задача – это уметь распарсить параметры из командной строки. Реализовать эту функциональность я решил способом, который используется в MsBuild.exe, ну или очень похожим на него. Параметры должны быть разделены точкой с запятой ‘;’, имя параметра и значение должно разделять двоеточие ‘:’, если значение параметра включает в себя пробелы или точку запятой, то это значение лучше заключить в кавычки, и так же есть возможность использовать ‘\”’, ‘\\’. Реализация ниже:

/// <summary>
/// Parse string of parameters 
/// </summary>
public static class ParametersParser
{
    private readonly static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 
 
    /// <summary>
    /// Parse string of parameters <paramref name="parametersString"/> separated by semi ';'.
    /// Value should be separated from name by colon ':'. 
    /// If value has spaces or semi you can use quotes for value. 
    /// You can escape symbols '\' and '"' with \.
    /// </summary>
    /// <param name="parametersString">String of parameters</param>
    /// <returns>Dicrionary of parameters, where keys are names and values are values of parameters. 
    /// Can be null if <paramref name="parametersString"/> is empty or null.</returns>
    public static IDictionary<string, string> ReadParameters(string parametersString)
    {
        if (string.IsNullOrWhiteSpace(parametersString)) return null;
 
        Dictionary<string, string> parameters = new Dictionary<string, string>();
 
        var source = parametersString.ToCharArray();
 
        int index = 0;
 
        bool fParameterNameRead = true;
        bool fForceParameterValueRead = false;
 
        StringBuilder parameterName = new StringBuilder();
        StringBuilder parameterValue = new StringBuilder();
 
        while (index < source.Length)
        {
            if (fParameterNameRead && source[index] == ':')
            {
                fParameterNameRead = false;
                index++;
 
                if (index < source.Length && source[index] == '"')
                {
                    fForceParameterValueRead = true;
                    index++;
                }
 
                continue;
            }
 
            if ((!fForceParameterValueRead && source[index] == ';')
                || (fForceParameterValueRead && source[index] == '"' && ((index + 1) == source.Length || source[index + 1] == ';')))
            {
                AddParameter(parameters, parameterName, parameterValue);
                index++;
                if (fForceParameterValueRead)
                    index++;
                parameterName.Clear();
                parameterValue.Clear();
                fParameterNameRead = true;
                fForceParameterValueRead = false;
                continue;
            }
 
            // Check is this escape \{ \} \\
            if (source[index] == '\\')
            {
                var nextIndex = index + 1;
                if (nextIndex < source.Length)
                {
                    var nextChar = source[nextIndex];
                    if (nextChar == '"' || nextChar == '\\')
                    {
                        index++;
                    }
                }
            }
 
            if (fParameterNameRead)
            {
                parameterName.Append(source[index]);
            }
            else
            {
                parameterValue.Append(source[index]);
            }
 
            index++;
        }
 
        AddParameter(parameters, parameterName, parameterValue);
 
        if (Log.IsDebugEnabled)
        {
            foreach (var parameter in parameters)
            {
                Log.DebugFormat("Parameter Name: '{0}', Value: '{1}'", parameter.Key, parameter.Value);
            }
        }
 
        return parameters;
    }
 
    private static void AddParameter(Dictionary<string, string> parameters, StringBuilder parameterName, StringBuilder parameterValue)
    {
        var name = parameterName.ToString();
        if (!string.IsNullOrWhiteSpace(name))
        {
            if (parameters.ContainsKey(name))
                parameters.Remove(name);
            parameters.Add(name, parameterValue.ToString());
        }
    }
}

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

/// <summary>
/// Check simple parameters command line
/// </summary>
[Test]
public void Sample()
{
    const string parametersLine = "Parameter1:Value1;Parameter2:121.232";
 
    var parameters = ParametersParser.ReadParameters(parametersLine);
 
    Assert.AreEqual("Value1", parameters["Parameter1"]);
    Assert.AreEqual("121.232", parameters["Parameter2"]);
}
 
/// <summary>
/// Check parameters command line when one of parameter has semi in value string
/// </summary>
[Test]
public void String_With_Semicolon_In_Value()
{
    const string parametersLine = "Parameter1:Value1;Parameter2:\"121;232\"";
 
    var parameters = ParametersParser.ReadParameters(parametersLine);
 
    Assert.AreEqual("Value1", parameters["Parameter1"]);
    Assert.AreEqual("121;232", parameters["Parameter2"]);
}
 
/// <summary>
/// Check that if command line has semicon at end parameters will be loaded
/// </summary>
[Test]
public void String_With_Semicolon_At_End()
{
    const string parametersLine = "Parameter1:Value1;Parameter2:\"121.232\";";
 
    var parameters = ParametersParser.ReadParameters(parametersLine);
 
    Assert.AreEqual("Value1", parameters["Parameter1"]);
    Assert.AreEqual("121.232", parameters["Parameter2"]);
}
 
/// <summary>
/// Check that value of parameter can contain escaped quotes
/// </summary>
[Test]
public void String_With_Values_With_Quotes()
{
    const string parametersLine = @"Parameter1:Value1;Parameter2:""12\""1.2\""32"";";
 
    var parameters = ParametersParser.ReadParameters(parametersLine);
 
    Assert.AreEqual("Value1", parameters["Parameter1"]);
    Assert.AreEqual("12\"1.2\"32", parameters["Parameter2"]);
}

Результат

В итоге теперь можно при помощи утилиты проработать такой пример. Исходный файл (s.config):

<?xml version="1.0"?>
 
<configuration>
 
    <custom>
        <groups>
            <group name="TestGroup1">
                <values>
                    <value key="Test1" value="False" />
                    <value key="Test2" value="600" />
                </values>
            </group>
 
            <group name="TestGroup2">
                <values>
                    <value key="Test3" value="C:\Test\" />
                </values>
            </group>
 
        </groups>
    </custom>
    
</configuration>

Теперь файл трансформации (t.config), в нем описано два параметра, один Parameter1, значение которого указывать необязательно, и параметр с именем Test3Value:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    
    <custom>
        <groups>
            <group name="TestGroup1">
                <values>
                    <value key="Test2" value="601" xdt:Transform="Replace"  xdt:Locator="Match(key)" />
                    <value key="Test1" value="{Parameter1:True5665}" xdt:Transform="Replace"  xdt:Locator="Match(key)" />
                </values>
            </group>
            
            <group name="TestGroup2">
                <values>
                    <value key="Test3" value="{Test3Value}" xdt:Transform="Replace"  xdt:Locator="Match(key)" />
                </values>
            </group>
        </groups>
    </custom>
    
</configuration>

Вызываем утилиту:

ctt s:s.config t:t.config d:d.config p:Parameter1:True;Test3Value:"c:\Program Files\Test"

Как и следовало ожидать получаем d.config:

<?xml version="1.0"?>
<configuration>
  <custom>
    <groups>
      <group name="TestGroup1">
        <values>
          <value key="Test1" value="True" />
          <value key="Test2" value="601" />
        </values>
      </group>
      <group name="TestGroup2">
        <values>
          <value key="Test3" value="c:\Program Files\Test" />
        </values>
      </group>
    </groups>
  </custom>
</configuration>

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

Итог

Скорее всего есть очень много проблемных мест и недочетов, если видите что-то, либо знаете коварный тест, где точно не проработает – отпишитесь, пожалуйста, в комментариях, постараюсь все допилить. Ну и буду рад любым замечаниям и предложениям. Исходники и собранную версию можно скачать на сайте проекта на CodePlex: http://ctt.codeplex.com, последняя версия Config Transformation Tool v1.1.


Вас также может заинтересовать

rss twitter

Комментарии (56)

Александр ( ) #
avatar
Какой жуткий говнокод
Denis Gladkikh ( ) #
avatar
Александр, что ж вам в нем не понравилось? Раскидаете по методам? Работать будет в два раза дольше. Сделаете более красивее с большем количеством циклов - пожалуйста, только работать будет в 10 раз дольше ;)
Denis Gladkikh ( ) #
avatar
Александр, вы меня заинтриговали. Зашел на ваш блог, смотрю вы любите писать предложения "Жду ваших ответов с примерами кода в комментариях.", могу ли я к вам выйти с таким предложением? Покажите пример, пожалуйста, реализации public string ApplyParameters(string sourceString). Я не могу сказать, что мой код на 100% идеален, но вот "Какой жуткий говнокод" - это уж прям ударили :)
sol ( ) #
avatar
А не проще использовать XSLT преобразование?
Denis Gladkikh ( ) #
avatar
sol, xslt я обожаю, но он, имхо, избыточен для этого случая. Тут следует учитывать, что в команде будет еще народ, который может xslt и не знает, а XDT предлагаемый для web.config трансформации уж очень прост для понимания, и его более чем хватит для того, чтобы изменять конфиги.
sol ( ) #
avatar
ну в вашем варианте, чтобы изменить один конфиг, надо написать другой. Не проще ли сразу написать первый?:) Это к слову об избыточности...
При использовании XSLT необязательно всем знать синтаксис XSL - его нужно составить всего один раз и при формировании конфига подсовывать XML, содержащий нужные данные.
Denis Gladkikh ( ) #
avatar
sol, проект развивается очень быстрыми темпами. Поддерживать конфиги постоянно в актуальном состоянии для каждой конфигурации не хочется. Так как приложение уж очень у нас модульное, то в конфиги выносятся по максимуму возможные параметры, которые можно встретить в системе, и которые, может быть, реально никогда и не изменять. В результате для деплоя есть набор предположим 10 полей, которые нужно поменять в версии 3.0, в будущем и для версии 4.0 так же и останется, что нужно будет менять всего 10 полей, хотя с новой версией в программу пришло настроек в два раза больше. И т.п. Просто так проще поддерживать актуальность, не нужно следить, что куда-то что-то забыли добавить.
По поводу XSLT - все было бы так, если бы состав этой конфигурации не менялся.
Александр ( ) #
avatar
Вы делает ложные предпосылки к оптимизации. Глупо гнаться за производительностью приложений на C#, тем более в тех местах, где разница между работой в 1 и 10 секунд не важна: полная сборка наших приложений идет от 15 минут(только юнит тесты) до 3х часов (+интеграционные). Если эта ваша трансформация будет занимать даже минуту времени - этого никто и никогда не заметит.

Теперь по поводу кода.
ApplyParameters:
source[index] встречается 5 раз. Не проще сделать переменную?
source[index + 1] встречается примерно то же количество раз.

Regex тут будет работать в разы быстрее и занимать в разы меньше кода - если уж вы гонитесь за производительностью:)
Denis Gladkikh ( ) #
avatar
Александр, по поводу использования RegEx рекомендую вам почитать Назад, к основам (статье почти 10 лет, а она еще все равно имеет ценность). У меня в целом в программе может быть до 100 параметров, знаете во сколько раз будет работать медленее программа на regex? Ага, если так прикинуть то ровно в 100 раз, так как будет 100 проходов по строке вместо одного. Или вы рекомендуете написать сверх умный немаленький regex сражу применимый на все параметры, сможете его поддерживать? Дальше, на 10 конфигураций, если они занимают времени на обратку по полсекунды, а в сумме 5 секунд, то мне потребуется около 8 минут на обработку этого с regex. Как вам разница? Если для вас это не существенное различие, что ж это ваше дело.

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

А это "source[index] встречается 5 раз. Не проще сделать переменную?" - да вы шутите, это все что можно сказать? Если сделаю по вашему, то код перестанет быть "Какой жуткий говнокод"?

Александр, в принципе, я понял ваше мнение, можете дальше и не отвечать. На все свои вопросы я и так знаю ваши ответы.
Александр ( ) #
avatar
Проходы по строке считаете... круто....

int colonIndex = parameter.ToString().IndexOf(':')

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

Почему я сказал только о source[index] - только потому что сейчас я нахожусь на работе, и у меня есть более важные задачи, чем доказывать вам, почему ваш код на букву "Г".
Александр ( ) #
avatar
Далее...


string parameterValue = null;
if (_parameters != null && _parameters.ContainsKey(parameterName))
parameterValue = _parameters[parameterName];

Обращение к словарю 2 раза, вместо одного.

PS: Я могу находить мелкие неточности вашего кода, убивающие всю производительность бесконечно)

PPS: Я все-таки перепишу эту функцию, и сравню скорость выполнения.
Denis Gladkikh ( ) #
avatar
Александр, вы просто придираетесь к коду, ну честно. Вы можете долго пытаться что-то тут доказывать. Я могу с вами пообсуждать что-то, но "в другом тоне".

Александр, а по поводу этого замечания, вы это с сарказмом или шутите так? Видите разницу: пройтись по файлу трансормеру, который может быть несколько тысяч символов, и параметру, который в среднем 10 символов. Вот именно в этом случае как раз и не имеет смысла применять метод одного прохода, а оставить более приятные для восприятия стандартные методы .Net ;) Выигрышь тут был бы в милисекундах, даже на нескольких тысячах параметров. Просто жесть, Александр, у меня уже есть сомнения в ваших способностях. Давайте на этом закончим?

Все мы работает, я же вас не подгонял дать мне ответ сейчас и сегодня. То, что у вас есть работа - это важное замечание, спасибо, мне стало легче :)

Поймите, я очень уважаю критику, и тех людей, которые могут критиковать мой код/решения, это значит, что у этих людей я могу чему-то научиться. Мне не важно сравнение, что у кого и на сколько длинее, но вот ваши придирки просто не приятны и не понятны. Занимайтесь лучше вашими важными задачами.
Denis Gladkikh ( ) #
avatar
Александр, написал комментарий, не увидев ваш второй комментарий. Обращение к словарю, опять не о той производительности идет дело.
"Я все-таки перепишу эту функцию, и сравню скорость выполнения." - будет просто замечательно, покажите?
Александр ( ) #
avatar
Ну дак это вы начали утверждать, что самый простой прием рефакторинга "выделение метода" замедлит выполнение в 2 раза, что на самом деле спорно, т.к. компилятор, скорее всего, этот метод встроит.
Denis Gladkikh ( ) #
avatar
Я не сказал "выделение метода", я сказал раскидаете по методам, подразумевая совершенно другой подход. "самый простой прием рефакторинга "выделение метода"", сколько слов, хотите произвести впечатление, чтоли? Самым простым приемом рефакторинга я бы назвал какой-нибудь метод переименования.

По поводу inline методов - ага, напишите их удовлетворяющие вот этим условиям Managed Code and the CLR JIT.
Скорее всего метод типа char GetCurrent() {_source[index];} будет inline. Сильно упростит дело?

Еще раз по поводу производительности. Как пишется программа? Есть задача, выбирается решение одно из множества: решение должно решать задачу, быть оптимальным по времени реализации, поддержке, развитию, и перфомансу. В моем случае я выбрал решение рекурсивного спуска (не совсем его, но близкого), потому что если мне на пути встретится файл в 100000 (или 100 файлов по 1000 символов) символов и 10000 параметров я буду точно знать, что код мне переписывать не нужно будет, а может быть нужно будет пооптимизировать те места, если они будут вызывать сложности, о которых вы говорили (и кстати мне об этом скажет профайлер). Этот код можно рефакторить, оптимизировать и т.п., главное что изначально подход был выбран правильный, вот и все.
Александр ( ) #
avatar
Вот)
Субъективные плюсы моего решения: читается легче, меньше кода, в каждый момент времени обрабатываем только 1 символ, на 10млн. итераций дает 12с выигрыша.

00:01:18.1777090 // время работы вашего кода
00:01:05.6916084 // время работы моего.

public string ApplyParameters(string sourceString)
{
var result = new StringBuilder();
var parameter = new StringBuilder();
bool readingParameter = false;
var escapeNextCharacter = false;
var source = sourceString.ToCharArray();
foreach (var c in source)
{
if (escapeNextCharacter == false)
{
if (readingParameter && c == '}')
{
result.Append(GetParameterValue(parameter.ToString()));
readingParameter = false;
continue;
}
if (c == '{')
{
parameter.Clear();
readingParameter = true;
continue;
}
if (c == '\\')
{
escapeNextCharacter = true;
continue;
}
}
escapeNextCharacter = false;
if (readingParameter)
parameter.Append(c);
else
result.Append(c);
}

return result.ToString();
}

private string GetParameterValue(string parameter)
{
int colonIndex = parameter.IndexOf(':');

string parameterName = colonIndex > 0
? parameter.Substring(0, colonIndex)
: parameter;

string defaultValue = colonIndex > 0
? parameter.Substring(colonIndex + 1, parameter.Length - colonIndex - 1)
: string.Format("{{{0}}}", parameter);

return GetValueOrDefault(parameterName, defaultValue);

}

private string GetValueOrDefault(string s, string def)
{
string parameterValue;
if (parameters.TryGetValue(s, out parameterValue) == false)
return def;
return parameterValue;
}
Denis Gladkikh ( ) #
avatar
Александр, поздравляю, действительно, читается проще. Я надеюсь вы самоутвердились? Надеюсь, вы теперь, может, не будете думать, что задачу подобной этой нужно решать при помощи RegEx ;) Удачи вам.
Александр ( ) #
avatar
Очень похоже на идею конфигурации в NAnt (xmlpeek/xmlpoke). Мы используем такой подход для выставления настроек при заливке релиза.
Denis Gladkikh ( ) #
avatar
Александр, ага похожая штука, но имхо не такая прозрачная на вид.
Александр ( ) #
avatar
Дело в том, что ты используешь специальные обозначения - {Parameter1:True5665}. Такое подход не годится, например, для строки подключения. При разработке она одна, а на живом сервер другая. Т.е. разрабатывая, ты должен тоже иметь возможно подключения к БД и в строку подключения нельзя написать {Parameter1:True5665}.

Ты использовал NAnt для автоматического выпуска релиза?
Denis Gladkikh ( ) #
avatar
Александр, не так. Эти параметры используются в файле трансфармации, то есть исходный файл чист. Это я накосячил с определениями, вроде поправил сейчас.
То есть web.config у нас остается такой же как был, а вот web.config.transformation - имеет параметры на подстановку, а так же информацию о трансформировании файла.
Александр ( ) #
avatar
> То есть web.config у нас остается такой же как был, а вот web.config.transformation - имеет параметры на подстановку, а так же информацию о трансформировании файла

А, понял.

Как на счет использования NAnt? Есть ли у тебя опыт работы с ним и в чем разница между его концепцией и твоей? Интересно твое вИдение.
Denis Gladkikh ( ) #
avatar
Александр, nAnt я использую в связке с CCNet, пока на MsBuild и TFS не перешли. Я завязывался на задачу, вроде такой: есть у нас интерфейс ISmsSender, есть две реализации, одна которая просто кладет в лог инфу, что смс отправлено, вторая отсылает смс при помощи какого-нибудь gateway. Соответственно unity сконфигурирован примерно так
<type type="MyNamespace.ISmsSender, MyAssembly" mapTo="MyNamespace.SmsSenderLog, MyAssembly" />


Нам нужно его поменять для продакшена на
<type type="MyNamespace.ISmsSender, MyAssembly" mapTo="MyNamespace.SmsSenderSomeGateway, MyAssembly">
      <constructor>
        <param name="userName" >
            <value value="UserNameForGateway"/>
        </param>
        <param name="password" >
            <value value="PasswordForGateway"/>
        </param>
    </constructor>
</type>


Соответственно вопрос, как это реализовать при помощи xmlpeek/xmlpoke? Задачу, как я понимаю, нужно будет решать примерно так Appending nodes in XML files with xmlpeek and xmlpoke using NAnt.

Не очень то явно и понятно, и главное не просто поддерживать, а вот с XDT решается просто добавлением атрибутов:
<type type="MyNamespace.ISmsSender, MyAssembly" mapTo="MyNamespace.SmsSenderSomeGateway, MyAssembly"  
    xdt:Transform="Replace" xdt:Locator="Match(type)">
      <constructor>
        <param name="userName" >
            <value value="UserNameForGateway"/>
        </param>
        <param name="password" >
            <value value="PasswordForGateway"/>
        </param>
    </constructor>
</type>
hazzik ( ) #
avatar
Мне кажется последняя проблема высосана из пальца.
Александр ( ) #
avatar
Ага, понял. Действительно есть такая проблема с NAnt'ом, но она решается просто - написать свой Task, который возможно будет иметь код приближенный к твоему.

Проблема в том, что сомнительно использовать специальную утилиту, которая решает такую узкую задачу. Думаю будет круто, если ты встроишь такую возможность в NAnt или MsBuid.
Denis Gladkikh ( ) #
avatar
Александр, ну в MsBuild она и так есть :) Оттуда же я ее и взял. Только, вроде, получается она не задокументированна.

Написание плагина именно для nant - это вопрос. Просто по затратам, зачем мне с этим разбираться, когда можно запросто использовать exec:
<exec program="ctt.exe">
    <arg value='source:"${transformation.source}"' />
    <arg value='transformation:"${transformation.file}"' />
    <arg value='destination:"${transformation.destination}"' />
</exec>


Александр, в чем будут приемущества от использования именно плагина?
Александр ( ) #
avatar
> Александр, в чем будут приемущества от использования именно плагина?

В том, что тебе не надо явно вызывать утилиту. Ты будешь вызывать задачу с названием, например, Код выполнения будет лежать в DLL, как плагин к NAnt по аналогии с NAntContrib.
Denis Gladkikh ( ) #
avatar
hazzik, какая? про использование xmlpeek/xmlpoke для замены в unity конфигурации? и почему?
Denis Gladkikh ( ) #
avatar
Александр, то есть реально никакой? Я понимаю как это будет происходить, но каков плюс то? только в том, что можно будет написать что-то вроде вместо моего примера и все?
<ctt>
  <source>...</source>
  <transform>...</transform>
  <destination>...</destination>
</ctt>
hazzik ( ) #
avatar
"есть две реализации ... нам нужно его поменять для продакшена" - вот эта проблема
Александр ( ) #
avatar
> Я понимаю как это будет происходить, но каков плюс то?
Да, в этом и есть плюс. Ты же не пишешь вместо задачи exec вызов процесса. Это дает абстракцию. Иначе скрипт превратится просто в вызов сторонних утилит.
Denis Gladkikh ( ) #
avatar
hazzik, ок ну то есть из моего комментария. Так а почему проблема высосана из пальца? Не нужно менять? :) или с xmlpeek/xmlpoke решается просто? Или другим способом?

Александр, в принципе, можно попробовать реализовать больше ради интереса как это работает у nant, в плане знакомства с их плагинами. :) Создал задачу в проекте, а там посмотрим как со временм будет. За идею спасибо.
hazzik ( ) #
avatar
"Не нужно менять :)"

Почему? Потому, что на продакшене и стейджинге должна быть абсолютно одинаковая конфиграция, иначе теряется смысл стейджинга. А на машине девелопера вообще пофигу, что там прописано, потому что ему незачем запускать интеграционные тесты.
Denis Gladkikh ( ) #
avatar
hazzik, вы не о том. Давайте сначала по моему примеру. Еще раз, у всех разработчиков стоит логгер вместо настоящей реализации отправки смс, кстати реализация смс для каждого продакшена может быть разная, все используют разные gateway. Для тестирования включать какую-нибудь из реализаций отправки смс уведомлений бессмысленно. Зачем тратить деньги? Все реализации тестируются при помощи интеграционных тестов (опять же такие тесты стоит запускать только при реальном изменнии или даже ручном запуске - тратить деньги попросту незачем). Притом при необходимости и при реализации новых, для новых gateway их можно настроить для тестового стенда, чтобы быть спокойным. Итого - нужно из TFS (другая система хранения исходников) забрать последнюю версию и создать тестовый стенд, там, например, либо оставляем тот же логгер, либо включаем одну из реализаций. Второе - для каждого продакшена нужно иметь возможность собрать пакет с установленной для него реализацией. Итого, мы тут видем, где такая подмена необходима.

Теперь давайте разберемся с тем, что говорите вы. Потому, что на продакшене и стейджинге должна быть абсолютно одинаковая конфиграция, иначе теряется смысл стейджинга. А тут я бы посоветовал вам без фанатизма, создать 100% одинаковую конфигурацию у вас не получится, приблизиться к ней - это пожалуйста, а сделать 100% - нет. Особенно, в случае когда есть некоторая от вас не зависящая интеграция с внешними системами, как, например, в моем примере "смс gateway". Смотрите, когда у вас есть интеграция с внешними системами бывает, что вас просто ставят перед фактом, что нужно реализовать, дают вам, например, wsdl описание, и вперед - крутитесь, как хотите. Соответственно, тут вы можете сделать по разному, либо состряпать интеграционную-заглушку-сервер, который будет исполнять обязанности того сервера. Либо просто тупо напишите логгер/fake класс, и будете использовать его вместо реальной реализации. Первый случай стоит выбирать, когда вам даже на день не предоставят сервер для тестирования. Второй случай стоит выбирать, если вам все-таки назначат испытание перед тем как ввести свой продукт в продакшн. Затем следует договориться о том, что при изменении контракта между участниками интеграции перед деплоем проводить испытания. Закладываться на чужую фирму в плане того, что их тестовый стенд будет всегда онлайн - глупо, просить об этом их еще глупее.
hazzik ( ) #
avatar
Я не слова не понял из вашего многобуквенного сочинения. Проблема высосана и вы ее дальше пытаетесь высасывать. Инициализация зависимостей приложения в xml это одна из самых больших граблей, на которые наступила наша небольшая компания при разработке своих продуктов. А темболее подменять зависимости для продакшена - это глупо. Почитайте статью на хабре про AARD в win 3.11.
Denis Gladkikh ( ) #
avatar
Печально, что вы не умеете/не хотите читать. То, что ваша компания наступает на грабли, а не умеет правильно готовить - это ваша проблема, ну и вашей компании. Удачи вам.

P.S. По поводу статьи о Win3.11 и DR-DOS, она, кстати, совершенно не по той теме, что вы подняли, но, видимо, ее вы тоже не особо-то прочли или не поняли. Да, как бы интеграция там тоже есть, но решали они не то, как бы им их продукт сделать хорошо совместимым, а о том, как бы избавить в будущем техподдержку от вопросов несовместимости Win с DR-DOS. К чему вы ее привели, я, честно, не понял. Но так как конструктивного разговора от вас добиться сложно (извините, но Я не слова не понял из вашего многобуквенного сочинения. - это жесть, очевидно, что не понял легко заменяется на не хочу читать), да и нет для меня в этом необходимости - все же рекомендую, либо (а) закончить обсудение этой темы (б) перевести разговор, например, в skype, где бы я вам объяснил то, о чем я написал.
hazzik ( ) #
avatar
Статья, как раз, в тему. Цитата:
"Обойти выполнение кода, вставив команду JMP, довольно безопасно; а если удалить его, в оставшемся коде изменятся смещения функций — т.е. это будет уже новый, непротестированный код. Бета-тестирование было уже окончено, поэтому разработчики старались не заменять протестированный код непротестированным."

В вашем же случае получается, что SmsSenderSomeGateway будет залит на продакшн ВООБЩЕ не протестированным, вместо него вы, зачем-то интеграционными тестами тестируете на стейджинге заглушку.
Denis Gladkikh ( ) #
avatar
hazzik, ок, отличное предложение (вырезка из статьи), связь есть в словах "тест". :) Да, тема очевидная, но совершенно не об этом.

SmsSenderSomeGateway будет ОЧЕНЬ ХОРОШО протестирован. Реально, я не вижу смысла в продолжении этого разговора тут, трата времени ни на что, хотите об этом поговорить (я хочу, но побыстрее), давайте в skype обсудим это за 5 минут голосом, пойдет?
hazzik ( ) #
avatar
на работе нет средств работы со звуком, за ненадобностью.

Каким образом он будет протестирован? SmsSenderSomeGateway если в конфиге для стейджинга вместо него будет SmsSenderLog, а на продакшене SmsSenderSomeGateway? Кто и когда будет его тестировать?
Denis Gladkikh ( ) #
avatar
ок, тогда вкратце отвечу, что в моем примере SmsSenderSomeGateway должен быть протестирован:
а) при помощи тестов только затрагивающие данную реализацию, этот тип тестов лучше запускать только при изменениях реализации, чтобы попросту не тратить лишние деньги на отправку смс сообщений (и не забывайте, что еще должен быть получатель этих самых сообщений).
б) при помощи интеграционных тестов, но тестируются единожды после реализации/изменении (либо в рамках финального прогона) полностью вместе со всей конфигурацией.

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

Один из примеров еще - это SMTP сервер. В .Net даже есть реализация, которая вместо отправки сообщений складывает их в определенную папку, чтобы тестеры могли оценить работоспособность системы. Когда вы тестируете нехилый продукт, в котором должны быть тысячи пользователей (да пускай даже несколько), то не хотелось бы, наверное, тестируя функциональность вроде "отослать все сообщение", действительно разослать по всем пользователям сообщения ;) Уж наверняка хватит того, что функционал проработает и сложится в папку 1000 email (по количеству пользователей), а протестировать связку "Отправка Email"-"SMTP сервер" можно и единожды.

hazzik, а по поводу граблей, на которые наступил ваша компания, рекомендовал бы вам побольше внимания уделять просто проверке входных параметров в ваших реализациях, а не ехидному поведению на чужих блогах ;) Так, по крайней мере, мне кажется: то, чем вы занимаетесь, если это не так - прошу извинить.
hazzik ( ) #
avatar
Как вы гарантируете, что этот код никто не менял, или не сломалось что-нибудь через транзитивные зависимости?

Наш код постоянно рефакторится: с xml конфигурацией можно огрести просто переименовав какой-либо класс. А такое происходит в день раза 2-3. С xml конфигурацией невозможно отследить, что класс ни кем не используется и т.д. и т.п.
Denis Gladkikh ( ) #
avatar
hazzik, скажите честно, вы хотите докопаться до истины или хотите "докопаться" до меня?
Как вы гарантируете, что этот код никто не менял, или не сломалось что-нибудь через транзитивные зависимости?
Так же как гарантируется, что не найдется человек, который может поменять код и задизаблить тест. Так же как гарантируется, что не найдется человек, который может дописать код, но не дописать тесты. Так же, если бы человек писал не безопасный код. Я бы гарантировал бы это политикой компании. К таким людям приставлял бы людей, которые проверяли их код.
не сломалось что-нибудь через транзитивные зависимости?
В этом примере такого не будет, здесь мы выносим только ресурс - отсылка сообщений, код SendMessage и все. А если там что-то отвалится из-за логгера или еще чего-то низкоуровнего, то смотри предыдущий ответ.
Наш код постоянно рефакторится: с xml конфигурацией можно огрести просто переименовав какой-либо класс. А такое происходит в день раза 2-3. С xml конфигурацией невозможно отследить, что класс ни кем не используется и т.д. и т.п.
У нас тоже такое бывает. Контейнеры у Unity конфигурируется при запуске приложения, эта проблема всплывает сразу же. Вот здесь вы "высасываете проблему из пальца".

Слушайте, ну это не относится к теме поста, если хотите еще поговорить или обсудить, давайте перенесем это на личную почту/мессенджер.
Аноним ( ) #
avatar
Хочу узнать истину.

> У нас тоже такое бывает. Контейнеры у Unity конфигурируется при запуске приложения, эта проблема всплывает сразу же.

У нас в одном из продуктов 67 проектов, из них 15-20 самостоятельные приложения. Угадать, что вот сейчас я переименую вот этот класс и не упадет ни одно приложение просто не реально. На каждый чих запускать все интеграционные тесты? Или все 15-20 приложений?


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

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

Вы вообще понимаете тот факт, что интеграционные тесты никак не должны вмешиваться в целостность приложения? Его нужно тестировать все вместе. И в данном случае, для того, чтобы корректно протестировать ваш сервис, нужно подменять не его, а ВНЕШНЮЮ систему.
Denis Gladkikh ( ) #
avatar
У нас в одном из продуктов 67 проектов, из них 15-20 самостоятельные приложения. Угадать, что вот сейчас я переименую вот этот класс и не упадет ни одно приложение просто не реально. На каждый чих запускать все интеграционные тесты? Или все 15-20 приложений?
Не вижу проблемы, нужно написать тесты, которые будут просто пытаться конфигурировать ваши unity настройки, если часто такие проблемы всплывают. Мы так же пользуемся r#, который помогает в переименовании. По поводу 15-20 приложений, честно, не вижу ни единой причины, зачем так делать. Но все же предположу, что в этом случае было бы лучше, видимо, раскидать все по функциональным проектам, модулям, вынести ядро, к которому обращаться более щепетильно. Ну это стандартные практики, рекомендации.
И как же это гарантируется? Расскажите подробнее. Над каждым человеком стоит надзиратель с плеткой? Не смешите меня.
Не с плеткой, но XP никто не отменял. Вариант о котором я говорил не совсем такой, так как идейно у него другие цели. Но все же в сложные моменты XP - это хороший выход, чтобы писать качественный код. Если не знали, ознакомьтесь с этим подходом. Про тот случай, который я говорил - уделять час на ревью кода, который пишет такой человек - не вижу проблемы.
Вы вообще понимаете тот факт, что интеграционные тесты никак не должны вмешиваться в целостность приложения? Его нужно тестировать все вместе. И в данном случае, для того, чтобы корректно протестировать ваш сервис, нужно подменять не его, а ВНЕШНЮЮ систему.
Ок, про это я тоже говорил. Случаи разные бывают. Наверное, знаете про Тройственную ограниченность? Вы же не будете писать фейковый SMTP ради этого?
hazzik ( ) #
avatar
>Не вижу проблемы, нужно написать тесты, которые будут просто пытаться конфигурировать ваши unity настройки, если часто такие проблемы всплывают.

Я тоже не вижу проблемы - просто не использовать xml конфигурации для настройки зависимостей.

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

Как мне XP поможет с проблемой конфигурирования через XML? И какая связь между некачественным кодом и разрешением зависимостей через XML?

>Вы же не будете писать фейковый SMTP ради этого?
Представьте себе - написали.
Denis Gladkikh ( ) #
avatar
Я тоже не вижу проблемы - просто не использовать xml конфигурации для настройки зависимостей.
Ок, я вас и не заставлял :) Видите в этом сложности, что ж бывает все по разному. Где-то работает, где-то нет.
Не наблюдаю тут никаких стандартов. Каждое из приложений это отдельная точка соприкосновения внешнего мира с нашей доменной моделью.
Очевидно, что вам виднее. Если доменная модель одна (либо пересекается), что скорее всего так и есть, так как что-то их заставляет лежать в одном solution, то я бы задумался бы о создании модульного приложения, а не о 15-20 приложений. В реальности, получается одному пользователю нужно пользоваться 10 приложениями, в зависимости от его нужд или как? Что это такое 15-20 приложений?
Как мне XP поможет с проблемой конфигурирования через XML? И какая связь между некачественным кодом и разрешением зависимостей через XML?
Мне кажется я начинаю теряться. XP с XML я никак не связывал, мы заговорили об XP, так как вы спросили, как это реализовать, чтобы ревьюить код, а про это мы заговорили после того, что сделать чтобы избежать непротестированности того участка кода. Но XP так то напрямую вам поможет, один разработчик будет менять имя класса, второй вспомнит про то, что этот класс используется в unity конфигурации. Кстати, так же можно разрешать такие проблемы тупо комментированием. По поводу второго вопроса - есть четкая зависимость между небезопасным кодом и разрешением зависимостей вообще.
Представьте себе - написали.
Счастливые люди.
Сергей Попов ( ) #
avatar
Забавная дискуссия получилась: «идеологическая чистота» vs «достаточно близко для любых практических целей» ;-)

Меня вот тоже вчера убеждали, что обертывание вызовов методов MVC-контроллера в тестах во врапперы, эмулирующие среду исполнения HTTP-приложение это идеологически неверно ;-)
hazzik ( ) #
avatar
@Сергей
А кто тут с какой стороны?

@Denis
>Что это такое 15-20 приложений?
Сама веб-морда, и еще 15-20 приложений реализованы в виде виндовых сервисов: рассыльщик сообщений клиентам, сервис для приема-отправки смс!!, сервис статистики, парсер, и т.п. Все работают с одной доменной моделью, поэтому лежат в одном солюшене.
Сергей Попов ( ) #
avatar
hazzik
Ну, ты (я надеюсь, мы можем общаться на «ты»?) IMHO скорее относишься к группе борьбы за «идеологическую чистоту» - из-за явного неприятия XML-конфигурации и требования, чтобы тестовый стенд на 100% совпадал с production.

Лично я в XML-конфигурациях проблем не вижу, т.к. при необходимости они прекрасно валидируются в процессе сборки.

И необходимости 100%-го совпадение тестового стенда с production тоже не вижу, т.к. тут может быть слишком много тонкостей... Не говоря уже о том, что для некоторых конфигураций достижение 100%-го совпадения будет неоправданно дорогим.
hazzik ( ) #
avatar
@Сергей

>И необходимости 100%-го совпадение тестового стенда с production тоже не вижу, т.к. тут может быть слишком много тонкостей... Не говоря уже о том, что для некоторых конфигураций достижение 100%-го совпадения будет неоправданно дорогим.

Я говорил о конфигурации программ, в частности в данном случае подмена зависимостей делает из одной программы ДРУГУЮ (надеюсь я понятно объяснил:)).

Я не против XML конфигураций вообще, я против разрешения зависимостей и написания маппингов для NHibernate и т.п в XML.
Сергей Попов ( ) #
avatar
hazzikЯ говорил о конфигурации программ, в частности в данном случае подмена зависимостей делает из одной программы ДРУГУЮ (надеюсь я понятно объяснил:)).

В чем-то ты конечно прав, но IMHO не все так однозначно. Сам я рассматриваю необходимость максимального приближения тестового стенда к production только как рекомендацию, от которой во многих случаях вполне можно отступить. Возможность такого отступления и последствия, к которым это может привести, нужно рассматривать применительно к каждому конкретному случаю.
Я не против XML конфигураций вообще, я против разрешения зависимостей и написания маппингов для NHibernate и т.п в XML.

А я – нет. В некоторых случаях они могут быть весьма удобны, и тут все IMHO зависит от пути, каким итоговая сборка попадет в production. Все эти mappings можно валидировать – как вручную, силами девочек-тестеров, так и автоматически на этапе сборки проекта.
Denis Gladkikh ( ) #
avatar
hazzik, у вас от 14 до 19 сервисов? Я бы явно это решил бы через модульность и расширяемость, точнее так и сделано у меня. Есть один сервис, который может в себе гонять сколько угодно джобов (плагинов). Если прям нужно, чтобы были сервисы независимы, то опять же не проблема, просто этот сервис разворачиваем из двух папок с разными именами с разными наборами джобов.

Все же, раз у вас тоже есть смс рассылки. Вот расскажите. Как у вас происходит тестирование. То есть тестовый стенд поднят постоянно? Реально каждый день тратятся деньги на смс-тестирование? К примеру, у нас есть задача - ежедневное информирование о состоянии показателей.
Denis Gladkikh ( ) #
avatar
Я говорил о конфигурации программ, в частности в данном случае подмена зависимостей делает из одной программы ДРУГУЮ (надеюсь я понятно объяснил:)).


А у нас модульное приложение, каждый модуль может быть куплен по отдельной цене. Один модуль - "администрирование" есть всегда. Итого пускай остается 9 модулей, итого у нас получается 2^9 различных вариаций/сборок программ, как изволите с таким поступать? У нас же 2^9 различных программ ;)
hazzik ( ) #
avatar
@Denis
Вместе сервиса SMS поднят тестовый двойник, который эмулирует работу реального, но это отдельная внешняя система к нашей.

Нет разницы в том, отдельные приложения или в качестве плагинов к какому-то диспечеру, факт в том, что каждое такое приложение (плагин) имеет свои зависимости, и граф этих зависимостей далеко не маленький (SRP дает о себе знать;)) и уследить что там происходит в каждой из этих XML просто нереально.
hazzik ( ) #
avatar
В вашем случае я б в XML вынес только конфигурирование модулей(если уж очень приспичило бы), а модуль свои зависимости резолвил бы сам.
Denis Gladkikh ( ) #
avatar
hazzik, по поводу смс: ну что ж, видимо есть время на поднятие таких двойников. По поводу модулей, я не о том, ну да ладно. Давайте закончим этот флейм ;)
Добавить комментарий

Если вы хотите получать уведомления о новых комментариях к данному топику, укажите, пожалуйста, email и отметьте соответствующий пункт в форме. Если вы хотите добавить код в тексте комментария, то заключите его внутри тега [code]...[/code], более того можно уточнить язык, на котором написан данный код при помощи [code cs]...[/code], где вместо cs могут быть cs, html, xml, java, js, php, sql, cpp, css.

 

busy