Best way to refactor complex if expression [closed] - javascript

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I have just come on board to a big e-commerce project that has an angularJS front-end. I have been tasked with adding a lot of complex features to the checkout pages... which already have a lot of complex logic.
To make things harder I keep coming across lots of if statement expressions like the one below which are making it hard to understand and it is a slow process going through this code with many of these type of if expressions.
Some of these expressions are pretty critical and are sometimes even longer... There are no unit tests and when I ask other devs what this is checking for and why (just to be sure I understand) I usually get pointed to someone else rather than an explanation.
if ((!previousBillingAddress) || (previousBillingAddress && previousBillingAddress.id !== bag.paymentAddress.id)){
console.log('all of the above was true'); // just a dummy log
} else {
console.log('all of the above was false'); // just a dummy log
}
Does anyone have a good tip for refactoring these types of expressions?
I thought of breaking them down into functions that have descriptive names and the functions could return true or false but Im not sure if there is a better way.

Let there be
A = previousBillingAddress
B = previousBillingAddress.id !== bag.paymentAddress.id
then your expression is:
if (!A || (A && B)) {
log1
} else {
log2
}
What we can do we with !A || (A && B)? It's equal to !A || B:
A | B | !A | A && B | !A || (A && B) | !A || B
==========================================================
1 | 1 | 0 | 1 | 1 | 1
1 | 0 | 0 | 0 | 0 | 0
0 | 1 | 1 | 0 | 1 | 1
0 | 0 | 1 | 0 | 1 | 1
That's why your expression is equal to:
if (!previousBillingAddress || previousBillingAddress.id !== bag.paymentAddress.id) {
console.log('all of the above was true'); // just a dummy log
} else {
console.log('all of the above was false'); // just a dummy log
}
TL;DR
Above table is only check if !A || (A && B) is equal to !A || B. How to guess !A || B? In case of such expressions it's good to play with following rules:
A == !(!(A)) (rule 1)
!(A && B) == !A || !B (rule 2)
!(A || B) == !A && !B (rule 3)
A && (B || C) == A && B || A && C (rule 4)
So we have !A || (A && B), let's play. Due to rule 1 it's equal to
!(!(!A || (A && B)))
Now we use rule 3:
!(!(!A || (A && B))) == !(A && !( A && B))
Rule 2:
!(A && !( A && B)) == !(A && (!A || !B)) (*)
Due to rule 4:
A && (!A || !B) == (A && !A) || (A && !B)
We have (A && !A) || (A && !B) and it can be reduce to (A && !B). Now we can back to (*) and we have:
!(A && (!A || !B)) == !((A && !A) || (A && !B)) == !(A && !B)
With rule 2 we got:
!(A && !B) == !A || B

You can drop the previousBillingAddress && part - in the second operand of the || you already have estblished that previousBillingAddress is not falsy. That would make the overall condition
if (!previousBillingAddress || previousBillingAddress.id !== bag.paymentAddress.id) {
console.log('all of the above was true'); // just a dummy log
} else {
console.log('all of the above was false'); // just a dummy log
}
which seems short enough for me. If not, make an appropriately named helper function to which you pass the previousBillingAddress and the bag.

In my personal opinion, i think you can refactor and encapsulate the validation/check inside of a function, then if the same validation applies to other part of your code (file/module/etc..), you can reuse it. In this case you are using angular, so might be good to use an angular custom service for this type of business validations, since they also can manipulate the scope

Comments are our best friend. You want to make sure that the next person to take a look at your code knows exactly what it is supposed to do and from that, it will be quicker and easier to see exactly what it is doing.
In this case the expression in the if statement can be shortened. This is how I would comment it with almost a pseudocode approach:
// we need to check if the user has, and is using, a previous billing address
if (previousBillingAddress && previousBillingAddress.id === bag.paymentAddress.id)){
// the user is using a previous billing address
// do some work with the previous billing address
} else {
// the user is using a new address
// do some work with the new address
// save the new billing address
}
Note that previousBillingAddress is the main subject of this if statement. If we are checking for previousBillingAddress then in our first block we want to make sure we deal with what happens if we have a previous billing address. It's more logical that way.
It makes a lot less sense to check if we don't have a previous billing address and then what do we do if we have one.
Look how clean and logical it is! <3

I recommend hoisting the complex conditionals into Boolean variables (or constants, if your supported level of JS allows you to use const). For example:
var weakPrevBillingAddress =
!previousBillingAddress ||
(previousBillingAddress &&
(previousBillingAddress.id !== bag.paymentAddress.id));
if (weakPrevBillingAddress) {
console.log('judged weak previousBillingAddress');
// other processing expected here
} else {
console.log('judged strong previousBillingAddress');
// other processing expected here
}
As you and philip yoo suggest, you could hoist the conditional into a Boolean helper function. But most of the ways of defining such functions will put the logic farther away from the point of use, making comprehension harder. Boolean variables (a.k.a. "predicates") can be easily defined close to their use (e.g. right before your if statement). So there isn't much additional distance, but they still separate the computation of the Boolean value from its use. Your ability to focus on the computation separately can make things simpler and more comprehensible. As shown above, it also allows you to split long
lines and use indentation for better clarity.
I don't know if you have the flexibility to rename existing variable names. If you do, the ones you're using are exceedingly long. Long names can help increase understanding, but a 22-character name repeated several times per line is distracting, not clarifying. Shorter names, either for the source values, or just for the predicates you compute, can help. E.g.:
var weakPrevBA = !prevBA ||
(prevBA.id !== bag.paymentAddress.id);
Choosing good variable names is an art, and developer tastes about "how much description is enough" vary. But in an app that deals with billing addresses all the time, line after line after line, spelling out billingAddress every time doesn't necessarily help. If you're reading the code and comments, you probably are highly aware you're dealing with billing addresses. If not, add a quick comment, rather than spell out the full concept multiple times per line.
Note that I've also simplified the Boolean expression in this shorter example. (This was also suggested by Bergi) That can be a good idea--as long you're sure it can be simplified correctly. However, do this only with great caution. You don't have unit tests, so you have no easy way to test if simplifications are truly identical. And you're dealing with complex code. I recommend keeping the original Boolean expressions, at least to start (possibly with shortened names). That reduces one place bugs can creep in, at least until you understand the code more fully. The better you understand the code, and the simpler you have made it (e.g. by adding computed predicates, rather than long, complex expressions inside conditionals), the more freedom and safety you have in rewriting expressions.

Something like this
var billingId = previousBillingAddress && previousBillingAddress.id;
if (billingId || billingId === bag.paymentAddress.id){
console.log('all of the above was false'); // just a dummy log
} else {
console.log('all of the above was true'); // just a dummy log
}
what i did
since you are using if and else its not a good practice to use negation in the if statement. so i removed that
if you only want the negated part (without an else)
if (!(billingId || billingId === bag.paymentAddress.id)){
console.log('all of the above was true'); // just a dummy log
}
the && will return the first false, or the last true value. so billingId will equal false or the id. building upon that, your first if is checking if its "falsey" continue, so you can use the same variable as well.

Related

Javascript printing all if conditions in textbot [duplicate]

This question already has answers here:
What's the prettiest way to compare one value against multiple values? [duplicate]
(8 answers)
Closed 4 years ago.
This is literally the first thing I have ever coded and I am self-taught, so I'm sure it's a mess and I've made a dumb error, but after an hour of looking up the issue and trying things I cannot solve this.
To try and teach myself very basic javascript concepts I've written a textbot that has existential conversations with you. The problem is that regardless of user input, all the if statements for the condition of whatDream are printing in a row. Can anyone tell me what I've screwed up here? Apologies if my methods are wack - 100% of what I'm doing is from self teaching and letting myself flail to learn, and I'll appreciate any advice.
function yesHappy (){
alert ("Good for you. To be happy as a human is a great gift.");
alert ("The content human starts over again everyday, in spite of all they know, against all they know.");
alert ("If one advances each day confidently in the direction of their dreams, and endeavors to live the life which they have imagined, they will meet with a success unexpected in common hours.");
var g = 0
while (g < 2) {
g = 0
var whatDream = prompt ("What is your dream?");
if (whatDream === "success" || "Success" || "power" || "Power" || "wealth" || "Wealth"){
alert ("That is a goal that takes much will in the earthly domain, and is not one I can readily reccomend.");
alert ("But, read your fate, see what is before you, and walk into futurity.");
g = 3
}
if (whatDream === "friends" || "Friends" || "family" || "Family" || "love" || "Love"){
alert ("To surround oneself with others fulfills a basic human goal to be social, thus your dream is only natural.");
alert ("Properly speaking, a human has as many social selves as there are individuals who recognize them.");
alert ("To all of these you surround yourself with, see in your goals only to show your true social self.");
g = 3
}
if (whatDream === "fun" || "Fun" || "charity" || "Charity" || "faith" || "Faith" || "travel" || "Travel"){
alert ("These are honorable dreams, chosen to bring joy to yourself and others.");
alert ("Above all however, avoid falsehood, especially falseness to yourself.");
alert ("Be true to your purpose.");
g = 3
}
if (g === 0){
alert ("Though knowledge is the plague of life, and consciousness an open wound in its heart, I am still sad to say I don't know what you mean.");
alert ("Let's try that again.");
}
}
}
You need to refactor all your conditional statements. For example: This one
if (whatDream === "success" || "Success" || "power" || "Power" || "wealth" || "Wealth")
Would have to be translated to:
if (whatDream === "success" || whatDream === "Success" || whatDream === "power" || whatDream === "Power" || whatDream === "wealth" || whatDream === "Wealth")
Which does what you want: Checks to see if whatDream equals any of those. Or alternatively you can use a data structure and use a method to check if the string exists in that data structure- but above is the simplest refactoring you'd have to do.
Javascript has "truthy" and "Falsey" values. Strings like "Success" map to true whereas "" map to false. So your conditionals were always evaluating to true because strings with length > 0 are "truthy". This is probably more explanation that you need- but its important to know that in the future of javascript development.
In essence- your issue is that you weren't checking the values of whatDream in your boolean logic. The above simple refactoring I did fixes it.
If you're curious: You can read up on truthy and falsey values here:
https://j11y.io/javascript/truthy-falsey/

Using Both AND and OR in IF Statement

I'm working on a Google Spreadsheet adding scripts (Not formulas) and I am stuck on a problem.
I need to find a way to use something like this:
If (Product == "Shampoo" && (Box == "15" OR Box == "17"))
{
//Do Something...
}
Basically IF the product is a shampoo and the box it belongs to is either 15 or 17 then do something. I know doing a If (Product == "Shampoo" && Box == "15" OR Box == "17") will produce unexpected or bad results. How do we go about using an OR with AND in google scripting?
From what I understand Google Scripts are based on Javascript but I can't seem to find help posts online or here in SO, all I get are solutions in formula not script.
First a note about the OP code
JavaScript methods are case sensitive.
Instead of IF the correct syntax is if. By the other hand the Logical OR operator is ||.
Considering the above, the OP code could be replaced by
if(Product == "Shampoo" && (Box == "15" || Box == "17"))
{
//Do Something...
}
and
if(Product == "Shampoo" && Box == "15" || Box == "17")
Regarding the question, if (Product == "Shampoo" && Box == "15" || Box == "17") returns unexpected or bad results, this could be hard to read and lead to confusions for humans but JavaScript have very specific rules regarding how operations should be made by the engine, in this case Rhino which is used by Google Apps Script.
As was mentioned in a previous answer in this case the rule is called operator precedence but in order to make the code easier to read and to prevent confusions a good practice is to enclose each comparison in parenthesis.
Considering that the Logical AND has a higher precedence than Logical OR
(Product == "Shampoo" && Box == "15" || Box == "17")
is the same as
((Product == "Shampoo" && Box == "15") || Box == "17")
References
if..else
Logical OR
What are you looking for is the operator precedence of the two operators logical AND && and logical OR ||.
Part of the table:
Precedence Operator type Associativity Individual operators
---------- ------------- --------------- --------------------
6 Logical AND left-to-right … && …
5 Logical OR left-to-right … || …
You see a higher operator precendece of logical AND over logical OR. That means you need some parenthesis for the OR statement.

ng-if statement works one way but not another, why?

When I write my ng-if statement like this...
ng-if="$state.params.Step != vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 2 && vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 1"
it doesn't work at all. I thought the above was just a shorter form of...
ng-if="$state.params.Step != vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 2 && $state.params.Step != vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 1"
What am I doing wrong? I don't want to repeat myself if I can help it.
ng-if="$state.params.Step != vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 2 && vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 1"
This is basically saying:
($state.params.Step != vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 2) &&
vm.$localStorage.builder[$state.params.ID].Data.Steps.length - 1
This compares then &&s the result with the last number. Valid; almost certainly not what you want.
This logic should almost certainly be moved out of the ng-if where it's almost impossible to read.
What you're doing wrong is not repeating yourself. :-)
Angular evaluates expressions like JavaScript does (I think it actually uses JavaScript to do it, but I haven't checked under the covesr). JavaScript, like nearly all the languages with syntax derived from B (C, C++, C#, Java, JavaScript, ...) don't have a x != a && b or x != a || b, you have to spell it out: x != a && x != b or x != a || x != b. That's just how it is.
The only reason you don't get an error is that JavaScript has type coercion, and so x != a && b evaluates to (x != a) && b, which ends up being true && b or false && b. b is then coerced to boolean according to the usual rules (anything falsy ends up being false, everything else ends up true). (The falsy values are 0, "", NaN, null, undefined, and of course, false.)

&& / || operator strangeness in JavaScript

So, I was working on a project of mine, when I came across a problem like this:
var one = 1;
var two = 2;
var three = 7;
if (one === 1 || two === 2 && three === 3) {
console.log("ok"); // prints out ok
}
I'm pretty confused with this since I don't think it should print out "ok". I thought that since the condition after the && operator was false, it shouldn't run, but I'm evidently wrong. Can anyone clarify why it's working?
In Javascript, operators are not just evaluated left-to-right, certain operators have more precedence than others. Those with higher precedence (in this case, the && operator of precedence 13) will be evaluated before others (||, precedence 14).
For your particular case, you need to group your conditionals in order to achieve the functionality you want:
if ((one === 1 || two === 2) && three === 3) {
console.log("ok"); // prints out ok
}
JSFiddle

Fastest way to perform this? Ternary Operator? Switch? Array?

What is the fastest way to perform this? The vars in the parens on the left return boolean and they represent window size ranges. (e.g. o1281 returns true for screens 1281 and up, o1025 returns true for 1025 and up, etc.)
markup = // ternary triangle (the rows set the markup priority at each band)
( o1281 ) ? r1281 || r1025 || r961 || r641 || r481 || r320 || r0 || omarkup:
( o1025 ) ? r1025 || r961 || r641 || r481 || r320 || r0 || omarkup:
( o961 ) ? r961 || r641 || r481 || r320 || r0 || omarkup:
( o641 ) ? r641 || r481 || r320 || r0 || omarkup:
( o481 ) ? r481 || r320 || r0 || omarkup:
( o320 ) ? r320 || r0 || omarkup:
( o0 ) ? r0 || omarkup:
omarkup;
I was thinking maybe break it into 2 ranges based on the middle (o641) condition.
Is it worth it?
Okay so what you are doing is looking for the first truthy o-value going in descending order, and once you find one you are looking for the first truthy r-value whose name is <= this o-value. Finding none, you wish to return the value omarkup.
Because you only have a few o-values and r-values, your code, as "interesting" as it may be, appears to make the least number of tests possible.
I can't see how switches or hashes would help, since you do appear to be searching in descending order for the first truthy value.... Because of this, I don't see why splitting would help performance either. Leave it as is, or, if you prefer readability (as many programmers do), make objects for which 1281, 1025, etc. are fields.
Also, worrying about performance is usually something one does when faced with an operation that will executed many, many times. Is this the case here? This looks like something you would only execute once, assuming the o and r values don't change. (Just an FYI.)
ADDENDUM
Based on the comment added to the question above, it looks like an operation you would like to execute multiple times. In this case it probably okay to self-optimize at the JavaScript level, although modern compilers are pretty good. The biggest concern from a code-review perspective would be that the values 1281, 1025, 961, 941, and so on are laid out manually and are part of the source code, so maintenance, such as adding new size-values here is, well, tricky and error-prone. That said, based on what you have shown, I think it is safe to say your JavaScript as written is the best obtainable given a naive compiler. You can always shoot for an implementation that defines these values exactly once and then profile it to see if it is "fast enough". For example you can define an array such as
var sizes = [1281,1025,961,641,481,320,0]
and loop through, but yes, there is overhead in such implementations.
One issue that might help you here is to consider carefully what can and cannot be cached. This could help speed up future executions of this code.

Categories

Resources