Or put another way: What, Why, and When?
When programmers first learn their craft they have the realization that they don't need to re-type logic over and over again with only slight changes, instead they can create functions that take arguments, and those arguments change the result of the function:
int x = 2 * 2;
int y = 3 * 3 * 3;
int z = 5 * 5 * 5 * 5;
becomes:
int pow(int x, int y)
{
ASSERT(y >= 0);
if (0 == y)
{
return 0;
}
int retval = x;
for (int i = 1; i < y; i++)
{
retval *= x;
}
return retval;
}
...
int x = pow(2, 2);
int y = pow(3, 3);
int z = pow(5, 4);
It takes a while to develop this skill, and I believe it is part of the formative stages of a programmer's career. The next level is to really extract out the data:
struct powers
{
int number;
int power;
} data[] = {
{ 2, 2 },
{ 3, 3 },
{ 5, 4 }
};
...
int array_size = sizeof(data) / sizeof(powers);
int results[sizeof(data) / sizeof(powers)];
for (int i = 0; i < array_size; i++)
{
results[i] = pow(data[i].number, data[i].power);
}
We've now got a very nice separation between the algorithm (the What) which in this case is the overly simple pow() function and the data (the Why). It would be very simple to read that data in from a file or from the user and pass it to the algorithm.
Which gets us to the third question, the flow (or When). This is the next level of separation and in my experience is also the next less common. Most programmers know how to separate out the functions, most good ones know how to more cleanly separate their data from their algorithms.
The next level is a clean separation of the data from the control flow of the code. In other words, the code that determines when things will be done should be separated from the code that determines what data will be operated on should be separated from the code that operates on the data.