Live data from Hacker News

Be Nice and Write Stable Code

technosophos.com

151–159 of 159 posts

Re: Be Nice and Write Stable Code

#151
> To that end, these are safe as part of a feature release: > > Adding a field or method to a struct/class/enum/etc.

Um, adding fields is a breaking change if you do a binary distribution.

> Z is the patch version. Changes to this indicate that internal changes were made, but that no changes (even compatible changes) were made to the API.

then, contradicting that

> Mark a thing as deprecated as soon as it is considered deprecated, even if that is a patch or minor release.

Deprecation should be considered a change to the API. I'm not going to thank you for filling up my build logs with warnings when I pull in a patch update of your library.

> Deprecation, after all, is a warning condition, not an error condition.

No, not if you use -Werror or equivalent.

Re: Be Nice and Write Stable Code

#152
post #43

Earlier quoted context omitted.

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 t…

The halting problem doesn't matter if your API is required to define timeouts / time guarantees. Thus if a#1.0() returns "hello" after 100ms and a#1.1() returns "hello" after 20s, even though they return the same result, they are deemed functionally different. This follows real-world expectations where performance which suddenly slows to the point of unusability is considered breakage, even if the same result is retu…

I think that's essentially the same point as LBA in a sibling comment. The same intractability-in-practice applies to it: even if your timeout is 10 milliseconds, that's still (tens of) millions of operations on most modern CPUs and order of a gigabit written to/read from memory, which is a huge state space.

Re: Be Nice and Write Stable Code

#153
post #110
post #43

Earlier quoted context omitted.

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 t…

Thanks for that pedantic insight, doc. It's patently obvious that API compatibility is a computable problem. You are just checking if API1 is a subset of API2. Even if foo() never returns, the Application Programming Interface is unchanged if the function signature is the same.

If you're narrowly interpreting an API to mean just the function signature, then sure, if your type system is appropriately restricted then it is computable (although many type systems are Turing complete, meaning this won't be computable in all cases, for the same reasons). This is, in fact, something my comment explicitly called out.

Having signature compatibility is an important prerequisite, but I think it's possible worse to have code that compiles but does the wrong thing than to have code that doesn't compile (at least it's obvious to the developer that something needs to be fixed, in the latter case).

Re: Be Nice and Write Stable Code

#154

Earlier quoted context omitted.

Jokes aside, a lot of developers would call a project "dead" when is working as intended and not receiving new commits.

'ls' is dead, we should stop using it.

Bad example ;) Updated on June 17th: https://github.com/coreutils/coreutils/commit/24053fbd8f9bd3...

OpenBSD true(1) however... seems pretty much dead. https://github.com/openbsd/src/tree/master/usr.bin/true

Re: Be Nice and Write Stable Code

#155

Earlier quoted context omitted.

Relying on tests is naive. Your tests can't cover every case. The article even mentions this -- their tests passed, but it failed in production.

They built tests that explicitly assumed that the library's interface wasn't going to change: >When we upgraded, all our tests passed (because our test fixtures emulated the old behavior) Doing that, upgrading your dependencies and expecting everything to work just because those tests passed? That's naivete. If they'd built decent integration tests that used the actual library (instead of "assume nothing changes" fix…

I wasn't talking about that paragraph, but the following paragraph where they had tests, but they didn't test with large enough packets.

Tests can never cover every scenario. They are very useful, and they catch a lot of unexpected regressions. But they're just a part of the puzzle, not a replacement for good development practices.

Updating a dependency without bothering to read the release notes because you have tests -- maybe naive is the wrong word, maybe hubris fits better.

Re: Be Nice and Write Stable Code

#156
post #8
post #4

Earlier quoted context omitted.

While it is a good example, you could also use the same example and conclude that the problem was inadequate tests. SemVer is great, but you can't count on dependencies that you do not control actually adhering to it, either intentionally or unintentionally. The only thing that could have prevented something like this for sure was mentioned: > And while nothing in our early testing sent messages larger than 256k, the…

But isn't it impractical to test every feature of every library you are using? In an ideal world you would have everything tested in isolation as well as integration. But in practice there will always be a corner case that remains untested because you don't know all internals of the libraries you use.

If you want to be able to randomly upgrade those dependencies and not have to worry about a breaking change, then yes. Semver is not going to help you there. Server is only going to help you when someone knows they are releasing breaking change. And even then, only if they are nice enough to actually follow the spec.

You don't have to test every bit of every dependency you use, but upgrading them without either carefully reviewing the changes or having tests in place for at least critical functionality is asking for something like this to happen eventually.

Re: Be Nice and Write Stable Code

#157

Earlier quoted context omitted.

They built tests that explicitly assumed that the library's interface wasn't going to change: >When we upgraded, all our tests passed (because our test fixtures emulated the old behavior) Doing that, upgrading your dependencies and expecting everything to work just because those tests passed? That's naivete. If they'd built decent integration tests that used the actual library (instead of "assume nothing changes" fix…

I wasn't talking about that paragraph, but the following paragraph where they had tests, but they didn't test with large enough packets. Tests can never cover every scenario. They are very useful, and they catch a lot of unexpected regressions. But they're just a part of the puzzle, not a replacement for good development practices. Updating a dependency without bothering to read the release notes because you have tes…

Tests can't cover every scenario, no, but had they made a bit more of an effort to test realistically then it's absolutely possible that they could have covered every scenario that mattered here.

Over-reliance on unrealistic unit tests (which is likely what led to them not testing large packets) is a pattern I've seen cause issues like this many, many times before.

I upgrade pretty regularly without reading release notes - relying on realistic tests to catch everything. What they do catch is usually not in the slightest bit obvious from release notes (often a regression in the dependency). Call it hubris if you like, but it works for me.

Re: Be Nice and Write Stable Code

#158

Earlier quoted context omitted.

'ls' is dead, we should stop using it.

Bad example ;) Updated on June 17th: https://github.com/coreutils/coreutils/commit/24053fbd8f9bd3... OpenBSD true(1) however... seems pretty much dead. https://github.com/openbsd/src/tree/master/usr.bin/true

It's pretty amazing.

Re: Be Nice and Write Stable Code

#159

SemVer is a social construct, not a contract. It's nice when it applies, but you cannot rely on other developers to adhere to it. One man's bugfix is another man's breaking change. If product A implements a workaround for a bug in product B, but the bug gets fixed in a patch version, it could break product A's code, so it becomes a breaking change. The only way to anticipate these changes is reading the change logs/r…

Build numbers is engineering, semver is marketing.

Private processes vs what you tell the world.

Build numbers unlocks delta debugging achievement. Add 'last known good' and 'found' build numbers to tickets, along with repo steps, then use diff to find bug.

Build numbers also unlocks QA/testing achievement. Add 'found', 'fixed', 'verified' fields to tickets. Now your team is certain when individual changes are ready to merge, ship/deploy.

Post reply on HN