The conditional operator in its usual form is a relatively rare source of problems. However, the condition itself is sometimes quite complicated and gets in the way of any developer’s dream. This, of course, is about beautiful and readable code.
Probably, I was not looking there, but never in the standards for the design of the code did I encounter any mention of how to be with difficult conditions. Dealing with them is the goal of this article.
Since I have problems with getting out of my finger, some GCC 4.8.2 source codes were taken as an example source, for whose authors the decoration standards are not an empty sound. Using the examples, I will give the file and the start line so that everyone can be sure that everything is fair.
Immediately, I note that, since the examples are real and were taken from a limited source, some of them may not be the most successful.
')
In the context of this article, we will consider a condition that consists of several sub-conditions and does not satisfy the requirements when writing in one line. Implied requirements are, for example, string length or readability.
Readability, as usual, is determined by eye, because in some cases 3-4 sub-conditions are distinguished at the first glance at a section of code, and in others and with two features, the leg is broken. For example, the use of functions, type conversions, bitwise operations, or nesting greatly complicates the reading of conditions.
The following example is somewhere on the verge.
libgcc / fp-bit.c - 205:
if (LARGEST_EXPONENT_IS_NORMAL (FRAC_NBITS) && (isnan (src) || isinf (src)))
But sometimes even a simple condition makes you think for a moment.
libgcc / fp-bit.c - 1579:
if ((in.fraction.ll & (((USItype) 1 << F_D_BITOFF) - 1)) != 0)
Naturally, if this is part of the condition, then it is difficult to call it (the resulting condition) simple.
I deliberately did not take examples with preprocessor directives inside the conditions, since this is about even more complex conditions that go beyond the article.
To improve readability, you need to break the condition into blocks. You will have to do the same if the length of the string with the condition exceeds the allowable (often stipulated in the standards). We are interested in the principles of this partition for two main reasons. First, it should be done in such a way as not to aggravate the situation. Secondly, it is necessary to come to an unofficial, but standard, since the very fact of uniformity is already a plus to readability.
The first thing that comes to mind is the use of nested conditional statements.
libgcc / fixed-bit.c - 84:
if ((((x ^ y) >> I_F_BITS) & 1) == 0) { if (((z ^ x) >> I_F_BITS) & 1) { ... } }
libgcc / fp-bit.c - 361:
if (exp < EXPMAX) if (low > unity || (low == unity && (high & 1) == 1)) { ... }
If in the first case, not everything is so bad, in the second case the nested condition is close to the fact that it will also need to be split. Meanwhile, the nesting of the code is growing. Moreover, in the case of a disjunction, this trick will not work.
A more interesting option is multi-line conditions. Such a solution for some seems unexpected, although it is supported in many places (in the same C, PHP, Python).
The idea of ​​splitting is that only one sub-condition is left on each line.
libgcc / libgcc2.c - 1980
if (!recalc && (isinf (ac) || isinf (bd) || isinf (ad) || isinf (bc)))
This condition is readable and easily understood. But it does not comply with the previously mentioned rule. Here comes the use of monotony. If the need to write one sub-condition per line is not specified, then the analysis of this condition while reading it becomes more complicated. In the opposite case, even having encountered something like
libgcc / fp-bit.c - 1579 as a sub-condition, it is known in advance that it is not complicated.
Most of the multi-line conditions in the source code under consideration do correspond to this idea. But not limited to it. The indents in the last example suggest that you can also visualize the nesting of conditions with this approach. The only thing that caused doubt was that all such conditions were too monotonous, were just such a “ladder”, because of which even the thought appeared that it was just a coincidence.
Fortunately, there was a whole one example which, no doubt, fits my definition of a beautiful and understandable complex condition.
libgcc / libgcov-driver.c - 688:
if (!all_prg->checksum && (cs_all->num != cs_prg->num || cs_all->runs != cs_prg->runs || cs_all->sum_all != cs_prg->sum_all || cs_all->run_max != cs_prg->run_max || cs_all->sum_max != cs_prg->sum_max))
Here the principle of one subcondition per line is observed, and nesting is clearly shown. Analysis of this condition is simple, and this is pleasant. Present it as one line or as three or four nested ifs.
Naturally,
there is no need to combine this with the nested conditional operator (as is done in
libgcc / libgcc2.c - 1611 (there is no example in the article) ).
Another version of the implementation of this approach comes to my mind, but it is more cumbersome, and in practice I have not met it. Something like the following:
if ( condition1 && ( condition2 || condition3 ) )
This is just one of the options. The idea is to make the closing brackets on a separate line. This eliminates the "jumping" indents, as, for example, in the following example.
libgcc / fixed-bit.c - 1013:
if ((BIG_SINT_C_TYPE) high > (BIG_SINT_C_TYPE) max_high || ((BIG_SINT_C_TYPE) high == (BIG_SINT_C_TYPE) max_high && (BIG_UINT_C_TYPE) low > (BIG_UINT_C_TYPE) max_low)) low = max_low;
Well, the arrangement of brackets is similar to the arrangement of all familiar operator brackets and therefore is obvious and understandable. The parentheses for the allocation of nested conditions, of course, are mandatory, since they allow to avoid errors associated with the priority of logical operations.
I think the findings are not needed, everyone will make it for himself. Well, if this question is still addressed in some standards or literature, then links, titles, authors will not interfere (quotes are welcome).
UPD: Another good option is to select the parts of a complex condition into separate boolean variables. I did not find a suitable working example, so I did not mention it initially. For the indicative code, thanks
lexasss .
bool mustRdraw = (frame.isChanged() || target.isChanged()) || experiment.isRunning(); bool isFullScreen = frame.getSize().equal(screen.getSize()); if (isFullScreen && mustRedraw) {
With proper grouping conditions and variable naming, this approach also has a documenting function.