3 ms·
I find the fastest way to introduce bugs into programs is to compile programs differently in development than in production. It also warps developer's perspecti
by crawshaw 7y ago
I find the fastest way to introduce bugs into programs is to compile programs differently in development than in production. It also warps developer's perspectives of how their program performs. As they say at NASA, "Test what you fly, fly what you test."
That said, you can do this easily enough in Go. Define a package:
package debug
const Debug = true
func Assert(cond bool, msg string) {
if Debug && cond {
panic(msg)
}
}
Have your build system generate the file that contains Debug = true. In optimization mode, make it Debug = false. As it is const, the compiler will propagate it, Assert will be inlined and the dead code removed. If you don't like relying on the compiler, have your code generator generate empty Assert functions. If you are working in a very bad code base that catches all panics, you'll need an extra 5 lines of code in the implementation of Assert, and your stack traces won't be pretty. (Roughly: put the condition check in a goroutine, block the Assert call on a channel signal from that goroutine.) I'd recommend the first project be to remove those panic catch-alls.
But really, all of this is only appropriate when you are far out in the weeds. Ship your program with those asserts. If they are too slow for production they are too slow for development.
- kasey_junk 7y agoThat will still evaluate the cond in all cases. Passing a func will make it lazy but might have performance implications (I’d have to test it).
- crawshaw 7y agoIt won't, because of inlining and dead code removal. (At least, it didn't back around Go 1.11 when I last tested this.)
- kasey_junk 7y agoMaybe if the thing that is generating the cond at the call location is inline-able, but not if it's doing an expensive operation. https://play.golang.org/p/Ts6QqRrWwuQ https://play.golang.org/p/Ts6QqRrWwuQ I couldn't get that to not trigger the lock. [later] I could get it to remove it if you pass in a func instead.
- chronial 7y agoYour Cond() has a side-effect. It can never be optimized away.
- kasey_junk 7y agoCorrect. Which is one of the differences between this approach & macros. Though the lazy approach appears to work.
- chronial 7y agoIf you do Assert(ExpensiveOp()), removing all of it is only a valid optimization if ExpensiveOp() is 100% side-effect free. But even when that is the case, the compiler won't inline ExpensiveOp() and go through the effort of proving that it's side effect free if it's sufficiently complex.
- SQLite 7y ago"Test what you fly and fly what you test" is what we live by at SQLite. Testing is done three times: (1) One test uses compiler and preprocessor options set up to measure coverage. This is not so much a test of SQLite itself as it is a test of the test logic, to verify that the testing provides 100% MC/DC. (2) The second test runs with assert(), ALWAYS(), and NEVER() enabled. This is like a unit test. It verifies internal assumptions and state, at the expense of running 3x slower. (3) Finally, we build as for delivery and test once again. This is the string test, where we "test what we fly". All three test runs must give the same answer (modulo performance) before a release. During day-to-day development work, we usually run (2), but occasionally toss in a (1) or (3) just to confirm that we haven't introduced any gaps in test coverage, or bugs that are masked by the debugging logic. So you are correct that you should not do all your testing using one configuration and then deliver a different build. But that does not mean you can't have extra logic in your code that helps do unit testing and debugging during development and which is excluded from release builds. You just need to make sure that you rerun all tests in the release configuration.