4 ms·
How do you define "compatible"? Semver doesn't define it. An HTTP server can remain API compatible, and still drop or break support for a major feature you car
by slimsag 5y ago
How do you define "compatible"? Semver doesn't define it.
An HTTP server can remain API compatible, and still drop or break support for a major feature you care about. Surely you don't want that to ship in a patch release.
Adding a new struct field, even a private one, can break API compatibility in Go if people use your struct without named fields. Do you want a new library in this case or not?
Semver doesn't cover CLI compatibility, either, do you want a CLI redesign to remain v1 or become v2?
Nuance matters. Stating to "just be strict about semantic versioning" doesn't help, semver is fuzzy.
- paskozdilar 5y ago> How do you define "compatible"? I define it as "provides the equivalent API".
- travisjungroth 5y agoI’ve added a method to a class. You use the same method signature but with different meaning in your code base. My release breaks your duck typing code. Was my release compatible? Is that an equivalent API?
- paskozdilar 5y agoIf a class with its methods is a part of your API, and you add another method to the class that doesn't change the behavior of other methods, that it's a backwards-compatible release. Other than that, I don't understand your question. What does "use the same method signature but with different meaning" mean? What is my "duck typing code"? Is duck typing part of your API?
- anonymoushn 5y agoIn golang, you can reasonably have a collection of things accessed through interfaces, check whether each one conforms to additional interfaces, and call the methods of those additional interfaces if so. If user code does this, then adding a new method to a library can cause user code to break. You may say "Well, don't do that," but it seems like golang users think of this as a way to provide backwards-compatible API upgrades[0]. [0]: https://avtok.com/2014/11/05/interface-upgrades.html https://avtok.com/2014/11/05/interface-upgrades.html
- travisjungroth 5y agoSwitching to Python examples from Go, but I believe it all applies just the same. > What does "use the same method signature but with different meaning" mean? .draw(times: int) In your code that adds a shape to the canvas. In my code it adds a card to your hand. > What is my "duck typing code"? You collect everything with the draw signature as options to draw with. > Is duck typing part of your API? What do you mean “your API?”. Maybe I mention it in the docs, maybe I don’t. But that doesn’t really matter because if we’re going to compare intent, then this is subjective. And it really doesn’t matter because duck typing isn’t part of an API, it’s a technique / language feature. By just adding a method, I removed your ability to do something. My point with all this is your definition is lacking. You just shifted the pain of defining “breaking change” to the pain of defining “compatible API”. Which is fine if you’re then going to define that strictly (see Hickey’s talk on SemVer), but you haven’t.
- paskozdilar 5y agoAn API is defined through two parts: 1) Machine-readable definitions of the functions and data types used 2) Human-readable explanation of what the previously defined functions do when passed each of the allowed data types, with a high-level explanation of how is the API supposed to be used. The first part is usually implemented as part of your programming language - in case of Python, type hints can be used as a substitution. The second part is necessarily informal, since it targets humans. But that does not mean it cannot be well-defined. For example: mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument specifies the length of the mapping (which must be greater than 0). There is no concept of "virtual address space" or "memory mapping" in C. All C knows about is functions and values. But we still know what mmap() does and there is absolutely no ambiguity to its behavior.
- pdonis 5y ago> Other than that, I don't understand your question. It's simple: a user inherits from your class and adds a method to their user-defined subclass. You then ship a new version of your library that adds a method with the same name to your class, but with different semantics, and other parts of your library call this method expecting it to have the semantics you defined. You've just broken the user's code, even though you say this was a "backwards-compatible" release, and it isn't the user's fault because they were using your library as it was documented.
- pgwhalen 5y agoThis is a good point, arguably even fixing any bug breaks compatibility, at least if you're a kernel maintainer. An (in)famous Linus quote: If a change results in user programs breaking, it's a bug in the kernel. We never EVER blame the user programs.
- slaymaker1907 5y agoI don't think this is all that useful of a quote outside of operating systems (and even there I still find it of questionable value). You really need to define how people can use your software (at least at a high level) and receive backwards compatibility guarantees. Even in the Linux example, kernel modules do not receive compatibility guarantees because it is difficult to keep. You may need to rewrite your module depending on how it is written when upgrading the kernel. It also doesn't apply in case of security vulnerabilities and certain classes of bugs. Technically viruses can be user programs which rely on those vulnerabilities and even excluding those, there are cases where some API seems benign but later turns out to be flawed (like precise timers in the context of browsers).
- noctune 5y agoI think it's a difference in what one considers to be "the API". Linux is very much to the de facto API side, whereas some other project might be more one the de jure API side with a rigid specification and allowing any change within that. Most things are probably somewhere between the two.
- Macha 5y agoAnd yet even the kernel deprecates and removes things like legacy architectures, filesystems, etc.
- int_19h 5y agoFor some examples of how complicated determining what is "breaking" can be in general, here's a StackOverflow wiki that I created a long time ago to document this kind of stuff for .NET: https://stackoverflow.com/questions/1456785/a-definitive-guide-to-api-breaking-changes-in-net https://stackoverflow.com/questions/1456785/a-definitive-gui...
- Vinnl 5y agoI'd define it as 'everything you've documented so far still works as documented, and possibly if you're aware that many of your downstreams are relying on some undocumented behaviour, you try to account for that too (but that part is a nicety, not a commitment).
- dmitriid 5y ago> Nuance matters. Stating to "just be strict about semantic versioning" doesn't help, semver is fuzzy. That's why I prefer ComVer: https://gitlab.com/staltz/comver https://gitlab.com/staltz/comver
- paskozdilar 5y agoComVer doesn't account for changes that don't change the API. For example, assume that you're using a dependency, libparser, v1.0, which uses ComVer. In version v1.1 they add a new function, foo() which does something that you want. However, at the time of you writing the software, the latest version was v1.2, which you use because you're a good engineer who likes to keep things up-to-date. Now think about what happens when you distribute your application to three people: - person 1 has libparser v1.0 - you program can't work, because v1.0 doesn't have foo(), so you obviously must require minor version that your program was built with - which is v1.2 - person 2 has libparser v1.2 or higher - you program works. - person 3 has libparser v1.1 - you program CAN work, since v1.1 contains foo(), but it WILL NOT work, because your application requires at least v1.2. Now, you might say, "well a developer should know that foo() was added in v1.1 and pin that as the lowest required vesion" - in which case I reply that ComVer doesn't help you a tiniest bit with this, because both API extensions and bugfixes are mashed together into a minor version, so now you have an order of magnitude more changelogs to go through. SemVer stricly separates API extensions and bugfixes so you can just browse the minor version changelogs and see which version added the feature you require.