Wednesday, April 11, 2018

One more case for guard clauses

Another case where guard clauses make the code more readable is when you have to verify some parameter state before applying your logic.

Isolating all the checks in guard clauses right at the beginning of your function makes the logic much clearer.

Example

Using nested conditionals:

 if (file != null && file.isAFile()){
   if (user != null && user.isLogged()){
     if (user.isAdmin()){
       if (page != null && page.isAnInteger && page > 0){
         text = file.read();
         return getPageFromText(text, page);
       } else {
         return "Invalid page."
       }
     } else {
       return "User does not have sufficient privileges."
     }
   } else {
     return "Invalid user."
   }
 } else {
   return "Invalid file.";
 }

Using guard clauses:

 if (file == null || !file.isAFile()){
   return "Invalid file.";
 }
 if (user == null || !user.isLogged()){
   return "Invalid user."
 }
 if (!user.isAdmin()){
   return "User does not have sufficient privileges."
 }
 if (page == null || !page.isAnInteger || page <= 0){
   return "Invalid page."
 }

 text = file.read();
 return getPageFromText(text, page);

Guard clauses and single exit point

Developers are often told to have a single exit point in functions but in my opinion this just makes things more difficult than they have to be.

Instead of having a single exit point I prefer to use guard clauses in most of the functions I write, using the single exit point paradigm only when dealing with resources that have to be manually closed (having a single exit point makes resource handling easier and less prone to error if you don't have something like a finally block to help you).

As a matter of fact, I prefer to use what I call "return early" paradigm, that is, return as soon as the job is done (guard clauses are a specific case of this). That avoids nested conditionals and makes the code simpler and easier to read.

Example

Using nested conditional to have a single exit point:

 String ret = null;
 if (isGreen && isRound && smellsCitric){
   ret = getLime();
 } else {
   if (isGreen && isRound){
     ret = getGreenBall();
   } else {
     if (isGreen && smellsCitric){
       ret = getCitricSoap();
     } else {
       if (isRound && smellsCitric){
         ret = getOrange();
       } else {
         if (isGreen){
           ret = getGreenColor();
         } else {
           ret = getUnknown();
         }
       }
     }
   }
 }

 return ret;

Using the return early paradigm:

 if (isGreen && isRound && smellsCitric){
   return getLime();
 }
 if (isGreen && isRound){
   return getGreenBall();
 }
 if (isGreen && smellsCitric){
   return getCitricSoap();
 }
 if (isRound && smellsCitric){
   return getOrange();
 }
 if (isGreen){
   return getGreenColor();
 } 

 return getUnknown();