Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class versioning

I'm looking for a clean way to make incremental updates to my code library, without breaking backwards compatibility. This could mean adding new members to classes, or changing existing members to provide additional functionality. Sometimes I am required to change a member in such a way that it would break existing code (e.g. renaming a method or changing its return type), so I'd rather not touch any of my existing types once they are shipped.

The way I currently set this up is through inheritance and polymorphism by creating a new class that extends the previous "version" of that class.

class diagram

The way this works is by creating the appropriate version of StatusResult (e.g. StatusResultVersion3), based on the actual value of the ProtocolVersion property, and returning it as an instance of CommandResult.

Because .NET does not seem to have a concept of class versioning, I had to come up with my own: appending the version number to the end of the class name. This will no doubt make you cringe. I could easily imagine yourself scratching your eyes out after zooming in on the diagram. But it works. I can add new members and override existing members, without introducing any code breaking changes.

Is there a better way to version my classes?

like image 271
Steven Liekens Avatar asked Nov 04 '22 03:11

Steven Liekens


2 Answers

There are typically two approaches when considering existing code and assembly updates:

  1. Regression Testing

    This is a great approach for non-breaking changes, where you can simply overload functions to provide new parameters, etc. Visual Studio has some very advanced unit testing capabilities to make your regression testing relatively easy and automated.

  2. Assembly Versions

    If your changes are going to start breaking things, like rewriting the way some utility works, then it's time for a new assembly version. .NET is very good about working with assembly versions. You can deploy the versioned assemblies to different folders so that existing code can continue to reference the old version while new code can take advantage of the features in the new version.

like image 140
JDB Avatar answered Nov 13 '22 05:11

JDB


The problem with interfaces is that once published they're largely set in stone. To quote Anders Hejlsburg:

... It's like adding a method to an interface. After you publish an interface, it is for all practical purposes immutable, because any implementation of it might have the methods that you want to add in the next version. So you've got to create a new interface instead.

So you can never just update an interface, you need to create a completely new one. Of course, you can have a single class implement both interfaces so your maintainability effort is fairly low compared with (say) polymorphic classes where your code will become spread out between multiple classes over time.

Multiple Interfaces also allows you to remove methods in a way that classes do not (Sure, you can Deprecate them but that can result in very noisy intellisense after a few iterations)

I personally lean towards having entirely stand-alone versions of the interface in each assembly version.

That is to say...

v 0.1.0.0

interface IExample
{
    String DoSomething();
}

v 0.2.0.0

interface IExample
{
    void DoSomethingElse();
}

How you implement them behind the scenes is up to you, but most likely it'll be the same classes with slightly different methods doing similar jobs (otherwise, why use the same interface?)

All the old code should be referencing 0.1.x.x and new code will reference 0.2.x.x. About the only issue is when you find (say) a security flaw and the fix needs to be back-ported to an earlier version. This is where a decent VCS comes in (Personal preference is TFS but SVN or anything else which supports branching/merging will do).

Merge the fixes from the 0.2 branch back into the 0.1 branch and then do a recompile to result in (say) 0.1.1.0.

As long as you stick to a process like this:

  • Major or Minor build will increment if there are any breaking changes (aka signatures will not change on Build/Revision increments)
  • Use publisher policies if the new Major/Minor version should be used by older programs (equivalent to guaranteeing nothing broke so use the new version anyway)
  • References in client apps should point at a Major/Minor version but not specify revision/build

This gives you:

  • A clean codebase without legacy clutter
  • Allows clients to use the latest version with no code changes if nothing has broken
  • Prevents clients using newer versions of an assembly which do have breaking changes until they recompile (and, one hopes, update their code as appropriate to take advantage of the new features.)
  • Allows you to release security patches for previous versions
like image 28
Basic Avatar answered Nov 13 '22 04:11

Basic