
A few arguments against Dependency Injection and Inversion of Control
I remember that in the times of .NET 1.1 and 2.0, one could often see the prophecies of Microsoft evangelists, saying that soon any housewife will be able to create websites and write programs. Most programmers laughed, but as it turned out, someone took this seriously. In any case, this explains why IoC / DI design patterns got a second wind in the 2000s, even inside MS itself (I wish you never to encounter SCSF in your life).

From the point of view of software development theory, I personally had to read or hear laudatory articles and reviews about IoC / DI much more often, but, as always, there is criticism too. You can find, for example, here (English), here (English), here (Habr), more (English). In particular, the violation of the principle of encapsulation in OOP is blamed.
But I would like not to go into theological disputes (in which I do not consider myself an expert), but focus on workdays (which, in my opinion, are few covered in publications).
Indeed, have you seen a lot of books or articles on programming that indicated that the code always contains errors (even the calculator cannot be covered with 100% testing) and you need to insert the possibilities of diagnosing errors in a productive environment where no one will let you put Studio and debug? What if the product turns out to be worthy and finds its user, then it will certainly be finalized, but it is likely that other people with unknown level of training will do it? So I can’t remember a single one.
By the will of fateAs a result of personal and other management errors, I happened to run the project in splendid isolation and plunge into the code, to which I had no special desire before. Using IoC / DI in one of the modules was not the biggest problem, but it was the most vivid and memorable, especially since I already had to deal with this miracle of abstractionist thought at the previous place of work.
So.
I read somewhere in my time - either Brooks, or Lister and DeMarco, I don’t remember exactly - that programming languages were invented not for machines, but for people. In the end, it doesn’t matter to the machine whether you will manually zeroes and ones, or write text commands first and then compile them into executable code. The compiler does not care if the programmer inserts comments into his code or considers that he is self-documenting. The JavaScript interpreter equally handles an obfuscated, compressed script and formatted, with human-readable names of variables and functions.
The same can be said about the architecture and programming paradigms: all this was invented for a person, not for a machine, in order to facilitate the task of writing programs and increase their level of complexity. Therefore, the first and most important minus, in my deep conviction, of IoC / DI templates is the distribution of logic into different pieces of the project, which greatly complicates the understanding and perception of the solution as a whole. More precisely, the problem is not in the actual discontinuity, but in the fact that it is very difficult to connect all this together in statics, only in runtime.
If your program consists of a dozen or two objects (i.e. these are no more than 100 files with descriptions of classes and interfaces), then taking all this together as a whole is relatively simple. At one time I happened to accompany a desktop application created on the basis of Microsoft Smart Client Software Factory (then he replaced Prism with MS, but I’m sure that IoC / DI is also tightly used there), by its functionality it’s not so complicated, but it consisted from a couple of dozens of projects (in terms of Visual Studio), and these are hundreds and hundreds of classes responsible for DAL, and for logic, and for the user interface, and for the event model under it. Each time, when the task of adding a new feature appeared on the horizon, I started to pound slightly from the inside, because a prospect surfaced to spend a few days wondering where you need to "stick" the processing of a new field of the object from the database, more precisely - according to what classes dependencies are pushed. With weakly connected classes, believe me, this is not the most trivial task.
Perhaps my brain began to stiffen and became less susceptible to new ideas (although IoC / DI were invented, it seems, in the early 90's), but it’s hard for me to understand why the principle of encapsulation from OOP has become objectionable.
I recall a quote from bashorg:
(*) the phrase was somewhat softened so as not to bring to the Resource.
Funny, isn't it? But suchjokes I met in my project at the launch stage in PE (and it was not quite funny for me):
Very informative, agree? BBB, DDD, NNN - I intentionally changed the name of the projects and namespace, which indicated the name of the subcontracting company. But there was nothing interesting for debugging. Dispatcher.Start () - this is the launch of the MS Windows service, the entry point to the program. StructureMap is an IoC library. Not a single mention of any of the business methods, as Everything was done to exclude the context from the call stack.
However, I still managed to find out the cause of the error using the method “what has changed compared to the previous working version?”, But I want to devote a separate argument to it, which goes as follows.
As the saying goes, trouble does not come alone. So IoC / DI go hand in hand with the Service Locator pattern , which is closely related to the idea of late binding. At the same time, when compiling the solution, it is not checked whether the signature of the methods meets the requirements at the point of call.
So it happened in my case from the example above. A new parameter has been added to one of the business methods. The project successfully compiled, but refused to start. I got lucky.
Firstly, in this project there were only about 50 classes and using the [scientific] poke method, we were able to relatively quickly establish that we still need to refine the configuration loading class.
Secondly, an exception was thrown at the very beginning of the program, and there was no way to get past this. Unfortunately, I could not find another real life example from the same project, because I didn’t immediately put it “in memoris”, and then everything was lost in kilotons of logs. But the instantiation of the handlers may not be carried out immediately at the start of the application, but, as necessary, when user data appears that require appropriate processing. This can happen after hours, days, or even weeks. However, the effectiveness of the “was - became” error finding method mentioned above is inversely proportional to the time elapsed since the problematic release and the number of releases following it.
The complexity of perceiving the code itself and the difficulty of debugging are not fatal. The trouble is that for the purpose of introducing, accompanying and developing the product, people from flesh and blood are needed. And obviously, the more complex the product, the higher the requirements for the level of training of specialists. And here already there are problems of the labor market and the education sector, which, I believe, need not be told to anyone here: it takes a lot of time to find competent, experienced specialists, it is not cheap to maintain, it is even more difficult to keep them. And the study of "materiel" takes weeks and even months.
As a consequence of this argument, I would like to highlight 3 more clarifications.
At the beginning of the article, links were given to articles, some of which provide conditions when DI / IoC can be applied, and when it is better not to do this. I propose to supplement the list when you do NOT need to use these templates with rules, rather, at the managerial level, rather than from the field of computer science. Do not use such templates if:

From the point of view of software development theory, I personally had to read or hear laudatory articles and reviews about IoC / DI much more often, but, as always, there is criticism too. You can find, for example, here (English), here (English), here (Habr), more (English). In particular, the violation of the principle of encapsulation in OOP is blamed.
But I would like not to go into theological disputes (in which I do not consider myself an expert), but focus on workdays (which, in my opinion, are few covered in publications).
Indeed, have you seen a lot of books or articles on programming that indicated that the code always contains errors (even the calculator cannot be covered with 100% testing) and you need to insert the possibilities of diagnosing errors in a productive environment where no one will let you put Studio and debug? What if the product turns out to be worthy and finds its user, then it will certainly be finalized, but it is likely that other people with unknown level of training will do it? So I can’t remember a single one.
So.
Difficulty to understand
I read somewhere in my time - either Brooks, or Lister and DeMarco, I don’t remember exactly - that programming languages were invented not for machines, but for people. In the end, it doesn’t matter to the machine whether you will manually zeroes and ones, or write text commands first and then compile them into executable code. The compiler does not care if the programmer inserts comments into his code or considers that he is self-documenting. The JavaScript interpreter equally handles an obfuscated, compressed script and formatted, with human-readable names of variables and functions.
The same can be said about the architecture and programming paradigms: all this was invented for a person, not for a machine, in order to facilitate the task of writing programs and increase their level of complexity. Therefore, the first and most important minus, in my deep conviction, of IoC / DI templates is the distribution of logic into different pieces of the project, which greatly complicates the understanding and perception of the solution as a whole. More precisely, the problem is not in the actual discontinuity, but in the fact that it is very difficult to connect all this together in statics, only in runtime.
If your program consists of a dozen or two objects (i.e. these are no more than 100 files with descriptions of classes and interfaces), then taking all this together as a whole is relatively simple. At one time I happened to accompany a desktop application created on the basis of Microsoft Smart Client Software Factory (then he replaced Prism with MS, but I’m sure that IoC / DI is also tightly used there), by its functionality it’s not so complicated, but it consisted from a couple of dozens of projects (in terms of Visual Studio), and these are hundreds and hundreds of classes responsible for DAL, and for logic, and for the user interface, and for the event model under it. Each time, when the task of adding a new feature appeared on the horizon, I started to pound slightly from the inside, because a prospect surfaced to spend a few days wondering where you need to "stick" the processing of a new field of the object from the database, more precisely - according to what classes dependencies are pushed. With weakly connected classes, believe me, this is not the most trivial task.
Perhaps my brain began to stiffen and became less susceptible to new ideas (although IoC / DI were invented, it seems, in the early 90's), but it’s hard for me to understand why the principle of encapsulation from OOP has become objectionable.
Uninformative debugging data
I recall a quote from bashorg:
#define TRUE FALSE //счастливой отладки, уроды (*)
(*) the phrase was somewhat softened so as not to bring to the Resource.
Funny, isn't it? But such
Stack trace
StructureMap.StructureMapException: StructureMap Exception Code: 202
No Default Instance defined for PluginFamily System.Func`2 [[System.String, mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089], [System.Boolean, mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089]], mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089
at StructureMap.BuildSession. <. Ctor> b__0 (Type t)
at StructureMap.Util. Cache`2.get_Item (KEY key)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginTessionpe, Build
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> c__DisplayClass2.b__0 (IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.b__0 (IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.b__0 (IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.b__0 (IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.Container.GetInstance [T] (String instanceKey)
at NNN.BBB.Integration.Uvhd.Dispatcher.Start () in j: \. projects \ DDD \ trunk \ NNN.BBB.UvhdIntegrationService \ Dispatcher.cs: line 30
No Default Instance defined for PluginFamily System.Func`2 [[System.String, mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089], [System.Boolean, mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089]], mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089
at StructureMap.BuildSession. <. Ctor> b__0 (Type t)
at StructureMap.Util. Cache`2.get_Item (KEY key)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginTessionpe, Build
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> c__DisplayClass2.
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance (Type pluginType)
StructureMap.Pipeline.Instance.createRawObject at (Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get [T] (String propertyName, BuildSession session)
at lambda_method (Closure, IArguments)
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1. <> C__DisplayClass2.
at StructureMap.Construction.InstanceBuilder.BuildInstance (IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build (Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.StartInbuilder BuildInstrild session)
at StructureMap.Pipeline.Instance.Build (Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve (Type pluginType
Session Instance
at StructureMap.BuildSession.CreateInstance (Type pluginType, Instance instance)
at StructureMap.Container.GetInstance [T] (String instanceKey)
at NNN.BBB.Integration.Uvhd.Dispatcher.Start () in j: \. projects \ DDD \ trunk \ NNN.BBB.UvhdIntegrationService \ Dispatcher.cs: line 30
Very informative, agree? BBB, DDD, NNN - I intentionally changed the name of the projects and namespace, which indicated the name of the subcontracting company. But there was nothing interesting for debugging. Dispatcher.Start () - this is the launch of the MS Windows service, the entry point to the program. StructureMap is an IoC library. Not a single mention of any of the business methods, as Everything was done to exclude the context from the call stack.
However, I still managed to find out the cause of the error using the method “what has changed compared to the previous working version?”, But I want to devote a separate argument to it, which goes as follows.
Leveling the advantages of compiled languages
As the saying goes, trouble does not come alone. So IoC / DI go hand in hand with the Service Locator pattern , which is closely related to the idea of late binding. At the same time, when compiling the solution, it is not checked whether the signature of the methods meets the requirements at the point of call.
So it happened in my case from the example above. A new parameter has been added to one of the business methods. The project successfully compiled, but refused to start. I got lucky.
Firstly, in this project there were only about 50 classes and using the [scientific] poke method, we were able to relatively quickly establish that we still need to refine the configuration loading class.
Secondly, an exception was thrown at the very beginning of the program, and there was no way to get past this. Unfortunately, I could not find another real life example from the same project, because I didn’t immediately put it “in memoris”, and then everything was lost in kilotons of logs. But the instantiation of the handlers may not be carried out immediately at the start of the application, but, as necessary, when user data appears that require appropriate processing. This can happen after hours, days, or even weeks. However, the effectiveness of the “was - became” error finding method mentioned above is inversely proportional to the time elapsed since the problematic release and the number of releases following it.
Increased requirements for the level of training of specialists
The complexity of perceiving the code itself and the difficulty of debugging are not fatal. The trouble is that for the purpose of introducing, accompanying and developing the product, people from flesh and blood are needed. And obviously, the more complex the product, the higher the requirements for the level of training of specialists. And here already there are problems of the labor market and the education sector, which, I believe, need not be told to anyone here: it takes a lot of time to find competent, experienced specialists, it is not cheap to maintain, it is even more difficult to keep them. And the study of "materiel" takes weeks and even months.
As a consequence of this argument, I would like to highlight 3 more clarifications.
- In my practice, I have often come across a situation where the initial development team was gradually replaced by another, with less advanced knowledge in programming theory and advanced software development methods. Just because after the launch of a product, it happens that knowledge of the subject area (for example, legislation, business processes of the target audience, communication skills) comes to the fore, and not coding technologies. In such a situation, the more complex the code, the faster the product “evolves” into an intricate necklace of crutches and patches.
- The more complex the code, the longer the immersion in the beginner’s project takes.
- It is equally obvious that the circle of team members is narrowing, who can contribute to the code. Someone will consider it even a plus. But I will not even refer to my experience, but to Rainwater’s book, How to Graze Cats. A guide for programmers supervising other programmers . ” It tells the story of PM and his programmer on the eve of the deadline, when both were fired due to failure, although most likely they would be on time if the head helped his programmer.
Conclusion
At the beginning of the article, links were given to articles, some of which provide conditions when DI / IoC can be applied, and when it is better not to do this. I propose to supplement the list when you do NOT need to use these templates with rules, rather, at the managerial level, rather than from the field of computer science. Do not use such templates if:
- You are not 100% sure that at the stage of launching the project, its stabilization, experimental and even industrial operation, the developers of this product will be quickly available.
- The designer and developer of the system is the one and only person who (not surprisingly) does not conduct detailed documentation of the architecture of his brainchild.
- You have no reason to believe that at the design stage you have provided all the options for using the product and at the stage of delivery of the project or even after its launch there will be no urgent need to “finish” the business logic in a hurry; or if the business environment in which the product is to be floated is too volatile, for example, frequent changes in regulators, market competition, lack of clear standards / recommendations / practices in the industry.