I mentor at a few different online courses, and a common theme I've noticed across quite a few of them is an odd "gap" in the training.
But both approaches, while great in terms of sheer information, lack something that strikes me as vital. I'm hoping to start addressing some of that.
It seems to me that a significant missing piece is "starting to think more like a developer." We are teaching some valuable coding skills, and regardless of which approach, the technical resources are great.
But there's a difference between simply coding, and developing.
Coding is, to my mind, sitting down at the keyboard and actually writing the code. The stuff that we are teaching. It is not only teachable, but its replicable. We can guide folks through the same series of steps, and they can find a similar way through those steps each time.
Developing is a bit different. Before I sit down in front of my editor to put on my coder hat, I should already be wearing that developer hat. When I am presented a spec, thoughts should already be percolating, bubbling up, considering the best strategy to approach the problem.
Coders think in code. Developers think in patterns. And that, it seems to me, is where I want to spend some time.
As a developer, I need to think about a few different things.
- I need to stay aware of the big-picture;
- I need to be aware of the moving parts;
- I need to be constantly thinking about the data my project manipulates (the state).
Let's consider a concrete example, and one common to most courses: Building a calculator in HTML, CSS and JS.
As soon as a coder sees that, they're likely to be planning out their HTML, or figuring how to fire off button events, or thinking in code.
As soon as a developer sees that, while they might be considering code at some level, they're more likely to be thinking about the parts that make up the whole, and how they interact.
Good development is about planning. I spoke last time about the three original tenets of OO, Encapsulation, Communication and Late Instantiation, and good developers think about those things first:
- how can I encapsulate my parts so they don't interfere with each other?
- how can I enable my parts to communicate with each other, so each can respond appropriately?
- how can I make my parts reusable, and create more as I need them?
If we look at that, there are three main bits to it:
- A display;
- A keypad;
- A container holding the parts together.
As a developer, I think about those parts. I often start with a high-level brain-dump of my ideas, and refine from there:
* Components of a calculator: * Display * Upper display showing entire operation until equals? * Lower display showing current number * Keypad containing different types of keys * Numbers * Operators * Equals (still an operator, but special case?) * Special Keys (C/AC/Backspace of some sort) * Container or Manager * Operations Stack Not necessarily a displayed component, but important.
That's a birds-eye view of the components of the calculator, and really, that's about it. There isn't much to it, when you break it down. But if we were to dive in and try to code it without a starting plan, we'd likely be quickly stuck.
Next, we look at the parts. The components list above is a good representation of the encapsulation we want - the display should be self-contained, the keypad should as well, and the calculator container should encapsulate them both.
The next step is to think about communication. How can the parts talk to each other?
This is planning out an interface, and to my mind is one of the more fun parts of developing. I am creating this thing, I am defining the "language" we use to talk to it, and I can "wish-list" whatever verbs I like here!
The display is easy enough: it might maintain its own internal state or display logic, but that isn't what we should be thinking about just yet. At this point, how do we want to talk to the display, and is there anything it needs to tell us in return? We might want to tell the display to update, for example, by passing in a single character of some sort. Or we might want to tell the display to clear part or all of the display. Let's start small:
* Display interface: * update(string) display the given string * reset() clear the entire display * value maybe a way to get back the currently displayed value?
I think that'll do for a start. We aren't bound to that, if later we want to modify that interface, we can, but at the planning stage, that might work well.
Let's move on to the keypad.
Thinking about the communication from the keypad, things are pretty simple: we want to know if a key has been pressed, maybe the type of the key, and maybe the value of it. We could easily do this with HTML, CSS and JS, and I know more than a few of you are thinking about event delegation, having the keypad container listening for clicks on the child buttons and...
And you're right. We can do that. It's easy to get excited and to think ahead, and it's totally okay. It means we might be working on a good pattern, because it's easier to see the components and how to make them go!
But still, let's slow down. We don't know what keys we might have in the keypad, and again, we might like to encapsulate the behaviors here. So we think again: How do we want to talk to this keypad? Do we have anything we need to tell it? And how does the keypad talk back to us? Let's start with the known:
* Keypad Interface * when a key is clicked, emit a notification. Indicate the type and value of the key.
Note that we haven't coded anything yet. We have simply defined our components, and we have started talking about their communication paths. In the container, we might start thinking differently, and even start thinking in code.
The display and the keypad are sibling components. Usually, they wouldn't know about each other. Done well, each part will work on its own without requiring the other, but allowing communication in and out.
That matters, because the container acts as the manager of those smaller components. It can handle communication from the keypad, and can notify the display about those communications.
The manager is the consumer of those components' interfaces. It uses the defined paths to facilitate communications, and might provide an interface of its own. At this point, it won't, but it could.
What it will do, though, is contain one more piece that neither of the others have. Neither the display nor the keypad have a true "state", in that they don't really need to track anything. The keypad passes a click notification, but it doesn't keep that event. The display updates itself on command, but it likely doesn't keep data about the data its been passed.
The container acts as a manager, not only of the components, but of the state of the calculator. In the comonents list above, the only entry under the container was the
Operations Stack, which represents the state of the calculator internally.
But rather than outline the interface the container provides, now's the time to think about how the container handles internal communications:
* Container Interface * Keypad events: * update the Operations Stack as needed, * notify the Display as needed.
That's really it - calculators are a largely one-directional application. A key is clicked, the internal state is updated, and we tell the display to update itself.
Some particular applications or user stories might be:
* User clicks '9','4','.','3','+' - at each click, the Operations Stack (the state) is updated by appending; - and the Display is updated with each digit, or cleared when an operator is pressed. * User then clicks '3','=' - the Operations Stack should append the digit, and then collapse to a single value for the equals; - the Display is updated with the final value. * User clicks "AC" - the Operations Stack should be emptied; - the display should be cleared.
So we can see each component part, and how it encapsulates its own functionality. We can also see ways in which those parts can communicate (an interface), allowing them to interact. By taking the time to pre-plan, by moving from being a coder to a developer, we save ourselves time and aggravation later.
It is common to refactor our projects, to revisit and post-mortem after completion - but its just as important that we pre-factor our projects, designing and developing with intent.
Next time, we'll start coding our component parts, building to a completed calculator project.