The Subtle Pitfall of Extraneous Mapping

Stephen Young
Zumba Tech
Published in
4 min readAug 28, 2015

--

First, some disambiguation is in order. Whether you call it a “map,” or a “dictionary,” or an “associative array,” or an “object literal” I am referring to the same concept: keys that map to values. “Mapping” used in this context is the process of structuring data as a collection of key/value pairs.

Mapping is a Powerful Tool

Here’s a fabricated example, written in PHP:

$firstName = 'Thomas';
$lastName = 'Anderson';
$company = [ 'name' => 'Meta Cortex' ];
$birthdate = '1962-03-11';
$profile = [
'fullName' => $firstName . ' ' . $lastName,
'workplace' => $company['name'],
'age' => (new DateTime($birthdate))->diff(new DateTime())->y
];

“$profile” is a map of data derived from the local variables. There is nothing inherently wrong with the above code. Imagine “$profile” is a collection of data that will be used in HTML to present the data to a user. This is very useful. It’s easy to reason about, especially given the proximity of the variable definitions and the mapping.

However, a small amount of software entropy has been introduced. As entropy increases, one’s ability to reason about code decreases.

Model View Complicated

I see this pattern used heavily in web based MVC applications.

In a typical HTTP request, a controller will call one or more model methods to retrieve some data — possibly passing request parameters that have been mapped into a format that the model will consume. Then the controller maps the returned model data into a format that the view can consume.

Building on the previous example, it would look something like the following.

The View

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>User Profile</title>
</head>
<body>
<h1><?= $fullName ?></h1>
<p>Workplace: <?= $workplace ?></p>
<p>Age: <?= $age ?></p>
</body>
</html>

The Controller

class UserController {
pubic function user_profile($username) {
$data = $this->UserModel->getByUsername($username);
$data['company'] = $this->UserModel
->getCompanyByUserId($data['id']);

$this->setViewData([
'fullName' => $data['firstName'] . ' ' . $data['lastName'],
'workplace' => $data['company']['name'],
'age' => (new DateTime($data['birthdate']))
->diff(new DateTime())->y
]);
}
}

This isn’t terrible but it’s starting to get ugly. Let’s break it down.

  1. A request is made to the url `/user_profile/mr-anderson`
  2. The controller retrieves a map of user data by passing the username to the UserModel.
  3. The user data doesn’t contain the company name where Anderson works, but we need that info for the view. So, the controller adds a new key “company” to the map by passing the “id” key from the map to the model.
  4. Finally, the controller converts this data map into something that the view can consume, so we re-map it to the keys “fullName,” “workplace,” and “age”.

Entropy builds until the system is chaotic. Each little re-map of the data is one more thing that developers have to keep in their heads when modifying, refactoring, or adding new code. The mental model is starting to get full.

It’s not easy to get a program into your head. If you leave a project for a few months, it can take days to really understand it again when you return to it. Even when you’re actively working on a program it can take half an hour to load into your head when you start work each day. And that’s in the best case.

— Paul Graham

Mapping As Glue

The most egregious mapping that I’ve seen occurs when layers of an application are not built in tandem. Small differences in naming accrue because one developer built the view and another developer built the model. These layers are glued together by mapping one key to a different, yet similar key. For example:

The View Variables

<?= $profileUrl ?>, <?= $profileImg ?>

The Model Data

$user = [ 'profileurl' => 'http://...', 'userImg' : 'blah.jpg' ];

It’s easy to see the difference between “profileImg” and “userImg,” but also notice the case mismatch in the profile url key. So, when passing the data from the model to the view, it gets mapped:

The Glue

$viewData = [
'profileUrl' => $user['profileurl'],
'profileImg' => $user['userImg']
];

This kind of data manipulation is a trap. Bugs will abound, new features will take longer to implement, and chaos will ensue.

If You Own It, Refactor It

Some mapping is unavoidable. If you are using a database abstraction, odds are good that it’s an ORM; mapping a relational database to objects is a dirty — but useful — necessity. If you are consuming a third party API, the data may be in a completely disparate format that you need to map into something useful.

However, as data flows through the layers of an application that you control, mapping should be avoided and only used as a last resort. It is much more effective to refactor minor discrepancies than it is to create a map that must now be maintained.

Here’s a refactored version of the “view” and “controller” example from above.

The View

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>User Profile</title>
</head>
<body>
<h1><?= $user['firstName'] ?> <?= $user['lastName'] ?></h1>
<p>Workplace: <?= $company['name'] ?></p>
<p>Age: <?= getAgeFromBirthdate($user['birthdate']) ?></p>
</body>
</html>

The Controller

class UserController {
pubic function user_profile($username) {
$user = $this->UserModel->getByUsername($username);
$company = $this->UserModel->getCompanyByUserId($user['id']);
$this->setViewData(compact('user', 'company'));
}
}

The first clue that this is more maintainable is the amount of code reduction in the controller. The view is slightly more verbose but markup is inherently verbose to begin with. I did a little handwaving with “getAgeFromBirthdate” in the view, but this kind of data formatting seems appropriate for a view helper function.

Most importantly, the variables in the view now reflect the variables that were created in the controller, and their keys are the same strings that were returned from the model. That’s several fewer things someone must keep track of when working with this code.

Fewer things to keep in your head is a good thing.

--

--

Stephen Young
Zumba Tech

Software Engineer, Gamer, Molecular Gastronomist