How to Write Simple Code to Accomplish Complex Tasks
Today’s guest post is written by guest author Miguel Raggi. Miguel is a Computer Science and Math professor at UNAM, Mexico’s largest university. He loves clean, expressive, performant C++ code (and strives to convince students to write it in this way!). Miguel is the author of discreture, an open source C++ library to efficiently generate combinatorial objects, such as combinations, partitions, set partitions, and many more.
Interested to write on Fluent C++ too? Check out the guest posting area.
One of the best parts of my job is teaching students advanced algorithms. However, a constant source of frustration comes from watching them fumble to implement them. Sometimes they don’t even know where to start. And when they do, they are constantly getting lost in the logic and details of their own code. They come to me when they’ve spent hours debugging and more often than not, shamefully, I can’t help them: I don’t understand their code either.
It’s not that they don’t understand the algorithm they are supposed to implement. They do. Sometimes they even come up with the algorithms on their own, maybe with some minor guidance.
It’s just that, often, they can’t see the value of clean code. Specially when no one else (apart from myself, when grading) will read their code. How can I convince them that clean, expressive, readable code invariably helps them more than it hurts them?
In this post I wish to share a method I discovered, somewhat by accident, but that has worked very well so far. I’ve seen the quality of their code improve considerably after using this method a few times. I’ve only used it with small groups (less than 15 people), but I think it can be adapted to bigger groups, or even a single person—yourself.
But rest assured, you don’t need to be a teacher to benefit from this. By the end of the article I’ll provide some guidance on how to use this method to teach yourself to write cleaner code. However, I’ll explain the method as if you, the reader, were a computer science teacher, simply because it’s easier to explain it like this, and it’s what I’ve been doing.
After all, I must confess that I think the person whose code has improved the most by this method is myself.
The method: a setup for respecting levels of abstraction
The method, put simply, consists of having the students implement the algorithm in question as a collaborative effort, in front of the class, in real time.
Now, I understand you might be skeptical. Let me elaborate on exactly what to do and then you can judge if it’s worth it.
Detailed description
Imagine the students have just learned (and hopefully understood) a fresh new algorithm that is somewhat complicated, but they need to implement it in order to cement their understanding .
Pick a random order for the students (I usually have a little script that does this for me) and have each of them in turn come to the front and sit at the computer connected to the projector, so everyone can see the code they type.
When a student comes to the front, their job is to implement only one function. A full function. And, except for the first student, it must be a function that someone else has already “called”. You, the teacher, must insist that they should, as much as reasonable, pass the baton to someone else.
Passing the baton
What I mean by passing the baton to someone else is that the student is allowed to call any conceivable function they want, whether it already exists or not, as long as they can invent a name for it. Obviously they aren’t allowed to just call a function that does exactly what the current function is supposed to accomplish.
Another thing is that it’s fine if they don’t pass the correct parameters to the functions they just invented. This will be the function implementer’s problem, not theirs. After all, they’ll see what information the function needs while attempting to implement it.
As to make the most out of the students’ time, the teacher should provide an initial setup – compilation script, #include directives, function signature and maybe a description of what the algorithm solves and some example use- when the first student comes to the front.
The first student usually has the hardest job (or they think they do). But it’s random and everyone will be the first student at some point. The point is that they’ll probably need some guidance from you, the teacher. If stuck, ask them: “Explain, in your own words, what the algorithm does.” When they start talking, just interrupt them and tell them “yes, write that down in the form of calling some functions”. Then, hopefully, they’ll invent four or five function names, corresponding to the four or five steps of the algorithm.
Note: My experience is that virtually all algorithms can be thought of as having four or five steps at most. If an algorithm has more, then some of those steps could probably be thought of as a single big step (i.e. raising the level of abstraction). The point is that we humans have trouble thinking about more than four or five steps at the same time.
Once that first student finishes her job, have her take her seat (applause optional) and call the second student. That second student then picks one of the four or five functions the first student invented and implements it, probably using other invented functions. If they find their functions need more information to operate than what a previous student passed as parameters, part of their job is to fix the call site and signature.
This goes on until the implementation is finished. Then we test and debug until the algorithm is correctly implemented.
More likely, this will not go exactly as described. A student might not be able to properly divide the work into four or five steps and might start going into details. In other words, they might not respect the current level of abstraction. When this happens, stop them and tell them to pass the baton to the next student by encapsulating what they were going to write into a function that they themselves won’t have to implement. They’ll usually be happy about this: they can stop worrying about the details. That is someone else’s problem.
The teacher’s job
The teacher has three main responsibilities while the students are coding in front of everyone.
1) The first and most important one is to make sure that levels of abstraction are respected. As soon as a student starts disrespecting levels of abstraction, stop them and remind them to try to pass the baton to someone else. This usually happens when they write a for/while loop in the middle of a function, or if they write nested loops*.
2) The second job is to make sure every single variable/function has a good name. Don’t allow poorly named variables anywhere.
3) Gently guide them if they get stuck, by asking questions such as “what does the algorithm do next?”
Note: Very rarely should a single function contain nested loops, and only when together the loops form a single coherent cognitive unit (e.g. going through all (x,y) entries in a matrix).
Students will often get into discussions about which function has a particular job (e.g. which function is responsible for normalizing the input?). After the discussion, they would set pre and post conditions to reflect the new consensus, and if feasible, change the function and variable names to reflect new roles (e.g. input -> normalizedInput).
If a few functions operate on the same set of parameters, they might be a good candidate for encapsulating the behavior as a class. If so, one student’s job might be to write the class definition and public/private interface, and fix all function signatures. This student won’t actually implement anything: that’s someone else’s problem.
Behind the scenes: practical advice if you are teaching
If you aren’t (and never plan to be) teaching a group of students, you can safely skip this section. However, if you are actually teaching a class and are thinking of using this method to help your students, there are a few things to consider.
It’s important to implement the algorithm yourself in as clean a way as possible before class. It should obviously be as bug free as you can make it. This helps flatten out any wrinkles, minor details, etc. that students might run into.
Try to let students propose function and variable names. If they come up with a bad name, do suggest a better one, but let them come up with the names initially.
Also, I know it’s difficult, but keep your hands off the keyboard while a student is there! Refrain from correcting their bugs right away, or from telling them exactly what to type. Debugging is also part of software development, after all.
But do provide guidance, and don’t let any student stay at the computer too long, or else the other students might stop paying attention.
If you see a student struggling, kindly point them in the right direction, have other students provide them with hints, or simply suggest they implement another, perhaps easier, function.
I don’t know if it will be the case with you, but I found that very smart students often have the most trouble respecting levels of abstraction, and are often the ones I have to guide the most during this exercise. Since they can hold more things in their brains at once, they want to do more at once. This almost always backfires in the form of bugs and code that is hard to read and refactor. Another reason is that they have a hard time passing the baton to someone else: they want to show that they’re up to the task of implementing the entire algorithm. Make sure you pay attention to this and don’t let them write more than one for/while loop, and even then, only if it matches the current level of abstraction.
Finally, I usually make the students write some unit tests as well. Sometimes I write them before class, if I’m short on time. Sometimes I write some and let them write others. But make sure the implementation is well tested.
Using the method on yourself
This method is easy to conceive for a class of students, but what makes it powerful is that it also works for anyone who wishes to improve their code. The key is to constantly be thinking of passing the baton to someone else: your future self.
I think the reason this method is a natural fit for groups of people is that, psychologically, it’s easier to think of a subtask as somebody else’s problem when it’s actually somebody else’s problem. If you’re coding alone, you need to convince yourself not to think about the details of every function you wish you had available. Simply call them and worry about implementing them later.
Thinking of a single function at any time ultimately helps you implement complicated algorithms without getting lost in the details by making it easier to reason about code and reducing the amount of bugs.
To summarize: the key to learning to respect levels of abstraction is to constantly think “that’s future-me’s problem”. Not yours.
Let me know in the comments if you’ve used a similar method, or if you have any suggestions for improvement.
You will also like
Don't want to miss out ? Follow:   Share this post!