| I'm going to mention my own tiny experience report as a frequent SQL user, of Go's simplicity pushing a lot of burden onto the user in the relatively simple task of querying a list of things from a SQL table: http://go-database-sql.org/retrieving.html - lets pretend we return errors rather than dying with log.Fatals :) There's a specific order of operations to be done here, and even a slight deviance could be a significant bug. To start of with we: 1. Do query
2. Check err
3. Defer close
1 is straight forward.
2 you can forget, then something bad may happen.
3 you can forget, which will cause a resource leak.
If you switch the ordering of 2 and 3 something bad may happen.
Instead of defer close(), a user might opt to just close() at the end of the function, but there's a reasonable chance they'll overlook the unhappy paths.Next we have iterating over the results: 1. For rows.next()
2. Scan into a &var
3. Check for scanning errors
4. Exit for loop, check rows.Err
1 is hard to mess up.
2 has some potential to mess up if you're not clear on which bit of memory you're storing the result in.
3 you can forget, which could cause you to get the previously scanned var show up multiple times, or zero values.
4. you can forget, which will cause your results set to (I guess?) be incomplete.
There's a lot of different permutations in this code that will compile, and I think has a good chance of passing the average paid dev's test suite (it's a bit of work to exhaustively test the unhappy paths).In my experience people are especially prone to messing up closing rows. I don't blame em, we know from decades of C that manually freeing resources, especially in the case of errors, is tricky. If I've made any mistakes, please point them out, as it supports my stance ;) (There's more fun to be had in SQL with Go, like that SQL has optional types, but Go doesn't, which leads us to sql.NullString et al). |