There are 7 main development areas and decoupling, especially decoupling using interface-based design, will be most successful when it occurs in all possible areas.
- Code – writing code, sharing code, linking to libraries
- Test Code – Unit Tests, Mocked tests, coded functional tests
- User Experience – UI design. The UI should be decoupled from code.
- Source Control – Code repositories, branching
- Build – compiling, versioning, signing, publishing
- Install – Any install method such as MSI, EXE, RPM. Any updates or patches.
- Localization – Make this independent of build/compiling
If you decouple everywhere, it is easy to remain decoupled. This leads to good architecture – the overall design of your software.
Code should be easy to write and maintain always.
Let’s face it. Coupled code is often the easiest to start with. However, while it is easy to start out using coupled code, it becomes very difficult to use coupled code as a project grows.
Interface-based design is one method of decoupling code. You are still coupled, just instead of coupled to code, you are coupled to an interface that rarely changes and the code that implements that interface is free to be refactored as long as the tests against the interface pass.
If your code is decoupled, you are free to write your code as you will.
I often hear someone say they don’t need to use interface-based design because their code is never going to be an API. This is not true. If you are Unit Testing properly, your code is always going to be an API even if only your Unit Tests and Functional Tests interface with your code/API.
In decoupled code using interface-based design, your interfaces should have 400% coverage.
100% for each of the following:
- Expected values
- Null values
- Empty values
- Exception causing values
Write Unit Tests that are exhaustive for your interfaces. The object that implements the interface will be easier to refactor this way because the new refactoring can implement the interface either directly or through an adapter and you will know nothing can be broken in your refactored code because all the tests pass.
All code that touches the system or requires resources not on a clean build system should be mockable. If you use interface-based design your are mockable by default. For your development language, do a search for mocking libraries such as RhinoMocks for C#. Also, be aware of how to use an adapter or wrapper to allow for mocking, such as SystemWrapper for C#.
User Interfaces (UIs) should be developed independent of the back-end code. They should be developed first or at least simultaneously to the back-end code. This allows for user experience tests to occur early in the process and changes based on the feedback to be easy and quick. Tools to design UIs that allow for customer feedback already exist, such as SketchFlow for rich Windows WPF UIs.
The UI should be decoupled from the code. You should probably have two separate UIs just so you can switch between them to show how the UI is not coupled to the code and can be switched at any time.
Decouple your source control. I don’t mean that one team uses Subversion, another GIT, and another TFS. I mean that your project is in its own repository, and has few as possible prerequisites projects as possible. This is made possible by interface-based design. If you have a 10 features, all on top of core functionality, you should have one core functionality repository and 10 feature repositories.
Also, if you source is not decoupled you are tempted to couple your code with other code. This lead us to build.
Build does not just mean on a build system. Lets start with the developer building on their local machine. A developer should be able to check out a project and build it without any effort beyond the checkout. Ok, maybe if you are C# developer you need to at least install Visual Studio. But then you should be able to check out a solution, open it, and press F5 to build it and the build should work. If not, your process is broken and developers’ time is wasted.
Now to the build system or CI builds.
Coupled code is hard to build. Dependency loops can occur. Builds have to occur in specific order, and this takes longer.
With interface-based design, you build your interface libraries first and they build almost instantly as they have no real code. Then you simultaneously build everything else. Your build is extremely fast because you can build everything with different threads simultaneously (or even on different build machines), even for extremely large applications because they only link to the interfaces.
Different parts of your project can and should have different versions. Just because you release YourProduct 3.0 doesn’t mean that the a library hasn’t already matured beyond to 5.1. And because of your decoupled interface-based design, you can upgrade YourProduct 3.0 to use version 5.2 of the library and it won’t know the difference. This leads right into install.
Installing should be decoupled. If your project is big enough, it is ok to have 100 different installs. Sure, for Windows don’t show 100 items in Add / Remove Programs, just one or a few. But that doesn’t mean that you don’t have each piece of your product separately installable, patchable, and upgradeable.
Think of a product with four different parts. You should be able to install, upgrade, and patch each of the four parts separately. The product as a whole can be version 3.0 but different parts can be different versions. You may have a piece that is very mature, at version 5.1 and a piece that was just refactored for your 3.0 release and it is version 1.0. Your four products could be: 3.0, 5.1, 1.0, 3.0.
If you are properly decoupled and using interface-based design, you can upgrade any of the four parts to a new version without the other three parts having any adverse affects at all.
It is frustrating to have to wait for a build to see and test your localization. Sure some people only have to wait for the first build and then can generate their satellite assemblies from there. However, I disagree with any localization strategy that involves build. I cringe when I hear “localization lock down” because the release is soon. Really? Does it have to be this way?
How can you do this? Decouple text from the software.
Text should stay where text belongs, in text files. Whether it be a property-value-list or an XML, localization should be dynamic. Let the software pull in text dynamically from a text file. Then switching language in a UI is just a matter of changing from one text file set to another (probably as simple as changing a two, three, or five-letter path).
And the text is patchable without build risk. If you are 1 day from shipping and you realize you have a completely erroneous string, you can edit a text file to fix it without little to no risk.
Edit the text file(s) without a build and run time just picks up the corrected text.
Since the UI was done before or simultaneous to the back-end code, the localization will be done early and changing may be made often.
Decoupling is a massive undertaking and doesn’t just mean to decouple code. Decoupling is methodology that should be used everywhere in all aspects of the software development life cycle.