New is Glue
Be wary of the
new keyword in your code, and recognize the decision you're making by using it.
Show Notes / Transcript
This week we're going to talk about the phrase, "New is Glue". I came up with this phrase a few years ago and wrote a blog post about it, and have incorporated it into a number of my conference presentations and training courses on Pluralsight and DevIQ. The goal of the phrase is to stick in developers' heads so that when they are writing or reviewing code that directly instantiates an object, they understand that this is tightly coupling their code to that particular object implementation. Let's step back for a moment and talk about coupling, and then return to why tight coupling and the new keyword can be problematic.
Your software is going to be coupled to a variety of things in order to function. Software that has no coupling is too abstract to actually do anything. Some things, like your choice of framework or language, are likely tight coupling decisions you're happy with because you've considered the alternatives and are satisfied that you can achieve your application's goals in the language and framework of choice. Other things, like the infrastructure your application depends on, might be tightly or loosely coupled depending on how you write your code. Code that is tightly coupled to a particular file system or a particular vendor's datbase tends to be more difficult to test, change, and maintain than software that is loosely coupled to its infrastructure. Although coupling is unavoidable, you want to make a conscious decision about whether and where you want to tightly couple your code to specific implementations, rather than having accidental coupling spread througout your codebase.
There are typically three ways in which code references specific implementations in a tightly coupled manner. Each of these violates the Open-Closed Principle since functions that use these techniques cannot change their implementation logic without the code itself being changed. The first two are making calls to static methods and the closely related technique of referencing a Singleton pattern instance, particularly when accessed using a static Instance method (making this just a variation of the static method call). The third is the use of the new keyword to directly instantiate a particular instance type. Your application needs to work with certain implementation types in order to be useful, but the decision of which implementation types to use should be made in ideally one place, not scattered throughout your system.
new a problem?
Using the new keyword can be a problem when the type being created is not just a POCO, or Plain Old CLR Type, and especially when the type has side effects that make the calling logic difficult to test in isolation. If you find yourself instantiating a string or a DateTime in a function, or a custom DTO, that's probably not going to make that function any more difficult to test. The fact that you're gluing your code to that particular implementation is not a large concern, so you should recognize it but move on because it isn't a major risk to the software's maintainability.
However, if you discover a function that is directly instantiating a SqlConnection that, during its construction, immediately tries to connect to a database server and throws an exception if it can't find one, that presents a much bigger risk. Without special tools, there's no simple way to test the calling code in a way that doesn't involve setting up a real database for it to try to connect to. Further, the lack of abstraction around the connection may make it more difficult to implement connection pooling, caching, or a different kind of data store. If you find code like this in your data access layer, implementing an interface, then it's probably fine and in the right location. If you find it in your business logic or UI layer, you should think long and hard before you decide it's a good risk to tightly couple to a particular data technology from this location in your code.
The point of "New is Glue" is not to say that "new is bad", but rather to raise awareness. You should think about the fact that you're tightly coupling the current function or class to a particular implementation when you use the new keyword. In many cases, after briefly considering this, you'll conclude that you're fine with the coupling and continue on. But in some cases you may realize that you're about to add tight coupling to an implementation, and the current code isn't where you want to make that decision. In that case, you'll want to refactor your code so that it works with an abstraction and some other code is responsible for providing the actual implementation the function or class will use.
What about in tests?
Often, our unit tests become very brittle and slow us down when there is a lot of repetition in them. Repeatedly instantiating types to be tested within each test is a common problem. You can mitigate this issue by ensuring that your code for creating a class for test purposes in a particular way only exists at most once per test class, rather than once per test. You can easily achieve this by placing instantiation logic in setup or constructor methods of your test classes. You can further extract common construction logic from your tests by using the Builder design pattern and having common builder classes that all of your test classes rely on to create the instances they will use. With these approaches in place, changes to how objects are instantiated will require minimal changes in your tests.