Yesterday, in an astounding flurry, I made a magic word open a lock. I also made a bit of a mess, which is partly good but probably mostly bad. Today, I set out to improve the code—and the feature. (As usual, that’s not what happens.)

Before we begin, I want to mention Brian Marick’s interesting podcast “about how software people have applied ideas from outside software”. I started on Episode 4, apparently named Part 3, for reasons, and I will be going back for more. Brian always brings outside ideas and references to his talks, and the podcast is no exception. Very interesting, out of my normal range, yet very accessible. Recommended.

As usually happens, yesterday impacts today, and Brian’s podcast leads me to mention here what I think you, and other programmers, and everyone in the world really, should do, about things like TDD. Here’s what I think you should do:

You should do whatever you think is best1.

I’m not here to tell you what to do. I’m here to show you what I do, try to examine why I think I do it, and to show you what happens. I do think that things like incremental development, TDD, refactoring, and all that jazz, are valuable. I think most developers would benefit from having those things in their Skill Library, and from having enough experience to know when to deploy those skills, and when to go with others.

Take TDD2. Strict proponents and moral authoritarians would have it that not using TDD is both insane, and somehow at the same time, a mortal sin, for which your immortal soul will be condemned for eternity to a place without pizza. If you don’t use TDD, they’d say, you can’t call yourself a Real Programmer or Craftsperson or Programming Expert or whatever title.

I’m not here with that story. I find TDD to be really useful, because it helps me produce working software more rapidly, and with less stress, than when I don’t use it. It gives me moment-by-moment assurance that what I’m doing works, and—on days like yesterday—it tells me when something I’ve done breaks something I did in the past. In a quick phrase, TDD keeps me from needing to do so much debugging.

I find it useful—and I don’t use it all the time. Sometimes I just go off my feed and 3 out a bunch of code that I have in my head, and bang it until it works. Some things I just can’t figure out how to use TDD on. Graphics, for example. I don’t know how to write a test for “looks good”.

I don’t use TDD all the time. When I don’t, I often wish that I had, but not always. I use judgment in deciding when to use it, and my judgment is pretty good, though not perfect. I rarely regret choosing to use TDD, and frequently regret not choosing it. So I know that, for my own comfort, my leaning is further away from TDD than I wish it were. So be it, I’m just some guy.

So, what should you do? You should do whatever you think is best. I think everyone would benefit from building up enough experience to choose more wisely: so could I. We always benefit from learning new things and having new experiences, assuming some details about the parachute working and such.

But you, you should do whatever you think is best4.

Today

Today, what I think is best is to consider the work done yesterday and see what needs improvement. And I have a couple of ideas.

Yesterday, I changed things so that a Room is passed the World when it executes, so that it can accomplish things outside itself, or remember details over long periods of game time … [trails off …]

Wait!

Wait! Yesterday, I passed the World in so that a palace room could remember that its padlock was unlocked. I did that with a Map in World, from String to Boolean. As I wrote the paragraph above, it occurred to me that the code that does that is in the Room instance. Not a general RoomProcessor, the actual room instance whose name is “palace”. There was no need, in this case, to store a “global” bit of information up in World. I could have kept this bit of into in the Room.

Wait again!

Wait again! I was thinking, in the paragraph above, that maybe the whole rigmarole of passing the World down was unnecessary. Fortunately for my ego, it seems that it is necessary. Suppose there was a secret passage that dropped you from the ceiling into the treasure room. If you had not already cast the spell to unlock the door, you couldn’t escape into the palace, and therefore couldn’t cart the treasure out. So—whew—we do need the ability to store world-level info.

I’m glad I had that series of thoughts: it’ll help me to keep things stored in Rooms when I can, and only in the World when it’s necessary. Thinking: almost always good.

So, as I was saying before I rudely interrupted myself, I implemented a simple Map from String to Boolean for yesterday’s trick. I have a tentative plan for today, to provide the ability to store Boolean flags, and integers, and to distinguish them by providing methods on World, methods like these:

  • setTrue(name)
  • setFalse(name)
  • isTrue(name)
  • isFalse(name)
  • increment(name)
  • getCount(name)

Now I’m certainly not going to implement all those today: I don’t need them and I tend to follow the YAGNI5 notion of not building things until I need them.

We do need the boolean ones, so lets find our code and use it to drive out those features. Well, no. Use it to see what we need and then we’ll use TDD to drive out the features.

Let’s get to it.

Our Magic Word

Our magic word, well, phrase, was “cast wd40”. Here’s some of the key code around making the door work.

    @Test
    fun `magic unlocks door`() {
        val world = world {
            room("palace") {
                desc("You are in an empty room.",
                    "You are in an empty room in the palace."
                            + "There is a padlocked door to the east")
                go("e","treasure") {
                    if (it.status.getOrDefault("unlocked", false))
                        true
                    else {
                        it.say("The room is locked by a glowing lock")
                        false
                    }
                }
            }
            room("treasure") {
                desc("You're in the treasure room",
                    "You are in a treasure room, rich with gold and jewels")
                go("w", "palace")
            }
        }
        val game = Game(world,"palace")
        game.command("e")
        assertThat(game.resultString).isEqualTo("The room is locked by a glowing lock\n" +
                "You are in an empty room in the palace.There is a padlocked door to the east")
        game.command("cast wd40")
        assertThat(game.resultString).contains("unlocked")
        game.command("e")
        assertThat(game.resultString).contains("rich with gold")
    }

That’s our test. In the game definition, we have this:

  go("e","treasure") {
      if (it.status.getOrDefault("unlocked", false))
          true
      else {
          it.say("The room is locked by a glowing lock")
          false
      }
  }

Are those references to it required? They are not. Remove them. Green. Commit: Remove unneeded it.

The “it” in the code above is the World, and we are ripping out its status object and fetching from it.

I’d like for that code to read differently. What would be ideal? What if we could say this:

  go("e","treasure") {
      if (status("unlocked").isTrue())
          true
      else {
          say("The room is locked by a glowing lock")
          false
      }
  }

That would be cool. Maybe we could do more of that …

  status("unlocked") = false
  status("dwarves") = status("dwarves") - 1

I think that’s probably possible in Kotlin. I also think it’s too big a step for right now. I’m tempted to work on it, and I probably will later today or tomorrow, but it seems a bit too ambitious.

Maybe not. We’re on a fresh commit. Let’s do a Spike. This one, I might well throw away, though, as always, I reserve the right to keep it.

I’m going to try to define a new object, a Status, that can respond to things like the above. I don’t know if it’s even possible.

Blue Sky

A Status will be a mutable object, because it is behaving like a Kotlin var. We set them to integers or booleans, over and over. The World will include a dictionary from name to Status instance, and the instance will respond to messages like .isTrue or maybe even == true, though that seems dangerous. We’ll see. This is a Spike.

Rather quickly, I get this:

class StatusTest {
    @Test
    fun `status isTrue`() {
        val status = BooleanStatus()
        assertThat(status.isTrue()).isEqualTo(false)
        status.set(true)
        assertThat(status.isTrue()).isEqualTo(true)

    }
}

class BooleanStatus(var value: Boolean = false) {

    fun set(truth: Boolean) {
        value = truth
    }

    fun isTrue(): Boolean {
        return value
    }

    fun isFalse(): Boolean {
        return !value
    }
}

This runs6. I can imagine a similar class for Ints. Let’s work on the Status object itself, whose job will be keeping track of all the statuses, by name.

This goes well also:

    @Test
    fun `Status map`() {
        val status = Status()
        assertThat(status.item("unlocked").isFalse()).isEqualTo(true)
    }

class Status {
    val map = mutableMapOf<String, BooleanStatus>()

    fun item(name:String): BooleanStatus {
        return map.getOrPut(name) { BooleanStatus() }
    }
}

I need to reflect

Reflection

It’s pretty easy to imagine various classes of Status, IntStatus, BooleanStatus, and so on. They'd each have a few methods. I'm kind of giving up on allowing them to be used directly in arithmetic, but I might allow extraction of the value for the Int kind. We'd probably need that.

I was hoping that the Status object could somehow make the right decision about the type of Status we have, with booleans defaulting to false and ints to zero. In the current hacked implementation, I finessed that by defining a general Room function "cast wd40".

    private fun castSpell(spell: String, world: World): String {
        world.status.put("unlocked", true)
        world.response.say("The magic wd40 works! The padlock is unlocked!")
        return roomName
    }

I have too many ideas in my head right now. Should we have a StatusMap (should rename it that, probably) in both World and each Room? Only in World, with a naming convention like “palace.unlocked”? Could we implicitly prepend that room name in room code? (Probably.)

I’m unsure how far to push this idea, and how fast. Could we somehow defer the decision on what kind of Status you're creating and generate the right thing at the last minute, so that as long as your world definition uses the thing consistently, it'll work? Could we arrange that nothing terrible happens even if you accidentally use an IntStats as a BooleanStatus?

I’ve been writing, thinking, and coding for nearly two hours. I’ve learned some stuff about how I might do status. I need to decide what to do next.

First thing, I’ll rename Status to StatusMap. (I should mention that the classes are in the test file for now, by the way.)

I recast my test like this:

    @Test
    fun `Status map`() {
        StatusMap().apply {
            assertThat(item("unlocked").isFalse()).isEqualTo(true)
        }
    }

The apply runs the code inside the {}, with this as the StatusMap, so that item refers to the map. I can extend the test and the classes:

    @Test
    fun `Status map`() {
        StatusMap().apply {
            assertThat(item("unlocked").isFalse()).isEqualTo(true)
            item("unlocked").not()
            assertThat(item("unlocked").isTrue()).isEqualTo(true)
        }
    }

That requires the addition of not, of course:

    fun not(): Unit {
        value = !value
    }

Still blue skying, we may want an init paragraph in World and Room, that allows us to define initial variables. (We’ll need to keep in mind that, with this particular DSL, the Rooms are created as soon as they are declared, not later with some final scan of a RoomMaker tree.)

A Final Tentative Plan

I think I’ll keep this Spike, which is presently isolated to a single file, StatusTest. Next time, I’ll see about installing it to support my current single test, the wd40 trick. By way of that, let’s refresh our memory of how it works currently. There’s almost nothing to review:

class World {
    val status = mutableMapOf<String,Boolean>()

Our code for the unlocking just untimely rips status from world:

    private fun castSpell(spell: String, world: World): String {
        world.status.put("unlocked", true)
        world.response.say("The magic wd40 works! The padlock is unlocked!")
        return roomName
    }

  go("e","treasure") {
      if (status.getOrDefault("unlocked", false))
          true
      else {
          say("The room is locked by a glowing lock")
          false
      }
  }

So, our plan for installing the StatusMap will be to make the status variable contain said map, and implementing the necessary methods on World to reference status this and that. We might want to name the variable flag to signify its containing BooleanStatus items:

  go("e","treasure") {
      if (flag("unlocked").isTrue())
          true
      else {
          say("The room is locked by a glowing lock")
          false
      }
  }

That might make a decent syntax. We’ll see. For now, we’ve learned a bit and primed our minds with ideas. Time to break.

Summary

Oddly but not atypically, this morning has not gone as I anticipated. As soon as I got going, I had an idea that seemed worth exploring, so I explored. I’m glad that I did.

The experiment with a StatusMap has helped me focus my mind on what is possible, what is impractical (like assigning false directly to a BooleanStatus instance), and what the syntax might want to be.

My mind has more ideas in it than I should implement, so I’ll take a break, have a bite, finish my chai, speak to the wife and cat, and see you next time!



  1. If you want to. It’s your decision is what I’m saying. 

  2. Please. 

  3. Is that the correct spelling? Should it be “blerch”? I’m not sure. I think “u” is better. 

  4. Or not. I’m not the boss of you. 

  5. “You Aren’t Going to Need It”, an XP notion that reminds us not to do what we think we’ll need, only what we do need. I’m reminded of it by Brian Marick’s podcast, highly recommended. 

  6. Note the violation of strict TDD in the code there. I do what I feel is best, and you should do the same. Or do what you feel is worst, I’m really not the boss of you.