Cover Image credit to René de Ruijter, at least, as far as I can tell.
I've been doing the Advent of Code 2017 challenge this Advent, and I noticed that as I've been working on some of the harder challenges (number spirals, amirite?) I've started doing something that seems to really help me stay locked in to the problem. It's definitely not something groundbreaking, but I figured that I'd share it, in case it helps somebody else.
The Problem: Short Attention Spans and --
Stop me if this scenario seems like a personal attack:
You've just been handed a problem/assignment. You know where you need to start, but as you begin to type the first outlines of your code, one of the things you need to do next pops into your head. You quickly jump over and create a new file for that and try to get a brief sketch down so you won't forget later. As you're doing that, you realize that the new thing that you're adding will need a test case, so you hop over and "just real quick" jot down the test case for what you were working on. You run your tests to make sure everything's failing as expected, but you forgot to configure your tests and the output is all ugly and who could continue to work in that kind of environment? You're not a barbarian after all! And then two hours later… wait a minute, what was I originally working on?
Some of the solution is just self-discipline -- forcing yourself to finish what you're working on before you start the next thing. However, one place I find these mental self-interruptions especially problematic is when I'm trying to solve a big problem and have to keep worrying about little parts of the solution. This is where my tip can come in handy.
One Solution: Use Methods You Wish You Had
This is probably best described with an example.
Let's say you're writing a script that controls an automatic treat dispenser for your dog.
This is Willy. He's a very good boy.
On your first pass through the code, you might write something like this.
def train(dogs)
dogs.each do |dog|
if dog. # Pause here
Now you might think to yourself, "Shoot! Now I need to figure out when these dogs should get treats!" And just like that, your train of thought for the rest of the high-level function is gone.
Here's my solution.
I propose that you confidently carry on like nothing is wrong, and use the methods that you hope Future-You will write.
def train(dogs)
dogs.each do |dog|
if dog.good_boy?
dog.pat!
give_treat(dog)
@treats -= 1
end
end
end
"But Ryan! We haven't written methods like good_boy?
, pat!
, or give_treat
yet." EXACTLY. That's a problem for Future-Us. The important thing now is to keep you in that flow state and make sure you get your whole thought on the screen before you forget what you're supposed to be doing. We'll come back and fill in the cracks later.
(For those of you that are really concerned, here's how good_boy?
is implemented):
class Dog
# ...
def good_boy?
true
end
end
😬
Wrap Up
So that's the tip! Write the code, and when you come to a section that seems like it might bog you down even a little bit, just pretend you already have a method for that. A secondary benefit of this is that it helps you identify where good places to pull code into separate methods are. You can always come back and clean things up after everything's working a little better.
Got any other pro-tips for blocking out the distractions? Let me know — I'd love to hear them!
Originally posted on assert_not magic?
Top comments (31)
This is actually really good advice!
For people who think a little differently (like me), it works the other way around, too. I think bottom-up: from the bottom of the eventual call stack upward. Thus, I will plan out my approach, sometimes stub out my top-level function like you did, and then begin to build the lowest level functions first.
By way of example, when I wrote Omission, I followed this basic workflow:
Create bare minimum to display a message - basically a modified "hello, world!" I did not start with the UI yet!
On paper, jot down the eventual call order. I knew the game would need the content generator for anything to work, so I'd start there. I could temporarily hardcode source content in, instead of loading from a file, so I did that. I got the program to just display some generated content.
Next I added code to load content from a file. Nothing fancy here, I was only wanting to replace the hardcoded source content with material from a text file.
Now I needed interaction, so I wrote the code to allow a user to answer, and for the game to verify that answer. Again, I was still only using a text-based interface. Once I had that, I added scoring. I now had a technically functional command line game.
Now I added the timing-based functions. I didn't need to see any fancy timers, just to make sure the score reflected how long I took to answer.
To get decent further progress, I needed the GUI, so I constructed just the gameplay screen. Don't get hung up on menus at the start!
I only allowed myself to get the interface to work with the game as it existed. Once that worked...
I began to tweak and improve the gameplay and interface to be closer to what I imagined, working in much the same manner as before: plan it out, write the lowest part first, and work my way up.
Finally, I went back and added the other functionality: menus, additional gameplay modes, etc.
I'm oversimplifying, obviously, but you get the idea.
By working in this manner, I had the advantage that all my code was technically correct as I worked.
Now, a DISCLAIMER: If you think in the top-down described in the article, you probably shouldn't use my workflow. If you aren't a bottom-up programmer, this method is likely only going to confuse you. I intended this only for people who think like me. I merely wanted to show that the author's basic approach works in either direction.
That’s a really good juxtaposition of the two methods. It is interesting seeing how much they have in common!
This is great. I've gotten to doing this but hadn't really formalized it. I'm now going to practice this with a lot more intention.
dude!
Great tip! I also do that all the time. When I'm focused and deep in the domain I don't want to jump around in the code or between the files. No, I want to write a function, component, ... in one go. That way it all makes sense later when I or someone else will read it.
In some of my programs this programming style has lead to two layers of code:
However it's not always a good idea to introduce these layers into the structure of a given framework, because most frameworks give you a (more complex, more technical) structure already. Let's take MVC for example. In order to keep the controllers lean, your domain code should be in the models. But do you need to split your model layer into a domain layer and a library layer? Maybe. But if you need just a few functions in the library / helper layer a simple helper library (not part of the model layer) might be enough. Or think about concerns - normally they should contain domain code (shared behaviour between multiple models), but it's also a good place to fully implement your functions in order to keep them out of your models, so you can use them as a library layer as well.
I've been doing this for a while now and my code has been noticeably cleaner.
This is also a great way to introduce the idea of methods to complete beginners. I used this recently in a Java intro class.
In the beginning we pretended many "complicated" methods already existed (readNumber(), isPrime(int), etc.), but as we learned new things, we started filling them in. That way, we could focus on the program logic before learning about all the language specifics (which is a very good thing for when they switch to a different language).
That’s really cool! I hadn’t thought about using this idea while teaching. I can see how that would make functions/methods more natural
Just in case you didn’t get enough #goodboye
He's a goodbwoy 😍💙🙌
This to me is the purpose of pseudocode. Planning out what you want to happen, without worrying about all the nitty-gritty details to start.
I do something similar when I code, especially if it's a larger problem. Often, I use comments, to first put down my thoughts similar to "do this, then if this, do that, else do this." After, I can start coding by tackling one comment at a time (which is a lot less daunting). And, usually each comment a good place to test that it's working as expected.
"Use Methods You Wish You Had". This is exactly the key. We try so often to do what we want with what we have, instead of thinking what we wish we had.
I've heard this referred to as error driven development. I try to use it when I remember. F2 and alt+Enter in IntelliJ help speed this up as well! Goto next error and the "magical fix my code" key combo.
This also often leads to cleaner code, i.e. methods with code at one level of abstraction, rather than a mix (and often mess)
Yep! I have had more than a few ‘rake test’ /NoMethodError/head-desk moments.
dude!
That's what Fred Brooks wrote about in Mythical man-month. He proposed to build a higher-level skeleton first, the one that just compiles but implements no useful behavior. Then delving deeper and deeper, considering fail scenarios and edge cases. Though I guess pretty much everybody has came to this approach already by the time he or she has read the book.
Neat! I'll have to check that out. He probably spells it out more clearly and concretely than what I have in my head, so that should be helpful.
I love this approach 🙌
Personally, I think this helps a lot to organize the code in reasonable levels of abstraction - if I think in terms of high-level methods or functions that I’d like to have, I’ll be less tempted to reach for the file system or make an HTTP request when I should be thinking in terms of my business classes. Getting to the lower levels usually comes naturally out of filling in the missing methods.