4 ms·
> You are changing the behavior of the function. That is a breaking change. That makes almost everything breaking.
by patrickthebold 3y ago
> You are changing the behavior of the function. That is a breaking change.
That makes almost everything breaking.
- nimih 3y agoWell, yes. Any time you change a function’s behavior, you’re risking breaking the behavior of its callers. That’s just the nature of programming and doesn’t seem like particularly controversial statement, honestly.
- Tcepsa 3y agoOne of the big benefits of using functions is encapsulation: the idea that you (as the caller) do not need to understand how a function does what it does, you just need to know what it does (perhaps along with some performance guarantees so you know it’s not going to do e.g. an O(n^2) operation). Beyond that, I don’t want to care and should not generally have to care about what the function is doing. As long as I get out (including side effects) what I expect based on what I put in, the internal behavior of the function can change any which way and I would not consider that breaking, because my code—using that function—would still run just fine.
- nimih 3y agoYes, I agree. I probably should've been more clear that when I said "behavior," I meant "behavior observable by the caller." Going back to the original article, it seems reasonable to me that the following qualifies as such a user-observable change: foo(null) # throws InvalidArgumentException updated_foo(null) # the same as foo(0)
- Tcepsa 3y agoThank you for the clarification; that's a much more compelling argument, and I think I am inclined to agree. I believe, however, that that is not what Hickey is saying. He's not saying "previously you could pass null and it would throw an exception, and now you can pass null and it won't throw an exception". He's saying "Previously if you tried to pass null you'd get a compiler error, but then I figured out that the method as written can handle nulls, so I changed the type signature to allow null to be passed in without the compiler throwing an error about it. See the updated documentation to understand what it does if you pass in a null value." This isn't a breaking change because up until that point any code that was written to use that method already wasn't passing null (because it couldn't, because it wouldn't compile). The method's behavior hasn't changed, just its type signature, and so for any of the arguments that the existing code might possibly pass to it, it will still handle all of those exactly the same as it would have before (because, again, the implementation did not change). Therefore, it is not a breaking change.
- tshaddox 3y agoThen why talk about “breaking” changes? Just say “changes.” Of course, the reason we use the phrase “breaking changes” is that we are making a distinction between certain types of changes.
- hansvm 3y agoWhichever line in the sand you draw as an API boundary, if the break doesn't propagate across it then the change isn't breaking in that context. Suppose (as a crude example to simply illustrate the point), a codebase exposes some CRUD operations. Internally it relies on sorting methods and comparator methods for that sorting. If you reverse the order that sort works and also reverse all the comparators then internally you've made a bunch of breaking changes, but externally you can describe the change as non-breaking (probably -- performance regressions can be tricky). The change itself was both breaking and non-breaking, and the choice of description depends on who you're describing it to.
- tshaddox 3y ago> externally you can describe the change as non-breaking (probably -- performance regressions can be tricky) Yes, in most programming languages/environments it is always possible to write your application in such a way that any change of a dependency is I’ll break your app. Heck, you could throw an exception if someLib.version != “1.0.2” and then complain that a patch to 1.0.3 is actually a breaking change. Or your app could test that some API method does not exist, then complain when that method gets added later. Or you could complain when performance improvements reveal race conditions in your app (or break your usage of your computer as a space heater).
- nimih 3y agoYou can improve performance without changing the function's interface. Or remove a bug which causes an unrecoverable error. Or fix a memory leak. Or improve the error message on an internal assertion. Or add new methods to an object's interface. These would not change the extant observable behavior for most problem domains (although you could probably reasonably argue that any changes in performance characteristics are potentially breaking changes in e.g. game development), and are thus not "breaking", but are nevertheless useful changes.
- kazinator 3y agoA breaking change occurs if the function (before the proposed change) can be called in a way that doesn't crash (stop the program, or render it intoperable, or blow up at compile time or basically bring the show to a halt), and after the change, that call yields a different result or has a different effect or output. We can divide those breaking changes into two: 1. The call obeys the specification. 2. The call, though apparently successful, circumvents the specification, relying on undocumented behavior. The question is how much we care about 2, and there is no 100% answer. There can be situation in which some kinds of 2 breakages are such that we care about them more than 1 breakages. Suppose there is a certain correct, documented way of using the API, but almost nobody out there uses it that way. And suppose there is some undocumented way of using the API, which millions of installations use, thousands of times per second. Suppose we need to implement something new, or even ore importantly, fix a critical bug, and suppose that the work boils down to either breaking one, or the other. It may be better to break the former to keep the latter working (and possibly elevate the latter to documented status). In other words, in a perfect world we'd like to say that breakages of type 1 are non-negotiable, whereas 2 can be debated. But we can't even do that.