Thursday, March 10, 2016

General Programming Guidelines: Things They Should've Taught You in CS-101 but Didn't

As a bartender (moderator) at JavaRanch and as a senior developer and mentor to other developers, I often see programmers committing the same mistakes over and over. Although there's a wide variety of mistakes that can be made, they all have a few things in common, one of which is that they can usually be traced back to a lack of clarity and consistency in thought and practice.

Sure, rookies make mistakes and it's easy to just chalk those off to inexperience and not knowing any better. We've all gone through Shu, the first stage of the Shu-Ha-Ri learning process. The Shu stage is where all beginners start as they fumble around, not really knowing what to do or how to do things. They try different ways that, more often than not, turn out to be the wrong or the not-so-good way.

But I've seem many professional developers with years of experience make the same kind of mistakes. You'd think that after all those years on real-world projects, they'd know better. Obviously, there are many would disappoint you. We have to ask ourselves though, why are there so many programmers out there who don't seem to be learning from all the mistakes of the past?

An Explorer's Metaphor 

I agree with the idea that if you're not making mistakes, then you're not learning and growing. However, schools seem to assume that students will eventually figure things out for themselves. In the professional world of development, it seems like many of the leaders and managers assume that developers will naturally avoid doing things that are known to cause problems. We need to stop making those kinds of assumptions because we all know what happens to you and me when we assume.

Experience may be the best teacher but besides letting them discover things for themselves, new programmers also need to be taught some basic principles. They need to be given sensible guidelines to follow. Guidelines give them a sense of direction, a rope to hold on to so they don't get lost as they muddle their way through the dark. Principles provide a solid anchor around which to tie that rope.

General guidelines help programmers deal with specific obstacles and dangers along the way so they can navigate safely through or around them. Principles provide a place for them to go back to and find some truth and certainty in situations where guidelines fall short or are not strong enough to carry them through chaos and confusion.

When they make mistakes and stumble, programmers can use principles and guidelines to pick themselves up, assess what went wrong, and decide on a more appropriate course of action next time they encounter a similar challenge. They can take any new lessons they learn and lay those on top of the principles they already know, thus strengthening and solidifying their base of knowledge and understanding. This solid base is where intuition comes from and it's what will allow them to cast their ropes in different directions and venture into new areas for further exploration and learning.

Learning how to fly by jumping into the deep end

Yes, I know that subheading is incongruous. Did I mean to write "learning how to swim by jumping into the deep end" instead? No, I meant exactly what I wrote. That's the point. 

The way I see students taught in schools seems to be as incongruous as that subheading. The intent may be there, but the context and focus with which instruction is given leaves students totally out of sorts, IMO. Thus, the students wander into our little online community at the Ranch, like so many dazed and confused cattle desperately seeking to be prodded in the right direction. I see very little indication that clear guidance was provided to them by their instructors.

It's as if they had been led to the edge of a cliff, then suddenly pushed into the void with the expectation that they would know to spread their arms and find out they had wingsuits on that would allow them to glide smoothly and effortlessly to relative safety below.

Unfortunately, that very seldom is the case, if at all. It's more like they were given a pack to strap on their back only to find that when they pulled the ripcord, the pack was filled only with basic camping gear but no parachute. It's like they were given some rope but only enough with which to hang themselves.

That's the point in the incongruity of "learn how to fly by jumping into the deep end." It often seems like teachers focus on giving students the tools they need to do something but provide little to no guidance or support in learning how to use those tools safely or effectively. It's like they went, "Here's the deep end kids, now take your snorkels, jump in, and run to the shallow end! Anyone who doesn't drown gets a passing grade." That's kind of ridiculous, right? It's also patently unfair.

Now, you may say that I'm being unfair to the instructors and I will concede that most of them probably do their honest best, or at least as well as they know how. But I still don't think it's enough. We can do more. We can do better to get new programmers ready to meet the challenges of learning how to develop software.

Lack of Context

You need to stand before you can walk. You need to walk before you can run. There's a logical progression to the process of learning and each step forward builds on the progress already made before. Generally speaking, educators seem to have this down. They start off with simple concepts and slowly build up to more advance concepts. I don't have many problems with this aspect of pedagogy.

It's the context that's almost often very lacking in the delivery of computer programming instruction. Most of us learn how to walk on dry land, not in deep water. Without somewhere to set their feet down so they can get their bearings, all new learners can do is flail about aimlessly while trying to keep their heads above water and not drown. This is where I think the problem lies in the kind of instruction you normally find in a traditional classroom setting. 

There seems to be very little discussion around the practice of programming even as learners are introduced to more and more parts of the programming language they are studying. Focus is strong on language constructs like classes, variables, different ways to control program execution flow, data structures, compilers, errors, etc. Things like functional decomposition, code and design hygiene, basic layering and organization, and development process are deferred as "advanced" topics. These things are arguably as important, if not more, when you're developing software. Yet, I find that most new programmers are not even aware that there are principles and concepts that they need to consider or at least know about, if not sooner, then definitely later if they are going to become good software developers.

Some people may argue that topics like design principles, clean code, maintainability, and extensibility are too advanced for beginners and that introducing them too soon would not be very useful and would only confuse them. To some extent, I agree, but to totally defer all these until later is also irresponsible and I think even borders on being condescending and disingenuous.

Awareness leads to recognition and sensitivity which in turn leads to understanding. Without even an inkling of the things that make up the "big picture" of software development, new learners are deprived of a way to see how the little things they learn about at the start fit into a bigger context as they get further along in their studies.

We need to give learners more credit or at least set the bar a little bit higher and challenge them to think about some of the deeper, more abstract aspects of the practice of programming. Many new learners are just that, new. They're inexperienced, not stupid. I believe they're fully capable of comprehending even just the basic notions of some of these arguably "advanced" topics. I could be wrong but I'm willing to give them a chance to prove me so.

Lack of Preparation

Since they lack a basic understanding of the practice of programming, I find that many students are ill-prepared to handle the rigorous way of thinking that is needed to solve computer programming problems. The teaching process seems to go something like this: "Here's how you write a main method. Here's how you test for a condition. Here's how you set up a loop. Now go and write a program that generates a simple payroll report."

That's kind of like saying "Here are some pistons, some rods, some cams, a cylinder block, a crankshaft, spark plugs, etc. Now go build me an engine." Or like showing a teenager how the controls of a car work, then asking them to go straight out and drive on the road. That's a recipe for disaster, right? There's a lot more to building an engine than just putting together some mechanical parts. There's more to driving on the road than just knowing how to work the controls of a car. Likewise, there's a lot more to programming than just knowing how basic language constructs work.

Before they can drive a car out on the open road, new drivers must demonstrate that they have a good understanding of the rules of the road. These rules tell them how to operate a car safely in the context of their environment and in relation to other drivers who are also using the road. Even informal rules of thumb like the 3-second rule for keeping a safe distance is important to teach to new drivers. Especially new drivers. It is essential that all drivers follow the rules of the road consistently so that everyone can stay safe and roads stay clear and free of accidents and other hazards that would stop or impede the flow of traffic.

Likewise, before asking programmers to go out and solve problems with their newly acquired knowledge of basic language constructs, somebody should prepare them by giving them a basic set of rules to follow so that they can stay safe and so that they don't do things that will cause problems for themselves and/or other people who may come in contact with the code that they write.

If we don't give new programmers a framework around which they can organize their thoughts and clarify and evolve their ideas, we're not doing right by them. It would be like moving to a new place, buying your six-year-old a backpack, some comfortable shoes and clothes, pens, paper, a lunchbox, and then telling that child to go and find their new school for themselves. That's just not right. 

I'm often dismayed to see students asking for help on the Ranch who are halfway through their introductory programming course but are still writing Java programs that use static methods exclusively. They're still very confused and trying to figure out why the hundreds of lines of convoluted code that they've written in their main method doesn't work.

I just have to wonder what all these teachers are thinking. Or do they even care? Do they really expect that these kids will just figure it out for themselves? None of the course materials I have seen referenced tell me otherwise. 

I mean, c'mon, we have to do better than that for the new guys. We need to prepare them better for the challenges that they are going to face. We experienced guys all know what those are. It's not only for their sake that we should prepare them but also for the sake of whoever has to come in contact with code that these new guys write, now and in the future.

Programming is a way of thinking

Programming is about thinking and organizing our thoughts in a clear and coherent way so that our ideas come together as a solution to a given problem. I say "our" because programming nowadays usually involves more than just one person, so it's the aggregate of the ideas of all the people involved that need to come together to form a coherent solution. It's our problem, our ideas, our solution.

Most of the non-trivial problems that we try to solve with the help of computers are difficult, complex, and complicated. Naturally, the solutions we create are also often difficult to come by and just as, if not even more, complex and complicated. Nobody ever said that programming was easy. Anybody who does is lying or trying to sell you something. Same difference.

Regardless of how difficult, complex, and complicated the problems we try to tackle may be, the process of thinking that allows us to come to one or more solutions to those problems invariably follow some common patterns. I call these patterns in the same sense as I do when talking about design patterns. They are generally applicable to a wide variety of specific contexts and their applicability and appropriateness for use is also very dependent on context.

Much of these thought processes revolve around simplification. To solve a difficult, complex, and complicated problem, it's almost always necessary to have a systematic way of breaking it down into smaller, more manageable chunks, then organizing and coordinating the solutions to these small individual problems into something that addresses the original problem coherently and holistically.

The things that help me write better programs

Below you'll find some guidelines that I offered one confused student on JavaRanch this morning. They are the same guidelines that I use every day when I write code and design computer programs.

If you're a new programmer, read these guidelines. If you're an experience programmer, read them, too. You just might remember something. Or you might even learn something new. You never know.

If you don't have a lot of experience, don't be intimidated by words like "design", "parameterize", "extract", "duplication" and other words that maybe aren't part of your current daily vocabulary. Give yourself some credit. They are not too advanced for you. In fact, you have already encountered the concepts behind these words, albeit perhaps unknowingly, from the very first moment that you started thinking about writing your first computer program. You need to know these words and what they really stand for anyway, so you might as well start getting comfortable with them.

Read the guidelines below and try to always keep them in mind. Every time you sit down to write a single line of code, think about these guidelines. They're just like the rules of the road when you're driving: You should always have them in the back of your mind, even if you don't have any immediate need for them. When a situation arises where one or more of these guidelines do apply, at least they're there for reference to guide your decisions on what to do next.

Somewhere in there are some principles, too. I won't point them out but the fact that you're reading through this stuff will already make you aware of them, even if you don't know that's what they are. That should be good enough for now.

You can read my original post on JavaRanch but I will reiterate and refine it here. These are not my original ideas in any way. They have been stated more succinctly and eloquently by other, far smarter and better programmers that myself. I only offer these to you with a few more details and elaborations that I have found helpful over the years. I hope you find them helpful, too.

General Guidelines for Programmers

While you are programming, as often as you can, stop and check to...
  1. Make sure you're using names that make sense in a given context

    Ask yourself, "Is this name clear and unambiguous? Does it have the right semantics for how it's used and what it's used for? Does it fit with the ideas that I'm trying to reflect in the code? Does it imply any assumptions, mislead, or misinform the reader as to its purpose? Does it help to tell the story of what this program is doing? " If necessary, find a better name and rename that thing for clarity of intent.
  2. Make sure you're not repeating yourself unnecessarily

    Ask yourself, "Is there code elsewhere in the program that is very similar or exactly the same as what I just wrote here? Are there sections in the code that use the same values, formulas, calculations, conditions, as I am here? If I change something here, will I need to make the same kind of changes in other parts of my program?" If necessary, identify the pattern of repetition and put it in one place that can be accessed by everything that will need it. Extract and parameterize to eliminate duplication.
  3. Make sure you're telling a clear, coherent story with your code.

    Keep ideas that are high-level and abstract separated from low-level and concrete implementation details. Don't mix generalities with details. Prefer to tell the general story first and use good names to reveal your intent. Hide details but be sure to put them behind a good name that clearly and accurately represents what they do as a whole. Maintain single levels of abstraction.
  4. Make sure that you're following the 4 Rules of Simple Design.

    If you're already following the previous three guidelines, then you're already doing that for the most part.

Remember, the best way to eat an elephant is to take one bite at a time. Keep things small and manageable. Don't be a glutton. Go easy. When in doubt, slow down.

Most importantly, test your code. 

If there's one fundamental truth in computer programming, this is it: Testing can only show the presence, not the absence of bugs. A really smart computer scientist once said that. He was right. 

Therefore, use tests wisely and appropriately. Test your program frequently, as you're writing your code. If you're not writing a test for every few lines of production code, you're not writing enough tests. You're human, you're going to make mistakes. Lots of them. Tests will tell you when you do make them.  Listen to your tests. They'll help you stay out of trouble. They'll also help you get out of trouble. It's going to happen. You're going to miss a few bugs. Tests got your back though.

Nothing slows you down more than not being sure if your program works, or if you've broken anything, or why that thing that used to work isn't working anymore. So learn how to write automated tests. Again, you're only human. You forget things. You're inconsistent. You're not that fast. At least not as consistent or as fast as a computer when it comes to doing boring, repetitive, and tedious things like running dozens or hundreds of tests. No seriously, even a small, non-trivial program should have more than just three or four tests.

Related to testing is experimentation. Don't be afraid to try things, even if you know it's the wrong thing. If you can't think of a better name at the moment, just write something down, even if it's a totally goofball, silly name. Often, it's in seeing the wrong thing right there in front of you in the code that you find inspiration and realize what the right thing should be. If you don't know what the right thing is yet, at least you can definitely throw away the wrong things after you try them on for size.

Programming involves a lot of thought but it's not useful to sit on ideas and dwell on them. This is not theoretical physics. Thought experiments are not very useful in our daily work. Leave that up to the computer scientists and Turing Award winners, unless of course you're one of them. Write a test to formulate a hypothesis of how your program should behave. Then run that test as an experiment to see what happens. When the test fails, write an implementation to make it pass.

After you've run your experiment, go back and read your code. Read your code out loud. Discuss the ideas that are reflected in your code with your development partners. Make sure your understanding aligns with their understanding and that all your understandings align with what the code is saying and doing. If not, go back and apply the general guidelines to clean up your code and make it accurately reflect your understanding of how the software behaves. Check out what Ward Cunningham said about this stuff. Spoiler alert: Debt is supposed to be a good thing.

Learn how to use JUnit or something like it. Tools like this will save your life. Literally. Seriously.

Lastly, have fun. Programming is about learning. If you don't think that learning is fun, you probably won't be a good programmer. If that really is the case, find something better to do with your time. For everyone's sake.

Programming isn't for everyone but if you decide that it's really your thing, and that you want to get good at it, then you really should have fun doing it. Otherwise, what's the point?
Post a Comment