Blog Posts tagged with: naming

Jul 31 2013

Comments

On Naming

Why is naming so difficult? We write functions and arguments, or classes and methods, all day long. Every one of them has a name. With all that practice, you would expect us to be good at it.

One reason is that names are always metaphors. When we say "open a socket", no actual opening happens. Our servers don't grow new orifices like a Transformer. All it means is that we arrange some memory like this and make that entry in a table (a metaphor of its own!) in the kernel (metaphor) of the operating system (metaphor).

In Metaphors We Live By, George Lakoff shows how entirely embedded we are in metaphors. They go much deeper than our primary school teachers have told us. Deep metaphors are ideas such as "Love is War," or "Happy is Up." Sometimes we form compound metaphors, which gain richness through their interaction.

My examples so far are pretty obviously metaphors. Now consider this relationship between three objects arranged linearly: you, another person, and a mountain. The distance from you to the other person is less than the distance from you to the mountain. You would probably say the person is "in front of" the mountain. That's a metaphor. The mountain does not have a front. The idea that the side nearest you is the "front" of the mountain is actually a cultural construct. Lakoff points out that in Hausa that orientation would be reversed. You would say the person was behind the mountain if it were between you and the big rock.

So we see that names can be problematic if the speaker and listener activate different metaphors when they use the same name.

Naming can also be an act of creation. By attaching a name to a phenomenon, we suddenly see that phenomenon as a "thing". Anyone who lived through the U.S. Presidential election in 2000 learned all about "pregnant chads", "hanging chads", and other mangled chads. (I wonder what happened to the frequency of baby boys being named Chad. Did it go up or down after that?) Prior to those tortured neologisms, nobody gave any thought to what you called that little rectangle of paper that got popped out of a hole in a punchcard. Unless, of course, you were in the punch card business. Or punch card machine maintenance, I suppose.

Pretty much every name we use in programming was created by someone, some time. Many are humdrum words to which we have attached our own meanings: window, list, queue, or string. Others are puns or derivatives: trie (from tree), zipper, virus, array.

Object-oriented programming demands tremendous naming ability. Some programmers don't do it very well, and you get classes like "AbstractHandlerManagerFactory" and "HandlerManagerFactorySingletonImpl." Yawn.

Because naming gives us a handle to discuss things, it can be useful to name "non-things" in order to "thingify" them. Even if it's a bit nebulous, you should always name your preferred architectural style, coding conventions, or pattern of interaction. Only in that way can you distinguish it from the older structure. Be careful though, because someone else may come along with an even more nebulous, foggy, but desirable sounding term and outmaneuver you. I contend that "private cloud" would have no traction but for the comforting sound of it.

(The term "in the cloud" seems to have originated with the telecom carriers. Specifically, in their sales groups. Sales reps were often unclear where or how the service was actually being delivered, so they would draw the customer's locations on a diagram, connect them to a fluffy cloud, and show call-center people "in the cloud" handling the customer's calls. That became shorthand for "you shouldn't care where the service is" even though it originally meant "I don't know where it is.")

Once a name is created, it is impossible to destroy. Even when the concept is long past its natural life, it can be difficult to get rid of it. For example, imagine saying "we no longer need a User Acceptance Testing environment because we aren't going to do User Acceptance Testing any more." It sounds like the height of irresponsibility. Removing a flavor of testing is enormously difficult, even if you can prove that it caught zero defects over the last five years.

Fans of Jacques Lacan would describe this as the difference between a thing's death in reality (the death of the thing) and its death in the symbolic order (the death of its name). The "symbolic order" can be language, or ideology, or any other shared symbolic or metaphoric framework for trying to make sense of the universe. ("Agile development" is an example of a symbolic order.) A thing can become useless in reality, but remain quite useful in the symbolic order. For example, let's say your UAT testing never actually uncovers any bugs. (And let's also assume that your software is not perfect, and there are indeed bugs to be found.) Your UAT testing serves no purpose in reality. It's dead. But saying "we're not going to do UAT anymore" sounds irresponsible precisely because "UAT" is a familiar component in the symbolic order of your development process. If you remove "UAT" from that system, then suddenly the validity of the entire symbolic order becomes questionable. That's terrifying. (Or liberating.) Therefore, in order to prevent existential despair or psychosis or revolution, you might cling to this superfluous concept of UAT. This happens anytime you're going through the motions of some empty ritual. You do that because you haven't figured out how to reconfigure the symbolic order to account for reality. You haven't figured out how to rename things.

-Maggie Litton, Relevance coach and literary theory buff

Another tough thing about names is that they are almost always compounds---that is, they bind multiple concepts together. A single noun can give the illusion of conceptual unity. Most of the time, that name can be decomposed into constituent concepts. This is especially true in the world of domain modeling. For example, last week I was in a lengthy discussion at a retail company about the proper owner of "Cart" functionality. Everyone thought they knew what Cart meant, but when we enumerated the different aspects of Cart we found 8 - 12 distinct uses and behaviors. Cart is, of course, a metaphor. There's no secret underground facility where wire-mesh wheeled vehicles wander around letting goods drop into them. Once we started splitting those distinct behaviors apart, we found that Cart actually wasn't a thing at all. Rather, it was a label for a compound of several different things interacting together.

Sometimes we name things to separate them from other things. Retronyms fall into this category: "feature phone" versus "smart phone", "dequeue" versus "queue", "trie" versus "tree." This can be an effective maneuver in a political environment: when you need to steal the wind from another group's sails, name your project such that the semiotics imply superiority. Good words to use here are "open" and "version 2.0." If the enemy project uses names from classical literature, find out who slew their project's eponymous hero. Or, if you want to get in front of Sun, name your project "Eclipse."

Naming is difficult. But it is also a primal, shamanistic power. When you name, you are literally exerting mind control. Use it wisely.

Oct 20 2009

Comments

The Power of Names

Last week I had a great time at the Latin America Rails Summit. (Thanks to the organizers for inviting me!) During breaks in the action here and there, I spent time working on Tarantula, and for a lot of that time I was sitting with Chad Fowler and David Chelimsky. David was working on RSpec, and asked Chad and me for help coming up with a good name for a method. The ensuing conversation struck all of us as a great example of the power of good names.

It's often very difficult to find just the right name for something in a program. It's also hard to understand why names are so important. Names are just symbols, after all, and in some sense they're arbitrary. Certainly the computer doesn't care what name you use for something, as long as you're consistent. And programmers are fairly good at dealing with arbitrary names; think of all the acronyms we use without ever thinking of what the letters stand for, and repurposed words like "bus," "mask," and "string," with technical meanings that bear little resemblance to the original English meanings.

So if we can deal with non-optimal names just fine, and choosing good names is really difficult, it hardly seems worth it. But good programmers understand the value of names. This story illustrates how hard it can be to find just the right name, and also how good names can help you to understand your own program and domain in deeper ways.

Wrapping Assertions

David was working on RSpec's built-in DSL for defining matchers. A matcher in RSpec is the object that actually tests a particular condition to determine whether or not it holds, along with a method that's used to select the desired matcher. There was a time when creating a matcher was a bit cumbersome, but David and other RSpec contributors have made it much easier over time, and part of that is a little DSL. Here's an example, from the RSpec docs:

    Spec::Matchers.define :be_in_zone do |zone|
      match do |player|
        player.in_zone?(zone)
      end
    end

That snippet creates a matcher called be_in_zone that checks whether a supplied player (the actual value, in traditional unit-testing parlance) is in the expected zone. With that matcher defined, a programmer can write an expectation in this form:

    bob.should be_in_zone(3)

In some situations, RSpec needs to work with assertions defined for test/unit. That works just fine; when using RSpec with Rails, for example, you can use Rails' custom assertions like assert_select and it just works. But RSpec users would like to be able to easily wrap an assertion in an RSpec matcher, so that their tests are more consistent.

That's the feature David was working on: support in the matcher DSL for calling a test/unit assertion and mapping that to RSpec semantics. The matcher needs to call the assertion, determine the result, and report that result the way an RSpec matcher is supposed to.

David wrote the first version without thinking about names very much (just to get it working) and came up with the wrapped_assertion method, used like this:

    Spec::Matchers.define :equal do |expected|
      match do |actual|
        wrapped_assertion(AssertionFailedError) do
          assert_equal expected, actual
        end
      end
    end

Both test/unit and RSpec recognize three results of a check: success, failure, and error. Success is when everything happens as expected. An error occurs when the code under test (or perhaps the test itself) raises some unexpected exception. Failure, on the other hand, happens when the code all seems to work (in the sense of not raising any exceptions) but one of the expectations is not met. In test/unit, failure is indicated when an assertion raises AssertionFailedError.

So two of the three situations are indicated when an exception is raised; the distinction lies in which exception. The definition of wrapped_assertion is explicit about that: it takes an argument that is the exception class representing failure.

    def wrapped_assertion(error_to_rescue)
      begin
        yield
        true
      rescue error_to_rescue
        false
      end
    end

As you can see, the matcher is supposed to indicate success by returning true and failure by returning false. Any unexpected exception just bubbles out of the method, indicating an error.

When that was working, David checked it in and asked Chad and me for our thoughts about a better name than wrapped_assertion.

He started by explaining the situation and refreshing our memory about the semantics. We promptly threw out some possibilities: fail_when, fail_on, fail_on_raise ... I don't remember all of the suggestions, but I know those were in the mix. And I think we settled on fail_when_raises, because that made sense even if the parameter was optional and omitted: fail_when_raises will fail when any exception is thrown, whereas fail_when_raises(AssertionFailedError) will fail only when that explicit error is thrown.

That was that, we thought. It was dinner time, so we closed our laptops and forgot about the problem.

Keeping the Domains Straight

The next day, however, we found ourselves around a different table, back to working on our three different projects. David realized he wasn't happy with the method name, for a subtle reason.

This is a method that bridges two domains: the test/unit domain and the RSpec domain. Inside the method, test/unit rules: we call an assert method and interpret the result. Outside the method, however, is the domain of RSpec. The method name needs to be expressed in RSpec terminology that's appropriate for the situation.

The test/unit terminology of success, failure, and error does apply to RSpec, but not at the level of matchers. A match expression is not a complete expectation in RSpec; it only constitutes an expectation when matched with a should or should_not, like this:

    actual.should match(expected)
    # or
    actual.should_not match(expected)

So the matcher either matches or not, and returns true or false to indicate that. The presence of should or should_not indicates whether that match was expected, and translates it to success or failure. (This allows a single matcher to handle both positive and negative expectations, as opposed to test/unit assertions, which tend to come in positive/negative pairs such as assert_equal and assert_not_equal.)

Method names including the words "fail" or "succeed" were letting the test/unit domain leak out of the method body. A better name would be expressed in RSpec terminology. We toyed with false_when_raises until Chad suggested match_unless_raises and we all knew it was right.

Deeper Understanding

At that point we all thought we were done, and I mentioned that it would be good to blog about the whole dialogue; many programmers don't take names very seriously, and it would be good to show the real experience of three good programmers spending more than 5 minutes, in two separate conversations spread across two days, on a name for one simple method.

But the big payoff was yet to come. Once the correct name was in place, it helped David to see something important about his problem domain.

Let's look at that original example again, now with the new name:

    Spec::Matchers.define :equal do |expected|
      match do |actual|
        match_unless_raises(AssertionFailedError) do
          assert_equal expected, actual
        end
      end
    end

There's repetition there---the parallel structure of match and match_unless_raises---that is a clue to something wrong. David noticed it immediately and realized that match_unless_raises should be promoted to the same level of the API as match. Now the example is beautifully simple:

    Spec::Matchers.define :equal do |expected|
      match_unless_raises(AssertionFailedError) do |actual|
        assert_equal expected, actual
      end
    end

That's all it takes to write a matcher that wraps a test/unit assertion.

A lot has been written about the importance of names in programming, including Tim Ottinger's classic Ottinger's Rules for Variable and Class Naming. Good names help us communicate with other programmers, and with our customers. The notion of the ubiquitous language that features so prominently in domain-driven design is based largely on the choice of good, meaningful, shared names.

But good names also reflect the clarity of our thinking, and help us to communicate with ourselves about our own thoughts. And when we get the names right, our code and the problem domain can speak clearly to us, revealing accidental complexity and the underlying simplicity we're searching for.

Good names are worth the trouble.

Popular Tags