When I started the ‘Colibra’ project, I had two goals in my mind: eliminate complexity by providing an interface that will suit the needs of the applications I am writing, and encapsulate external dependencies to eliminate duplicate code and have a single point of maintenance when things change in the AutoCAD Civil 3D API.
Despite how straight forward these two goal seem, the reality is that we tend to take shortcuts when we write code and not always think through whether the implementation meets these requirements. But these requirements are very important. If there is one thing that we can be certain about the future, it is that there will be changes, and when those changes arrive, we want to be prepared.
Before I became part of the Civil 3D API team, I was a consumer of the Civil 3D API, very much like you. Back then, I was not that careful when I wrote code using the Civil 3D API. Working on Autodesk projects, I knew my code will always run together with Civil 3D, and I thought it was OK to slip one dependency or another. That is, until I started maintaining the projects. You know, the phase when you already shipped your application and a new release of Civil 3D becomes available and you want to support it.
With every release of Civil 3D there are changes to the API. There might be improvements to existing API’s you want to leverage, but this is not always easy if the code you need to change is wide spread throughout your application.
You may also find that features that were only available through the COM API are now available in the .NET API. You want to use the .NET API, but you have so many dependencies in your code related to COM, and the object models are so different that it is very hard to port the code safely.
You also see there are new features available, but using them create additional dependencies that you want to avoid. You want to overcome the difficulties, but you do not know how.
These are problems that we all, as developers, have experienced in the past. And we know, from that experience, that there is a solution to them; it just takes a little bit of care, and some analysis and thought before we write the code. The key to solve these issues is that you set the same goals I did for ‘Colibra’ in your own projects. That is, provide interfaces specific to your needs to reduce complexity and encapsulate external dependencies to eliminate repetitive code and provide a single point of maintenance.
When we talk about interfaces many things come to mind, so I want to explain what I am talking about. The public properties and methods of a class compose the interface of that class. This differs from the more recent concept of interface definitions, which are the specification of properties and methods that need to be implemented in a class to support a specific interface. Interface definitions are nice because they let us write our programs targeting them, as opposed to a specific implementation. They allow to have many implementations providing a more modern way of polymorphism (change of behavior during execution).
But when I talk about providing specific interfaces for your application, I am referring to the properties and methods you invoke in the classes that you implement. You can still have interface definitions that will allow you to have different implementations of the same interface, but the goal is to work with a specific interface that is related to the functionality of your application. The implementation hides the complexity of using lower-level objects provided by external API’s and centralizes the location of the code that accesses those API”s, eliminating code duplication.
Dependencies are known to be the source of most of the issues found in object oriented software these days. Most literature about object oriented programming and design defines principles that basically try to remove dependencies between objects. Design patterns, language idioms, and new features provided in modern languages try to reduce the number of dependencies introduced when we are writing software. Basically, the more dependencies you introduce in your code, the more difficult it is to change and maintain.
When you expose a type in the public interface of one of your classes, you are not only introducing a dependency in your class, but you are introducing a dependency on all client code that uses this class. If this type is provided by a external library or API, you are now forcing a dependency to this library on all the client code. If this type changes, not only you will have to change the implementation of your class, but also all the client code that uses it. This is especially bad because you do not have any control on how or when an external API is going to change.
You always want to contain external libraries in especial containers called classes. These classes will provide the required interface to your application. When things change, you will have to fix the implementation in your classes, which you have absolute control to do, but you will not have to fix any of the client code making the changes centralized and easier to spot and test.
Programming to interfaces and encapsulating dependencies are the way to success in software development. They allow to centralize points of change and eliminate code duplication. In subsequent posts, I will be applying these concepts while implementing the ‘Colibra’ library, starting with the implementation of a document manager for AutoCAD Civil 3D. Managing documents seems to be a source of pain and code duplication that I hope to help everyone resolve in a way that will serve you now and in the future, no matter what changes in the API.