This site will look much better in a browser that supports web standards, but is accessible to any browser or Internet device.

Anomaly ~ G. Wade Johnson Anomaly Home G. Wade Home

April 01, 2006

Resource Management Revisited

A couple of years ago, I wrote a set of three essays on Resource Management: The Semantics of Garbage Collection, More Thoughts on Resource Recovery, and Other Resource Recovery Approaches.

In summary, my argument in these articles was that the standard mark-and-sweep garbage collection falls down for two major reasons, it doesn't deal with any resources other than memory and it fails to account for object lifetime issues.

Recently, a comment showed up on the Artima forums that shows that I'm not the only one to wonder about these issues: Java Community News - When GC is Not Enough: Resource Management in Java. This comment was inspired by the article Java theory and practice: Good housekeeping practices. Both the article and the comment discuss handling resources other than memory and how important it is to be careful in your resource management.

I find amusing the advice to be careful with memory management in a context where garbage collect is used to remove the need for memory management. Aren't these two issues essentially the same thing? I can't count the number of times I've been told that garbage collection is required because ordinary programmers can't be trusted to manage memory correctly. (An argument that I don't particularly agree with despite examples proving the point.) If the argument holds for memory, why doesn't it hold for file handles and database connections? I would argue that it doesn't really hold for memory either.

I don't believe that garbage collection solves the memory management problem any more than it solves the resource management problem. I think that programmers should be accountable for managing both memory and resources. While we don't want to do this manually, giving up the ability and responsibility completely is not the answer. A better solution would be to have tools that support resource management without giving away the ability to manage them.

One of the most useful idioms in C++ is Resource Acquisition is Initialization (RAII). The key concept is tying the ownership of each resource to an object and using the object lifetime to determine when the resource is in use. Unfortunately, the normal mark-and-sweep garbage collector implementation discards the concept of defined object lifetime, so we don't have that ability in a language depending on that form of GC. In fact, Goetz refers to that issue when he talks about the problems with finalizers in the Good Housekeeping article cited above.

Unfortunately, the only solution in a language without defined object lifetimes is to explicitly manage the resources everywhere they are used. Both Goetz and one of the comments in the forum point out that using a try - finally allows you to force this cleanup to happen correctly. Unfortunately, what neither of them mention is that this violates the DRY principle (Don't Repeat Yourself). Now, everywhere you use a particular resource you will need to copy this identical piece of code. This repetition is almost guaranteed to be a source of bugs.

While I agree that memory management in a language without garbage collection is harder than it needs to be, I still maintain that classic garbage collection is not the solution. Unfortunately, I suspect that the advocates of mark-and-sweep will either continue to ignore the issue or try to force some unnatural connection between other resources and memory. This way they can apply their favorite hammer to the job.

Posted by GWade at 11:20 AM. Email comments | Comments (0)

March 25, 2006

Domain Specific Languages, a Renewed Interest

I've seen quite a bit of interest in Domain Specific Languages (DSLs) on the Internet lately. Some good examples include Martin Fowler's exploration of the subject:

* MF Bliki: DomainSpecificLanguage
* Language Workbenches: The Killer-App for Domain Specific Languages?

He does point out that this is not a new idea. He uses Unix as an example of a system that uses a large number of DSLs. The subject has gotten enough interest to reach the point where people are discussing when it is a good approach to apply (Artima Developer Spotlight Forum - How and When to Develop Domain-Specific Languages?). Others are beginning to apply the term DSL to extend their area of interest (Agile Development with Domain Specific Languages).

So, from this we can guess that interest in DSLs is on the rise. As Fowler pointed out, Unix has been a nexus for the creation of DSLs, including:

* make
* regular expressions
* awk
* sed
* yacc
* lex
* dot

and many more.

Recently, I have seen the suggestion that extending or modifying a general purpose language is a powerful way to build a useful DSL. To some extent, this is also well-trodden ground. The classic example of this approach was implemented using preprocessors to provide facilities not present in the original language, both Ratfor (for structured programming in FORTRAN) and cfront (for object oriented programming in C) used this approach.

A recent article, Creating DSLs with Ruby, discusses how the features of the Ruby language make it well suited to building DSLs without a separate preprocessing step. The Ruby language apparently supports features that supports creating simple DSL syntax that is still legal Ruby code.

This is a very powerful technique that is not very easy to do with most languages. Amusingly enough, this technique is also not very new. In fact, there was a general purpose programming language that was designed around the concept of writing a domain language: Forth. If I remember correctly, Charles Moore once described programming in Forth as writing a vocabulary for the problem domain, defining all of the words necessary to describe the answer, and then writing down the answer.

The Forth language is different than most programming languages you might have encountered because it has almost no syntax. What looks like syntax is actually a powerful technique for simple parsing, combined with the ability to execute code at compile time. This allows for extending the capabilities of the language in a very powerful way. One interesting effect of this feature is that many good Forth programmers naturally gravitate toward the DSL approach when solving problems above a certain level of complexity. We firmly believe that some problems are best served by a language, not an arcane set of configuration options.

Forth does give us an important insight into the problems with DSLs, as well. There is is a well-known joke among Forth programmers:

If you've seen one Forth...you've seen one Forth.

Unlike more traditional programming, Forth programs are built by extending the language. A new programmer trying to learn a Forth system needs to learn the new dialect including the extensions used in this environment. This is not technically much different than learning all of the libraries and utility classes used in a more traditional system, but there is a conceptual difference. In Forth (or a DSL-based system), there is no syntactic difference between the extensions and the base language. The language itself can be extended without an obvious cue in the code to say when you have changed languages. This means that a new programmer may not recognize a new piece to learn as readily as when seeing an obvious library call.

This becomes a very important tradeoff: Which is more important, ease of learning for new programmers or power for advanced users? A well-designed DSL gives the advanced user a succinct notation to use to express hie or her requirements concisely and precisely. This is the appeal of the DSL. The downside is that this represents a new set of knowledge for each programmer relating to troubleshooting and debugging. It also requires more care in the design to develop a consistent and usable notation.

As usual, the tradeoff is the key. We need to be able to decide if the benefits outweigh the disadvantages and build the system accordingly. I wish there was a magic formula that could be applied to tell how or when a DSL would improve a system. Unfortunately, I have not seen a sign of such a formula yet.

Posted by GWade at 11:40 PM. Email comments | Comments (0)

March 06, 2006

Programming and Writing

Over the years, I have come to the amusing realization that many people feel like they could program if they just tried. You know the type. He (or she) has used computers for a while. He picked up Computer Programming for Total Morons or Object Oriented Programming in C++ and Java in an Hour and a Half and now expects to be able to write code like a pro. Those of us who have been programming for a while, brace ourselves for the inevitable opinions and declarations about building large systems based on experience with single-page programs.

I have also realized that there is another field that is very familiar with this problem, writing. Similar to the way that many people seem to think that they can program just because they can enter code in an editor, many people also seem to think they can write just because they can put words on a page. Ask anyone who really writes for a living and they can tell you how little the ability to write words on a page has to do with actually writing. Despite this disparity, anecdotal evidence suggests that many people still expect to write a great novel someday. (Although, I suspect that percentage is smaller than it was a few decades ago.)

A couple of years ago, I compared writing and programming in The Recurring Myth of Programmer-less Programming. I made a very superficial connection between these two disciplines in order to make a very different point. Since then, this connection has been simmering in the back of my head and I have come to realize that they have more in common than I originally thought.

One thing these two disciplines have in common is communication. Now, I realize this is not a particularly earth-shattering revelation, but bear with me a bit. It often comes as a surprise to most people that communication is hard. If you are just talking with your friends, family, and co-workers, you probably manage to be understood most of the time. When misunderstandings do arise, most people seem to assume that someone wasn't paying attention or something.

The first time you try to communicate with someone from a different culture, you begin to see how hard communication can be. Even if you can speak a common language, misunderstandings abound. If you are communicating through a low-bandwidth channel like email, communication becomes even harder. Most people seem to assume that these kinds of communication problems are the other guy's fault. Really communicating doesn't allow for that particular cop-out. Cicero is supposed to have said

The aim of writing is not simply to be understood, but to make it impossible to be misunderstood.

This is much harder than most people realize. When you are speaking, you can watch your audience and clear up misunderstandings as they arise (at least in theory). When you are writing, you don't really know who your audience is. There is no feedback, so misunderstandings linger.

In programming, this is equally hard because your writing has two very different audiences: the computer and other programmers. The difficulty comes from the fact that the computer needs everything described in mind-numbing detail. It cannot make the leap from one concept to another. On the other hand, people (even programmers) are not very good at reading information at that level of detail. This difficulty of working at two very different levels at once is one of the things that separates an actual programmer from a coder who dumps code into an IDE or editor.

Thinking and communicating at these two very different levels is extremely difficult. Over the years, we have developed better programming languages to reduce the amount of detail we need to explicitly write for the computer. Unfortunately, this has not made the complexity go away. Instead, the programmer has to be aware of the details of each statement and the context in which it is made. While this is somewhat easier for humans than the alternative, it does not mean that the details have gone away. It just means that you don't have to think about all of them constantly.

A good programmer has to be able to think at multiple levels of detail at the same time. These levels include:

  • user-level details of the solution, such as the desire to select items by name
  • high-level details of the program, such as the need to find items quickly by name
  • medium-level details that the program needs, such as data structures and algorithms appropriate to the job at hand
  • low-level details that the machine requires, such as limits on data types, memory, and speed of the processor.

Not all of these details matter all of the time. But a good programmer has to be aware of them and know when particular details can be ignored. The programmer also has to be aware of the constraints and requirements of the problem he (or she) is trying to solve.

A good programmer is aware of multiple solutions to many different problems and is capable of making various trade-offs in choosing between them. Whereas a coder has probably found a solution that has worked in the past and tries to apply it to every problem.

Fortunately, some coders eventually gain enough experience to become actual programmers. Unfortunately, the percentage of coders that become programmers is probably close to the percentage of people that actually do write their great novel. Unfortunately, unlike potential writers, these coders are actually producing code that someone will need to maintain and repair.

Now, sooner or later someone is going to point out that here I am writing, when I have no business claiming to be a writer. Actually, I don't claim to be a writer. I think my best credentials for writing are that I know I'm pretty bad at it. For that reason, I work pretty hard at each article I write, trying very hard to get my point across without leaving too much probability of being misunderstood. As such, this article has been one of the hardest for me to write.

Posted by GWade at 06:48 AM. Email comments | Comments (1)

April 24, 2005

Thoughts on Measuring Proficiency

In On Proficiency, I talked a bit about what characteristics make up the concept we call proficiency. In trying to hire programmers, a company often tries to measure proficiency to find the best candidate for the job. Over a year ago, I began thinking about how to measure proficiency, and found it to be harder than I thought.

Unfortunately, proficiency is not a simple variable that can be measured. A particular person is likely to rate differently on each of the four aspects of proficiency. Some will have great theoretical knowledge, but no skills. Others will have skills and aptitude to spare, but lack the theoretical knowledge to be efficient.

Most of these aspects are a spectrum. A person may have a strong aptitude or none at all, someone else may be in between. A person may have a little interest, a lot of interest, or would rather have a root canal than even think about it. These aspects are more or less outside a person's control and are easy to recognize if you see the person working in the field. Someone with no aptitude will not be able to make the simplest things work. Someone with interest and a great deal of aptitude will be able to adapt to new requirements or approaches fairly quickly.

The other two aspects are also not simple variables. A person's knowledge of a field may be broad and shallow, deep and narrow, or somewhere in between. The skills a person brings to the job may involve heavy experience with a few tools and techniques, light experience with a large number of tools and techniques, or some combination. Although, these aspects are the easiest to measure, they are also very easy to misinterpret.

Many employers look for the deep and narrow knowledge, asking for people that have spent the last 10 years doing essentially the same work that the employer needs. On the other hand, someone with a broader knowledge of the field can bring insights and new ways of approaching problems. If the person's knowledge is too broad, they probably have not had time to develop any depth in a particular area. However, someone who has knowledge of several fields has probably shown the ability to adapt and learn as needed to solve problems. This is an important characteristic. It is possibly more important than extremely deep knowledge of the topic of the day.

A person's skill set is relatively easy to measure. Can the candidate do task X with technology Y? Has the person ever used technique Z? However, the absolute answers to these questions are less important than many people assume. If a person has never used a particular skill, but is experienced with a related skill, the lack may not be a problem. A practical example would be does a programmer know a particular language. If the answer is yes, then you know he/she can use the language. If the answer is no and they've only used one language, then you know they won't be immediately useful. If, however, the programmer has experience working in several languages, the actual yes or no answer is less important. He/she will probably be able to pick up the syntax of the language and work almost as effectively as the person who answered yes. More importantly, if the language of the day sinks beneath the waves and a new language is required, this programmer will be able to adapt.

What makes measuring these even harder is that the different aspects interact in interesting ways. We all know the programmer that has an amazing amount of theoretical knowledge, seems to know every aspect of the tools he/she uses, and is extremely interested in the topic, but always seem to solve the same problems, over and over again. In this case, several attributes combined in a way that is detrimental to the programmer involved.

Many of us have also seen the bright entry-level guy/gal that has interest and aptitude galore, who spends much of his time reinventing known solutions to known problems because he lacks the theoretical knowledge that would prevent this waste of time.

Sometimes we've also seen the one that doesn't know the language you are working with, hasn't used any of your tools, but after a month is the person everyone goes to for help with design or style or even code.

After over a year of pounding on this idea, I still don't feel that I have any insights into how to really measure proficiency in our field, except work with a person long enough to recognize some of his/her strengths and weaknesses. I've seen many people hired that I thought would be a good fit and find them lacking in one or more of the attributes we need. I've also seen some that looked like a complete waste of time, that turned out to be just what we need.

If anyone has found any wonderful insight into this issue, I'd really love to hear it.

Posted by GWade at 07:27 PM. Email comments | Comments (0)

March 25, 2005

On Proficiency

In order to become proficient at any activity, a person needs four things:

  • aptitude
  • interest
  • knowledge
  • skills

To some extent, you should be able to judge a person's proficiency by measuring each of the four attributes.

The first of these is the only one that a person has no control over. If you don't have the aptitude (or talent or knack) for an activity, no amount of the other attributes will make you truly proficient. For example, very few programmers have the physical aptitude to play professional sports. No amount of interest or training will make one of us into a linebacker for the NFL. In most cases this isn't a problem, because most people that have no aptitude for an activity either have no interest in it or lose interest in it after repeated failure. This is not a bad thing because different people have different aptitudes.

Interest is the quality that usually causes an aptitude to be noticed. If you have a true talent for music, but no interest, you probably will not become proficient in that area. Unlike aptitude, interest can change over time. For instance, a person can be exposed to an activity and find that it comes easily and that person's interest increases. On the other hand, an activity that has always held your interest may turn out to require more work than you plan to supply and your interest can wane. Aptitude and interest together seem to be a requirement of becoming proficient in an activity.

Most activities require some abstract knowledge to become truly proficient. Different activities require differing quality and quantity of knowledge. A person can acquire this knowledge through books, classes, and reasoning about experience. Obviously, different activities handle the acquisition of knowledge differently. Unfortunately, abstract knowledge is not always directly applicable to an activity.

The last piece of the puzzle is a set of skills that are used as part of the activity. Skills can only be developed by doing the activity. Different activities rely on differing amounts of skills. Unfortunately, many people pick up skills in an activity by rote and tend not to recognize the acquisition. Skills are acquired either alone or though the help of a mentor or senior person in that field.

Some of these attributes are innate, like aptitude. Some can possibly be taught to anyone, like knowledge or skills. However, unless all of these attributes are present, someone will never really become proficient in an activity. In order to analyze the productivity of software developers, we need to identify and analyze these attributes. Although aptitude and interest are hard to analyze, we can identify which knowledge and skills might lead to more productive software developers.

Part of the disparity we currently see when measuring productivity in programmers is caused by ignoring one or more of these attributes. When attempting to measure productivity, we look at years of experience or education to recognize equivalent candidates to compare. Unfortunately, abstract knowledge and some skills are easy to identify, but they do not make up the whole of proficiency.

Posted by GWade at 11:14 AM. Email comments | Comments (0)

January 02, 2005

Kinds of Problems

Of all of the lessons I have learned doing software development, one of the most important was to recognize what kind of problem I'm trying to solve. This sounds pretty trivial, but I'm not talking about the categorization you are probably thinking of. As software professionals, we tend to look at all problems as solvable. We can partition the problems we observe into multiple logical categories. This problem seems to be mostly a database problem. That problem is going to be mostly user interface. This other problem is going to be mostly about performance. Some problems cross multiple categories.

The hardest problems we have to solve as software professionals don't fall into these categories. Over time, I have begun partitioning problems into one of two categories before doing anything else. They are

  • technical problems
  • business problems

These categories are not hard and fast. Most of the problems we work on cover both kinds of issues. But, it is important to realize that some problems fall more into one category than into the other. Unfortunately, by our natures we are drawn to technical problems. Sometimes that pull is so strong that we forget about the other kind of problem. We assume that every problem can be solved given the right technical approach. Unfortunately, that isn't the case.

Several times in my career, I have supplied a good technical solution to a problem only to be shot down by someone in a non-technical role. In many cases, I was forced to implement a significantly inferior solution and then watch it fail in most (if not all) of the ways I had predicted. In most cases, I had to apply nasty hacks and band-aids to keep this bad solution running despite all of my warnings about how much this solution would cost in the long run. I have also seen many other programmers suffer a similar fate.

Eventually a couple of really good managers managed to get me to understand that sometimes the problem is not technical, it's a business problem. No amount of technological know-how can solve a business problem. Sometimes, the technically worse solution is more correct for the purposes of the business. It took years of fighting these kinds of issues before I finally came to understand this idea. Sometimes, the best you can do is to let the powers that be know what the ramifications of a technical decision are and then accept that they may overrule you for reasons that may never make any sense (to you).

Some of the best managers that have put me in this position went the extra step to let me know that they understood the implications of what I was saying. Sometimes this made things easier because I might be allowed to fix things later. Sometimes the issues were purely political and no amount of work or insight could solve the problem.

Eventually, I came to understand that no amount of tech can really solve a business problem. Throwing myself into the work to prove my solution is best or working long hours to bypass the issues are counter-productive. Now, when I recognize one of these kinds of business problems, I supply all of the information that I can to the appropriate individual. Then I do what I'm told and try to do the best I can within the constraints placed on me. Most importantly, I try not to be bothered by the illogical solutions I'm required to implement. For all I know, the whole project will be scrapped for political reasons and I'll never have to maintain the disaster I've been ordered to create.

It's not much consolation, but it's better than making myself sick over it.

Posted by GWade at 10:45 PM. Email comments | Comments (0)

October 17, 2004

Domain Knowledge and Programming

Over the years I've been a programmer, there seem to be two distinct areas of knowledge for any project. The first relates to programming. The second relates to the domain of the project. Over the almost two decades I've been programming professionally, I have worked in many domains. Interestingly, I have never started working on a project as a domain expert.

At different times, people have told me that they were looking for a programmer with extensive experience in X (whatever their domain is). I have even seen people go so far as to suggest that they would not hire a programmer who did not have a degree in the domain of interest. This seems completely backwards to me.

When hiring a construction company to build a bank, do we only consider companies that employ bankers? When finding a mechanic to work on an ambulance, would you only talk to those with EMS training? So why would you require a programmer to have a degree in biology to program for medical research?

Over my career, I have come to the conclusion that we programmers should always develop a some domain knowledge as part of a project. But, we never need to go too deep. The real need is to understand enough of the domain to be able to translate the real knowledge of the domain expert into a form that the computer can execute. Our real skills come in this translation, not in the knowledge of the domain. More importantly, what domain knowledge we have should match that of the customer. This is only guaranteed if we learn from the customer in the process of doing the job.

As an example, let's say that I went to work for an engineering firm. I have an electrical engineering degree, so this seems like a natural fit. However, my knowledge of the electrical engineering field is now twenty years out of date, and fuzzy to boot. Would that knowledge be a help or a hindrance to any work I might do.

Having some knowledge of a particular domain might be an advantage. Having no preconceptions about the domain might be a bigger advantage. In many of the programming positions I have held, I managed to do some good by approaching the problem differently than the domain experts. The key is to extract the knowledge from the domain experts in a form we can execute. If I accomplish that goal, I consider the project a success.

My field is software development. As such, I need an extensive knowledge of tools, methodologies, and techniques for the development of software. I also need the kinds of skills that are needed to develop software. One of those skills is the ability to stuff information into my head and retain it long enough to make it make sense to a computer. Most of the best programmers I know regularly do the same.

The reason for this rant is a problem I have run into several times in the last few years. I have been talking with someone about a job or a piece of code they want written and they decide either that I can not do the work or that I should be paid less because I am not an expert in their field. I have heard similar comments from friends. One fellow programmer told me how an employer in a medical research institution reduced the amount they were willing to pay him because he did not have a degree in biology. Now, I am pretty certain they had plenty of biologists on staff. But, they were hiring for a programming position, and a biology degree does not make you a better programmer.

I'm not sure what it is about our craft that makes people to think that there is nothing to it. Many seem to believe that they could pick it up if they just expended a little effort. But, of course, their field is something that you need loads of training to be able to do. Not like programming.

Most of the better programmers I've known seem to think about it differently. They assume that most people's area of expertise requires work, knowledge, and years of practice to be good. They also know that it takes similar work, knowledge, and years of practice to be good at what we do. That's why most of the code that I've seen written by domain experts is such a mess. They see code as a minor sideline away from the real work. We see code as the artifact we produce to help other people get on with the work they are interested in.

Posted by GWade at 02:55 PM. Email comments | Comments (2)

August 11, 2004

Participation vs. Hacking

In The Architecture of Participation vesus(sic) Hacking, Carlos Perez argues against points he sees in the essay Great Hackers by Paul Graham. Having read the two essays, I find Perez's comments enlightening, but maybe not in the way he intended. I found things in both essays that I agree with, and things in both that I disagree with. There are some that I feel deserve comment.

Perez begins by focusing on a comment made by Graham that of all the great programmers he can think of, only one programs in Java. Perez, and others, take immediate offense at this comment and move to respond.

However, they miss an important point of the comment. Graham specifies of the great programmers he can think of. Later in the essay, Graham describes how hard it is to recognize a great programmer unless you've worked with him or her. I could make a similar comment that none of the great programmers I know program voluntarily in Lisp or Python. This does not mean that great programmers don't write in those languages, I just don't know any.

Furthermore, I think I'd go even farther to point out that Java currently attracts a large number of programmers of varying levels of skill for one reason only; someone will pay them to work in Java. As a side effect, you are more likely to find average or below average programmers working in Java than great programmers simply because there are a lot more to sort through. That does not detract from the great programmers you will find.

Having blasted Graham for an attack on his favorite language, Perez goes on to attack other "Hacker" languages. He writes that

Nothing has ever been built in Lisp, Python or any other "Hacker" language that comes even close to the innovation in the Eclipse platform. I challenge anyone to point one out.

Many people in the comments section of his essay point out many counterexamples. There's no need for me to repeat those here.

I do find it interesting that Perez starts his whole essay based on one off-handed slap at Java and in the process makes an equivalent unsupported swipe at Perl (the write-only comment). I still find this ongoing misconception to be annoying. I've programmed professionally in Fortran, C, C++, Perl, Forth, and Java. I haven't seen a single one of these languages that did not allow write-only code. (In fact, I've seen some spectacular examples in Java.) I've also seen beautifully written code in all of these languages. The readability of any given piece of code is more a reflection on the particular programmer and the problem at hand than it is on the language.

Perez also finds confusion by what he calls Graham's definition of the word "Hacker". I find this amusing. Graham is using the definition of hacker that was accepted before the media appropriated it for "people who write viruses and other bad things". (See Jargon 4.2, node: hacker) I remember this definition of hacker in a version of the Jargon file back in the late 80's.

Perez's final mistake is in what he perceives as Paul Graham's fatal flaw. He points out that sooner or later, every programmer enters maintenance mode and that no hacker would remain at that point. Perez defends this idea by quoting Graham's comment that hackers avoid problems that are not interesting. He proceeds to use the legacy code argument to show why you shouldn't rely on hackers or hacker languages.

Unfortunately, Graham didn't say anything about great programmers and maintenance mode or legacy code. So this argument has nothing to do with Graham's essay. Moreover, I have seen many cases where the beginning of the project was better described by Graham's death of a thousand cuts comment than the maintenance of any legacy system I've worked on. More importantly, GNU software and the entire open source movement is driven by programmers (many of them great programmers) maintaining software for years.

All in all, I think Perez would have made his points better if he had not taken the Java comment quite so personally.

Posted by GWade at 11:03 AM. Email comments | Comments (0)

August 08, 2004

Programmers and Pattern Matching

Contrary to expectations, many programmers do not solve problems through logic. This should not be surprising, as the same is true of humans in general. But many programmers I've known whole-heartedly disagree with that comment.

I've noticed that many really good programmers are extremely good at pattern recognition and solve many problems through a creative use of that skill. The human brain actually has amazing capabilities for pattern recognition. Two really good examples of this skill are language and facial recognition. When you think about it, much of language consists of recognizing patterns of sounds at a level below conscious thought and reinterpreting that into a higher level meaning. The ability to pick up subtle clues from a person's expression or recognize a person after many years (and the physical changes that come with that time) is truly amazing when you stop to think about it.

Most really good programmers are fascinated by patterns. They often play with patterns in ways that non-programmers might find bizarre. Many programmers really get caught up in puns, word-play, debate, poetry, and other forms of linguistic patterns. Many are fascinated by puzzles and mazes. Some spend lots of time with art. In all of these cases, the programmers seem to be exercising their pattern skills more than most.

It turns out that pattern recognition can be a really efficient method of solving problems. Either deductive or inductive reasoning requires walking through all of the relevant data of the problem one step at a time and connectinve them all together. Pattern matching allows you to focus on the shape of the problem to recognize a problem you've seen before. If you don't agree that the pattern recognition is easier, compare recognizing the face of a friend to the effort of describing that friend in enough detail for someone to draw them.

This ability allows a programmer to solve complex problems by breaking it into simpler problems that are recognized and therefore already (or simply) solved. This approach leaves the programmer free to concentrate on the new, interesting problems, instead of wasting brain power on the stuff that he/she recognizes. Over time, this develops into a very large mental list of patterns and solutions that can be applied at a moment's notice.

Some programmers allow this habit to get them stuck in a rut. They always apply the same solution even if better techniques exist. The better programmers are constantly learning so that they have a set of tools to apply to most problems. This still allows the efficiency of the pattern recognition approach with the power of a (near) custom solution if necessary.

Posted by GWade at 10:52 PM. Email comments | Comments (0)

June 27, 2004

Other Resource Recovery Approaches

In The Semantics of Garbage Collection, I explained why I don't like the term garbage collection. I also introduced the term resource recovery, and suggested that this change in naming could generate a useful change in viewpoint.

In the next article, More Thoughts on Resource Recovery, I traced some of the history and side effects of the main current approach to garbage collection. I also pointed out where I think this approach makes a wrong turn.

Now, I'd like to turn to alternative approaches. Some that exist, and some that don't (yet).

The simplest and easiest to understand form of memory management is stack-based, local variables. The life-time of the variables matches the scope in which they are declared. Memory is automatically cleaned up when execution leaves the scope. If we could limit our use of memory to the current scope and any functions called from that scope, the memory problem would vanish. Unfortunately, this approach is too restrictive for most programming projects. At the very least, the lifetime of data in an object must persist beyond the scope of the constructor.

C++ has a very clean system for dealing with memory or any other resource. By defining both a constructor and a destructor, you can guarantee that memory is allocated when an object is created and freed when the object is destroyed. In the same way, you can allocate or acquire any resource at construction time and release or free it at destruction time. C++ programmers refer to this as the resource acquisition is initialization (RAII) idiom. Using this idiom consistently with only stack-based or member-data objects will also prevent resource leaks.

Unfortunately, this is still not the whole story. Sometimes, there is the need to share the same object between two places in the code or objects. There is also sometimes the need to have objects that live outside the particular scope in which they are created. Although, there are still special cases that can be handled simply, there is no way to always be able to recognize every way in which the programmer might want to deal with memory. For this reason, many computer scientists gave up and went the garbage collection route.

As I said in my last entry, the simplest case of automatic memory management uses reference counting. In this approach, every object carries with it a count of the number of references to it that currently exist. Many systems have relied on this relatively simple form of resource recovery. It has been re-implemented for use in many objects over time. There are a few subtleties to the implementation, but in general this approach works surprisingly well.

Reference counting has two problems, one minor and one major. The minor problem is the extra overhead of dealing with the count. For a small object, the count could be a significant increase in the size of the object. Dealing with the reference counts adds a small amount of effort when a new reference is made or an old one goes away. However, the real killer is circular references. A references B, that references A. Even if the last external reference to A or B goes away, these two items still have non-zero reference counts.

This issue is the result of a fundamental flaw in the reference counting approach. Reference counting mixes up life-time with ownership. These two concepts are strongly related, but they are not the same. In the C++ world, this problem has been explored in the Boost C++ Libraries. In this library, different programmers have designed a set of smart pointers with different semantics. You might wonder what kinds of semantics a pointer could have.

In many cases, the lifetime of an object is only really tied to one of it's references. That reference can be considered the owner of the object. The only time that reference counting makes any sense is when more than one reference must be the owner. So, let's look at ownership a little more closely. How do we define the ownership of an object? If an object is only referenced once, that reference is the owner. This includes objects on the stack. The only owner is the "reference" on the stack. Temporaries created by the compiler also only have one owner, the temporary reference that goes away at the next sequence point. Member or instance data usually only has one owner, the object it resides in. As much as we try to ignore them, globals and class data also only have one owner.

All of those kinds of references make up the easiest case. When the owner reaches the end of its life so does every object that it owns. The C++ destructor concept handles this very well for member data. Most languages handle it well for local objects. The C++ compiler handles this for temporaries. This is the easy case that everyone pretty much understands without thinking. It's also relatively easy to prove correctness for this case. Unfortunately, the whole world does not fit in this category.

Only slightly more complicated is assignment of ownership. This is when the old owner gives up it's ownership when making a new reference. The Standard C++ std::auto_ptr embodies this concept. This is often seen when a method or function returns an object. The old owner, the called method, gives ownership of the object to the calling method. Then the old reference ceases to exist. This sometimes also happens when putting items into a collection. The final case is when an object reference is copied to a new reference and the original reference is pointed to a new object. (Think manipulation of a linked list.)

The next level of complexity involves temporary shared access. This is the kind of sharing that happens when a temporary reference is made to an object but the original reference remains the owner. The most obvious example of this is passing an object to a function or method by reference. The function has a temporary reference to the object, but the object is owned by the calling code.

The interesting thing about all of these is that the compiler actually could determine the owner of the object fairly reliably in each case. By replacing the normal references with specialized references that either do or don't take control as needed, the compiler could convert each of these cases into something very close to the simple single owner, stack-based approach described earlier.

In these cases, we could get timely resource recovery and useful object lifetime semantics without resorting to a full-blown garbage collection system.

Unfortunately, that's not the whole story. The rest of the story will need to wait for later.

Posted by GWade at 10:56 PM. Email comments | Comments (0)

June 12, 2004

More Thoughts on Resource Recovery

In The Semantics of Garbage Collection, I explained why I don't like the term garbage collection. I also introduced the term resource recovery, and suggested that this change in naming could generate a useful change in viewpoint.

Many programmers have been indoctrinated with the belief that garbage collection is the solution to all memory problems, and that any language that does not use garbage collection is destined to be full of memory leaks. I disagree. But to really understand why I think otherwise will require a little history.

In the Beginning

Well, at least in the beginning of my programming career was Fortran. In the first dialect of Fortran I ever used, all memory was global memory. Even local variables in a subroutine were allocated as global memory; they just weren't visible outside the subroutine. There was no allocation of memory. Therefore, there were no memory leaks. There was also no recursion, because calling a subroutine from itself would wipe out the current values of its "local" variables.

These conditions no longer apply to Fortran, but they were the state of Fortran when I started. (Or at least the state of Fortran as I understood it then.)

The Stack and the Heap

Later in my career, I began to work in more modern languages. The first of these was C. In C, there were two types of memory besides the static memory I knew from Fortran. There was the stack and the heap. (I had actually seen these before, but I'll ignore that detail for now.)

The stack had the wonderful property that it would grow as you needed it and automatically be reclaimed at the end of the function (or block). It also allowed multiple copies of a function to have its own local variables, giving the possibility of recursion. Most of the time, stack memory is easy to use and is recovered automatically. As long as you didn't try to put a huge amount of stuff on the stack or recurse too deeply, everything worked out fine.

The heap had another wonderful property. I could allocate as much of it as I wanted (within reason) and give it back when I was finished. This allowed me to work with different amount of data without guessing the biggest size I could deal with ahead of time. However, this feature came with a dark side: the memory leak.

Objects

Then I discovered Object Oriented Programming. One of the most wonderful features of objects for me was the whole concept of the object lifetime. I could have an object that allocated memory as part of its construction and that would free that memory at destruction time. No more memory leaks! I could make allocated memory act like the stack memory that I had always liked. Well, of course things did not really work like that. People can allocate objects as well and automate the whole process of leaking memory.

Perl and Memory Management

At about the same time I found C++ and OOP, I also discovered Perl. Now Perl did not have objects at the time, but it did do automatic handling of memory in many cases. Over time, Perl developed real local variables (called my variables) and objects. Perl supported a simple garbage collection system called reference counting. Unlike the more popular mark and sweep garbage collection, reference counting has two really important virtues: it's simple to understand and it preserves object lifetime.

In a reference counting scheme, every object keeps count of the number of references pointing to it. As a reference goes away, the count of the references is decremented. When the count reaches 0, the object is really destroyed. This happens at a well-defined time, when the last reference is removed. This method is simple to understand and works very well in general. In fact, to my knowledge, reference counting has only one flaw: circular references. If object A references object B and object B references object A, and no other references point to either of them, we have a memory leak. These two objects are not referenced anywhere, but they cannot be reclaimed.

Reference counting works particularly well when objects are only referenced once or twice. This covers the simple object creation case and many uses of temporary objects created by the compiler. These objects are created and destroyed exactly the way you would expect.

Mark and Sweep

The great flaw of reference counting is one of the reasons that the mark and sweep approach was developed. Since mark and sweep starts from known references and works through all memory to find any that is referenced, circular references are not a problem. Unfortunately, mark and sweep suffers from a few problems of its own.

  • The garbage collector can not be allowed to delete or move memory that is part of the stack.
  • The time taken to garbage collect is a function of the amount of currently allocated memory, not the amount of memory to reclaim.
  • The garbage collector does not usually respect object lifetimes.

The first point is why Java does not allow objects to be created locally on the stack. Otherwise, objects on the stack would need to be cleaned up differently than objects on the heap. With mark and sweep, this would require recognizing what parts of the objects are in which locations and treating them accordingly. The easiest solution was to define the problem away and only allow heap-allocated objects.

In order to perform the mark portion of the algorithm, we will need to touch each reference to every object to verify what is still in use. In order to perform the sweep portion of the algorithm, all referenced objects need to be moved so that the old memory can be reclaimed. More advanced versions of the system partition objects in different ways (new and old objects, etc.) to reduce this overhead to some extent.

For me, the worst effect of mark and sweep is the loss of object lifetime. An object's lifetime should run from the point where it is created to the last place it can be referenced or when it is explicitly killed. With the mark and sweep system, there is a period of time when the object is left in a kind of limbo. It cannot be referenced, but it hasn't been cleaned up. It's like a ghost of an object.

The Wrong Turn

Although I understand the purpose of garbage collection, it seems to me that we've taken a wrong turn. There was a time when we had stack-based data that was cleaned up automatically and was easy to deal with, and we had heap-based data that was more powerful, but could be misused. Now we've added garbage collection to reduce the chance of misusing the heap-based data, but to make the garbage collection simpler, we've thrown out stack-based objects and defined object lifetimes.

In the quest to stop memory leaks, we've made every object creation a memory leak and provided an automatic system to clean up the leaks we've made. Moreover, the loss of a defined time for the object to die has resulted in the loss of the destructor concept. We cannot be guaranteed that the object's cleanup/destructor/finalizer method will ever be called (even in normal operation).

This is why I have been thinking that maybe a new approach is needed. Next time, I plan to explore a possible new approach.

Posted by GWade at 11:52 PM. Email comments | Comments (0)

June 10, 2004

The Semantics of Garbage Collection

I have a confession to make. I've never really been comfortable with garbage collection.

Now, before you dismiss me as a Luddite who has obviously never worked with an advanced programming language, let me explain.

I'm familiar with many different forms of garbage collection. I have worked in languages with and without garbage collection. I've worked with Java, Lisp, and Perl; each with its own concept and implementation of garbage collection. I have seen well-done garbage collection free programmers from the drudgery of dealing with their own memory management.

But, despite all of that, I don't really like garbage collection. Strangely enough, when I finally examined why I didn't like garbage collection, I realized that my problem was more semantic than anything else. I don't like the name and what it implies. I don't think of used memory as garbage to be collected. I think of it as a resource to be recovered. The funny thing is that this shift in viewpoint results in a whole new set of assumptions about how resource recovery should work.

Timeliness of Recovery

The first consequence of this change in terminology is a change in timeliness.

Garbage is something we don't want to think about. Taking out the garbage is something we tend to put off until we have to deal with it. (The trash can is full, it's starting to smell, others in the house are complaining, etc.) This philosophy shows itself in the current crop of garbage collection systems. Most of the garbage collectors I've seen follow some variation of the mark and sweep algorithm. In this algorithm, a background task walks through memory marking all referenced objects. Then, it sweeps out the unused objects, freeing up their memory.*

Unfortunately, running this algorithm uses CPU cycles that we obviously want to make use of elsewhere in our code. So most systems put it on a low priority thread; one that only gets called when there is nothing else to do (or when we begin running low on memory). Since this thread is just picking up our garbage, it doesn't need to be high priority. As long as the garbage is picked up before it gets in our way, we don't really care.

If you think of memory as a resource, things shift subtly. Resources are not things I'm ignoring, they are something I want. In fact, if it is a scarce (but renewable) resource, I want to make sure it is recycled or recovered as soon as possible. That way it will be available the next time I need it. It is no longer an issue of trying to put off this nasty chore off as long as possible. Now I want to try to handle it as soon as possible, without adversely impacting my other work.

Other Resources

Another advantage of thinking of memory as a resource, is that this puts it in the same category as other resources that we need to deal with. Most operating systems have limits on the numbers of certain resources. If a program tries to use too many without returning them to the system, it risks failure. Some kinds of scarce resources include:

  • File handles
  • Database handles
  • TCP/IP sockets
  • Process slots
  • Address space
  • Mutexes and semaphores
  • Window handles
  • Communications bandwidth

But, for some reason, we tend to treat memory as fundamentally different than these other resources. We tend to try to close/release each of the items in the list above as soon as we can. Because each is scarce and we know we will need it later. But, the current wisdom in garbage collection is to recover memory only when we have no choice or nothing better to do.

I find this approach disagrees with me on a gut-level.

*Okay, it usually involves copying the live objects around and doing some other maintenance work. But the simple description is enough for the purposes of the discussion.

Posted by GWade at 09:00 PM. Email comments | Comments (0)

May 26, 2004

The Fallacy of the One, True Way of Programming

What is it about programmers that cause them to latch onto one approach and try to apply it to every problem? Maybe it's a characteristic of people in general. Each new paradigm, methodology, or even language seems to grab hold of many programmers and make them forget anything they ever knew. They seem to develop an overwhelming need to re-implement and replace all that came before the new tool.

This isn't always a bad idea. Done deliberately, this technique can help you evaluate a new methodology or language by comparing the solution of a known problem with a previous solution of that problem. Unfortunately, many of us seem to assume that the new way is better and apply the new approach despite any evidence that it does not solve the problem more effectively.

Over time, I've come to the conclusion that there is no One, True Way of Programming. This is not my own insight. I have read this and heard this from numerous people with more experience. Earlier in my career, I did not believe them. I didn't disagree, I just didn't believe. Through experience with multiple paradigms, languages, and methodologies, I've finally come to understand what those senior people were trying to say.

Multiple paradigms are different tools in my bag of tricks. Each language is another tool that I can apply to a problem. Different methodologies give me different approaches to solving a problem. But, in the end, no single set of these tools can help me solve all problems. Each combination has strengths and weaknesses. One of the most important skills we can develop is the ability to recognize the right tool for the job.

Many years ago, I saw this point made in a book. (One day I'll remember which book.) The author pointed out that very different techniques and processes are needed when you are building a skyscraper and when you are building a doghouse. The author pointed out that the techniques that would be used to architect a skyscraper are not cost-effective when building a doghouse. I think I would now go a little further and suggest that the techniques are not appropriate, and that cost is one easy measure of why they are not appropriate.

Posted by GWade at 10:38 PM. Email comments | Comments (0)

May 12, 2004

Flow and Miller's Magic Number

Recently, I stumbled across Mental State Called Flow and was reminded of an idea I had about flow and Miller's Magic Number. When in the state of flow, I've sometimes had the experience that the problem I was working on was much easier to understand than it seemed to be when I was not in flow. In observing and talking to other programmers, I've come to the conclusion that this is not an unusual experience.

Several years ago, I worked with a senior programmer who always seemed to be in flow (or hacking, depending on who you asked). Unfortunately, the code he wrote was often difficult to understand. After a few years, I made a breakthrough. I could usually understand his code while in a state of flow. While in flow, not only I could understand his code, I could also refactor it into a form that I (and others) could understand under normal circumstances. Other programmers in the group had similar experiences.

One day, everything seemed to come together in a blinding flash. During flow, Miller's Magic Number is larger. Normally this number appears to be around 7 chunks of information that you can deal with at one time. But during flow, the number of chunks seemed to be much higher. I have no idea how large the number is, but problems that seem to be at the edge of my ability normally are quite easy to deal with while in flow.

This means that flow can have a downside. If you are not careful with how you write the code, this larger number of information chunks will always be required in order to understand the code. You have probably seen this phenomenon in code that was written as part of a long hacking run. It usually works, sometimes even works very well. But you can't quite make sense of why it works like it does. This may be one of the reasons that hacking code has gotten such a bad name. Code like this is nearly impossible to maintain.

However, you can use this enhanced facility to understand the problem and still strive for simple, elegant code. The result is code that solves a difficult problem in an obvious way. Much of the time, this solution is only obvious in hindsight. You required more information while working on the problem than you normally could handle in order to see the simple and obvious solution.

In the Jargon File, the term hack mode is used to describe this form of flow that particularly focuses on The Problem. I believe the ability to use the benefits of the increased magic number, without making your code require this concentration to understand is one of the more important skills to learn in programming.

Posted by GWade at 10:25 PM. Email comments | Comments (0)

May 07, 2004

Update on Competence

In Programmer Musings: More Thoughts on Mastering Programming I referenced the four levels of competence without being able to remember the original source. According to Four Levels Of Competence, the source is the "Kirkpatrick Model" by Donald L. Kirkpatrick.

Posted by GWade at 11:16 PM. Email comments | Comments (0)

May 01, 2004

Idiomatic Programming

Like any natural language, most programming languages support idioms. According to the American Heritage Dictionary of the English Language, an idiom is

A speech form or an expression of a given language that is peculiar to itself grammatically or cannot be understood from the individual meanings of its elements, as in keep tabs on.

Programming idioms can, of course, be understood functionally from its individual elements. The interesting thing about most programming idioms is what they say to the programmers who will read the code later. A programming idiom can reveal many things about the author of the code. To some extent, the idioms you choose are a function of your understanding of the language, what you may have been taught in school, and the other languages you have programmed in.

I made a comment on this subject earlier in Textual Analysis and Idiomatic Perl on Perl Monks. In that article, I pointed out that idioms are cultural, just like those in natural languages. Also like in natural languages, if you only have experience in one culture and language, the idioms of another appear unnatural or wrong. When learning a language, you learn the syntax and obvious semantics. You have to live with the language for a long while to learn the idioms.

The result of this is that the idioms of a language are foreign to new speakers. As they continue to use the language, simple idioms become part of their vocabulary. At first, they will misuse or over-use these idioms. Over time, they develop an appreciation for the subtleties of the expression. Eventually, those idioms are only used when necessary. With more exposure, more idioms become assimilated into your vocabulary. Soon, you learn to speak/program the language relatively fluently. Also, like natural languages, you may always retain a bit of an accent, based on your earlier languages.

This explanation seems to explain the course I've seen many programmers, as well as myself, take with a new programming language.

  1. You learn the basics.
  2. You begin to recognize and fight against the idioms.
  3. You slowly recognize the sense of simple idioms and try to use them.
  4. You over-use the idioms you know.
  5. You develop a reasonable understanding of those idioms and begin using them correctly.
  6. You learn new idioms and go back to 4.
  7. Eventually, you program with the language instead of fighting it.

The simpler the language, the quicker you get to the end of this cycle. But, a simpler language is not as expressive. So, you find yourself doing more work than should be necessary. A more expressive language takes longer to learn, but it's easier to pass on subtle shades of meaning to the maintenance programmer (possibly yourself).

This is not to say that you must use advanced idioms to be effective in a language. Sometimes, a simple expression is all that is warranted. But, complex ideas and complex problems often have subtle solutions. Subtle shades of expression can help the reader of your code recognize when they need to pay attention. In the example from my Perl Monks note, I talk about variations in intent expressed by different conditional expressions in Perl.

Idiom can also serve as a warning. An unusual or advanced idiom should tell the reader that he is entering dangerous territory. This is a message that it's time to pay close attention. A friend of mine is fond of referring to advanced code with the phrase Here there be dragons like on old maps. A properly placed idiom can do that more effectively than a comment. More importantly, if a good programmer runs into an new or unusual idiom, he should know to tread carefully and pay attention.

I think that idiomatic programming is a lot like becoming fluent in a natural language. You may be able to communicate with someone from Mexico using your high school Spanish. But going beyond simple, straight-forward communication will probably take a lot of effort. In some cases, that's enough. You can be mostly right when asking where is a restroom or how to find a taxi. But if you needed to argue a business or legal case or explain a medical condition, I'm sure you would want every possible nuance of the language at your disposal.

Programming is one of the most complex things that humans do. You have to be able to take complex concepts and translate them in such a way that they communicate to two very different audiences: an incredibly literal device and an intelligent person who may need to make subtle changes later. Explaining the instructions correctly to the computer is hard. Communicating your intent to the other programmer is even worse. If that programmer misunderstands your intent, he might ruin all of your carefully thought-out work. Well-chosen idioms can help in communicating that intent.

Posted by GWade at 11:45 PM. Email comments | Comments (0)

April 28, 2004

Refactoring, Factoring, and Algebra

I saw the article Refactoring reminiscent of high school algebra on Artima yesterday and it made me remember a new connection of my own.

In the article, David Goodger is working on a refactoring problem that is turning out to be worse than expected. After a little reflection, he manages to solve the problem. But the code had to get messier before it got better. In the end, he makes a connection between refactoring code and multiplying polynomials in high school algebra.

I had made a similar connection several years ago when teaching programming to entry-level programmers. We talked a lot about well factored code and how important it was to keep your code factored. (This was before the term refactoring became mainstream.) I had several students ask how to recognize when the code was well factored.

Now I truly hate to use the answer "experience" when someone asks me a question like this, so I spent some time talking with other senior programmers and thinking about it and came to a realization similar to Goodger's. (Re)Factoring code is like factoring composite numbers. You stop factoring composite numbers when you have nothing but prime factors left. You stop (re)factoring code when the code has reached the point of being minimal. Each piece does one thing and does that well. Your program is then the composite of these fundamental pieces. This is also a lot like factoring polynomials.

Now, I think I would go a little further to say that code that embodies one concept or action or code that has a simple or obvious name is factored enough. To try to factor it any further would make it more complicated.

I think another really important part of Goodger's article was the point that sometimes the code has to be made uglier before you can finish refactoring it.

Posted by GWade at 05:49 PM. Email comments | Comments (0)

April 23, 2004

A Report on UML Fever

ACM Queue often has very insightful articles, and this one is no exception. The article ACM Queue - Death by UML Fever - Are you (or your developers) sick? covers a major problem in software development in a somewhat humorous fashion. I've seen several variations of this problem, but I never thought of it as an illness.

For those who are appalled at the article and who consider the author to be a heretic, you might to step over to another article in the same issue. In ACM Queue - The Fever is Real -, Grady Booch comments on the UML Fever, both the article and the affliction.

Another good piece of commentary on the subject is java.net: Death by UML-more [April 23, 2004].

The most important thing to take away from all of these articles is that UML is a tool (or a set of tools), not a life style, not a religion, and certainly not a solution to all problems everywhere. I'll be glad when more people use the tool was it was intended, instead of using in yet another round of holy wars.

Posted by GWade at 10:52 PM. Email comments | Comments (0)

April 22, 2004

More Thoughts on Mastering Programming

Reading Dave Thomas's blog on Code Kata pointed to PragDave: MoreKata. I also stumbled across How To Practice on Chad Fowler's blog.

Both of these entries discuss exercises that we can use to master the art of programming. Perhaps surprisingly, they use metaphors and concepts from martial arts and Zen. Those of us who have been programming for a while can easily see the parallels.

I'd like to add a description of attaining mastery of a subject that I saw once a long time ago. (I can't find the reference right now, or I would give credit.) The description listed four phases of understanding.

  1. Unconscious incompetence
  2. Conscious incompetence
  3. Conscious competence
  4. Unconscious competence
Posted by GWade at 06:43 AM. Email comments | Comments (0)

April 21, 2004

Programming Practice

I'm not quite sure what lead me to this blog entry, but I find PragDave: Code Kata by Dave Thomas to be a very interesting idea. I had never thought of describing coding practice as similar to kata.

I did stumble into a similar idea once many years ago. I had just finished reading Software Tools by Kernighan and Plauger, when I came to the conclusion that implementing some of the classic Unix text tools from scratch would be a good way to improve my programming. None of the programming problems themselves were particularly hard, but I got the idea that building a clean implementation of each tools would help me focus on practical aspects of my programming.

Over the next few months, I built a number of the classic tools. In each case, I worked hard to generate a clean, minimal, and complete implementation of each tool. I went over each piece of code several times to ensure that there was nothing unnecessary or unclear in the code. When I finally declared this experiment finished, I really felt that my coding and design skills had improved dramatically.

I haven't thought about that exercise in a number of years. Now that PragDave has reminded me of it, I think I may need to pick a similar project to see if I can help keep those mental muscles in shape.

Posted by GWade at 10:26 PM. Email comments | Comments (0)

April 14, 2004

The Recurring Myth of Programmer-less Programming

Why is it that some people regularly suggest that someday soon we won't need programmers? I've recently been seeing this as part of the IT outsourcing debate. But, this isn't the first time I've seen it. The first time I saw this meme was shortly after I started programming professionally 17 years ago. I talked to some other people at the time and they said it was old at that time. In all of this time, I have yet to see it happen.

Why does this meme keep coming back? Why are some people so drawn to the idea? And, just as importantly, why is this a myth?

The most recent version of this meme has compared programmers to typists. After all, a decade ago no one would have believed that there would have been no need for typists. But now everyone types their own documents and there is no need for someone who's title is typist. So, with an interesting jump in logic, someday soon we won't need programmers either.

I'm sure that most people developing software would consider the comparison of programming to typing completely invalid. But, how do you explain that to someone who wants to fire the experienced developers to replace them with cheap college students or workers in third world countries? After all, if programming is equivalent to typing, shouldn't we just replace expensive programmers with cheap typists and make more money?

I think this a great example of a mistaken analogy. It would be more accurate to compare typists to scribes and programmers to writers.

There was a time in history when scribes were the only people trained in writing. But much of what the average scribe did was copy or transcribe the works of others. Some scribes were scholars, and many were quite skilled. But, most scribes did not do original writing. As more people learned to write, the need for scribes went away. Much like the use of word processors reduced the need for typists.

However, just because people learned to use pen and ink to write down everyday information, the need for writers did not go away. In fact, writers were more in demand as people became more literate. There is more to writing than the skill of using a pen and ink.

Likewise, there is more to programming than typing on a computer. I've noticed that many people who don't program or who have only a cursory understanding of programming assume that there's not much to it. After all, you figure out what you want to do, you type it in to the computer and the computer does it. How hard can that be?

Much like writing, there is more to programming than meets the eye. I've heard it said that for a good writer, getting an idea is only the beginning. There are hours of research and rewrites and real work that goes into almost everything worth reading. Much like a good piece of writing, a good program may look easy once it's finished, but not everyone is able to research and design and rework to get that finished product.

The two hardest parts of programming are similar to the hardest parts of writing. In both, you have to be able to really think clearly and understand to a degree that most people don't understand. Secondly, in writing and programming, you have to be able to communicate those thoughts.

Unfortunately, in programming you have two very different audiences. The first is the dumbest machine ever invented. Computers do exactly what you tell them to, no matter how dumb the instructions are. No person or animal would ever follow instructions that blindly. The second audience is other programmers (or yourself some time in the future). This audience has to be able to understand what you wrote well enough to adapt it at a later time.

Because this is much harder than almost any non-programmer can fathom, there is this myth that we can replace programmers with something mechanical or with non-programmers. This is much like saying that we'll replace the people that write the company's press releases or contracts with trained monkeys. After all, how hard can it be to crank out this stuff. The answer is (in both cases) some of the writing/programming can be done practically automatically. But the stuff that really matters, the stuff that makes the difference between success and failure needs someone skilled and talented to write it.

Posted by GWade at 07:20 PM. Email comments | Comments (0)

April 04, 2004

Miller's Magic Number

George A. Miller's paper The Magical Number Seven, Plus or Minus Two: Some Limits on Our Capacity for Processing Information discussed some limits of the human brain with respect to information processing. In particular, his research had found that people are unable to keep up with more than 5-9 different chunks of information at a time. This is actually why phone numbers in the United States are seven digits long, or more accurately, why they used to be an exchange and four digits. (The exchange was eventually replaced by three digits.)

I know a lot of you are thinking that information cannot be true. After all, you know you can keep more things in mind at one time. Right? According to Miller, the key is the word chunks. These chunks can be different sizes. A concept that carries a lot of information is still a single chunk. This is why is is harder to remember 10 randomly chosen numbers or words than it is to remember the words to a song.

A large portion of the history of programming has been devoted to making our chunks more meaningful. Higher level languages allow us to work without keeping trying to remember what each register is holding and how many bytes to we need for that jump instruction. Each succeeding level allows us to keep more in our heads by making the chunks bigger.

But that only works as long as the concepts map well to single chunks. If you don't have a name for a chunk of information or a concept, it takes up more of your memory. One of the more useful effects of Design Patterns was not new OO techniques. It was the new names. Suddenly, you could refer to the Singleton Pattern instead of this class that there's only one of in the whole system but is available to everyone, sort of like global data but not quite.

This same concept applies to user interface design. Grouping related items on the screen and putting the most commonly used items where they are immediately accessible are two ways to reduce the amount of your mind tied up by keeping up with what goes where.

The concept of chunks and Miller's magic number applies in many places in programming. Here's a few to ponder:

  • Good variable names make it easier to remember what they are used for.
  • Global variables make code more difficult to understand, because you use up chunks thinking about those variables.
  • Good method names replace all of the implementation details with one chunk.
  • Flags are bad for the same reason as global variables.
  • A good programming metaphor helps you develop because more concepts are tied into one chunk.
  • Programming paradigms help you solve programming problems by giving you names for concepts you need to work with.
  • A programming language is well suited for a problem when you can express your solution in fewer chunks in that language. (Some might say you can express it more naturally in that language.)
  • Good data structures help to design by reducing a number of related variables into a single coherent chunk.
  • Good classes embody one chunk of information. Bad classes either express multiple chunks or none.
Posted by GWade at 05:49 PM. Email comments | Comments (0)

March 20, 2004

The Paradigm Paradox

I've already written some of my ideas about paradigms, but the most important point still remains. I maintain that the real benefit of a paradigm is not the way of thinking that it defines, but the contrast between this way of thinking and others. Almost every programmer I've ever met that only knows one paradigm seems to be hampered by an inability to look a problems from more than one viewpoint. I believe that much of the benefit that is derived from changing to the newest paradigm comes from the change. This would also help to explain why the decision to train people only in this newer paradigm does not automatically create better programmers.

Most of the entry-level programmers that I've worked with have been about the same level of effectiveness no matter what paradigm they were trained in. This has been true of structured programmers, modular programmers, and object oriented programmers. This effect also applies to the languages they were trained in. I haven't seen any improvement in new Java-trained programmers over the older C-trained programmers or the Pascal-trained programmers before them.

Each generation of programmers seems to make a lot of similar kinds of mistakes. Although, the syntax of a new language may prevent you from making certain particular mistakes, there are certain classes of mistakes that remain. All of these entry-level programmers seem to start with a misunderstanding of complexity and an inability to look at a problem in an unusual way. They immediately begin to apply the paradigm without first understanding the problem. They also tend to solve problems in older code by adding more code, even when that is usually not the best solution.

No new paradigm that I've seen has solved these fundamental problems. I think that the effort to learn and work with more than one paradigm (or language) forces you to realize some of the differences between a good solution and an artifact of the paradigm (or language). For example, one of the most basic attributes of a good design is simplicity. This has been true in each of the programming paradigms that I have used. It is also true in mathematics, logic, and science.

Given two designs that meet all of the requirements, the simpler one is usually the best. One of the most important aspects of the OO paradigm is its ability to deal with complexity. A side effect of using OO is that many people add more complexity, because they can handle it. They are much more likely to build a framework of supporting classes, even for problems that don't require them. In this way the OO paradigm can lead an inexperienced programmer to build a worse design through the addition of unnecessary complexity.

If you have experience in more than one paradigm, you are more likely to recognize this unnecessary complexity than if you've just done OO programming. Why? Because if you could solve the problem more simply without the object approach, you would see that solution as well. The contrast between these two possible solutions would cause you to look for ways to get the benefits of OO but the simplicity of the other solution. The result is often a simpler design.

On the other hand, some problems actually only have complex solutions. When comparing two or more approaches in this case, the complexity is more apparent because you can see the difficulty with using the non-OO approach. Nevertheless, the second viewpoint can provide insight into other difficulties that might be masked by the OO design. In this case, the second viewpoint does not replace the OO design, it enhances it.

So, to reiterate, the most important feature of a new programming paradigm is the contrast between that paradigm and the ones that preceeded it.

My other blog entries on programming paradigms:

Posted by GWade at 10:15 PM. Email comments | Comments (0)

March 10, 2004

Bad Names and Good Bad Names

And just to prove that some ideas appear in many places all at once, Andy Lester explores naming again in O'Reilly Network: The world's two worst variable names [Mar. 07, 2004]. Apparently, Lester is reviewing the second edition of Code Complete, one of my favorite books on programming, and says there is "an entire chapter devoted to good variable naming practices."

The comments on the Lester's weblog are a spirited little discussion on variable naming. But I have my own thoughts, of course.

A large portion of programming is actually thinking. We spend time thinking about a problem and convert our thoughts and understanding into code. As such, names are critical to clear thought and code. One comment on Andy Lester's weblog suggested that the time spent coming up with good names was better spent writing code. This is an attitude I have heard many times in the past and have worked hard to remove when teaching.

Good names help when reading code. More importantly, good names help when thinking about the code, both during maintenance and during development. Humans are only capable of keeping a small number of distinct concepts in mind at once (7 +/- 2 according to George Miller), a good name can help by abstracting away a large amount of unnecessary detail. This allows you to focus on the important part of the code.

I try hard to always use good names for variables, functions, classes, programs, and any other artifact of my programming. I have learned over time that good names make my programming flow more easily. When I run into a method or variable that I can't name, I know that I don't truly understand that part. Many times I will give it a placeholder name until I understand it well enough that a name presents itself. At that time I try to always go back and rename the item to show my new understanding. To me, good naming is part of good design.

In contrast, if you use good names in general, there are places where bad names can be good ones. For example, i, j, and k are lousy names for variables. But, as loop variables, they become useful because we are used to them. Since the early days of FORTRAN these were the canonical loop control variables. Most people would recognize that without thinking about it.

One of my favorite bad variables is dummy. I only use it in one circumstance, I need a placeholder in an argument list that will receive a value that I will not use. Anybody looking at this code should understand the meaning pretty quickly. For example, in the C++ code below, dummy is used to receive a delimiter that I don't care about.


config >> row >> dummy >> column;

I also have one really bad program/script name that I use regularly. The name is so bad that it carries with it an important meta-meaning. I often write one-shot, throw-away programs or scripts and name each of them doit. These scripts are for jobs that are easy enough to rewrite that I would spend less time rewriting it than I would spend trying to remember what I named it last time. I often write the doit.pl Perl script for work that is a little too complicated for me to do as a one-liner, but not complicated enough to really build a real program for.

The meta-meaning of the doit program is interesting. If I find a doit script or program in any directory, I delete it. It was designed as a throw-away and I know that it is not really worth keeping. Every now and then one of my doit scripts evolves into something useful while I am working through a problem. At that point, I give it a real name and it becomes part of my toolkit, at least for that project.

The subject of names in programming is more important than many people realize. Some really bright people have written on the subject. Steve McConnell had some good advice in Code Complete. Andrew Hunt and David Thomas explain some of the reasons that bad names cause harm in The Pragmatic Programmer. Andy Lester had another weblog entry on names a short while back, O'Reilly Network: On the importance of names [Feb. 15, 2004]. In the comments to that entry, he pointed to Simon Cozen's article Themes, Dreams and Crazy Schemes: On the naming of things from a couple of years ago.

Posted by GWade at 09:32 PM. Email comments | Comments (0)

March 03, 2004

Review of The Logic of Failure

The Logic of Failure
Dietrich Döner
Perseus Books, 1996

This is a spectacular book on why people make mistakes. This book gives wonderful insight into design and troubleshooting that you won't get from any traditional book on programming.

A large portion of the book describes how very intelligent people make what seem like blindingly stupid mistakes. It shows how people stop thinking logically when there is a delay in feedback between cause and effect. It also shows how people react when the situation seems to be getting worse despite their efforts. You have probably observed some of these behaviors in people when things really begin to go wrong.

Amusingly enough, a friend of mine pointed out that this book would have been much more successful except for the mistake of focusing the title on failure. With that focus, obviously some of people avoided the book because the don't want to contemplate failure.

Although not a normal book on programming, I would recommend this book to any programmer who needs to do troubleshooting or development under the gun. It may not solve your problems, but it may help you understand when people including you start reacting to a stressful situation.

Posted by GWade at 10:23 PM. Email comments | Comments (0)

February 22, 2004

Paradigms Found

In my weblog entry Programmer Musings: Paradigms Lost from about five days ago, I talked about some of the negative effects of programming paradigms. That entry could give the impression that I don't believe in new paradigms. Nothing could be further from the truth. However, I think the main benefit of new programming paradigms is often overlooked.

Each programming paradigm that comes along gives us new tools to use in our quest to create great solutions to hard problems. As I said before, these new tools don't necessarily invalidate the last paradigm or its tools. So, what does a new paradigm give us?

Conventional wisdom says that each new paradigm has given us better tools and techniques for dealing with complexity. The object oriented paradigm allows us to deal with more complexity than the modular paradigm which came before it. These new tools and techniques come with a cost; a relatively high learning curve. It also came with some complexity of its own. This is not to say that the new complexity and learning curve aren't worth it, but it is important to acknowledge that cost. This cost is especially high on projects where the old paradigm was enough. A very good example of this cost is the standard Hello World program.

In the modular style of C, this would be


#include <stdio.h>
int main(void)
 {
  printf( "Hello World\n" );
  return 0;
 }

In the object oriented style of Java, this would be


class Hello
{
  public static void main( String [] args )
   {
     System.out.println( "Hello World" );
   }
}

In the object oriented style, we need to know about a class, static methods, public access, the signature of the main method, a String array, and the proper method of the out object in the System package to call for printing to the screen. In the modular style, we need to know the signature of the main function, how to include the I/O library, and the name of the library function to print a string.

Granted, in a larger, more complex program this overhead in understanding would not be quite so overwhelming. In a simpler or less complex program, this extra overhead may not be worthwhile.

You might ask "why not just learn one paradigm?" After all, if the latest paradigm handles complexity best, why not just use that one. Part of the answer is because of this understanding overhead. I've seen many cases where people did not automate a process they were going to manually execute dozens of times because it was not worth writing a program to do it. When you make this decision, the computer is no longer working for you. We're programmers. The computer is supposed to do the grunt work, not us.

Okay, why not just use the more advanced paradigm even on small quick-and-dirty programs. In a small program people often find that they must spend more time supporting the paradigm than solving the problem. This is why shell scripting is still used. Why write a program to automate a half dozen commands you type to get your work done when a short shell script or batch file can solve it for you? Agreed, it doesn't support the latest paradigms, but it does get the computer working for you, instead of the other way around.

However, we still haven't touched what I feel is the most important thing about new paradigms. New paradigms give you a different way to think about solving problems. This is not meant to imply a better way, just a different one. If you have two (or more) ways to look at a problem, you have a better chance of coming up with a great solution.

By having two or more ways to view a problem, you increase the number of approaches you can use to tackle the problem. Maybe you don't need a large object framework to handle this problem, maybe a straight-forward procedural filter implementation will do. In another problem, you might have too much required flexibility to deal with in a procedural program maybe the Strategy Pattern with appropriate objects is a better approach. Then again, possibly a little generic programming with the STL is exactly what you need.

The unfortunate problem with programming is that the complexity never goes away. Different paradigms just manage the complexity in different ways. Some paradigms handle some kinds of complexity better than others, but they do it by changing the form of the complexity, not by making it disappear.

The most important thing about knowing multiple paradigms is that it allows you to decide how to manage the complexity in any given problem. By giving yourself more options and more ways of looking at the problem, you increase the chances of finding a good match between solution and problem no matter what problem you are trying to solve. That, in my opinion, is the most important advantage of a new paradigm.

Posted by GWade at 10:10 PM. Email comments | Comments (0)

February 17, 2004

Paradigms Lost

An earlier weblog entry, Programmer Musings: Paradigms limit possible solutions, spent some time on what paradigms do for programming.

Now, I'd like to consider a slightly different take on programming paradigms. Why do programming paradigms seem to take on the force of religion for so many in the software field?

Although I haven't been a professional programmer as long as some, I have been in the business long enough to weather several paradigm shifts. Now before you run screaming away from that annoyingly over-used phrase, I really mean it in the literal sense: shifting from one dominant paradigm to another.

When I started structured programming was king. That was later refined with modular programming. I remember a brief stint in literate programming that never really seemed to catch on. The current big thing, object oriented programming, has even spun off sub-paradigms like aspect oriented programming. Let's also not forget generic programming that hit the mainstream with the C++ Standard Template Library and Design Patterns introduced by the Gang of Four book. Along the way, I even dabbled in functional programming.

Each of these, in it's time, helped programmers to build better code. Each paradigm has strong points and weaknesses. What I don't understand is why the people adopting each new paradigm find it necessary to throw away good ideas because they were associated with an older paradigm. I am continually amazed that someone would look at a perfectly reasonable program in the structured programming style and dismiss it as not object oriented. So what. Does it do the job? Is it still plugging away after being ported to three different operating systems and who knows how many environments? If the answer to these questions is "yes", who cares how it was designed?

A couple of weeks ago, I saw the weblog entry O'Reilly Network: Overpatterned and Overdesigned [Jan. 28, 2004], where chromatic blasts the overuse of patterns. I was genuinely amused and heartened by his example and the comments that followed. One of the points of his entry was that design patterns can be overused. I am willing to go a little further, any paradigm can be overused.

The biggest problem I see with programming paradigms is a misunderstanding of what they really mean. A paradigm is a way of thinking about problems and solutions. Using a paradigm should not be a life altering event. In my experience, no paradigm is inherently better than all others. However, it seems that as each new paradigm is discovered, a large fraction of the people using it feel the necessity to ignore all of the lessons of previous paradigms, as if they somehow no longer apply.

The really funny thing is that, over time, we see the same lessons come back, often with different names. In the old structured/modular programming days, you could improve a program by reducing the coupling between unrelated code and increasing the cohesiveness of your modules. Of course, no one does structured programming now. Instead we would refactor our classes to increase independence between our classes possibly through the use of interfaces or abstract classes. We would also work to provide minimal interfaces for our classes. We want to make sure that those classes only do one thing. Sounds familiar, doesn't it.

Hopefully, one day, we will learn to treat a new paradigm as just another tool in our kit. Remember carpenters did not throw out their hammers and nails when screws and screwdrivers were invented. Each serves a slightly different purpose and provides useful options. I see programming paradigms in a similar light.

Posted by GWade at 06:44 PM. Email comments | Comments (0)

February 07, 2004

The Forgotten OO Principle

When talking about Object Oriented Programming, there are several principles that are normally associated with the paradigm: polymorphism, inheritance, encapsulation, etc.

I feel that people tend to forget the first, most important principle of OO: object lifetime. One of the first things that struck me when I was learning OO programming in C++ over a decade ago, was something very simple. Constructors build objects and destructors clean them up. This seems obvious, but like many obvious concepts, it has subtleties that make it worth studying.

In an class with well-done constructors, you can rely on something very important. If the object is constructed it is valid. This means that you generally don't have to do a lot of grunt work to make sure the object is set up properly before you start using it. If you've only worked with well-done objects, this point may not be obvious. Those of us who programmed before OO got popular remember the redundant validation code that needed to go in a lot of places to make certain that our data structures were set up properly.

Since that time, I have seen many systems where the programmers forgot this basic guarantee. Every time this guarantee is violated in the class, all of the client programmers who use this class have a lot more work on their hands.

I'm talking about the kind of class where you must call an initialize method or a series of set methods on the object immediately after construction, otherwise you aren't guaranteed useful or reliable results. Among other things, these kinds of objects are very hard for new programmers to understand. After all, what is actually required to be set up before the object is valid? There's almost no way to tell, short of reading all of the source of the class and many of the places where it is used.

What tends to happen in these cases is the new client programmer copies code from somewhere else that works and tweaks it to do what he/she needs it to do. This form of voodoo programming is one of the things that OO was supposed to protect us from. Where this really begins to hurt is when a change must be made to the class to add some form of initialization, how are you going to fix all of the client code written with it. Granted, modern IDEs can make some of this a little easier, but the point is that I, as the client of the class, will need to change the usage of the object possibly many times if the class implementation changes.

That being said, it is still possible to do some forms of lazy initialization that save time at construction time. But, the guarantee must still apply for a good class. After construction, the object must be valid and usable. If it's not, you don't have an object, you have a mass of data and behavior.

The other end of the object's lifetime is handled by a destructor. When an object reaches the end of it's life, the destructor is called undoing any work done by the constructor. In the case of objects that hold resources, the destructor returns those resources to the system. Usually, the resource is memory. But, sometimes there are other resources, such as files, database handles, semaphores, mutexes, etc.

If the object is not properly destroyed, then the object may not be accessible, but it doesn't really die. Instead, it becomes kind of an undead object. It haunts the memory and resource space of the process until recovered by the death of the whole process. I know, it's a little corny. But, I kind of like the imagery.

This concept also explains one of the problems I have with some forms of garbage collection. Garbage collection tends to assume that the only thing associated with an object is memory. And, as long as the memory is returned before you need it again, it doesn't really matter when the object dies. This means that we will have many of these undead objects in the system at any time. They are not really alive, but not yet fully dead. In some cases, you are not even guaranteed that the destructor, or finalizer will be called. As a result, the client programmer has to do all of the end of object clean up explicitly. This once again encourages voodoo programming as we have to copy the shutdown code from usage to usage throughout the system.

So keep in mind the importance of the lifetime of your objects. This is a fundamental feature of object oriented programming that simplifies the use of your classes, and increases their usefulness.

Posted by GWade at 12:16 PM. Email comments | Comments (0)

January 14, 2004

What is a programming idiom?

In a natural language, an idiom is a phrase that conveys more information than the individual words combined. In order to understand an idiom, you must have knowledge of the culture of the person using the idiom, as well as a good grasp of the language.

Programming idioms are similar. They involve uses of particular aspects of the language in ways that convey extra information to the reader of the code. Sometimes idioms are used to expand the abilities of a language. Other times, idioms are used to restrict what you can do with a construct. In all cases, however, the idiom relies on an understanding of the culture fom which it is formed.

Posted by GWade at 08:08 PM. Email comments | Comments (0)

Paradigms limit possible solutions

Different paradigms are basically different ways of thinking about or looking at problem solving. Programming paradigms usually also include ways of organizing code or design.

The main purpose of any paradigm is to reduce the number of possible solutions to a problem from infinity to a small enough number that you have a chance of picking one. Most proponents of a given paradigm argue strongly that their paradigm of choice removes more possible invalid solutions without seriously impacting valid solutions.

In actual fact, every paradigm eliminates both valid and invalid solutions to any given problem. This is not necessarily bad. However, it does mean that by choosing a particular paradigm, you are closing yourself off from protentially useful solutions or ways of approaching a problem.

Posted by GWade at 08:05 PM. Email comments | Comments (0)