How I stopped worrying about the straitjacket and learned to love organization.
Spaghetti code is great! For many of us writing creative coding in Processing, starting with a simple idea and progressively adding layer and layer of complexity and functionality is a familiar workflow. It’s also a source of unexpected emergent features, what others would call bugs or mistakes *eyeroll*. The tangled messes we code…
Many of us balk at the thought of “formal” programming in explorative creative coding. The very idea feels contrary to what we want to achieve. We just want to be free! Computer Science is a straitjacket for the inner artist, or even worse, a suit.
Or at least, that’s how I – albeit slightly exaggerated – felt years ago. I still indulge in the occasional bowl of noodly code. But to be honest the food analogy for most of my current coding would be bento boxes. Organized, maybe even neat. And I think it is making my work better.
One of the reasons why I took me a few years to realize this, is that the attitude of formal computer science, or rather that of its most vocal proponents, isn’t exactly welcoming to those coming from different backgrounds, nor is it particular conductive to our brand of frivolity.
I remember my earlier days in coding where every sharing of any type of creative code ended up in a discussion about arrays vs. arraylists, intricacies of object-oriented programming, or on why it would be a lot better if I used a different language, probably LISP. Discussions that were held with a fervor that is typical of the post-101 student, before professional life tempers the idealism of best-practices-in-theory.
Probably that is still around and I just outgrew those parts of the community where these discussions live, but I’m quite happy to have escaped. Joking aside, there are tons of resources out there that are truly helpful and will teach everything we want to know about object-oriented programming, patterns, software architecture,… So much in fact that we run the risk of losing track of the creative coding.
There isn’t that much material out there that handles organization of creative code. It is of course implicitly present in many tutorials. But the very nature of the tutorial means it is mostly presented as a straight and narrow path, and the organization comes naturally. What has rarely been considered is the exploration, the wandering that refined into that single path. And my experience is that where we wander, spaghetti code manifests.
Taking a walk
Let’s start with a simple idea: we’d like to write some code that has particles moving along a path. At this point, we don’t know yet what kind of path, maybe a line, a circle, a more complex composite curve.
One approach would be to make the path an inherent part of the behavior of each particle, bake it in so to speak. We can explore code variations by changing that code, creating different types of walker. This is highly spaghetti-friendly.
What we want to pursue from the very beginning is modularity.
Less pasta, more bento
What we need are two entities, a walker and a path. For now, we’d like to keep our options open and a path is anything that can give a position to our walker, a line, a curve, a circle,… How can we define a position on a path?
Let’s take a parametric approach here. We’ll use a single parameter, a single value, and associate that with each point along a curve. A convenient range is [0,1], from beginning to end. If the path is a line segment 0 would be the start point and 1 would be the endpoint. If the path is a circle 0 could correspond to 0° and 1 to 360°, turning the path into a loop.
Our walker looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Walker { PVector position; Path path; float currentF; float stepF; Walker(Path path, float step) { this.path=path; stepF=step; currentF=0.0; position=path.getPointOnPath(currentF); } void update() { currentF+=stepF; if (currentF<0.0) { currentF+=1.0; } else if (currentF>1.0) { currentF-=1.0; } position.set(path.getPointOnPath(currentF)); } void draw() { ellipse(position.x, position.y, 4, 4); } } |
We give it a few properties:
- its current
position
, - the
path
it follows, - the current value
currentF
along the curve and - the value
stepF
by which we update its parameter value at each step.
The walker has two functionalities: we can update
it and we can draw
it. The update
method increments currentF
, asks the path
what position this corresponds to and sets the walker position
.
But wait, at this point Processing will complain it has no idea what a Path
is. Unfortunately, at this point neither do we really. All we know is what we expect from it: pass a parameter, get a point. We might be tempted to make an explicit Path
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Path{ PVector start; PVector end; Path(PVector start, PVector end) { this.start=start; this.end=end; } PVector getPointOnPath(float f) { return PVector.lerp(end, start, f); } } |
If we do this, we fully define the functionality of the path, including the getPointOnPath()
our Walker
relies on. In this case, we opted for a line segment. And this Path
we can pass to the walkers.
What if we want a different kind of path, maybe a circle? We can write a new class CirclePath
. With this apporach our Walker
isn’t equipped to deal with it, it only knows Path
. Do we need to adapt our walkers, create a new kind of Walker
for every type of path we want to explore? We could make the Path
more intricate to allow for different motions. But the walker still needs additional information so it can trigger the right behavior. Workable, but not modular at all.
This is where a bit of Java object-oriented programming knowledge comes in useful. Undoubtedly, this is a subtle and intricate subject worth a lifetime of study. So we respectfully rip out its core and use it with just enough bluffing to get away with it.
Interface
Conceptually, we left open what a path is. The only feature we fixed was a single functionality: “Here’s a parameter, give us a point.” However, when we implemented the path we left this openness behind and opted for a rigid implementation, a fully implemented class. That’s where our code no longer fits our idea. Spaghetti beckons.
Turns out that Java has just the thing for an open concept like ours: an interface
. It is a kind of placeholder, a promise we make, our way of telling our walkers: we’re going to give you a Path
, you don’t need the details, but we promise that you can use getPointOnPath
with it.
Interfaces are defined very similarly to classes except that
- we don’t define a constructor, an interface is an abstract promise after all, not an actual thing;
- similarly, we won’t define any object-specific properties, like
start
orend
; - we have to add the required methods, but only as promises.
In its full glory, it looks like this:
1 2 3 |
interface Path { PVector getPointOnPath(float f); } |
That’s it, nothing more is needed. Wherever we use Path
to declare a variable, argument, or property, the program knows it can use getPointOnPath
.
We’re one step away from a modular piece of code: we still have to create a type of Path
. Unlike a class, we can’t use new Path()
. An interface doesn’t have a constructor, the promise cannot be materialized by itself. To get the code to work we need an implementation, a class that keeps the promise the interface makes. A line segment seems a good start.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class LineSegment implements Path { PVector start; PVector end; LineSegment(PVector start, PVector end) { this.start=start; this.end=end; } PVector getPointOnPath(float f) { return PVector.lerp(end, start, f); } } |
The “implements Path
” is where we tell the code that this class LineSegment
can be expected to hold the promise of a Path
. Everywhere a Path
is expected we can pass on a LineSegment
, and its getPointOnPath
will be invoked. If we need a path anywhere, we can use a LineSegment
.
Path path = new LineSegment()
.
Wait…
“That’s the same image isn’t it?”
Yes, we changed the implementation, but haven’t changed what the code does. Was it worth it? Not if this image was our end goal. Fortunately, we just started exploring. The power of the interface becomes clear when we add another class Circle
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Circle implements Path { PVector origin; float radius; Circle(PVector origin, float radius) { this.origin=origin; this.radius=radius; } PVector getPointOnPath(float f) { return new PVector(origin.x+radius*cos(radians(360*f)), origin.y+radius*sin(radians(360*f))); } } |
This class is completely separate from LineSegment
, it doesn’t need to have anything in common with it, except its pledge to keep the Path
promise. Without changing anything to our walker, we can now plug in a circle, new Walker(new Circle(origin,radius), step)
, and have completely different behavior.
There is one thing more we need to get away with it: conceptually the walker only knows that it’s getting a path. As such its knowledge is restricted to what is defined inside the interface. It doesn’t care if its path is a Circle
or a LineSegment
, or whatever implementation we come up with. It can’t use the circle radius
or the line segment start
, those aren’t part of the interface. (There are ways around this, but just enough for now, right…)
The end is a beginning
There we have it, a little modular seed to grow. There’s plenty more to discover about interfaces that make them work for us. Classes can implement multiple interfaces for example. Or interfaces can be used to pass functions as arguments instead of mere parameters, another very powerful technique to keep generative code modular. I use it in my isogrid2020 library for example, to allow the end user to customize coloring.
Have fun and break stuff!