When it comes to coding, there are often many rules, but some rules are more like guidelines that are meant to be broken when necessary. So how do we know when we are dealing guidelines vs rules? Mostly by experience, so let’s investigate a few examples to increase our experience!
On Rules
Python is my favorite language to program in; however, I sometimes find myself at odds with various large movements in the Python community. I’m not a big fan of many of PEP8’s rules nor some of the (unconfigurable) rules of Black. [Black is a code style formatting software for Python.] On the other hand I love pytest and tox and other common tools used for Python development. I don’t think I’m just being contrarian regarding PEP8, Black, etc: I make my decisions around them based on experience needing to contradict them to achieve large maintainable software by a large team.
Writability is important. Readability is more important. Maintainability is the most important. So what is maintainable code? Maintainable code is quickly scannable by eye–you can easily spot “silly” errors just by the visual pattern of the code, without needing to read it deeply.
Comments
Maintainable code is also properly commented, saying what the code is intended to do, but more importantly why it does it a certain way. Additionally, only comments that are necessary to fully understand the code should exist.
# we need to force flush and close the file rather than using a
# with block to make sure the file is absolutely ready for other
# processes when we declare it to be ready
f = None
err = None
try:
f = open("myfile.txt", "rw")
f.write("mydata")
f.flush()
except Exception as e:
err = e
raise
finally:
if f is not None:
f.close()
# f is a file object
f = None
# err is an error object
err = None
# try to open and write / flush our file
try:
f = open("myfile.txt", "rw")
f.write("mydata")
f.flush()
# catch exceptions
except Exception as e:
# store exception for later
err = e
# raise the exception again
raise
finally:
# make sure the file gets closed
if f is not None:
f.close()
You can see above that in the “Good Example” we indicate why we are doing something a certain way, whereas in the “Bad Example” we’re just repeating exactly what the code does without really adding anything useful.
NOTE: The above example code was similar to some code I used in Python 2 with SCONS on machines with platter harddrives a number of years ago; it likely isn’t necessary these days, but I haven’t tested that.
The Degenerate Else
A related example of a good comment — when there is a degenerate else case to an if (i.e. what the state would be when no if clauses run), I put a commented out else block showing what the state will be, with a comment “# degenerate else” above it. This aids in quickly understanding the state of the code without needing to backtrack to figure it out.
y = 0;
if (x == 1) {
y = 5;
}
else if (x == 2) {
y = 15;
}
# degenerate else
# else {
# y = 0;
# }
This breaks DRY (Don’t Repeat Yourself) a little bit (which is a great guideline), especially in trivial examples like above. For more complex situations of initialization/state (i.e. you’re not just repeating the declaration lines) or where the blocks of the if statement are long or if there are many else if
blocks, this style is much more useful. You wouldn’t put the degenerate else block for such a trivial case as in the example.
Some may prefer to make the else clause real (not commented out) and to avoid any “pre-declarations” of variables and state, if that applies to their programming language as some languages prefer that style. However, I do this as an intentional pattern to avoid undeclared/out-of-scope/uninitialized variables. Additionally, in some languages (e.g. interpreted languages), have the else block will slow down the program, which is the reason for always having it commented out.
Overall, this is a cross-language construct that works in nearly all situations. One doesn’t need to remember when to use it or not, as it is almost always as efficient as the alternative AND is more secure/reliable.
Use Your Braces
I claim that having the degenerate else block is somewhat analogous as to always using braces in a C-esque if statement:
x = 0;
if ( x == 1 )
printf("yes 1 A\n"); // this line never runs
printf("yes 1 B\n"); // this line always runs
x = 2;
if ( x == 2 ) {
printf("yes 2 A\n");
printf("yes 2 B\n");
}
// Output:
// yes 1 B
// yes 2 A
// yes 2 B
You can see that line 4 always runs, even though a quick visual scan of the upper version of the code might indicate that it would not. This has led to serious and notorious security bugs in software past (and likely present and future), e.g. https://medium.com/swlh/apples-most-notorious-code-bug-6478ebaea44f .
However, be aware that you may encounter situations where you don’t want to, can’t, or shouldn’t use explicit braces as recommended by this guideline. Mind you, I can’t think of any particular examples like that, but that doesn’t mean they don’t exist!
On Goto
“Never use goto” is another guideline rather than rule, in my opinion, b/c there are times when You Should(tm) and times when You Shouldn’t(tm). Really Fast(tm) C code, particularly when creating a short-circuiting jump table, is one time when You Should(tm). While it’s an “okay” generalization to novices, it’s important to understand that it is more of a guideline than what you’d call a an actual rule.
Be Explicit, Be Clear, Be Maintainable
Sometimes longer/more-verbose/explicit code, “wide table” formatted code, or “pre-declared” variables will save you in the long run; in terms of maintenance ease and speed, as well as in security and compliance. Remember: short code does not (necessarily) run faster! If a rule hurts your code’s readability and maintainability, perhaps it is really just a guideline being dogmatically followed.
Hopefully you feel better equipped to decide whether you are dealing with guidelines vs rules when coding in the future!
Leave a Reply