Clearer Conditionals using De Morgan's Laws
robots.thoughtbot.com
robots.thoughtbot.com
"Not signed out" can be rephrased as "not not signed in" and thus simplified to "signed in". Same for "not untrusted ip" equaling "not not trusted ip" and, after DN, simply "trusted ip".
It's completely logical, so I'm not sure what your point is. Perhaps the article should have explained this better.
If you had "outside" and went from !outside?" to "inside?", that would be erroneous (you could also be on the threshold).
ETA: this is especially obvious for trusted/untrusted; it doesn't have to be the case that every ip is either positively trusted or positively untrusted. If, in some application, it is binary in that way, then you can, in that case, go from not untrusted to trusted. But that isn't justified by purely logical considerations.
ETA again, in fact a better example is this, it's not a logical fact that if you flip a coin and it comes up not-heads, it has come up tails. (Even ignoring improbably things like its landing on its side.) That's a conclusion that is justified by knowledge of the substantive domain of coins.
If you are fully signed-in, you can purchase something or make account changes. If you are signed-in-untrusted, you can put things in the cart associated with your account, but you can't purchase anything without typing a password. If you are signed out, you are fully dissociated from any account.
Note that you can refactor that single trinary property into two binary properties: signed-in/signed-out and trusted/untrusted. (Signed-out+trusted happens to be unused.)
Perhaps it's obvious, but it's true that every multi-state property (or set of properties) can be broken down into a set of binary properties like that, and the breakdown can be done in multiple ways. The resulting binary properties may not be have sensible names as they do in my example, but it can be done. In general, find all possible combinations of values for your set of many-state properties. Enumerate those combinations and write the numbers in binary. Each binary digit is a property in your new set of binary properties. There are multiple possible enumerations, so there are multiple possible mappings from a set of many-state properties to a set of binary properties.
For example, if you have a three-state property and a four-state property, then you'll a combination of 12 possible states. Number those 12 states. Write those numbers in binary. You'll need at least four binary digits. That means you'll have at minimum four binary properties. Those four properties can have 16 total states, so four of the states will be unused.
You can similarly decompose your properties into a set of three-state properties by writing your enumeration in base-three. I suppose you could also consider the current state of your program, with it's many multi-state properties (integers have lots of possible states, strings have even more), to be a single variable-base number that enumerates a state in the state-space of your program. If you consider the remaining input to your program to be part of your state, and the set of all possible outputs to be enumerated in a similar fashion, then the problem of programming is reduced to the building of a machine that maps numbers from one set to numbers in another. How hard can that be? So I'll need your project done by Monday.
I transferred midway through my undergraduate degree, and was surprised to discover that Karnaugh maps weren't taught at my destination school - they are such an intuitive and straightforward mechanism for whittling down complex logic into its simplest form.
There are very few requisites too. Some high schools and trade schools teach the material to 16-18 year-olds in a year or two. I suspect this is partially why it's so often omitted in CS curriculum. It's too easy.
Do you happen to have a link to an online course, textbook or other resource that covers this in a combined way that flows to well?
My Discrete Math 101 course (UNC-Wilmington) covered Karnaugh Maps, along with a heavy dose of Boolean Algebra. For various historical reasons, I've actually taken Discrete Math twice, and have noticed that the content of a class titled such can very dramatically. The other Discrete Math course had much less emphasis on Boolean Algebra and logic, and a lot more on elements of probability and statistics.
Experience then teaches that if the expression is complicated enough for that to matter, you've already lost. Instead, make a boolean function with explicit "short circuit" returns.
// return true if we're screwed
isScrewed ( relevant parameters...):
if failure-mode-one:
return true
if failure-mode-two:
return true
if guaranteed-save-otherwise:
return false
if failure-mode-three:
return true
return false
I remember seeing a horrible "if" statement that caused many thousands of dollars worth of wasted inventory back at one job cuz the clever coder thought he know the operator precedence and was saving time and money jamming a bunch of crap on one "if" line.Now if I could just get coworkers to stop writing "fooFlag == true" and "fooFlag == false" :-)
I always feel like it's better to positively name Boolean values, personally, but I know everyone is different.
def signed_out?
# Code
end
def signed_in?
!signed_out?
end
Which can easily start to become its own problem. On the other hand, with Ruby, it might be worthwhile to define something like Class#invert such that you have this: def signed_out?
# Code
end
method_invert :signed_out?, :signed_in?
Dunno. I haven't ever found myself in a position where it mattered. if (hasBread) {
// do something
}
Can be flattened to: if (!hasBread) {
return
}
As you start your function, flush out all the edge cases, invalid conditions and errors, then at every step forward in the function, you know you're always in a state were the odd balls have been taken care of and you deal with the general case.This has two advantages:
- it keeps the code tidy (the important parts are pretty
much never in a nested block).
- it makes you handle the odd balls explicitely.
I really hate seeing functions such as : func cookBread() {
if (hasBread) {
do()
a()
bunch()
of()
stuff()
}
}As for the refactoring, that might be a good choice (I myself prefer positive boolean methods) but it's not a logic lesson.
if(a) {
if(b) {
if (c) {
// Do something.
}
}
}
Into: if(!a) {
} elseif(!b) {
} elseif(!c) {
} else {
// Do something.
}A typical sequence is like the one to set the bus width.
err = sdio_select(rca);
if (! err) {
err = sdio_command(55, rca << 16);
if (! err) {
err = sdio_command(6, 2);
}
}
sdio_deslect();
return err;
I had originally done it the other way err = sdio_select(rca);
if (err) {
return err;
}
err = sdio_command(55, rca << 16);
if (err) {
sdio_deselect();
return err;
}
err = sdio_command(6, 2);
sdio_deselect();
return err;
I find the first form more readable. It also generates
fewer branches in the generated code. The sample code I first looked at was doing Goto's to the exit code (deselect/return error) which was unacceptable :-).In the original article the confusion arose around negative test cases and then testing for them negatively (double negatives) which I think are always bad from a readability point of view.
struct sdio_cmd {
enum { SDIO_SELECT, SDIO_COMMAND } cmd;
int reg; // Guesses at suitable names without
int val; // knowing anything about SDIO
}
int sdio_batch(int count, ...);
err = sdio_batch(3,
&(struct sdio_cmd){ SDIO_SELECT, .val = rca },
&(struct sdio_cmd){ SDIO_COMMAND, 55, rca << 16 },
&(struct sdio_cmd){ SDIO_COMMAND, 6, 2 },
);
I've been doing it in Ruby with network remote procedure calls, where it's not quite as ugly to use inline lists and variable argument counts as it is in C, and where branch count isn't quite as important.The sample code I first looked at was doing Goto's to the exit code (deselect/return error) which was unacceptable :-).
What's so bad about a little goto between friends?
int do_command(sdio_command_chain *cmd) {
int err = 0;
if (cmd->next) {
err = do_command(cmd->next);
}
return (err) ? err : call_command(cmd->parms);
}
Thanks for that!In the case of cleanup that must be done in the end, perhaps 2 functions would be better: a top level func to acquire and dispose of resources, calling an inner func to do as much work as it can with the resources. (assuming something like C that doesn't have a "finally" clause like Java)
I've never understood how finding the end of a long / nested mess of if/else blocks, rather than leaving the function, is somehow better. Which one feels more like a GOTO in terms of least astonishment?
> I actually prefer your second form: return as soon as
> you know you are done in the function/method -- if
> nothing more can be done, then don't pretend to do
> any more.
The first form actually does that. As soon as one if condition fails, the code falls through down to the bottom and returns. This is most impressive in the initialization/identification phase (see sdio_open() [1]) because that has like a dozen commands it has to get through successfully. By collapsing this way it allows for common cleanup, and if the cleanup requirements change you only have to change it in one place.The effect of the first form (in C) is to collapse all of the if's "else" clauses into a single one at the bottom.
As for finding the other end of an off screen if clause, I agree with you, that it is a crutch to use the 'find matching' command in the editor with the open brace. The interesting about this problem is that it is driven by the SDIO spec, which was driven by a strict requirement of backwards compatibility, which results in very carefully crafted command / response / flow options.
This discussion has been helpful for me as at some point I am going to have to describe this to people new to, or possibly unfamiliar with, programming which should be interesting.
[1] https://github.com/ChuckM/stm32f4-sdio-driver/blob/master/sd...
do
command1
command2
...
and have the failures propagate automatically. if (a && b && c) {
// Do something.
}There is no easy method to reduce every expression to a simple normal form - the conjunctive normal form of a given be exponentially larger than the original expression, etc.
Suffice to say that it contains such tips as avoiding negation in conditionals.
In the end it boils down to strategic optimization towards readability with the least mental overhead (the cycles you spend parsing, the more you can spend thinking).
The refactored version reads like:
Allow access to the site if the user is signed in or has a trusted IP.
The original (DeMorgan's applied):
Allow access to the site if the user isn't signed out or doesn't have an untrusted IP.
It does help to have a good understanding of propositional logic and Boolean algebra, though.
Deleted comment
Can you name any widely-used programming language(s) that has a short-circuit AND that returns on the first non-true result but not a short-circuit OR that returns on the first non-false result?
eat_gruel unless has_parents?
puts "Please sir, I want some more" if shortest_straw?