BobDalgleish has already noted that this (anti-)pattern is called "tramp data".
In my experience, the most common cause of excessive tramp data is having a bunch of linked state variables that should really be encapsulated in an object or a data structure. Sometimes, it may even be necessary to nest a bunch of objects to properly organize the data.
For a simple example, consider a game that has a customizable player character, with properties like playerName
, playerEyeColor
and so on. Of course, the player also has a physical position on the game map, and various other properties like, say, current and maximum health level, and so on.
In a first iteration of such a game, it might be a perfectly reasonable choice to make all these properties into global variables — after all, there's only one player, and almost everything in the game somehow involves the player. So your global state might contain variables like:
playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100
But at some point, you might find that you need to change this design, perhaps because you want to add a multiplayer mode into the game. As a first attempt, you could try making all those variables local, and passing them to functions that need them. However, you may then find that a particular action in your game might involve a function call chain like, say:
mainGameLoop()
-> processInputEvent()
-> doPlayerAction()
-> movePlayer()
-> checkCollision()
-> interactWithNPC()
-> interactWithShopkeeper()
...and the interactWithShopkeeper()
function has the shopkeeper address the player by name, so you now suddenly need to pass playerName
as tramp data through all those functions. And, of course, if the shopkeeper thinks that blue-eyed players are naïve, and will charge higher prices for them, then you'd need to pass playerEyeColor
through the whole chain of functions, and so on.
The proper solution, in this case, is of course to define a player object that encapsulates the name, eye color, position, health and any other properties of the player character. That way, you only need to pass that single object around to all the functions that somehow involve the player.
Also, several of the functions above could be naturally made into methods of that player object, which would automatically give them access to the player's properties. In a way, this is just syntactic sugar, since calling a method on a object effectively passes the object instance as a hidden parameter to the method anyway, but it does make the code look clearer and more natural if used properly.
Of course, a typical game would have a lot more "global" state than just the player; for example, you'd almost certainly have some kind of a map on which the game takes place, and a list of non-player characters moving on the map, and maybe items placed on it, and so on. You could pass all those around as tramp objects too, but that would again clutter up your method arguments.
Instead, the solution is to have the objects store references to any other objects that they have permanent or temporary relationships with. So, for example, the player object (and probably any NPC objects too) probably should store a reference to the "game world" object, which would have a reference to the current level / map, so that a method like player.moveTo(x, y)
does not need to be explicitly given the map as a parameter.
Similarly, if our player character had, say, a pet dog that followed them around, we would naturally group all the state variables describing the dog into a single object, and give the player object a reference to the dog (so that the player can, say, call the dog by name) and vice versa (so that the dog knows where the player is). And, of course, we'd probably want to make the player and the dog objects both subclasses of a more generic "actor" object, so that we can reuse the same code for, say, moving both around the map.
Ps. Even though I've used a game as an example, there are other kinds of programs where such issues come up as well. In my experience, though, the underlying problem tends to always be the same: you have a bunch of separate variables (whether local or global) that really want to be bunched together into one or more interlinked objects. Whether the "tramp data" intruding into your functions consists of "global" option settings or cached database queries or state vectors in a numerical simulation, the solution is invariably to identify the natural context that the data belongs to, and make that into an object (or whatever is the closest equivalent in your chosen language).