Dave1707 wrote a nice demo showing how to get project information in Codea. Let’s think about what it tells us.
Dave’s demo shows, for each project you have in Codea, a list of each tab and the number of lines and characters in the tab. Here’s a picture of the display of our dungeon program, D2.
With 41 tabs, D2 is by far my largest Codea project. Asteroids is 13. For Invaders I have versions ranging from 13 to 21 tabs. Spacewar was 10.
I am not getting any sense that what we have here is too much for Codea. although managing 41 tabs is difficult for me in editing, since your choices for finding the tab you want are between dragging the tab bar and trying to type something into the search window that will identify the tab you want and not too many others. I’m getting fairly good at that.
My tabs are as small as 19 lines (AdjustedSprite), and as large as 399 for Tile and 349 for Monster. Monster includes the monster definition tables, but it’s still pretty large. Tile carries a lot of weight but at a quick look I don’t see much that’s clearly out of place. I’m sure there are things we could break out, such as the definition of the tile sprites and such, but I don’t get a sense that it’s doing too may things.
Perhaps it is, though. Tiles make up the dungeon. They have contents, the items on a given tile, including any monsters or player that happen to be present. They draw themselves and they draw their contents.
They can answer questions that are used during dungeon definition, such as whether they are a hallway tile or in an open room position. They can calculate what tiles are visible from the player’s viewpoint, and they remember which tiles have ever been seen, so that they can be drawn properly on the small map.
After the rooms and hallways have been laid in, Tiles assist in putting down the edges, and giving each edge tile the best look we can find for its position.
And tiles determine when an Entity can enter them, and will initiate a connection between their contents and the player, leading to battle, perhaps to damage, and sometimes even to the player receiving a goodie.
That’s a lot, and some of it might be moved off into other tabs, but by and large, despite the size of the class, I think Tile is reasonably cohesive. At least it doesn’t raise my hackles. Do I even have hackles? I don’t know.
What about tests?
Yeah? What about ‘em? Oh, sorry, I got a bit defensive there. Dave’s display shows me about 700 lines of tests, and there are more that aren’t in their own tabs. Dave’s program can’t identify individual functions, just lines. I’d bet that there are at least 1000 lines of tests, but I wouldn’t bet much on 1500. And there are almost 6500 lines of code, so if I had 1500 that would be a ratio of less than one line of test per four lines of code.
That’s not impressive. Impressive would be 50-50.
I still don’t know how to test some things, especially things that are graphical, and I think that’s a somewhat valid excuse. But I also freely grant that some of my objects get in the way of testing. I’ve resorted to a number of test doubles, and have had decent results, but they are a poor substitute for testing using the real objects.
Am I going to fire myself? No, nor am I going to beat myself up. What I will continue to do is to try to stay conscious that the code is probably under-tested. I’ll try, when I find a defect in running the game, to devise a test to find the defect. (I’ve done that rarely, if at all, in this program.)
Too Many Notes?
Often–almost always–we have fewer classes than would be ideal in terms of cohesion and coupling. We have one or more classes that just do too darn much. In this program, GameRunner is a candidate for that title, and while I don’t know how I’d do it, I suspect Tile, Player, and Monster all could benefit from splitting.
But given where we are now, do we have too many classes, as well as too few? It seems to me that there may be.
There is the DeferredTable class, whose whole purpose is to save up additions to a table while the table is being iterated over, because adding to a table while you’re looping over it can lead to weirdness. That class is used in exactly one line of code, although that line is used over 4000 times: the contents of a tile are a DeferredTable.
We could handle that with a few lines of code inside Tile, and save an entire 55 lines class.
There’s AdjustedSprite, a 18 line class that we use to scale the floor sprites down to 64x64. We could eliminate that class, and simplify some code if we’d just resize our sprites to the right size once and for all. (Of course, that’s something you want to get right. And possibly we should be scaling differently on the iPhone, if we ever target that device.)
Speaking of Sprites, there are those slicing classes, SpriteSheet and Sprites, which are used to slice up a sprite sheet into individual images that can be displayed with the
sprite statement of Codea. They are used in two different ways now.
One way is that some of our treasures are represented by a named slice from the sprite sheet of containers or staffs.
The other way is when we provide a sequence of these sliced-out images to the Animation. That’s a bit off, because we make a table of the names of the slices, and then the Animation fetches them by name. You’d think it could have been passed an array of images and avoid the fetch. Not much harm done, but it’s weird.
However … the “real bug” is that
sprite requires an image, and we are often provided sheets of multiple images, either constituting some motion sequence of a creature, or just a handy series of jewels or containers or debris.
In essence, my sprite slicing is there to accommodate a mismatch between the data I have and the data I need. It’s kind of an adaptor, although probably not an example of the Adaptor pattern.
It turns out that there is another way:
Codea includes a thing called “mesh”. This is exactly analogous to the notion of mesh objects such as are created in Blender or other 3D apps. A mesh is a two- or three-dimensional collection of triangles, defining some shape.
Codea mesh includes two aspects that seem useful to us.
First, you can define a rectangle, which will create the obvious two triangles that make up the rectangle. The rectangle can have essentially any coordinates or size, and can be rotated to any angle.
Second, you can apply a texture (image) to the mesh rectangle. Essentially you can tell your rectangle to refer to any rectangular subset of the image, expressed as fractional coordinates x,y,w,h.
The Mimic animations that I have are all ten frames long. So if I have the frames in a variable frames, I could cycle through the frames (very roughly) by:
local i = 0 local step = size/10 while (true) do mimic:setRectText(i*step,0, 0.1, 1.0) i = (i + 1)%10 end
setRectTex selects a rectangle from a previously assigned texture, starting i tenths of the way in from the left edge, extending one tenth in x, and selecting the full neight of the sheet.
I have actually tried that as an experiment, and it works just fine.
We could use mesh for the individual treasures as well, just telling all the container treasures to use the same texture sheet, but different fixed coordinates within the texture sheet.
If we were to go to mesh, we would, I believe, simplify the Animator / Animation logic substantially, and would eliminate the Sprites and SpriteSheet objects entirely. We might find that we wanted some new object that could contain the mapping between the textures, the coordinates and the names.
An interesting side effect of this change would be that the scaling is done by the mesh, so that we probably wouldn’t need the AdjustedSprite either.
This, lords and ladies, is what we really mean by “technical debt”.
Technical debt, as Ward defined it, assuming that I understood him, reflects a difference between the design we have, and the design we would do now, given all our learning to date.
Knowing what I just learned about mesh, I think I’d build this program around mesh rather than around sprites. If that were done, I think we’d find that the design would be more clear, and the overall code size would be reduced.
In a real product, we’d have an interesting decision to make at this point. We only have experienced speculation that says we’d benefit from going to mesh, and the benefits might not show up in faster delivery, just a smaller, possibly faster program. Pretty weak arguments in the face of feature ideas and deadlines.
If we’re able to make mesh adjustments easily, by which I mean in a few hours, along the way to some other feature, then we can perhaps get to mesh over time. If we can’t see a way to proceed incrementally, we may never be able to get there.
Even worse, what if we convince our leadership to let us take the time to go to mesh, because after that the program will be better and we’ll go faster and other benefits … and then it takes longer than we predicted and the benefits don’t materialize? We’ll have spent valuable trust to no one’s benefit.
This is why I say that I would always choose the incremental approach, and never choose large refactorings or rewrites. Of course, always and never may have exceptions, but I lean very very much toward always and never.
In this program, of course, we can do it if we want to. We’re here, not just to deliver features, but to learn how to do things, how to change things, how to keep our code alive, how to move from one place to another in the space of design.
What will we do? Right now, I’m not sure, but I think we’ll move toward mesh in one way or another, probably very soon.
It’s 1009. We haven’t written a line of code, and we started at 0800, right after feeding the cat. Whee, I do run on, don’t I?
But thinking is important. It’s more important than coding. And realistically, had it been you and me and a whiteboard, all this would have taken twenty or thirty minutes, not two hours.
Let’s call it a morning, and we’ll look at what to do next tomorrow, or maybe later today if I get all ambitious.
Summing Up: Thinking is OK
Reviewing Dave’s display of our program’s size gave us a different place to stand while assessing where we are. We can examine things that seem very large, or things that seem very small. We can notice tabs we had forgotten about and look to see whether they have anything useful in them.
And we can focus on why certain things exist, and note overlaps, or realize that we now know ways of doing things that would make those objects unnecessary.
We probably need to time-box this kind of speculation. I know that I am inclined to do too much of it sometimes, because I enjoy thinking about our work and how things happen. On the other hand, I’ve trained myself to do as Kent Beck advised long ago, and to let the code participate in my design thinking as often and as soon as possible.
If I were working for a living, I’d spend the rest of the morning building some feature, with your help, and maybe in the afternoon we’d play with mesh a bit to get a better feel for it.
Since I’m not working for a living, I’m going to go sit with my wife and read a book.
See you tomorrow! (Or maybe later today, but don’t count on it!)