Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 83 additions & 31 deletions Documentation/coding-guidelines/breaking-changes.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
Breaking Changes
================
# Breaking Changes

We take compatibility in the .NET Framework and .NET Core extremely seriously.

Although .NET Core can be deployed app local, we are engineering it such that portable libraries can target it and still run on the full desktop framework as well. This means that the behavior of the full .NET Framework constrains the implementation of any overlapping API in .NET Core.
Although .NET Core can be deployed app local, we are engineering it such that
portable libraries can target it and still run on the full desktop framework as
well. This means that the behavior of the full .NET Framework constrains the
implementation of any overlapping API in .NET Core.

Below is a summary of some documentation we have internally about what kinds of things constitute breaking changes, how we categorize them, and how we decide what we're willing to take.
Below is a summary of some documentation we have internally about what kinds of
things constitute breaking changes, how we categorize them, and how we decide
what we're willing to take.

Note that these rules only apply to API that have shipped in a previous RTM release. New API still under development can be modified but we are still cautious not to disrupt the ecosystem unnecessarily when prerelease API change.
Note that these rules only apply to API that have shipped in a previous RTM
release. New API still under development can be modified but we are still
cautious not to disrupt the ecosystem unnecessarily when prerelease API change.

To help triage breaking changes, we classify them in to four buckets:

Expand All @@ -16,47 +22,93 @@ To help triage breaking changes, we classify them in to four buckets:
3. Unlikely Grey Area
4. Clearly Non-Public
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 This would still apply if the exception type and scenario is explicitly documented

Copy link
Copy Markdown
Author

@terrajobst terrajobst Nov 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking whether documentation changes anything? No, because people usually can't and thus don't test if their code works in accordance to our docs. They test whether their code works with our implementation :-)


### Bucket 1: Public Contract
*Clear violation of public contract.*
## Bucket 1: Public Contract
*Clear [violation of public contract][breaking-change].*

Examples:
* throwing a new/different exception type in an existing common scenario
* Renaming or removing of a public type, member, or parameter
* Changing the value of a public constant or enum member
* Sealing a type that wasn't sealed
* Making a virtual member abstract
* Adding an interface to the set of base types of an interfaces
* Removing a type or interface from the the of base types
* Changing the return type of a member
* ...or any other [incompatible change][breaking-change] to the shape of an API

[breaking-change]: breaking-change-rules.md#source-and-binary-compatibility-changes

## Bucket 2: Reasonable Grey Area
*[Change of behavior][behavioral-changes] that customers would have reasonably
depended on.*

Examples:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we link to this section of the other document.


* Throwing a new/different exception type in an existing common scenario
* An exception is no longer thrown
* A different behavior is observed after the change for an input
* renaming a public type, member, or parameter
* decreasing the range of accepted values within a given parameter
* A new instance field is added to a type (impacts serialization)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only if we support cross version binary serialization right? Do we?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we have to.

* changing the value of a public constant or enum member
* Change in timing/order of events (even when not specified in docs)
* Change in parsing of input and throwing new errors (even if parsing behavior
is not specified in the docs)

### Bucket 2: Reasonable Grey Area
*Change of behavior that customers would have reasonably depended on.*
These require judgment: how predictable, obvious, consistent was the behavior?

Examples:
* change in timing/order of events (even when not specified in docs)
* change in parsing of input and throwing new errors (even if parsing behavior is not specified in the docs)
[behavioral-changes]: breaking-change-rules.md#behavioral-changes

These require judgment: how predictable, obvious, consistent was the behavior?
## Bucket 3: Unlikely Grey Area
*Change of behavior that customers could have depended on, but probably
wouldn't.*

Examples:

### Bucket 3: Unlikely Grey Area
*Change of behavior that customers could have depended on, but probably wouldn't.*
* Correcting behavior in a subtle corner case

**Examples:**
* correcting behavior in a subtle corner case
As with type 2 changes, these require judgment: what is reasonable and what’s
not?

As with type 2 changes, these require judgment: what is reasonable and what’s not?
## Bucket 4: Clearly Non-Public
*Changes to surface area or behavior that is clearly internal or non-breaking
in theory, but breaks an app.*

### Bucket 4: Clearly Non-Public
*Changes to surface area or behavior that is clearly internal or non-breaking in theory, but breaks an app.*
Examples:

**Examples:**
* Changes to internal API that break private reflection

It is impossible to evolve a code base without making such changes, so we don't require up-front approval for these, but we will sometimes have to go back and revisit such change if there's too much pain inflicted on the ecosystem through a popular app or library.
It is impossible to evolve a code base without making such changes, so we don't
require up-front approval for these, but we will sometimes have to go back and
revisit such change if there's too much pain inflicted on the ecosystem through
a popular app or library.

This bucket is painful for the machine-wide .NET Framework, but we do have much
more latitude here in .NET Core.

## What This Means for Contributors

* All bucket 1, 2, and 3 breaking changes require talking to the repo owners
first:
- We generally **don't accept** change proposals that are in bucket #1.
- We **might accept** change proposals that are in #2 and #3 after a
risk-benefit analysis. See below for more details.
- We **usually accept** changes that are in bucket #4
* If you're not sure in which bucket applies to a given change, contact us as
well.

### Risk-Benefit Analysis

For buckets #2 and #3 we apply a risk-benefit analysis. It doesn't matter if the
old behavior is "wrong", we still need to think through the implications. This
can result in one of the following outcomes:

* **Accepted with compat switch**. Depending on the estimated customer impact,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this talking about .NET Core or just .NET Framework? I don't think we should ever have compat switches (a la .NET Framework) for .NET Core, especially not for future fixes we make.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have compat switches for .NET Core.

Copy link
Copy Markdown
Contributor

@mellinoe mellinoe Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you link me where we're using them? I searched around and didn't see any. If we have some for existing code that's a bit unfortunate, but I really don't think we should have any more going forward, personally.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mellinoe why do you think we should not have compat switches? They are quite useful in various scenarios.

Currently we do not have the quirk semantics for them (i.e. Automatically enable switches based on TFM) but one can define them in the json file for your application and/or set them in code.

The link Immo added is about the quirks inside Corelib, but we also have them inside system.Xml as well.

Copy link
Copy Markdown
Member

@jkotas jkotas Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've made tons of changes that could have been put behind compat switches

It is what we have been in full .NET Framework. In .NET Core, we should be more data driven and only have quirks for behaviors that are proven to be actually breaking real .NET Core apps and being asked for. My 2 cents... .

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember right, it was an intentional decision to not quirk every single breaking change.

The idea (at the time) was that we would handle breaking changes by package versions. If we introduced a break in a version, people would either upgrade to the new version and deal with the break, or continue to use the previous version and not be impacted.

That model works great until we start talking about shared framework and the possibility of rolling forward user applications. In that case, you are talking about a very similar model to what we have on Desktop where the framework on which a user application is running on might change underneath them. And for these cases we have to have a way to prevent breaking them.

I don't think quirking every change is the way to go - we don't do that on Desktop either. Having some good telemetry as @jkotas suggests is going to make it easier to decide if a change needs a quirk or not.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shared framework and the possibility of rolling forward user applications

I guess this is where part of my confusion is coming from, because "rolling forward applications" is something that the product is explicitly designed to forbid right now, on quite a few different levels. Compat switches make a little more sense in that world, although I still don't like them as a technical solution.

Copy link
Copy Markdown
Author

@terrajobst terrajobst Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no one is arguing that we need to quirk every single breaking change. The purpose of this document is to outline what flexibility we have and how we're thinking about the process, specifically for .NET Core. In fact, you're preaching to the choir as I was involved in quite a few (perfectly fine 😄 ) API additions that we had to back out or quirk due to the (insanely) high bar for .NET Framework. That's why I'm spreading the word that app-local deployments are the way to go.

At the same time, @weshaggard keeps reminding me (and right fully so) that the combination of success and sharing will involve a higher compat bar than we like. However, I also think it will never be as high as it is for .NET Framework. But practically speaking, compat switches/TFM-specific behaviors will eventually be the saving grace in order to not prevent progress.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes lets avoid quirks as long and much as possible... but there will come a day in .NET Core were we will need them mark my words :)

we may decide to add a compat switch that allows consumers to bring back the
old behavior if neessary.

* **Accepted**. In some minor cases, we may decide to accept the change if the
the benefit is large and the risk is super low or if the risk is moderate and
quirking isn't viable.

This bucket is painful for the machine-wide .NET Framework, but we do have much more latitude here in .NET Core.
* **Rejected**. If the risk is too high and/or the improvement too minor, we may
decide not to accept the change proposal at all. We can help identify
alternatives such as introducing a new API and obsoleting the old one.

### What This Means for Contributors
* All bucket 1, 2, and 3 breaking changes require talking to the repo owners first.
* If you're not sure in which bucket applies to a given change, contact us as well.
* It doesn't matter if the old behavior is "wrong", we still need to think through the implications.
* If a change is deemed too breaking, we can help identify alternatives such as introducing a new API and obsoleting the old one.