Balancing exception safety, code coverage and fixing issues found by sonar

I will try and demonstrate a general issue with a specific example.
Here is an example of some legacy C++ code which already has reasonably thorough (but verbose) error and exception handling:

bool Decompiler::decompile()
{
   bool ok = true;
   DIR *dir = nullptr;
   try
   {
       dir = opendir(this->getRootDirectory().c_str());
       if (dir != nullptr)
       {
          this->start();
          ok = this->decompile(this->getRootDirectory(), dir) && ok;
          this->end();
       }
       else
       {
         ok = false;
         this->gotBadDirectory(this->getRootDirectory());
       }
   }
   catch (...)
   {
       ok = false;
       closedir(dir);
       throw;
   }
   closedir(dir);
   return ok;

}

Sonar rightly detected that closedir() should not be called with a nullptr (though it is probably harmless in practice).
So to correct this the simple and obvious way the code becomes:

    bool Decompiler::decompile()
    {
       bool ok = true;
       DIR *dir = nullptr;
       try
       {
           dir = opendir(this->getRootDirectory().c_str());
           if (dir != nullptr)
           {
              this->start();
              ok = this->decompile(this->getRootDirectory(), dir) && ok;
              this->end();
           }
           else
           {
             ok = false;
             this->gotBadDirectory(this->getRootDirectory());
           }
       }
       catch (...)
       {
           ok = false;
           if (dir != nullptr)
           {
              closedir(dir);
	   }
           throw;
       }
       if (dir != nullptr)
       {
          closedir(dir);
       }
       return ok;
    }

So I remove the issue found.
But because I have resolved this issue there are new lines which must be covered.

The quality gate rejects the code as having insufficient coverage.
No current tests execute the catch block.

The kind of exception that this handles might be something like std::bad_alloc
Adding a test to induce an exception will result in even bigger changes.

It does not seem realistic to use dependency injection to replace every function call (for example the decompile() and gotBadDirectory() methods).
That would generate even more new code which will also need sufficient tests to meet the coverage goal.

I know there is an option to disable coverage of exceptional paths.
It would be a big mistake to disable it as coverage of exceptions is highly important in many cases.
I am strongly against this.

Now there are other ways to rewrite this to avoid problems. Make the directory an RAII resource for example.

But the original task was to fix a tiny defect found by sonar (with a estimate time of 1m) not the missing coverage of an obscure exceptional path.

The issue is that making even a small change potentially spirals into a much bigger job.

How can this be balanced effectively?
Or how can I fix one issue and defer fixing unrelated issues in nearby code?

How are other people approaching this balancing act?

Hi,

In fact, this is one of the many reasons we preach a Clean as You Code approach.

Rather than setting out to fix issues, the idea is that you just make sure the code you need write anyway is clean - error-free and tested. That way you don’t fall down a rabbit hole if spiraling code changes (to thoroughly mix a metaphor!). And when some business request naturally brings you into this code, then you clean up this issue.

That’s why the default Quality Gate is about new code, rather than overall measures - we consciously don’t incent you to go digging into code that didn’t already need changes.

Does that make sense?

 
Ann

I agree with “clean as you code” as the basic approach.

However, I also want to go just a little bit further and tackle some legacy issues as I go as otherwise the remaining issues will never be tackled if the code never changes.

Ultimately I want my whole project to be at the level new code is.
I am doing this one issue at a time and trying to sneak changes in wherever I can without compromising things.

In fact as a result of tackling this case I found/did several things:

  • a whole set of test code had not been imported when it was copy-pasted from another project.
  • branch coverage was adversely affected by assertions - I found a way around this see Disable branch coverage for assertions?
  • I also added an RAII class for the directory in this case.
  • and added some test cases to improve coverage

For me it has been positive experience.
However, I am very keen on static analysis and improving old code. Others may be more easily put off.

But this task was not the 1m fix sonar suggested because of the interactions.
So I am interested in other people’s perspectives on this.

1 Like