Well, the comments describing the logic could be macros so the code becomes identical to the comments.
#define A (round(tIn) > userSet[setPnt]+1)
...
// from the truth table...
if ((A || (!A && !B)) && C && D) || (B && C) { //close valves and note millis()
But macros used like this can be dangerous with side-effects. I would also give them meaningful names instead of ABCD.
Assuming all of those variables are global, you can use functions, once again meaningful names are preferred but I don’t know what your code means so I have to use ABCD:
boolean A() {
return (round(tIn) > userSet[setPnt]+1);
}
...
// from the truth table...
if ((A() || (!A() && !B())) && C() && D()) || (B() && C()) { //close valves and note millis()
if (well_named_predicate (...))
well_named_function1 () ;
else
well_named_function2 () ;
Err on the side of more smaller functions, rather than a few complicated ones and name them
carefully - then the code is readable and maintainable and easy to adapt to changing requirements.
Most functions should be 10 lines or fewer. There are times when you need a long list of stuff
in a function, but it should be the exception not the rule.
Use switch/case whenever its possible, its more concise for a whole set of possible values to
match.
Personally I prefer the style in Reply #4. I have no confidence in my ability to get something like this
if (((A || (!A && !B)) && C && D) || (B && C))
correct. And even if I got it correct I know that I would need to spend a long time trying to convince myself it is correct when I return to the program after an absence of a few weeks.
You’re right! And that’s a pretty cool website. I remembered truth tables from my (long ago) college days, but not Karnaugh maps. By looking at my truth table, I should have realized that (A or (!A and !B)) = 1, but the CalPoly tool shows it clearly. (And, if I’d remembered the distributive law, it would have been obvious, too…)
robtillaart:
instead of functions use some local bool variables (your comment becomes code)
bool A = round(tIn) > userSet[setPnt]+1;
bool B = round(tIn) < userSet[setPnt];
bool C = vOpen;
bool D = tOut > tIn;
bool E = (millis() - tmVclosed) > tmIntrvl * 60 * 1000;
if (((A || (!A && !B)) && C && D) || (B && C))
{
closeValves();
tmVclosed=millis();
vOpen=false;
}
else if ((A && !C && !D) || (A && !C && D && E))
{
openValves();
vOpen=true;
}
according to http://www.ee.calpoly.edu/media/uploads/resources/KarnaughExplorer_1.html
**(((A || (!A && !B)) && C && D) || (B && C))**
can be written as
**CD + BC => ((C && D) || (B && C))**
completely removing expression A
(please check if I didn't make mistakes :))
It's been said that 80% of the cost of software development is in the testing and debugging. Anything you can do to make your code easier to read is usually a good thing. (There are probably times when "easier-to-read" causes a severe performance hit.)
Personally, I find cascading if statements much harder to understand than a switch. I find complex AND and OR statements like those discussed here difficult to debug. Often during testing, I break these multiple conditionals out into single statements, one after the other, until I'm sure I have them correct. I then comment out the single statement version and build the "complex" one. That way, if I come back six months later, I can read the commented code and "get up to speed" again a tad bit quicker than otherwise. If the final analysis, it's partly a question of your style and how your brain views such complexity.