Live data from Hacker News

Be Nice and Write Stable Code

technosophos.com

121–130 of 159 posts

Re: Be Nice and Write Stable Code

#121

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…

> 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. Whats the solution here? Fuck standards? Imagine if we had that same attitude with regards to HTTP.

A lot of people do not respect HTTP standards. We've all seen or heard of APIs returning the infamous HTTP 200 { error: true, errorMessage: "..." }

Re: Be Nice and Write Stable Code

#122
post #113
post #95

Earlier quoted context omitted.

This only covers a small part of the problem. Things that should be private and requite separate yesting are atill required to be more visible than they are supposed to.

I have the opinion that is the job of functional tests anyway. Unit tests should only exercise public interfaces, with internals and private parts being tested as side effect of calling them.

There is also composibility: ie make public components and compose them in public, use private as little as possible.

Re: Be Nice and Write Stable Code

#123
post #113
post #95

Earlier quoted context omitted.

This only covers a small part of the problem. Things that should be private and requite separate yesting are atill required to be more visible than they are supposed to.

I have the opinion that is the job of functional tests anyway. Unit tests should only exercise public interfaces, with internals and private parts being tested as side effect of calling them.

This simply cannot work in many cases. It is quite unrealistic to test complex logic that is hidden behind a narrow interface completely. You are hit with the full combinatorial complexity of what is behind that interface, even if might consist of independent parts internally. If you can test these parts independently, the number of required tests is a fraction of what a black box approach requires.

Another situation is checking numerical code for correctness and accuracy. There it is extremely advantageous to have testable small functions that map to individual mathematical expressions. But these are again implementation details that need to be hidden behind interfaces.

Re: Be Nice and Write Stable Code

#124
post #58
post #51

Earlier quoted context omitted.

I'm doing this kind of automation with Maven in Java. There is a plugin (build helper I believe is the name) that gives you properties like "next.release.version", "current.release.version", "next.snapshot.version", etc. So I've setup an infrastructure where you just click a button and it performs a release with _proper_ version number in accordance with semver, simply does the right thing. Works like a charm. I don'…

And who decides if a change is breaking or not?

I can think of two (relatively) simple tests:

A) you generate an export of the public API (entrypoints, arguments, types) in some stable format.

B) you run the (public-api) testsuite of the previous release against the current release.

- If A yields no changes wrt the previous release, then B should probably succeed as well (barring changed tests). Bump the patchlevel.

- If A has changes but B succeeds, it's a minor version bump.

- If A has changes and B reports failures, it's a major version bump.

Of course, this isn't fool-proof, so you still want some way of overriding the automated version bump. But I think it's reliable enough to at least force developers to think about the changes they're making. Of course, it does rely on having a testsuite, and explicitly marking parts of it as public-api validation.

Re: Be Nice and Write Stable Code

#125
post #113

Earlier quoted context omitted.

I have the opinion that is the job of functional tests anyway. Unit tests should only exercise public interfaces, with internals and private parts being tested as side effect of calling them.

This simply cannot work in many cases. It is quite unrealistic to test complex logic that is hidden behind a narrow interface completely. You are hit with the full combinatorial complexity of what is behind that interface, even if might consist of independent parts internally. If you can test these parts independently, the number of required tests is a fraction of what a black box approach requires. Another situation…

That leads to program for unit tests, exposing parts that shouldn't be visible in first place.

Your numerical code example can be achieved with an Assembly of internal functions/methods, exposed only to the implementation and unit tests.

Of course, this is easy to do in a greenfield project from the start, not so easy on legacy code.

Re: Be Nice and Write Stable Code

#126
post #113

Earlier quoted context omitted.

I have the opinion that is the job of functional tests anyway. Unit tests should only exercise public interfaces, with internals and private parts being tested as side effect of calling them.

There is also composibility: ie make public components and compose them in public, use private as little as possible.

True, just that when you make the pieces too small you can get into component spaghetti as well.

Re: Be Nice and Write Stable Code

#127
post #79
post #50

Earlier quoted context omitted.

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…

Irrelevant?? That is the most relevant question that a developer could ask in this situation. Often, there are ways to work around bugs in an API. And, just as often, those workarounds will break as soon as the bug is fixed. The API developer is in a particularly poor place to judge whether a bug fix is breaking or not. Sometimes, they can talk to users to see if a change will break their code, but other times they c…

Irrelevant?? That is the most relevant question that a developer could ask in this situation.

No, it isn't. The most relevant question is "can somebody be using the current API". It doesn't matter if your current API matches the documentation, what matters is whether your current API is out there for others to build on.

Don't try to use a crystal ball or other form of divination to predict what your downstream users have been doing with the code; you will always lose. Instead, suck it up, acknowledge the mistake, and signal the breaking change by bumping the major version.

Maybe next time your developers will spend more time validating their public contract, so they won't have to endure the embarrassment of a major version bump.

Re: Be Nice and Write Stable Code

#128
post #125

Earlier quoted context omitted.

This simply cannot work in many cases. It is quite unrealistic to test complex logic that is hidden behind a narrow interface completely. You are hit with the full combinatorial complexity of what is behind that interface, even if might consist of independent parts internally. If you can test these parts independently, the number of required tests is a fraction of what a black box approach requires. Another situation…

That leads to program for unit tests, exposing parts that shouldn't be visible in first place. Your numerical code example can be achieved with an Assembly of internal functions/methods, exposed only to the implementation and unit tests. Of course, this is easy to do in a greenfield project from the start, not so easy on legacy code.

Your first statement is exactly what I've arrived at. It's just not avoidable in general.

I have to clarify that I'm not fixated on C#. Sure, you could create a helper assembly in .NET that is a mess of essentially of disembodied functions for computing every slightly more complex function that happens to be in your program. But this breaks OOD.

In C/C++ you can't do quite the same. The best you could do there is break OOD and try to hide these global functions by using private headers (which are ugly in their own ways).

Re: Be Nice and Write Stable Code

#129
post #126

Earlier quoted context omitted.

There is also composibility: ie make public components and compose them in public, use private as little as possible.

True, just that when you make the pieces too small you can get into component spaghetti as well.

Yup. And you can completely ruin performance, if that is something you need (I do in some of my code). Abstractions in the wrong places can hurt.

Re: Be Nice and Write Stable Code

#130
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 my experience the more things are public the better. Very often a quick workaround turns into a monster bodge because some method is marked strict private instead of protected or public.

So I usually make most stuff public as such, but put internals in a namespace/scope that makes it clear that these are implementation details. Relying on implementation details always carries the risk of breaking when upgrading.

This allows for a lot of flexibility when needed, while also not polluting the "truly public" API.

Post reply on HN