Live data from Hacker News

Be Nice and Write Stable Code

technosophos.com

141–150 of 159 posts

Re: Be Nice and Write Stable Code

#141
post #63

Earlier quoted context omitted.

Probably pretty simple. Destructively making a public API change is breaking. Additively changing an API isn’t. Adding an optional public param or new symbols isn’t. Harder to detect cases in which the signature stays the same but the expected behavior changes, though (as noted in TFA).

One somewhat subtle implication is that you need to have a clearly defined expected behaviour. If people have to read your implementation to use your API, then your implementation has become the specification and basically every change will be a breaking change.

Basically, if your old unit tests break, or if the post-conditions changed (in case you use design by contract), that's a new major version, otherwise, it's not.

But yeah, there are counter-examples, and anyway a user can expect a behavior to remain even if it is not explicitely specified. For instance, I developed a SAT solver. I could make a non-breaking change that improves the efficiency of the solver in like 99.9% of the cases, because of a cool heuristic. That would be considered a "patch", since I didn't change the API at all, and all unit tests are still working perfectly. But a former user could be pissed off because he's part of the 0.1% and now the tool became just a little too slow for his use. Is that a breaking change or not?

Re: Be Nice and Write Stable Code

#142
post #43
post #30

Earlier quoted context omitted.

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 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 returned in the end.

Re: Be Nice and Write Stable Code

#143
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 it's more like a lower bound on difficulty. In other words, halting is one of many guarantees that would need to be checked to ensure that an API didn't break, and it is impossible to check, so it cannot be done, irrelevant of the difficulty of the other problems (like timeouts or whatever).

Re: Be Nice and Write Stable Code

#144

Earlier quoted context omitted.

> 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: "..." }

That's not disrespecting HTTP standard. That's the consequence of using HTTP in place of a proper RPC protocol.

Re: Be Nice and Write Stable Code

#145

Earlier quoted context omitted.

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 it's more like a lower bound on difficulty. In other words, halting is one of many guarantees that would need to be checked to ensure that an API didn't break, and it is impossible to check, so it cannot be done, irrelevant of the difficulty of the other problems (like timeouts or whatever).

I don't think you understood my comment - you don't need to check for halting if all methods in the API have timeout guarantees. Halting is only a problem if runtime is uncertain. If runtime is certain (again, because of promised performance figures) then it is entirely possible to verify whether an API has been broken or not.

Re: Be Nice and Write Stable Code

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

Future-friendly error-handling can indeed be tricky. I ran into this trying to make a lasting email-sending API. I was hesitant to depend on the API's specific exception types, and so considered mapping them to more general categories, yet still giving details for troubleshooting.

     // pseudo-code
     err = new Error(hasError=false); // innocent until proven guilty
     try {
       sendEmail(...);
     catch (e in excptn1, excptn6, excptn7)  // dummy names
       err.hasError=true;
       err.recipientProblem=true;
       err.errorType = e;
     catch (e in excptn2, excptn4, excptn9)
       err.hasError=true;
       err.contentProblem=true;
       err.errorType = e;
     catch (else)
       err.hasError=true;
       err.errorType = e;
     }
     ...
     return(err);
One could make an emumerable list of error categories, but in this case I wasn't even sure they were mutually exclusive because it still sends to the rest if one recipient is bad.

Re: Be Nice and Write Stable Code

#147
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…

Replace it with any other nonsense.

In practice your time guarantee isn't going to let you produce a sound and complete static analysis that proves the equivalence of two arbitrary (modulo termination guarantees) functions.

In the real world all programs have a timeout set to the time to the heat death of the universe. This hasn't helped us make sound and complete static analysis.

Re: Be Nice and Write Stable Code

#148
post #94

Earlier quoted context omitted.

What if: * Function order changes * Prototype goes from int function(char * ); to int function(char * arg); * Typedef is added so instead of int function(char* );, it's typedef char* str; int function(str);

Good points, the second one in particular I don't think could be fixed without a full parser. Function order changes could possibly be worked around by a formatter/linter that can reorder functions, at the risk of creating more issues. The last could be handled be passing the code through the preprocessor (the -E flag in gcc) first. By this point it's probably gone beyond the "perfect is the enemy of good" threshold…

That's still going to be way "less perfect" than diffing the output of `javap` or `godoc` or probably even the `help(module)` for a Python module.

The ability to diff APIs objectively gets better when we switch away from C header files.

Re: Be Nice and Write Stable Code

#149

Earlier quoted context omitted.

One somewhat subtle implication is that you need to have a clearly defined expected behaviour. If people have to read your implementation to use your API, then your implementation has become the specification and basically every change will be a breaking change.

Basically, if your old unit tests break, or if the post-conditions changed (in case you use design by contract), that's a new major version, otherwise, it's not. But yeah, there are counter-examples, and anyway a user can expect a behavior to remain even if it is not explicitely specified. For instance, I developed a SAT solver. I could make a non-breaking change that improves the efficiency of the solver in like 99.…

Unless you also provide performance guarantees ( specified by benchmark testing, as per your defination), then no

Re: Be Nice and Write Stable Code

#150
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 fo…

I specifically said "when tools are not expressive enough to capture and enforce intent".

Suppose you're writing a library in Python. Everything in it is public. Even the dunder class members are, because it's just name mangling, and the language spec even documents what exactly it does!

Now, is anyone going to seriously claim that every single identifier in every Python library is part of its public API, and any change that affects it is a breaking change? Because that's certainly not the "expected social norm".

Granted, Python is a somewhat extreme example. But in practice, this also comes up in languages like Java and C#, when dependencies are more intricate than what the access control system in those languages can fully express.

And then there are backdoors:

> What can happen, tends to happen. The actual code actually existing always has the final say.

You can use Reflection to access any private field of any object in Java. There's actual existing code doing that in practice, too. Does it have the final say, and does it mean that internal representation of any Java class in any shipped Java library has to be immutable, so as to not break the API clients?

Post reply on HN