Code Generators or T4 Templates, are they really evil?

前端 未结 15 1384
[愿得一人]
[愿得一人] 2021-01-31 12:19

I have heard people state that Code Generators and T4 templates should not be used. The logic behind that is that if you are generating code with a generator then there is a bet

相关标签:
15条回答
  • 2021-01-31 13:08

    It seems to me code generators are fine as long as the code generation is part of your normal build process, rather than something you run once and then keep its output. I add this caveat because if just use the code generator once and discard the data that created it, you're just automatically creating a massive DRY violation and maintenance headache; whereas generating the code every time effectively means that whatever you are using to do the generating is the real source code, and the generated files are just intermediate compile stages that you should mostly ignore.

    Lex and yacc are classic examples of tools of allow you to specify functionality in an efficient manner and generate efficient code from it. Trying to do their jobs by hand will lengthen your development time and probably produce less efficient and less readable code. And while you could certainly incorporate something like lex and yacc directly into your code and do their jobs at run time instead of at compile time, that would certainly add considerable complexity to your code and slow it down. If you actually need to change your specification at run time it might be worth it, but in most normal cases using lex/yacc to generate code for you at compile time is a big win.

    0 讨论(0)
  • 2021-01-31 13:12

    More code means more complexity. More complexity means more places for bugs to hide, which means longer fix cycles, which in turn means higher costs throughout the project.

    Whenever possible, I prefer to minimize the amount of code to provide equivalent functionality; ideally using dynamic (programmatic) approaches rather than code generation. Reflection, attributes, aspects and generics provide lots of options for a DRY strategy, leaving generation as a last resort.

    0 讨论(0)
  • 2021-01-31 13:14

    You can do new T(); if you do this

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

    As for code-generators. I use one every day without any problems. I'm using one right now in fact :-)

    Generics solve one problem, code-generators solve another. For example, creating a business model using a UML editor and then generating your classes with persistence code as I do all of the time using this tool couldn't be achieved with generics, because each persistent class is completely different.

    As for a good source on generics. The best has got to be Jon Skeet's book of course! :-)

    0 讨论(0)
  • 2021-01-31 13:15

    As the originator of T4, I've had to defend this question quite a few times as you can imagine :-)

    My belief is that at its best code generation is a step on the way to producing equivalent value using reusable libraries.

    As many others have said, the key concept to maintain DRY is never, ever changing generated code manually, but rather preserving your ability to regenerate when the source metadata changes or you find a bug in the code generator. At that point the generated code has many of the characteristics of object code and you don't run into copy/paste type problems.

    In general, it's much less effort to produce a parameterized code generator (especially with template-based systems) than it is to correctly engineer a high quality base library that gets the usage cost down to the same level, so it's a quick way to get value from consistency and remove repetition errors.

    However, I still believe that the finished system would most often be improved by having less total code. If nothing else, its memory footprint would almost always be significantly smaller (although folks tend to think of generics as cost free in this regard, which they most certainly are not).

    If you've realised some value using a code generator, then this often buys you some time or money or goodwill to invest in harvesting a library from the generated codebase. You can then incrementally reengineer the code generator to target the new library and hopefully generate much less code. Rinse and repeat.

    One interesting counterpoint that has been made to me and that comes up in this thread is that rich, complex, parametric libraries are not the easiest thing in terms of learning curve, especially for those not deeply immersed in the platform. Sticking with code generation onto simpler basic frameworks can produce verbose code, but it can often be quite simple and easy to read.

    Of course, where you have a lot of variance and extremely rich parameterization in your generator, you might just be trading off complexity an your product for complexity in your templates. This is an easy path to slide into and can make maintenance just as much of a headache - watch out for that.

    0 讨论(0)
  • 2021-01-31 13:15

    I've used T4 for code generation and also Generics. Both are good, have their pros and cons, and are suited for different purposes.

    In my case, I use T4 to generate Entities, DAL and BLL based on a database schema. However, DAL and BLL reference a mini-ORM I built, based on Generics and Reflection. So I think you can use them side by side, as long as you keep in control and keep it small and simple.

    T4 generates static code, while Generics is dynamic. If you use Generics, you use Reflection which is said to be less performant than "hard-coded" solution. Of course you can cache reflection results.

    Regarding "return new T();", I use Dynamic Methods like this:

    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();
        }
    }
    

    Then, I call it like this:

    ObjectCreateMethod _MetodoDinamico = new ObjectCreateMethod(info.PropertyType);
    object _nuevaEntidad = _MetodoDinamico.CreateInstance();
    
    0 讨论(0)
  • 2021-01-31 13:16

    Maybe it is a bit harsh, but for me code generation smells.

    That code generation is used means that there are numerous underlying common principles which may be expressed in a "Don't repeat yourself" fashion. It may take a bit longer, but it is satisfying when you end up with classes that only contain the bits that really change, based on an infrastructure that contains the mechanics.

    As to Generics...no I don't have too many issues with it. The only thing that currently doesn't work is saying that

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

    But even that will be possible in the next version of C#.

    0 讨论(0)
提交回复
热议问题