I'm a big fan of Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, now in second edition. It's virtually unique in my experience as being a book published by a software maker that actually includes multiple notes admitting that their product isn't perfect. It's incredibly confidence inspiring to know that the people on the other side of my library think about what they're doing and are willing to learn and improve.

That said I don't fully agree with some of the recommendations the book contains. In particular the section on the use of abstract base classes vs. interfaces concerns me. This is inevitable an area where tradeoffs need to be made. They argue quite correctly that an interface cannot easily be extended because every class that implements it must be extended to match. This does not apply to an abstract base class. Extending an interface is therefore a breaking change.

My problem with the argument comes down to two primary points:

  • As a user of a framework interfaces are generally significantly easier to work with because they work more naturally with TDD and mocking and stubbing (which I consider key practices).
  • I believe the concern over introducing breaking changes is overstated, particularly for higher level libraries than those delivered by Microsoft.

On my first point; an interface may be readily mocked or stubbed (but not necessarily easily depending on complexity and interaction model). A mocked or stubbed instance will display only the behaviour it is defined to have. This is not true when dealing with an abstract base class. If the abstract class defines methods and properties that are not virtual then (unless you do some rather dodgy low level hacks) you are going to get the behaviour of the abstract class. This may be inappropriate and in some cases unworkable. There are many classes in the BCL that are simply not mockable. This leads to a situation whereby you must accept that portions of your own code are not testable. You can write a wrapper about these classes but this is both duplicative and not completely effective. If your framework requires me to give up TDD in order to use it effectively then I'll find an alternative.

On my second point I feel that in this case Microsoft's inclination to not break existing code is to some extent counterproductive. This is not to understate the cost of introducing breaking changes which can be significant. It is not something that should be down often or lightly. However it is at times necessary in order to allow your code to evolve and stay relevant over time.

It is my opinion that that the Microsoft perspective of producing the lowest level libraries prejudices their opinions on introducing breaking changes. In their position doing so is a very serious matter that can affect large numbers of users. This means that their disincentive is significantly larger than that of the average framework developer. For developers of smaller frameworks and especially open source frameworks the consequences affect less people. Additionally there is a greater likelihood that the changes will be contained to a subset of an application.

My final concern with the argument is the level to which it assumes that users of the framework will extend interfaces as opposed to consuming them. Extending an interface is only a breaking change to classes that implement it not to the consumers of the interface. In my experience I am significantly more likely to implement interfaces defined within the application than those provided by frameworks (where generally a provided implementation is used). The argument presented in the book is very much focused on the concerns of implementers of the interface. If there will be relatively few implementers of the interface proportionally to the consumers this may be a smaller concern.

In making this consideration you must also consider implementers of your interface who make their implementations available to third parties. In breaking your interface you are then breaking the code of another party which may be viewed negatively. It may also hinder adoption of the new version of your framework if its users cannot adopt it until the additional classes on which they depend in their application have been updated to support your new version.

My personal preference is that you have clearly defined and communicated guidelines for when breaking changes are permitted and that breaking changes are rare and performed for compelling reasons. I would suggest that:

  • Breaking changes be confined to significant releases only. Minor releases are not an appropriate place for such changes due to the frequency with which they contain important bug fixes.
  • Where possible consider providing an alternate interface and marking the original version obsolete.
  • Have a clearly defined timeline for the removal of obsolete version.
  • Consider the requirements of your users and their need or desire to implement the interfaces you provide, or use third party implementations.

As such my position is not the opposite of what is provided in Framework Design Guidelines. Rather it is a moderation and a suggestion that alternate concerns are also applicable in making these decisions. Tread carefully.