What's wrong with many arguments

Whether the code that passes around a lot of arguments was written this way from the start or has grown organically, it is fragile and vulnerable to programmer mistakes. The main problem is that there are, usually, many arguments of the same type, and they can be miscounted. Consider designing a civilization-building game—when a player creates a new city, a corresponding object is constructed. The player gets to choose what facilities to build in the city, and the game sets the options for available resources:

class City {
public:
enum center_t { KEEP, PALACE, CITADEL };
City(size_t number_of_buildings,
size_t number_of_towers,
size_t guard_strength,
center_t center,
bool with_forge,
bool with_granary,
bool has_fresh_water,
bool is_coastal,
bool has_forest);
...
};

It looks like we have taken care of everything. To start the game, let's give each player a city with a keep, a guard tower, two buildings, and a guard company:

City Capital(2, 1, 1, City::KEEP, false, false, false, false);

Can you see the mistake? The compiler, fortunately, can—not enough arguments. Since the compiler won't let us make a mistake here, this is no big deal, we just need to add the argument for has_forest.  Also, let's say the game placed the city near a river, so it has water now:

City Capital(2, 1, 1, City::KEEP, false, true, false, false, false);

That was easy ... oops. We now have the city on the river but without fresh water (just what is in that river?). At least the townsfolk won't starve, thanks to the free granary they accidentally received. That error—where the true value was passed to the wrong
parameter—will have to be found during debugging. 
Also, this code is quite verbose, and we may find ourselves typing the same values over and over. Maybe the game tries to place cities near the rivers and forests by default? OK then:

class City {
public:
enum center_t { KEEP, PALACE, CITADEL };
City(size_t number_of_buildings,
size_t number_of_towers,
size_t guard_strength,
center_t center,
bool with_forge,
bool with_granary,
bool has_fresh_water = true,
bool is_coastal = false,
bool has_forest = true);
...
};

Now, let's go back to our first attempt to create a city—it now compiles, one argument short, and we are none the wiser that we miscounted the arguments. The game is a great success, and, in the next update, we get an exciting new building—a temple! We need to add a new argument to the constructor, of course. It makes sense to add it after with_granary,  with all the other buildings and before the terrain features. But then we have to edit every call to the City constructor. What is worse, it is very easy to make a mistake since the false for no temple looks, to both the programmer and the compiler, exactly like the false for no fresh water. The new argument has to be inserted in the right place, in a long line of very similarly-looking values. 

Of course, the existing game code works without temples, so they are only needed in the new updated code. There is some value in not disturbing existing code unless necessary. We could do that if we added the new argument at the end and gave it the default value, so any constructor call that was not changed still creates the exact same city as before:

class City {
public:
enum center_t { KEEP, PALACE, CITADEL };
City(size_t number_of_buildings,
size_t number_of_towers,
size_t guard_strength,
center_t center,
bool with_forge,
bool with_granary,
bool has_fresh_water = true,
bool is_coastal = false,
bool has_forest = true,
bool with_temple = false);
...
};

But now, we let short-term convenience dictate our long-term interface design. The parameters no longer have at least a logical grouping, and in the long run, mistakes are even more likely. Also, we did not fully solve the problem of not updating the code that
does not need to change—the next release adds a new terrain, desert, and with it, another argument:

class City {
public:
enum center_t { KEEP, PALACE, CITADEL };
City(size_t number_of_buildings,
size_t number_of_towers,
size_t guard_strength,
center_t center,
bool with_forge,
bool with_granary,
bool has_fresh_water = true,
bool is_coastal = false,
bool has_forest = true,
bool with_temple = false,
bool is_desert = false);
...
};

Once started, we have to give default values to all new arguments added at the end. Also, in order to create a city in the desert, we also have to specify whether it has the temple. There is no logical reason why it has to be this way, but we are bound by the process in which the interface evolved. The situation gets even worse when you consider that many types we used are convertible to each other:

City Capital(2, 1, false, City::KEEP, false, true, false, false, false);

This creates a city with zero guard companies and not whatever the programmer expected to disable when he set the third argument to false.  Even enum types do not offer full protection. You probably noticed that all new cities usually start as a keep, so it would make sense to have that as default as well:

class City {
public:
enum center_t { KEEP, PALACE, CITADEL };
City(size_t number_of_buildings,
size_t number_of_towers,
size_t guard_strength,
center_t center = KEEP,
bool with_forge = false,
bool with_granary = false,
bool has_fresh_water = true,
bool is_coastal = false,
bool has_forest = true,
bool with_temple = false,
bool is_desert = false);
...
};

Now, we don't have to type as many arguments and might even avoid some mistakes (if you don't write arguments, you can't write them in the wrong order). But, we can make new ones:

City Capital(2, 1, City::CITADEL);

The two guard companies we just hired (because the numerical value of CITADEL is 2) will find themselves quite short on space in the lowly keep (which we intended to change but did not). The enum class of C++11 offers better protection since each one is a different type without conversions to integers, but the overall problem remains. As we have seen, there are two problems with passing a lot of values to C++ functions as separate arguments. First, it creates very long declarations and function calls that are error-prone. Second, if we need to add a value or change the type of a parameter, there is a lot of code to be edited. The solution to both problems existed even before C++ was created; it comes from C—use aggregates—that is, structs—to combine many values into one parameter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.118.211