Michael Zuskin

Elegant Code - Intriguing World of Logic



Boolean flags naming


Don't give Boolean variables and parameters "super-universal" names like flag or switchIt. Instead, write what exactly they flag/switch.


For example, if a method's argument enables or disables fields with personal information then call it enablePersonalInfo - you should describe what the argument is doing or which information it brings, you don't need to explain other programmers what Boolean type is! The only exception to this rule - if the meaning of the Boolean argument is conveyed by the method's name: for example, the method SetReadOnlyMode can have an argument named simply switch (so it's understood that passing false cancels the mode).

Positive wording in Boolean names


Give Boolean variables and methods positive names. Avoid both - direct negation and words which carry a tinge of negativity.


Think positively! :-) That will simplify reading and understanding of Boolean expressions, especially if they are nested. As an example, you can see the expressions if !DocNotPrinted() and if DocPrinted(): they are logically equivalent, but the positive version is easier to understand. As you see, the difference appears even when the expressions are stand-alone, but imagine if they would be a part of a complex Boolean expression! You can contradict: and what if the calling code wants to ask if the document has not been printed yet? Whether if !DocPrinted() is better than if DocNotPrinted()? Ok, we always have a chance that our Boolean var or method will be used with NOT, but using of positive wording will cause maximum ONE negation in that situation, while using negative wording can result in TWO of them - "negation of negation"! The following table contains a few examples of "negative" and "positive" words:


NEGATIVE WORDS
(TO AVOID):
POSITIVE WORDS
(TO USE INSTEAD):
disabled enabled)
disallowed(prohibited) allowed
hidden displayed
excluded included
failed ok(succeeded)

But please remember - this advice is only about Boolean names. If your not-Boolean method performs a negative action, its name must reflect exactly that action, for example: DisableSaveButton, ProhibitPrinting, ExcludeExpiredRecords.

Positive logic in Boolean expressions


Comparing values, try to use =/== instead of <>/!=.


*** BAD code: ***


PB
if ls_order_status <> n_order_status.CLOSED) then
   [actions for orders which are not closed]
else
   [actions for closed orders]
end if
C#
if (orderStatus <> OrderStatuses.Closed)
   {
   [actions for orders which are not closed]
   }
else
   {
   [actions for closed orders]
   }

*** GOOD code: ***


PB
if ls_order_status = n_order_status.CLOSED) then
   [actions for closed orders]
else
   [actions for orders which are not closed]
end if
C#
if (orderStatus = OrderStatuses.Closed)
   {
   [actions for closed orders]
   }
else
   {
   [actions for orders which are not closed]
   }

This advice is especially important if the value comparison is nested into a Boolean expression:


*** BAD code: ***


PB
if ls_order_status <> n_order_status.CLOSED or &
         ls_state <> n_state.MONTANA or &
         ls_day_type <> n_day_type.WEEKEND then
   [actions if: order is not closed OR state differs from Montana OR day is not a weekend]
else
   [actions for other cases]
end if
C#
if (orderStatus <> OrderStatuses.Closed ||
         state <> States.Montana ||
         dayType <> DayTypes.WeekEnd)
   {
   [actions if: order is not closed OR state differs from Montana OR day is not a weekend]
   }
else
   {
   [actions for other cases]
   {

*** GOOD code: ***


PB
if ls_order_status = n_order_status.CLOSED and &
         ls_state = n_state.MONTANA and &
         ls_day_type = n_day_type.WEEKEND then
   [actions if: order is closed AND state is Montana AND day is a weekend]
else
   [actions for other cases]
end if
C#
if (orderStatus = OrderStatuses.Closed &&
         state = States.Montana &&
         dayType = DayTypes.WeekEnd)
   {
   [actions if: order is closed AND state is Montana AND day is a weekend]
   }
else
   {
   [actions for other cases]
   }

Use "switch"/"choose case" to compare one value with many


Comparing one value with many other values, use the switch/choose case construction instead of multiple ||/OR operators, so the compared value will be mentioned in the code only once.


In this situation, we are not really switching/choosing between options (so we can call it "a fake switch/choose"), but why not to do our code more readable if the programming language doesn't allow the IN clause like T-SQL and PL/SQL do?


*** BAD code: ***


PB
if ls_city = "Toronto" or ls_city = "Boston" or ls_city = "Chicago" then
   ls_message = "Nice city!"
end if
C#
if (city == "Toronto" || city == "Boston" || city == "Chicago")
{
   message = "Nice city!";
}

*** GOOD code: ***


PB
choose case ls_city
case "Toronto", "Boston", "Chicago"
   ls_message = "Nice city!"
end choose
C#
switch (city)
{
   case "Toronto":
   case "Boston":
   case "Chicago":
      message = "Nice city!";
      break;
}

PowerBuilder developers can also utilize the iin() function.


Short conditions in IFs


Don't write complicated expressions inside of if statements (just after the if); instead, store the expression's result in a Boolean variable with a quality, descriptive name and use that variable in the if statement.


That will make the logic being implemented extremely easy to understand!


*** BAD code: ***

PB
if (ls_calc_method = n_calc_method.ADDITIVE and lb_additive_calculation_passed) or &
        (ls_calc_method = n_calc_method.RATIO and lb_ratio_calculation_passed) then...
C#
if ((calcMethod == CalcMethods.Additive && additiveCalculationPassed) ||
        (calcMethod == CalcMethods.Ratio && ratioCalculationPassed))...

*** GOOD code: ***

PB
lb_structural_change_occurred = &
		(ls_calc_method = n_calc_method.ADDITIVE and lb_additive_calculation_passed) or &
		(ls_calc_method = n_calc_method.RATIO and lb_ratio_calculation_passed)
        
if lb_structural_change_occurred then...
C#
bool structuralChangeOccurred;

structuralChangeOccurred =
		(calcMethod == CalcMethods.Additive && additiveCalculationPassed) ||
		(calcMethod == CalcMethods.Ratio && ratioCalculationPassed);
        
if (structuralChangeOccurred)...

As you see, the bad code tells us only WHEN the condition is true, but the good code tells us both WHEN and WHY!!! It is so important that I easily go against the rule to decrease scripts size - I don't try to keep my scripts short in any price, sometimes I prefer to ADD LINES if that makes the code more understandable or less looking like a heap of rubbish. And, of course, don't forget that the expression in my example is pretty small because it's enough to convey the idea; in the real life I have seen statements with half-a-screen Boolean expressions inside an if! If the logic is complicated then don't populate the Boolean variable in one assignment statement (as described above) but come to the truth step by step, using a number of simple Boolean expressions instead of one monstrous construction. Please see the next example of populating a variable step by step instead of inserting everything in the if statement:


PB
boolean lb_the_show_must_go_on

lb_the_show_must_go_on = false
choose case true
case [cond1], [cond2]
   lb_the_show_must_go_on = true
case [cond3] and [cond4]
   lb_the_show_must_go_on = this.uf_xxx()
case [cond3]
   lb_the_show_must_go_on = this.uf_yyy()
end choose
   
if lb_the_show_must_go_on then...
C#
bool theShowMustGoOn;

theShowMustGoOn = false;
if ([cond1]||[cond2])
   {
   theShowMustGoOn = true;
   }
elseif ([cond3]&&[cond4])
   {
   theShowMustGoOn = this.isXxx();
   }
elseif ([cond3])
   {
   theShowMustGoOn = this.isYyy();
   }

if (theShowMustGoOn)...

Boolean methods


If a method returns Boolean then its name should convey the meaning of the returned value to make the business logic in the calling scripts easily understandable. Calling that function as an if statement's condition must produce a correct real-English sentence.


Here are some examples of well-named Boolean methods: RowIsValidated, OrdersAreOk, FileNameAlreadyExists. A strange tradition dictates to put the words "Is"/"Are"/"Does"/"Do" in the beginning (like IsRowValidated, AreOrdersOk, DoesFileNameAlreadyExist etc.), but I personally don't like that approach because it produces incorrect English in calling scripts: we say "if row is validated then do something", not "if is row validated...". Of course, it's not an issue of high importance - you can even omit "Is"/"Are" at all, and the names still will be fine: RowValidated, OrdersOk.


Never use verbs in imperative to name Boolean methods. For example, the following names are absolutely unacceptable for methods returning Boolean: ValidateRow, CheckOrders, CheckFileExistence - we don't say "if validate row then save", "if check orders then print", "if check file existence then open file"!


Populating Boolean variables


If it's possible, populate Boolean variables with results of Boolean expressions instead of direct assigning of true and false.


*** BAD code: ***


PB
if ll_row_count > 1 then
   lb_multi_rows_mode = true
else
   lb_multi_rows_mode = false
end if
C#
if (rowCount > 1)
        {
        multiRowsMode = true;
        }
else
        {
        multiRowsMode = false;
        }

*** GOOD code: ***


PB
lb_multi_rows_mode = (ll_row_count > 1)
C#
multiRowsMode = (rowCount > 1);

Don't return null from Boolean functions


Boolean functions must always return true or false (but not null). Think defensively if the function fails to return the asked value.


You cannot be sure if the calling script (which must make a code branching decision) will check the returned result for null. In fact, usually there is no such check, especially when the function is directly put inside an if construction. So, there is a high probability that a null, returned by a Boolean method, will be treated as false - it's acceptable in most cases, but not always. So, what do we have to return if the method cannot answer the asked question? In this situation, think pessimistically. If the method checks an eligibility, return a value, saying "the entity is not eligible for what you asked". For example, if the method CustomerCanGetDiscount() failed to connect to the database, it should return false, but the method PassengerMustBeDoubleCheckedBySecurity should return true in that situation - simply use the common sense trying to prevent the bad. But the best way to treat technical failures is throwing an exception!

De Morgan's Laws


Simplify Boolean expressions using De Morgan's Laws.


To ensure that logical statements are expressed simply, use the De Morgan's Laws:

(NOT P) OR (NOT Q) can be expressed shorter as NOT (P AND Q)
(NOT P) AND (NOT Q) can be expressed shorter as NOT (P OR Q)

So, in that situation, use the NOT operator only once, as shown below.

EXAMPLE 1 - "AND" BECOMES "OR":

PB lb_is_gas = (not lb_is_liquid) and (not lb_is_solid) and (not lb_is_plasma)
C# isGas = (!isLiquid) && (!isSolid) && (!isPlasma);

should be simplified to

PB lb_is_gas = not (lb_is_liquid or lb_is_solid)
C# isGas = !(isLiquid || isSolid || isPlasma);


EXAMPLE 2 - "OR" BECOMES "AND"

(To go to a restaurant, 2 conditions must be satisfied: "we are hungry" AND "restaurants are open now"):

PB lb_we_will_stay_home = (not lb_we_are_hungry) or (not lb_restaurants_are_open_now)
C# weWillStayHome = (!weAreHungry) || (!restaurantsAreOpenNow);

should be simplified to

PB lb_we_will_stay_home = not (lb_we_are_hungry and lb_restaurants_are_open_now)
C# weWillStayHome = !(weAreHungry && restaurantsAreOpenNow);

Short-circuit evaluation


Use short-circuit evaluation to improve performance of your program.


Short-circuit evaluation (SCE), also called "minimal", denotes the semantics of Boolean operators in logical expressions in formats "A AND B" and "A OR B" in which the second argument is only executed or evaluated if the first argument does not suffice to determine the value of the expression. In other words, the evaluation of a Boolean expression terminates as soon as the result value, produced by the whole expression, is known, even if not all the components in the expression have been evaluated. Thus, try to build the expression so that the decision is made as soon as possible:

To clarify the idea, let's write a Boolean method which determines if the person is eligible to work in security (to pass the validation, the person must be older than the minimum age, defined by law, AND to have no criminal record). The age checking is simply a quick comparison between instance variables, but to check criminal record, the application needs to send a query to the database via the network (which is much slower operation). Here is the method's code with the correct order of components (PB doesn't have short-circuit evaluation, so we have to imitate it by slightly more complicated code):


PB if this.ii_age < this.ii_min_age then return false
return this.uf_has_clear_criminal_record()
C# return (this.Age >= this.MinAge && this.HasClearCriminalRecord());

So, if the candidate is immature, we will not bother the network and the DB at all.

The short-circuit is not the only possible evaluation, there is also the regular one (also called "eager" or "long-circuit") - when the second part of the Boolean expression is evaluated anyway, even if its result cannot change the already known value, which will be produced by the whole expression. In most programming languages you can utilize both the evaluations using different logical operators, but some languages have only one. Look at the following table to ascertain what happens in the language you are working with:


Language Eager operators Short-circuit operators
Ada, Eiffel and , or and then , or else
C, C++ none && , ||
C# & , | && , ||
ColdFusion none AND , OR , && , ||
Fortran .and. , .or. none
Java & , | && , ||
JavaScript none && , ||
Lisp none and , or
Pascal and, or and_then , or_else
Perl & , | && , and , || , or
PHP none && , and , || , or
PL/SQL none AND , OR
PowerScript (PowerBuilder) and , or none
Python none and , or
Smalltalk & , | and: , or:
Transact-SQL (T-SQL) none AND , OR
VB.NET And , Or AndAlso , OrElse
VB, VBA, VB Script And , Or Select Case

If your programming language doesn't appear in the table, you can easily check if it supports SCE by assignin a Boolean variable the results of the expressions, having zero divide in the second part (translate them to your language if needed):

BooleanVariable = ((1 = 2) AND (1 = (1 / 0)))
BooleanVariable = ((1 = 1) OR (1 = (1 / 0)))

If you don't get the zero divide error then SCE is supported.




blog comments powered by Disqus



<< prev CONTENTS next >>