Is it really a breaking change if a method changes output after an update?
This might seem obviously "yes" at first, but consider a method like foo.debugRepr() which outputs the string FOO and has documentation which says it is meant only to be used for logging / debugging. Then you make a new release of your library and want to update the debug representation to be **FOO**.
Based on the semantics of debugRepr() I would argue that this is NOT a breaking change even though it is returning a different value, because it should only affect logging. However, if someone relies on this and uses it the wrong way, it will break their code.
What do you think? Is this a breaking change or not?
IMO it doesn’t really matter what you said the method was for. If you change the format of a string that is returned by a method that returns a string, there’s a risk of breaking user code, even if it’s just in the context of their dev environment.
Philosophically, whether or not the behavior of your API has changed is completely disconnected from whether or not others are using it “right”. If I can depend on a function to return a certain type of value when given certain arguments, and if it doesn’t produce other side effects, then it doesn’t matter what the docs say or what the function is named, I can use it in any context where I need that type of return value and have this type of arguments available. This type of function is just mapping data to other data. If you modify the function in such a way that the return value changes after being given the same arguments, that’s a breaking change in my book.
The way I do it, patches are backward-compatible bug fixes. Minor versions are additional features that don’t change existing functionality. Major versions include breaking changes. I totally get that it seems crazy to bump to another major version just over a string format change. But overall the philosophy works well IMO.