Showing posts with label efficient. Show all posts
Showing posts with label efficient. Show all posts

Thursday, 31 January 2019

C++ Coding Standards: Don't Return Early

I've just had one of those mini-conversations which has be scratching my head, I asked a coworker about "return early" being part of the coding standard or not, as it was being discussed by other parties.

His response was an emphatic, slightly arrogant and touchily obtuse, "I have no time for anyone arguing for not returning early from a function you've discovered you should not be in".

I however don't take such a black and white view, I see the pro's and con's of both approaches, importantly not returning from a function can, and is part of, driving process flow conversations and it aids in picking out structure which can be encapsulated away.  The result being that instead of instantly exiting and potentially leaving spaghetti code you can see a flow, see where branching is occurring and deal with it...

But, that said, it must be understood "returning early" is often seen as "faster", as the chap argued "I see no point being in a function you know you should already have left", so I took to compiler explorer... In a very trivial example:

 

Here we see a decision being made and either a return or the active code, easy to read, simple, trivial... But don't dismiss it too early, is it the neatest code?

Well, we could go for less lines of code which generates near identical assembly thus:


This is certainly less lines of C++ however, inverting the test is a little messy and could easily be overlooked in our code or in review, better might therefore be expanding the test to a true opposite:


One could argue the code is a little neater to read without the inverting, critically though it has made no difference to the assembly output, it's identical.

And it is identical in all three cases.

You could argue therefore that "returning early has no bearing on the functionality of the code", but that's too simplistic, because "not returning early" also has no bearing on the functionality of the code.  The test and action and operation has been worked out by the magic of the compiler for us to be the same.

So with equivalent operation and generation we need think about what returning from the function early did affect, well it made the code on the left longer, yes this is an affectation of our coding with braces, but it was longer.  You could also see that there were two things to read and take in, the test was separate to the code and importantly for me the test and the actual functional code were on the same indentation horizontal alignment.  Being on that same alignment makes your eye not think a decision has been made.

Putting the test and the action of that test into the inner bracing communicates clearly that a decision has been made and our code has branched.

And when that structure is apparent you can think about what not returning early has done, well it's literally braced around the stanza of code to run after the decision, that packet of code could be spun off into it's own function do you start to think about encapsulation.  Of course you can think about the same thing after the return, but in my opinion having the content of the braces to work within is of benefit and most certainly does not afford any speed benefits.

Lets look at a more convoluted, but no less trivial example, a function to load a file in its entirety and return it as a known sized buffer... we'll start with a few common types:

#include <memory>
#include <cstdint>
#include <string>
#include <cstdio>
#include <optional>
#include <sys/stat.h>
#include <sys/types.h>
using byte = unsigned char;
using DataBlock = std::shared_ptr<byte>;
using Buffer = std::pair<uint32_t, DataBlock>;
using LoadResult = std::optional<Buffer>;

And we'll assume there's a function to get the file size, with stat...

const uint32_t GetFileSize(const std::string& p_Path)
{
    uint32_t l_result(0);

    struct stat l_FileStat;
    if ( stat(p_Path.c_str(), &l_FileStat) == 0)
    {
        l_result = l_FileStat.st_size;
    }

    return l_result;
}

Now, this file is return path safe because I define the result as zero and always get to that return, I could have written it thus:

const uint32_t GetFileSizeOther(const std::string& p_Path)
{    
    struct stat l_FileStat;
    if ( stat(p_Path.c_str(), &l_FileStat) != 0)
    {
        return 0;
    }

    return l_FileStat.st_size;
}

But I don't see the benefit, both returns generate an lvalue which is returned, except in the latter you have two code points to define the value, if anything I would argue you loose the ability to debug "l_result" from the first version, you can debug it before, during and after operating upon it... Where as the latter, you don't know the return value until the return, which results in the allocate and return.  And again in both cases the assembly produced is identical.

So, the load function, how could it be written with returning as soon as you see a problem?... Well, how about this?

LoadResult LoadFileReturning(const std::string& p_Path)
{
    LoadResult l_result;

    if ( p_Path.empty() )
    {
        return l_result;
    }
     
    auto l_FileSize(GetFileSize(p_Path));
    if ( l_FileSize == 0 )
    {                     
        return l_result;
    }

    FILE* l_file (fopen(p_Path.c_str(), "r+"));
    if ( !l_file )
    {
        return l_result;
    }

    Buffer l_temp { l_FileSize, std::make_shared<byte>(l_FileSize) };
    if ( !l_temp.second )
    {
        fclose(l_file);
        return l_result;
    }

    auto l_ReadBytes(
        fread(
            l_temp.second.get(),
            1,
            l_FileSize,
            l_file));

    if ( l_ReadBytes != l_FileSize )
    {
        fclose(l_file);
        return l_result;
    }

    l_result.emplace(l_temp);

    fclose(l_file); 

    return l_result;
}

We have six, count them (orange highlights) different places where we return the result, as we test the parameters, check the file size and then open and read the file all before we get to the meat of the function which is to setup the return content after a successful read.  We have three points where we must remember and maintain to close the file upon a problem (red highlights).  This duplication of effort and dispersal of what could be critical operations (like remembering to close a file) throughout your code flow is a problem down the line.

I very much know I've forgotten and missed things like this, reducing the possible points of failure for your code is important and my personal preference to not return from a function early is one such methodology.

Besides the duplicated points of failure I also found the code to not be communicating well, its 44 lines of code, better communication comes from code thus:

LoadResult LoadFile(const std::string& p_Path)
{
    LoadResult l_result;

    if ( !p_Path.empty() )
    {
        auto l_FileSize(GetFileSize(p_Path));
        if ( l_FileSize > 0 )
        {                        
            FILE* l_file (fopen(p_Path.c_str(), "r+"));
            if ( l_file )
            {
                Buffer l_temp { l_FileSize, std::make_shared<byte>(l_FileSize) };
                if ( l_temp.second )
                {
                    auto l_ReadBytes(
                        fread(
                            l_temp.second.get(),
                            1,
                            l_FileSize,
                            l_file));

                    if ( l_ReadBytes == l_FileSize )
                    {
                        l_result.emplace(l_temp);
                    }
                }                

                fclose(l_file);
            }
        }
    }

    return l_result;
}

This time we have 33 lines of code, we can see the stanzas of code indenting into the functionality, at each point we have the same decisions taking us back out to always return.  When we've had a successful open of the file we have one (for all following decisions) place where it's closed and ultimately we can identify the success conditions easily.

I've heard this described as forward positive code, you look for success and always cope with failure, whilst the former is only coping with failure as it appears.

I prefer the latter, ultimately it comes down to a personal choice, some folks argue indenting like this is bad, I've yet to hear why if the compiled object code is the same, you are communicating so much more fluently and have less points of possible human error in the code to deal with and maintain.

From the latter we could pick out reused code and we can target logging or performance metrics more directly on specific stanzas within their local scopes.  Instead of everything being against the left hand indent line.

Now, I will happily tell you it hasn't been a comfortable ride to my thoughts on this topic, I started programming on a tiny screen (40x20 characters - Atari ST Low Res GEM and when I moved to DOS having 80 character widths I felt spoiled.  Now we have tracts of huge screen space, arguing you need to stay on the left is moot, you don't, use your screen, make your code communicate its meaning, use the indents.

And yes, before you ask, I was doing things this way long before I started to use Python.

Tuesday, 12 July 2016

Software Engineering : Arduino Uno - Flashing Clock

What is this flashing clock?  Well, it's just an arduino uno, with three LED's, one each for the seconds, minutes and hours.  They don't show the time, they just flash as each period they're counting passes.

So, we're going to count the seconds and flash just the second LED.  Then each 60th flash, we reset the seconds counter, increment the minute counter and have the minute LED flash.  Ditto for the hour counter each 60th minute.

I've got the LED's VCC legs connected to digital pins 8, 9 and 10, with the other connected to the three grounds.  When I come to make this an actual board I can have a single ground pin being used up, rather then using all three on the board.

An important part of the code to focus on is how to turn on each light in turn...

If you want to turn on the second, the minute and the hour LED all at the same time, you could have some horrid "if" statement controlling them, but much more clean looking is this:

// Turn lights on
switch(CycleCount)
{
    case 2:  digitalWrite(HourPin, HIGH);
    case 1:  digitalWrite(MinutePin, HIGH);
    case 0:  digitalWrite(SecondPin, HIGH);
                break;
}

So, the count is reset to zero each cycle it is incremented if we add to the minutes, and incremented if we add to the hours.

The switch statement then drops through, notice that there are no "breaks" on the 2 and 1 (hours & minutes) cases.  So, if we have incremented the hour, we have a count of two and so we set the HourPin High, drop through to the MinutePin High and finally the Second Pin High before we break.

Likewise, if we only set the minutes this cycle, then the count will be one, so the switch statement will drop down to the Minute Pin high, skipping the Hour pin totally.

The effect of this is to seemingly instantly turn on all the LED's each cycle as they are needed together, there is no humanly noticeable staggering of the lights coming on, as one might get with an if statement, or once might get if one were to set the LED as we increment the values.  Doing Lights on when we increment actually clearly shows the LED's going on and off in a staggered pattern.

Our task after turning all of them on is to just wait a short time (100 milliseconds) and then turn them all off with another switch statement.  And with them all off again simply wait for the remainder of the second (i.e. 900 ms).


Keep this in mind as you read the following code:

// The pins
int SecondPin = 8;
int MinutePin = 9;
int HourPin = 10;

// The counts
int CycleCount = 0;
int SecondCount = 0;
int MinuteCount = 0;
int HourCount = 0;

void setup() 
{ 
  // Setup the pins
  pinMode (SecondPin, OUTPUT);
  pinMode (MinutePin, OUTPUT);
  pinMode (HourPin, OUTPUT);
}

void loop() 
{
  // Increment the counts
  CycleCount = 0;
  SecondCount = SecondCount + 1;
  if ( SecondCount > 59 )
  {
      SecondCount = 0;
      ++MinuteCount;
      ++CycleCount;

      if ( MinuteCount > 59 )
      {
        ++CycleCount;
        MinuteCount = 0;
        ++HourCount;

        if ( HourCount > 23 )
        {
          HourCount = 0;
        }
      }
  }

  // Turn lights on
  switch(CycleCount)
  {
    case 2:
      digitalWrite(HourPin, HIGH);
    case 1:
      digitalWrite(MinutePin, HIGH);
    case 0:
      digitalWrite(SecondPin, HIGH);
      break;
  }   

  // Light are on for 1/10 of a second
  delay(100);

  // Tuen lights off
  switch(CycleCount)
  {
    case 2:
      digitalWrite(HourPin, LOW);
    case 1:
      digitalWrite(MinutePin, LOW);
    case 0:
      digitalWrite(SecondPin, LOW);
      break;
  }   

  // Lights all off for 9/10 of a second
  delay (900);
}

If you found anything of use here, please return again, and don't forget to check out my other YouTube videos.