Compatibility and Breaking Changes



I posted a comment to ScottW’s weblog about sealing classes and it got me thinking about breaking changes.


 


A breaking change is a typically defined as a change made to an API, or an API’s behavior that would cause code compiled on one version of the .NET Framework to somehow fail when run on a different version.


 


This brings up the .NET Framework’s breaking change policy.  A typical knee-jerk reaction is, “Well, just don’t make any breaking changes.  Then apps will always run, right?”


 


The problem comes when you try and define the term “fail”.  What is a failure?  Surely, an exception where none was thrown before.  How about a different order of events?  What about a UI fit and finish breaking changes?  On the far end of the spectrum, any bug fix you make is breaking change.  This is obviously unacceptable.  So where is the line drawn?


 


This is a question we debate about internally at MS to no end.  Dozens of the brightest folks I know can sit in a room for an hour and get no closer to a resolution on even a single change.


 


It gets even more interesting when you consider the other types of breaking changes.  The scenario described above, v1 app running on v1.1 is known as a backwards breaking change.  A change that affects a v1.1 app running on v1 is known as a forward breaking change.  A slightly better description of what this means can be found on gotdotnet.


 


When all is said and done, and bits are released, we do end up making some breaking changes.  Reasons for a breaking change getting checked in include a security fix or a change with a large customer benefit and small chance of breaking apps.  And as I mentioned earlier, any breaking change which gets into a build is debated at length.


 


And then we publish them.  Both backwards and forwards.


 


One of the new compatibility rules for the next version of the Framework we are struggling with is the no enum additions rule.  We asked the Windows compatibility team what their top reasons for an app to break are and they told us additions to an enumeration. 


 


Imagine this:


 


There is the FoofyEnum with members ValueA, ValueB, and Other.  You write a switch statement with a case for ValueA, ValueB and then the default case.  In the next verison of the Framework, we add the ValueC enum.  Your code would never be expecting the ValueC and would fall through to the default processing when it was encountered.


 


Another mitigating factor to consider is the .NET Frameworks side by side policy.  SxS lets you run multiple versions of the Framework at the same time on the same machine.  But, as a general rule, you can’t rely on SxS.  There are situations where an app’s preferred runtime version won’t be present and it will default to a version that has breaking changes. 


 


What all this means is that we have to come to terms with the fact that we are a platform.  We need to learn what we can from the Windows compatibility lab and do our best to make smart decisions about which bugs we can fix and which ones will cause developers endless headaches.


 


Oh yeah, and we’re stuck with API names for eternity.  Make sure and thank Brian Pepin the next time you use the DesignerSerializationVisibilityAttribute.


 


I’d be interested in hearing your comments on what is an acceptable level of breaking change to make.


Comments (3)

  1. I’ve been thinking a lot about this recently. I’m having to interface with multiple versions the ‘nunit.framework’ assembly in my NUnitAddin project. It gets particularly fun when having to implement a changing interface! I’ve ended up making extensive use of Reflection and RealProxy. Not particularly pretty, but it works.

    This is how I would prefer to deal with changing APIs. Define a new attribute that lets you define binding redirects at the field/method/class level. Say between versions 1.0 and 2.0 a class is renamed and a method is changed to a property. The binding redirect attributes might look like this…

    [Forwards(Version="1.*", Name="HTTPProxy"]

    public class HttpProxy

    {

    public string Host

    {

    set { setHost(value); }

    }

    [Forwards(Version="1.*", Name="SetHost")]

    private void setHost(string host)

    {

    _host = host;

    }

    private string _host;

    }

    When an assembly compiled against version 1.0 is redirected to version 2.0, in effect the following redirects would occur…

    new HTTPProxy().SetHost(“mutantdesign.co.uk”)

    …would become…

    new HttpProxy().setHost(“mutantdesign.co.uk”)

    In reality the class HttpProxy would be renamed HTTPProxy and the method setHost would be renamed SetHost and made public. I imagine this could be done relatively easily by the assembly binder.

    Conversely, mappings could be made back from introduced elements to their old nearest equivalent. For example say Three was introduced to an enum. The nearest equivalent in the old API was Two. The binding attributes might look like this…

    public enum MyEnum

    {

    One,

    Two,

    [Backwards(Version="1.*", Name="Two")]

    Three

    }

    All occurrences of ‘MyEnum.Three

  2. …oops, ran out of space…

    All occurrences of MyEnum.Three would be replaced with MyEnum.Two when binding redirected from version 1.0 of the assembly.

    What do you think?



    Point and Test for Visual Studio.net

    http://dotnetweblogs.com/nunitaddin

  3. Anonymous says:

    Compatibility and Breaking Changes : ScottW’s ASP.NET WebLog

Skip to main content