Live data from Hacker News

Be Nice and Write Stable Code

technosophos.com

61–70 of 159 posts

Re: Be Nice and Write Stable Code

#61
post #42

Earlier quoted context omitted.

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.

C++ and C# have the same kind of problem: except for the iffy freind declaration in C++ there is no way in the language to denote that some method is bot meant for use in other modules. C# has the internal scope for each assembly, but this breaks in combination with unit tests placed in seperate testing assemblies.

Generally, proper unit testing is at odds with strict scope restrictions in the tested code. I guess we need more allowances fornunitbtesting at the language level to fix that. E.g. allow testing code to be marked as such and ignore that certain things are declared private, but in turn only allow it to be run in a testing context, but not regular builds, to prevent abuse.

Re: Be Nice and Write Stable Code

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

I wonder if tighter guarantees can be given if tools can work with constructs like D's contracts. These are extra sections in each function that are intended to check invariants. If these invariants change, then they were either broken and needed fixing or the function had a semantic change.

Re: Be Nice and Write Stable Code

#63
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?

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).

Re: Be Nice and Write Stable Code

#64
post #48
post #36

Earlier quoted context omitted.

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

Such as? White space changes can be ignored, comments can be stripped pre diff, both version can be run through the same formatter before hand. What you're then left with are the actual changes.

Anything removed or modified is a breaking change. Anything added to a public struct is a breaking change, which can and arguably should be hidden behind an interface. Adding functions is about all I can think of that's left.

Even if it's not perfect, it's a simple 90% solution with 40 year old tools.

Re: Be Nice and Write Stable Code

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

It's been a while, since I studied this, but linear bounded automata are arguably a better model for the real computers we can actually build and the halting problem is computable for LBAs.

Re: Be Nice and Write Stable Code

#66
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".

Unit testing.

Re: Be Nice and Write Stable Code

#67
post #61
post #42

Earlier quoted context omitted.

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.

C++ and C# have the same kind of problem: except for the iffy freind declaration in C++ there is no way in the language to denote that some method is bot meant for use in other modules. C# has the internal scope for each assembly, but this breaks in combination with unit tests placed in seperate testing assemblies. Generally, proper unit testing is at odds with strict scope restrictions in the tested code. I guess we…

> C# has the internal scope for each assembly, but this breaks in combination with unit tests placed in seperate testing assemblies.

This is a limitation imposed by the IDE, not the language. There's nothing stopping you from compiling code and tests into the same dll that you unit test. Likewise there is no need to separate code from the tests (apart from different files), they can simply not be included in release builds.

Source layout structure does not have to be a 1 to 1 mapping of the output structure.

Re: Be Nice and Write Stable Code

#68
post #65
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…

It's been a while, since I studied this, but linear bounded automata are arguably a better model for the real computers we can actually build and the halting problem is computable for LBAs.

Yeah, that seems true.

That said, in practice I'm not sure it is too useful (this is a slightly different question to the one originally asked, though). My understanding is the proof of decidability is essentially a pigeonhole principle argument based on LBA having a finite number of states: run the LBA for that many steps, if it hasn't halted, then it has looped. Even if a program is running on a system that only allows programs to use 1GB of memory, there's 2^(2^30*8) (approximately 10^(2.6e9)) states.

Re: Be Nice and Write Stable Code

#69
post #62
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…

I wonder if tighter guarantees can be given if tools can work with constructs like D's contracts. These are extra sections in each function that are intended to check invariants. If these invariants change, then they were either broken and needed fixing or the function had a semantic change.

There can still be a semantic change, even if conditions are unchanged and valid.

Re: Be Nice and Write Stable Code

#70
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.

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…

You certainly cannot determine that two programs do or do not have the same behaviour in the general case.

In specific cases, the proofs can be pretty trivial:

https://tinyurl.com/ycx2245q - proof your two programs are different

https://tinyurl.com/y7lcv3re - proof 'y + x' is the same as Version 1.

These proofs weren't automatically discovered, though for such simple programs I'd expect an SMT solver to be able to find the proof or a counterexample easily enough.

But even proving they're the same value for all inputs isn't all that helpful, because of lazy Haskell code like:

    version 1:
    fib :: Int -> Int
    fib n = if n  Int
    fib n = let fs = 1:1:zipWith (+) fs (tail fs) in fs !! n
They're (provably) the same for all (positive) input values, but if you call version 1 with n greater than, say, 35, you'll be waiting quite a while for an answer, while version 2 will be very snappy for the first few hundred thousand values of n, at least, after which the size of the answer will be a bottleneck.

If a library switched from the latter to the former, it'd have a good chance of breaking code.

While obviously exponential code is obviously exponential, this sort of behavioural change can show up in less obvious ways - a change in memory usage might blow your heap after a supposedly minor version change, eg.

In the end I don't think the value judgement of 'breaking' or even 'significant' is computable, and you'd need to rely on a human doing something that approximates to 'right' for your world view with their version numbering.

Post reply on HN