2021-02-18

Abstraction and debugging

Working on a new feature the last couple of days, and my naive initial implementation isn't working correctly. Trying to track the problem took me into some code that we've written to interface with a legacy system we rely on. The code in question was written by a junior dev and while it is funcitonal it has a number of raw loops that perfrom simple actions. We're talking about things like constructing a container of objects used by the legacy code from a container of objects used by our core code and vice versa.

These things can also be done using the algorithms library and a lambda.

So compare two ways of doing the same thing.1 Both assume the existance of a std::vector<coreGadget> named input. First, using a raw loop:

std::vector<LegacyWidget> output;
for (size_t i=0; i<input.size(); ++i)
{
	const CoreGadget & gadget = input[i];
    const LegacyWidget widget(gadget.data(), gadget.info());
    output.push_back(widget);
}

Second using std::transform:

std::vector<LegacyWidget> output;
std::transform(input.begin(), input.end(), std::back_inserter(output),
	[](const CoreGadget & gadget)
    {
    	const LegacyWidget widget(gadget.data(), gadget.info());
    	return widget;
    } );

Now, highly respected speaker and bloggers have been encouraging the latter over the former for some time, but I've wondered if this was really a home run or just a convincing gound-ball single. Basically because the std::transform version requires you to understand a lot. Iterators may be more general than indexing, but they are a thing you have to know. Similarly lambdas may be a cleaner way of doing function pointers, but they have their own syntax and in may cases you have to understand captures.

Personally I enjoy writing code that uses the algorithm header and once I started doing that I quickly became comfortable and adept at reading it. But you code for your teammates (and future teammates) as much as for yourself and my junior devs seem a little hesitant at times. So it's nice to have a quality of life argument in favor of the modern way of doing it, and that's what I discovered recently.

Imagine stepping your debugger through a function that performs this transformation in search of a bug. Imagine it's the third (or fourth or fifth or whatever) time through and you already know the transformation works as intended. How do you skip it?

Oc course you could just set another break point beyond the transformation and continue, but with the std::treansform version you could also use your debugger's "step over" feature. I'm not familiar with a dbugger tha has "step over loop".


1 There is also the intermediate option of using a iterator-based loop and the extra flourish of using an immediately invoked initializing lambda but I don't think they change the trade-offs here.

No comments:

Post a Comment