Business central blog
Subscribe to blog and get news about new posts.

The Dark Side of Ternary Operator

Have you heard about the addition of the ternary operator in AL? Some developers are happy, while others say that it is awful. In some ways, I am closer to those who believe that there is more harm than good in it. However, I still believe that the tool itself has a neutral connotation. It all depends on who uses it and how they do it. I want to thoroughly analyze the pros and cons of using the ternary operator with examples. Where it is worth using and where it is better never to use it.

Agenda

The ternary conditional operator is essentially a type of conditional expression, the most basic of which is the familiar if <condition> then <consequent> else <alternative>.
The operator itself has the following syntax in AL: <condition> ? <consequent> : <alternative>
The question immediately arises: why is this operator needed? Why not just use if-then-else? The answer is actually very simple: this syntax simplifies some scenarios for writing and reading code. Take Case statements, for example; we all understand when it's better to write a Case statement instead of a series of if-then-else. The same applies to the ternary operator. Of course, developers who have only worked with AL or C/AL may not be used to this syntax and might find it unusual to read/write such code. Fortunately, it is a fairly simple construct and easy to get used to.
So why are there developers who are unhappy with the introduction of the ternary operator in AL? There are several reasons for this, let's consider some of them:
  • New syntax inherently increases the cognitive load on the developer.
  • Poor developers will abuse ternary operators, which in turn will complicate code reading and debugging.
Therefore, in my personal opinion, every good developer should adhere to some good manners rules when working with the ternary operator. For me, these rules are quite simple. Let me share them:

  • Use the ternary operator only if it can be easily read in one line.
  • Do not use the ternary operator if the same condition is repeated multiple times.
  • Do not use the ternary operator to return boolean flags
  • Do not use nested ternary operators. This drastically reduces the readability of the code and its debugging.
  • Remember, good code is simple code. There is no need to impress anyone with a complex structure if it can be replaced with a simpler one.
In this section, we will explore some useful features of the ternary operator with examples of how it can be used. This is the light side of the ternary operator.
I have prepared three functions that clearly demonstrate the benefits of the ternary operator. Let's start by looking at their classic if-then-else form.

local procedure GetMinNumberClassic(a: Integer; b: Integer): Integer
begin
  if a < b then
    exit(a)
  else
    exit(b);
end;

local procedure GetAgeInfoClassic(Age: Integer): Text
begin
  if Age >= 18 then
    exit('Adult')
  else
    exit('Minor');
end;

local procedure AbsIntegerClassic(InputInteger: Integer): Integer
begin
  if InputInteger < 0 then
    exit(-InputInteger)
  else
    exit(InputInteger);
end;
When using the ternary operator, these functions can look different. These functions can be called ternary inline return.

local procedure GetMinNumber(a: Integer; b: Integer): Integer
begin
  exit(a < b ? a : b);
end;

local procedure GetAgeInfo(Age: Integer): Text
begin
  exit(Age >= 18 ? 'Adult' : 'Minor');
end;

local procedure AbsInteger(InputInteger: Integer): Integer
begin
  exit(InputInteger < 0 ? -InputInteger : InputInteger);
end;
I think this is a good way to use the ternary operator. But that's not all. Let's look at other applications. For example, instead of writing a function, we can immediately use the ternary operator for variable assignment. Let's call this ternary inline assign.

local procedure TestInlineAssign(a: Integer; b: Integer; Age: Integer; InputInteger: Integer)
var
  AgeInfo: Text;
  MinNumber: Integer;
begin
  MinNumber := a < b ? a : b;

  AgeInfo := Age >= 18 ? 'Adult' : 'Minor';

  AbsInteger := InputInteger < 0 ? -InputInteger : InputInteger
end;
In addition, we can pass the ternary operator as a function parameter. I would say this is already on the edge of good manners, but I think it still has the right to exist. In any case, let's consider this example, which we will call ternary inline parameter.

local procedure TestInlineParams(a: Integer; b: Integer; Age: Integer; InputInteger: Integer)
begin
  ProcessMinNumber(a < b ? a : b);

  ProcessAgeInfo(Age >= 18 ? 'Adult' : 'Minor');

  ProcessAbsInteger(InputInteger < 0 ? -InputInteger : InputInteger);
end;

local procedure ProcessAbsInteger(AbsInteger: Integer)
begin
  //Do something with integer
end;

local procedure ProcessAgeInfo(AgeInfo: Text)
begin
  //Do something with AgeInfo
end;

local procedure ProcessMinNumber(MinNumber: Integer)
begin
  //Do something with MinNumber
end;
I believe that without knowing the dark side, it is impossible to fully understand the power. Like yin and yang symbolizing the completeness and inevitability of the presence of both dark and light sides, we must explore the dark side to better understand the light side. Therefore, in this chapter, we will examine harmful and bad examples of using the ternary operator and remember that this is not the way to do it.
Let's start with a lesser evil, and it might even seem acceptable to you. But no, this is bad, and a case statement would be much more suitable here. Please do not use nested ternary operators!

procedure GetDayName(Day: Integer): Text
var
  DayName: Text;
begin
  DayName := (Day = 1) ? 'Monday' :
         (Day = 2) ? 'Tuesday' :
         (Day = 3) ? 'Wednesday' :
         (Day = 4) ? 'Thursday' :
         (Day = 5) ? 'Friday' :
         (Day = 6) ? 'Saturday' :
         (Day = 7) ? 'Sunday' :
         'Invalid Day';
  exit(DayName);
end;
Better:

procedure GetDayName(Day: Integer): Text
begin
  case Day of
    1:
      exit('Monday');
    2:
      exit('Tuesday');
    3:
      exit('Wednesday');
    4:
      exit('Thursday');
    5:
      exit('Friday');
    6:
      exit('Saturday');
    7:
      exit('Sunday');
    else
      exit('Invalid Day');
  end;
end;
Do you have a large and complex ternary operator that doesn't fit on one line? Then you don't need a ternary operator here!

procedure CalculateBonus(Salary: Decimal; YearsWorked: Integer; PerformanceRating: Integer): Decimal
var
 Bonus: Decimal;
begin
 Bonus := (YearsWorked > 10 and PerformanceRating > 8) ? Salary * 0.2 :
    (YearsWorked > 5 and PerformanceRating > 6) ? Salary * 0.1 :
    (YearsWorked > 3 and PerformanceRating > 4) ? Salary * 0.05 :
    0;
 exit(Bonus);
end;
Better:

procedure CalculateBonus(Salary: Decimal; YearsWorked: Integer; PerformanceRating: Integer): Decimal
var
  Bonus: Decimal;
begin
  case true of
    (YearsWorked > 10) and (PerformanceRating > 8):
      Bonus := Salary * 0.2;
    (YearsWorked > 5) and (PerformanceRating > 6):
      Bonus := Salary * 0.1;
    (YearsWorked > 3) and (PerformanceRating > 4):
      Bonus := Salary * 0.05;
    else
      Bonus := 0;
  end;

  exit(Bonus);
end;
A good example of bad code: do not use the ternary operator to return boolean flags. There is no point in using the ternary operator to return a boolean if we can return the boolean in one line without the ternary operator.

procedure IsEligibleForPromotion(YearsWorked: Integer; PerformanceRating: Integer): Boolean
var
  Eligible: Boolean;
begin
  Eligible := (YearsWorked > 5) and (PerformanceRating > 7) ? true : false;
  exit(Eligible);
end;
Better:

procedure IsEligibleForPromotion(YearsWorked: Integer; PerformanceRating: Integer): Boolean
var
  Eligible: Boolean;
begin
  Eligible := (YearsWorked > 5) and (PerformanceRating > 7);
  exit(Eligible);
end;
Please don't... I'll leave you this example without the "better" version, as it is an interesting problem to think about.

procedure CalculateShippingCost(OrderAmount: Decimal; Destination: Text[30]; IsExpress: Boolean): Decimal
var
  ShippingCost: Decimal;
begin
  ShippingCost := (Destination = 'US') ? (IsExpress ? (OrderAmount > 100 ? 10 : 15) : (OrderAmount > 100 ? 5 : 10)) :
          (Destination = 'EU') ? (IsExpress ? (OrderAmount > 100 ? 20 : 25) : (OrderAmount > 100 ? 15 : 20)) :
          (Destination = 'APAC') ? (IsExpress ? (OrderAmount > 100 ? 30 : 35) : (OrderAmount > 100 ? 25 : 30)) :
          50;
  exit(ShippingCost);
end;

So, the ternary operator can be a very convenient tool that simplifies reading and writing code. But bad developers can abuse it and create more problems, more bad code. At the same time, we need to remember that it is just a tool, and bad developers will generate bad code with or without the ternary operator. So let's strive to be good developers.
July 19, 2024