Генераторы кода или шаблоны T4, действительно ли они злые?

StackOverflow https://stackoverflow.com/questions/524295

Вопрос

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

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

return new T();

Кроме того, если я хочу сгенерировать код на основе значений базы данных, я обнаружил, что с помощью Microsoft.SqlServer.Management.SMO в сочетании с шаблонами T4 было замечательно генерировать огромное количество кода без необходимости копировать / вставлять или использовать resharper.

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

О чем вы думаете?Вы предпочитаете создавать генератор или предпочитаете использовать дженерики?Кроме того, как далеко могут зайти дженерики?Я прилично разбираюсь в дженериках, но есть ловушки, с которыми я всегда сталкиваюсь, которые заставляют меня прибегать к шаблону T4.

Каков более правильный способ обработки сценариев, где вам требуется большая гибкость?О, и в качестве бонуса к этому вопросу, каковы хорошие ресурсы по C # и дженерикам?

Это было полезно?

Решение

Вы можете создать новый T();если ты сделаешь это

public class Meh<T>
  where T : new()
{
  public static T CreateOne()
  {
    return new T();
  }
}

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

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

Что касается хорошего источника по дженерикам.Лучшее должно быть Книга Джона Скита конечно!:-)

Другие советы

Как создателю T4, мне приходилось защищать этот вопрос довольно много раз, как вы можете себе представить :-)

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

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

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

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

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

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

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

Генерация кода - это не зло, и оно не пахнет!Главное - сгенерировать правильный код в нужное время.Я думаю, что T4 великолепен - я использую его лишь изредка, но когда я это делаю, это очень полезно.Безоговорочно утверждать, что генерация кода плоха, - это безоговорочное безумие!

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

Lex и yacc являются классическими примерами инструментов, позволяющих эффективно определять функциональность и генерировать на ее основе эффективный код.Попытка выполнить их работу вручную увеличит время разработки и, вероятно, приведет к менее эффективному и менее читабельному коду.И хотя вы, безусловно, могли бы включить что-то вроде lex и yacc непосредственно в свой код и выполнять их работу во время выполнения, а не во время компиляции, это, безусловно, значительно усложнило бы ваш код и замедлило бы его работу.Если вам действительно нужно изменить свою спецификацию во время выполнения, возможно, это того стоит, но в большинстве обычных случаев использование lex / yacc для генерации кода для вас во время компиляции - большая победа.

Хороший процент того, что есть в Visual Studio 2010, был бы невозможен без генерации кода.Entity Framework была бы невозможна.Простое перетаскивание элемента управления в форму было бы невозможно, как и Linq.Говорить, что генерацию кода не следует использовать, странно, поскольку очень многие используют ее, даже не задумываясь об этом.

Может быть, это немного грубо, но для меня генерация кода пахнет.

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

Что касается дженериков...нет, у меня не слишком много проблем с этим.Единственное, что в настоящее время не работает, это сказать, что

List<Animal> a = new List<Animal>();
List<object> o = a;

Но даже это будет возможно в следующей версии C #.

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

Всякий раз, когда это возможно, я предпочитаю минимизировать объем кода, чтобы обеспечить эквивалентную функциональность;в идеале использовать динамические (программные) подходы, а не генерацию кода.Отражение, атрибуты, аспекты и обобщения предоставляют множество вариантов для СУХОЙ стратегии, оставляя генерацию в качестве последнего средства.

Генерация кода для меня - это обходной путь для многих проблем, возникающих в языке, фреймворках и т.д.Они не являются злом сами по себе, я бы сказал, что это очень, очень плохо (т.е.evil), чтобы выпустить язык (C #) и фреймворк, который заставляет вас копировать и вставлять (менять местами свойства, запускать события, отсутствие макросов) или использовать магические числа (привязка wpf).

Итак, я плачу, но я использую их, потому что я должен.

Я использовал T4 для генерации кода, а также дженериков.И то, и другое хорошо, имеет свои плюсы и минусы и подходит для разных целей.

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

T4 генерирует статический код, в то время как Generics является динамическим.Если вы используете дженерики, вы используете Отражение, которое, как говорят, менее производительно, чем "жестко закодированное" решение.Конечно, вы можете кэшировать результаты отражения.

Что касается "return new T();", я использую динамические методы, подобные этому:

public class ObjectCreateMethod
    {
    delegate object MethodInvoker();
    MethodInvoker methodHandler = null;

    public ObjectCreateMethod(Type type)
    {
        CreateMethod(type.GetConstructor(Type.EmptyTypes));
    }

    public ObjectCreateMethod(ConstructorInfo target)
    {
        CreateMethod(target);
    }

    void CreateMethod(ConstructorInfo target)
    {
        DynamicMethod dynamic = new DynamicMethod(string.Empty,
                    typeof(object),
                    new Type[0],
                    target.DeclaringType);
        ILGenerator il = dynamic.GetILGenerator();
        il.DeclareLocal(target.DeclaringType);
        il.Emit(OpCodes.Newobj, target);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ret);

        methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker));
    }

    public object CreateInstance()
    {
        return methodHandler();
    }
}

Тогда я называю это так:

ObjectCreateMethod _MetodoDinamico = new ObjectCreateMethod(info.PropertyType);
object _nuevaEntidad = _MetodoDinamico.CreateInstance();

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

Для всех случаев, когда вам просто нужно сгенерировать код на основе некоторых введенных данных, лучше всего использовать генерацию кода.Наиболее очевидным, но ни в коем случае не единственным примером является редактор форм в Visual Studio.Здесь входными данными являются дизайнерские данные, а выходными - код.В этом случае дженерики на самом деле вообще не помогают, но очень приятно, что VS просто генерирует код на основе макета GUI.

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

Например, хотя здесь было сказано, что "Объекты, которые сохраняются, не могут быть обобщены", было бы лучше думать об этом как "Объекты в C #, которые автоматически сохраняют свои данные, не могут быть обобщены в C #", потому что я, конечно, могу в Python с помощью различных методов.

Подход Python, однако, может быть эмулирован на статических языках с помощью operator[ ](method_name как строка), который возвращает либо функтор, либо строку, в зависимости от требований.К сожалению, это решение не всегда применимо, и возврат функтора может быть неудобным.

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

Тип копирования / вставки сгенерированного кода (например, ORMs make) также может быть очень полезен...

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

Преимущество возникает, когда вы меняете свое исходное определение (базу данных), нажимаете compile, и ORM (если у вас есть хороший) может повторно сгенерировать вашу копию определения.Теперь все ссылки на вашу базу данных могут быть проверены средством проверки типов компиляторов, и ваш код не сможет скомпилироваться, если вы используете таблицы или столбцы, которые больше не существуют.

Подумай об этом:Если я вызываю метод несколько раз в своем коде, не имею ли я в виду имя, которое я дал этому методу изначально?Я продолжаю повторять это имя снова и снова...Разработчики языка осознали эту проблему и предложили "Безопасность типов" в качестве решения.Не удаляя копии (как предлагает DRY, мы должны это сделать), а вместо этого проверяя их на правильность.

Сгенерированный ORM код обеспечивает то же самое решение при обращении к именам таблиц и столбцов.Не удаляя копии / ссылки, но переводя определение базы данных на ваш (типобезопасный) язык, где вместо этого вы можете ссылаться на классы и свойства.Вместе с проверкой типов компиляторов это решает аналогичную проблему аналогичным образом:Гарантируйте ошибки во время компиляции, а не во время выполнения, когда вы ссылаетесь на устаревшие таблицы (классы) или столбцы (свойства) с ошибками в написании.

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

вернуть новый T();

public abstract class MehBase<TSelf, TParam1, TParam2>
    where TSelf : MehBase<TSelf, TParam1, TParam2>, new()
{
    public static TSelf CreateOne()
    {
        return new TSelf();
    }
}

public class Meh<TParam1, TParam2> : MehBase<Meh<TParam1, TParam2>, TParam1, TParam2>
{
    public void Proof()
    {
        Meh<TParam1, TParam2> instanceOfSelf1 = Meh<TParam1, TParam2>.CreateOne();
        Meh<int, string> instanceOfSelf2 = Meh<int, string>.CreateOne();
    }
} 

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

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

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

Почему возможность копировать / вставлять действительно, очень быстро делает это более приемлемым?

Это единственное оправдание для генерации кода, которое я вижу.

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

И даже если он выполняется в нулевое время, он все равно раздувает код.

Я запустил свой собственный класс доступа к данным.Он знает все о соединениях, транзакциях, компонентах хранимых процедур и т.д. и т.п., И мне пришлось написать все это только один раз. ADO.NET материал.

Прошло так много времени с тех пор, как мне приходилось писать (или даже просматривать) что-либо с объектом connection в нем, что мне было бы трудно сразу запомнить синтаксис.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top