Monday, February 24, 2020

Getting Beyond Stack Overflow for CS Students

Question and answer sites like Stack Overflow (and similar Q&A sites) have become a valuable resource for programmers struggling with tricky problems.  However, I have noticed that Q&A sites can become a "trap" for students learning how to program.  You can get stuck for a long time searching for a question that doesn't quite address your question.

The problem is that Q&A sites are oriented toward solving very specific questions, rather than developing a general understanding of a technology.  If a post answers the very specific question that you have, that's nice, but it doesn't necessarily help you to develop your own solution to other problems.  What's worse, the answers are sometimes incorrect, or not applicable to the situation that you actually have.

Here is a classic train wreck with thousands of votes: How to validate an email address in JavaScript? One can assume the poster is looking for a quick one liner to solve this problem.   The problem is that the question is not sufficiently well formed.  How does one define an email address?  Do you want to be conservative or flexible?  What does it mean to "validate" anyhow?  Some of the answers posted provide colossal regular expressions, but unless you understand regular expressions, you won't know if they work correctly.  (Some of the solutions don't actually work.)

If you intend to be a professional who solves problems for people, you need to be able to think through these issues yourself, rather than just copy-pasting a solution.  In the case of validating email addresses, that means learning how regular expressions work from first principles, and then thinking carefully about what problem you really intend to solve.

Now, I can't prohibit anyone from looking on Stack Overflow, nor will I try. But I would like to suggest some different habits of learning that will lead to more fulfilling results and less frustration.  And, it will help you to become the sort of person who answers questions on Q&A sites.


An Approach To Solving Programming Problems

  1. Learn the atoms that make up your system.
  2. Experiment by combining them in simple ways.
  3. Solve your actual problem gradually by building up complexity.


Learn the Atoms

Every layer of a computer system is an abstraction that is made up of some fundamental set of basic operations, which I'll just call atoms.  Each atom manipulates the system in some particular and well-documented way.
  • If you are learning how to manage processes in Unix, then your atoms are system calls like fork, exec, wait, kill, and exit
  • If you are learning regular expressions, then your atoms are concatenation (xy), alternation (x|y, closure (x*), and so forth.
  • If you are learning how to render graphics in OpenGL, then your atoms are these functions like glBegin, glVertex, glColor, and glEnd.
  • If you are learning X86 assembly language, then your atoms are instructions like MOV, ADD, SUB, CMP, and JMP.
Sometimes just figuring out what the atoms are can be tricky.  This is where a good introductory guide or textbook is helpful. Without giving you every last detail of the atom, an introductory guide tells you the set of atoms needed to get started and their general relationship.  (This is probably the most important thing teachers do in the classroom: help you to understand the atoms of a system.)

Once you know what the atoms are, then you need to learn how they work in detail.  This is going to require some effort on your part. Find the reference manuals for those atoms and read them.  If it has a man page, read it.  Yes, really read it, I'm not kidding.  You ought to be able to easily summarize the purpose of each atom from memory, and look up the details when necessary.

A really elegant system design (e.g. Unix or LISP) will have only a handful of atoms.  (That's what makes it elegant.)  A not-so-elegant system (e.g. Win32) may have hundreds, and so you can't learn it all up front.  In that case, you have to pick a few atoms that appear to work together, and proceed to the next step.


Experiment by Combining Things

Now that you understand some of the atoms, start to combine them together in simple ways and test them out.  Don't even try to solve your initial problem yet, just try a few things at small scale.
For example, suppose you are learning Unix process management, and you think you have a basic understanding of the fork system call.  Begin by writing the smallest program that does something, perhaps this:

pid_t pid = fork();
printf("hello from pid %d\n",getpid());

Now, test your understanding by making a prediction.  What do you think this program will output?  Ok, now run it.  Did it output what you expect?  Great, continue.  If not, then go back and re-read the behavior of fork to see where you misunderstood it.

If that worked, then add one more atom and see what happens.  Maybe you try this:

pid_t pid = fork();

Again, make a prediction: what do you think this will output?  Test your prediction.  Did you get it right?  Ok, add a little more:

pid_t pid = fork();
if(pid==0) {
printf("created child %d\n",pid);

As you add complexity to your examples, start to think about undesired situations and edge cases.  For example, what happens if you attempt to execute a program that doesn't exist?  Change your little example to test that possibility, and predict its output:

pid_t pid = fork();
if(pid==0) {
printf("created child %d\n",pid);

Hmm, that probably didn't have the desired effect.  Can you explain what happened?  You will have to add a little bit more code to handle the possibility of execlp() failing.  Take a look at your atoms and see which one makes sense.

As you go on, you will gain confidence in your understanding of the atoms that make up the system, and you will be able to focus more on the structure of programs that use the atoms, rather than stumbling over how they work individually.


Solve Your Actual Problem Gradually

After spending some time working out the atoms and simple combinations, you are ready to come back to your initial problem.  With the details of each atom clearly in your head, you can rely less on internet searches and online manuals, and more on using your own brainpower to make new combinations.  Suppose you are given the following goal:

Your company has an important service that needs to be running continuously, except it has a habit of crashing every 30 seconds or so.  Your boss asks you to write a "watchdog" program that keeps four copies of the service running all the time.  Whenever any instance of the server crashes or exits, the watchdog should restart it.

There is no answer to this specific question on Stack Overflow, so you are going to have to figure it out for yourself.  If the solution doesn't jump out at you, do not despair.   Try solving a simpler problem first, and then approach the full problem by adding complexity gradually.  For example, you could write four versions of the solution like this:

  • Version 1: Run one instance of the service and wait for it to finish.
  • Version 2: Run four instances of the service sequentially, starting one as soon as the previous as finished.
  • Version 3: Run four instances of the server in parallel, and wait until all of them have finished.
  • Version 4: Run four instances of the service continuously, so that as soon as one of the four exits, another one is started.

As you go along, you may find that you have forgotten a detail about one of your atoms.  That's fine, go back to the manual and look it up again.  Perhaps you encounter a very strange error message that is not mentioned in the manual.  That's a great item to search for on the internet.  But if you understand your atoms well, most of your mental energy can be focused on combining them into a coherent whole.