Live data from Hacker News

Be Nice and Write Stable Code

technosophos.com

41–50 of 159 posts

Re: Be Nice and Write Stable Code

#41
I'm maintaining an open source project[0] and I'm struggling with using SemVer because my "app" doesn't have a single API but a few:

At it's core it's a node app. Though I also include a small web server that wraps around it (and a UI frontend).

1. It allows people to write scripts (js) that receives inputs and passes on events based on an API (the strategy API)[1].

2. It has extensive configuration[2] that sometimes changes form (the config API).

3. It talks to a number of external services (crypto exchanges), over a "common" protocol called the "exchange wrapper API"[3] (I am ignoring the version of the exchange API being consumed).

4. The wrapped webserver comes with an API (REST + WS)[4].

5. The "core app" is a chain of plugins, when they change the required config/events also change (usually breaking changes 1 and 4)[5].

I could take all of these components apart (microservice way) and version them separately, but I like the monorepo style I use now where pulling one repo means that everything is working together. Also the fact that (in bug reports) people only have to refer to one version (and when on nightly maybe the git commit if I need more details).

But versioning is a mess.

[0]: https://gekko.wizb.it/

[1]: https://gekko.wizb.it/docs/strategies/creating_a_strategy.ht...

[2]: https://github.com/askmike/gekko/blob/develop/sample-config....

[3]: https://gekko.wizb.it/docs/extending/add_an_exchange.html#Ge...

[4]: https://gekko.wizb.it/docs/internals/server_api.html#REST-AP...

[5]: https://gekko.wizb.it/docs/internals/events.html#List-of-eve...

-------

This is not a criticism, I typed this out in the hope that someone can point me in a sane direction (given the discussion on versioning)

Re: Be Nice and Write Stable Code

#42
post #33

> Stop trying to justify your refactoring with the "public but internal" argument. If the language spec says it's public, it's public. Your intentions have nothing to do with it. This is so wrong. APIs are for people, not tools, so intent is primary. When tools are not expressive enough to capture and enforce intent, you document it, but it's still primary. Someone using a "public" API that clearly says "for internal…

What reason would you have for publishing something in a public API if it actually is for "internal use only".

In some languages / project structures you need a way for internal components to connect that happens to be "public" but is not meant for public use.

I see this a lot in Java libraries, for instance.

Re: Be Nice and Write Stable Code

#43
post #30

A lot of people are commenting that SemVer doesn't work, because it's still at the mercy of humans choosing good version numbers. Elm's package manager, elm-package, actually tries to remove humans from the equation, by automatically choosing the next version number, based on a diff of the API and the exported types of a package: https://github.com/elm-lang/elm-package#publishing-updates It's not perfect, but it's be…

Is API compatibility computable in general? My instinct is that it is, but I’ve never seen a theorem.

No, it isn't computable (that is, correctly determining one of "these functions behave the same" or "these functions behave differently", and not "unknown") in general, as it is equivalent to the halting problem. Consider these two versions of a function, are they API compatible?

  def foo():
    return True

  def foo():
    return halts("some turing machine program")
They're only truly API compatible if the program halts, but the program can be arbitrary, so proving that any 'foo' of this style are equivalent is solving the halting problem.

Of course, one can still likely get useful answers of "definitely incompatible" etc., with much more tractable analyses. AIUI, the Elm version ends up just looking at the function signature, and catches things like removing or adding arguments: for appropriately restricted languages, it is likely to even be possible to determine if downstream code will still compile, but that's not a guarantee it will behave correctly.

Re: Be Nice and Write Stable Code

#44
post #33

> Stop trying to justify your refactoring with the "public but internal" argument. If the language spec says it's public, it's public. Your intentions have nothing to do with it. This is so wrong. APIs are for people, not tools, so intent is primary. When tools are not expressive enough to capture and enforce intent, you document it, but it's still primary. Someone using a "public" API that clearly says "for internal…

What reason would you have for publishing something in a public API if it actually is for "internal use only".

The language I'm using doesn't let me express the public/private divide I wish to make correctly (e.g. "private" implementation functions for a public C macro.)

The API is 100% intended for internal use only, but someone insists on ignoring that and consuming the private API anyways. Instead of forcing them to write their own headers which silently break at runtime when function signatures change in certain calling conventions which don't check those signatures, I instead allow them to include headers with a few keywords like "private", "internal", "do_not_use", or "i_am_voiding_my_semver_warranty" in the path, perhaps only after they make some similarly scary #define s, so it's at least a build failure.

Re: Be Nice and Write Stable Code

#45
post #6

Regarding exception handling, letting internal exceptions define external behavior is perhaps a bad idea. The possible exception types can be wide and change over time as new parts or features are added. Example: // pseudo-code qry = new query(sql=theSql, dbConfig=DB_FOO); if (! qry.Execute()) { errMsg = "Something went wrong during your query. "; if (qry.errorExceptionName=="DB_Busy") { errMsg += "The database appea…

It also bugged me that he was upset when the behavior they were relying on was an internal detail not included in the API's contract.

(Incidentally, the API itself did not change because it was something like func Read(in Reader) error, where error was a parent of all exceptions)

That's not incidental. In my opinion the authors of the API were completely fine changing the internal detail of which specific exception type was thrown because their public API never made a guarantee beyond it being an instance of error.

Re: Be Nice and Write Stable Code

#46
post #33

> Stop trying to justify your refactoring with the "public but internal" argument. If the language spec says it's public, it's public. Your intentions have nothing to do with it. This is so wrong. APIs are for people, not tools, so intent is primary. When tools are not expressive enough to capture and enforce intent, you document it, but it's still primary. Someone using a "public" API that clearly says "for internal…

> there is no obligation to keep things working for the.

You opened with the correct observation that APIs are mostly for people. Saying there is no obligation here contradicts the expected social norms. And even more importantly, intent does not tightly correspond with reality, and what can happen, tends to happen. The actual code actually existing always has the final say. If you intend to have the best outcome for everyone involve, conform to the unalterable realities as much as possible - if the interface should be public, make it public. If the interface should be private, make it private.

Re: Be Nice and Write Stable Code

#47
post #37
post #33

> Stop trying to justify your refactoring with the "public but internal" argument. If the language spec says it's public, it's public. Your intentions have nothing to do with it. This is so wrong. APIs are for people, not tools, so intent is primary. When tools are not expressive enough to capture and enforce intent, you document it, but it's still primary. Someone using a "public" API that clearly says "for internal…

Making an API public means that people can do whatever they want with it. If you are not sure if you want to allow the API in the future it should not be public. People will always look to do the laziest thing possible which might mean hooking into your "public internal API". Then you will never be able to change it and you will have to maintain it forever.

They can do whatever they want with it but you have no obligation to maintain nor support it if it’s not a documented public API, in my opinion.

It’s a bit like a house on a corner with a big front yard. People may cut through the grass to save time but you can’t blame the homeowner when he finally puts up a fence.

Re: Be Nice and Write Stable Code

#48
post #36

A lot of people are commenting that SemVer doesn't work, because it's still at the mercy of humans choosing good version numbers. Elm's package manager, elm-package, actually tries to remove humans from the equation, by automatically choosing the next version number, based on a diff of the API and the exported types of a package: https://github.com/elm-lang/elm-package#publishing-updates It's not perfect, but it's be…

This seems like something solved decades ago with c header files, they're easy to do a diff on and the only false negative is from adding a function. Even that would be fine if you weren't export raw structs. It seems like we gave up simple ways to do stuff like this because we hated header files and moved to tools like java and c# that eschewed them. Then they got reinvented and renamed to interfaces and we've come…

Diffing C header files has way more false negatives than just "adding a function".

Re: Be Nice and Write Stable Code

#49
post #35

Earlier quoted context omitted.

I'd imagine it isn't, at least depending on how you define API compatibility, and whether you're only looking at the API interfaces. Imagine two versions of a library that implement the function "add". Version 1: add Int -> Int -> Int add x y = x + y Version 2: add Int -> Int -> Int add x y = x * y Both versions expose the same API interface, but the functions that conform to that interface are semantically different…

I don't think 'does it work exactly the same' isn't necessarily the right question, given there are expected to be bug fixes which may change the behavior in some functions.

The right question is "what's the difference between a bug-fix and a breaking change?"

Re: Be Nice and Write Stable Code

#50
post #35

Earlier quoted context omitted.

I don't think 'does it work exactly the same' isn't necessarily the right question, given there are expected to be bug fixes which may change the behavior in some functions.

The right question is "what's the difference between a bug-fix and a breaking change?"

Irrelevant. If fixing a bug is a breaking change then it is a breaking change. This is the importance of pre-releases/"nightly" branches so that you don't have a mistake of addNumbers(5,5) returning 25 instead of 10 and not being caught and then needing to increment a major number to fix a typo of × to +.

A bug fix is a change. A breaking change is a change that breaks the API. Doesn't matter if the previous status was the intended one or not.

SemVer not working is almost entirely people simply not following it correctly. The projects that follow it as best as they can have very few mistakes were "woops that was a major change" occurs while projects that use it as a guideline may as well not be using it at all.

Post reply on HN