Balancing maintainability, readability and testability
There is an interesting discussion going on between Eli Lopian, Roy Osherove and Oren Eini on whether or not you should design your code for testability. Eli ended one of his posts with a question about an example Oren gave in one of his posts.
Oren’s example was all about having the benefits of designing for testability. Rather writing his IO code in the simplest way possible, he introduced a factory to resolve a TextWriter.
File.WriteAllText(filename, text);
using(TextWriter writer =
IoC.Resolve<IFileWriterFactory>().Create(filename))
writer.Write(text);
This is a classic example of keeping a healthy balance between your “itites”. The first snippet gets a high score in simplicity and readability, but it fails on extensibility and testability. The other snippet is very testable since you can mock the IFileWriterFactory, and it is extensible because the client code makes no assumptions about what sort for TextWriter it uses. This flexibility comes at the expense of readability and “recognizability”. A developer who is not familiar with inversion of control will have a hard time understanding the second snippet, while anyone will “get” the first one.
We should be careful about mixing up maintainability and readability. While it is of utmost importance that the code can be read, it is also important that one can change or extend it during maintenance. A common argument for test driven development is that it improves the design, and extensibility is one of the nice byproducts of testable object oriented programming. I agree with Eli that you shouldn’t design for testability for the sake of it, and I believe that Oren’s seemingly complex design for a simple task grew from a bigger need. If you have a simple application that just emits a file using the simple File.WriteAllText approach is sufficient. You can often test this by running the code and inspecting the created file, however when the complexity of the solution grows the testability requirements follow.
Even if you can use tools like TypeMock to mock the implementation of the File class if the testing needs grow I strongly believe that you should change your design for two reasons: It is often tedious to mock concrete framework classes such as System.IO.File. I once had to write a test fixture around framework that used the System.Data classes directly. TypeMock allowed me to mock low-level types like SqlConnection, but the effort required to do this is huge. Rather than making the tests depend heavily on frameworks, I rather address these dependencies to make the code easier to maintain and the tests easier to write.
The other reason is that the need to mock concrete types is an indication of too strong coupling in the design. Using inversion of control, factories or other means of abstraction makes the implicit dependencies explicit and the code becomes easier to maintain.
Even if there is a connection between readability and maintainability, you often have to make trade-offs on these. I believe that it is a good practice to optimize the whole rather than doing micro optimizations. An extensibility optimization might reduce readability for some passages of the source in exchange for improved maintainability for the entire solution.