Wednesday, January 19, 2011

Training Wheels

Parents put training wheels on bicycles when children first start learning to ride.  Once the children have enough experience and confidence, however, the training wheels get in the way and are removed.  You will never see an experienced cyclist using training wheels.  But you will often see experienced computer users, including software developers, using features meant for novices.

There is certainly a lot to learn about using computers, especially with complex applications such as development environments.  It makes sense to help people learn and adjust to the proper use of these applications.  But as they acquire experience, they should be able to remove the training wheels, lest they become a burden.  And giving people help on the learning curve should not mean dumbing down interfaces!

The mouse is a case in point.  I believe that taking the mouse away from developers' workstations will improve developer productivity more than any other action, possibly after a small adjustment period.  I keep seeing developers waste their time using the mouse to hunt for a specific menu entry, which may be hidden under one or more menu levels.  This is becoming more and more difficult as screen resolutions get higher and the clickable area gets proportionately smaller.  Instead, a short sequence of keys (two to five keystrokes) will achieve the same result in less than a second.

Of course, in order to take advantage of the keyboard you need to memorize quite a few such sequences.  This is impossible for a casual user of the application, but happens easily and automatically for an experienced user, given some learning time.  It's just like riding a bicycle: at first you need to think explicitly about every action you take, but after a while it becomes automatic.  And this is true for any repetitive activity, such as walking, swimming, driving, or touch typing: if you practice enough, it becomes automatic to the point where trying to think about it is actually confusing.  Try to think about the mechanics of walking while you do it, and you will lose your step.  I touch-type, and when I'm asked about the location of some key I need to mimic the typing movement in order to get reminded of the location through my muscle memory.  Similarly, I know a great many keyboard shortcuts for applications I use frequently, but I find it hard to articulate the key sequences.

I'm an avid  Emacs user.  Emacs (The One True Editor) has many excellent qualities, but it has a steep learning curve because of its many keyboard shortcuts, which can use any combination of the Ctrl and Alt modifiers with any other key, and also have multi-key combinations.  (The Lisp Machine keyboard, also called the space-cadet keyboard, had two additional modifiers, called Super and Hyper).  Once you learn these sequences, however, you can make text fly on the screen.  I remember sitting next to one expert when I first got to MIT; I was staring at the screen and seeing the program transform itself, and I couldn't relate what I was seeing to the keystrokes used to create that effect.  I have since had that effect myself on other people.

For many years I taught courses based on the excellent book Structure and Interpretation of Computer Programs, which uses the Scheme programming language.  My interpreter of choice for that course was MIT Scheme, whose editor component is based on Emacs.  However, students prefered to use Dr. Scheme, simply because it has a more conventional editor.  I can understand their reluctance to learn the Emacs keystrokes, since they thought this skill would only be useful in that single course.  But there is no justification for professional developers to avoid learning skills that will make their work easier and faster.

Unfortunately, the tendency among tool developers is just the opposite.  They target their interfaces at the lowest common denominator, thus making the tools attractive to the novice user but hampering the experienced user.  Mouse-based interfaces are today much preferred over keyboard shortcuts, to the extent that Microsoft has eliminated the indications of which keyboard shortcuts are available (using underlined characters); these can be revealed through the "accessibility options" dialog, as though you are somehow mouse-challenged if you want to use the keyboard.  (Hint: look for "Show extra keyboard help in programs".)

Because of this behavior of tool writers, users have no chance to learn the keyboard shortcuts and make their use automatic.  Thus, they are stuck with the laborious use of the mouse and never outgrow their training wheels.  As always, you should use the right tool for the job.  The mouse is good for applications you only use occasionally, and is indispensable for some graphics applications.  But keep your hands on the keyboard for software development!

Monday, January 10, 2011

Pointers: Two Views on Software Development

I recommend reading the article Risks of Undisciplined Development by David Parnas in the Inside Risks column of the October 2010 issue of Communications of the ACM.  David Parnas is famous (among other things) for opposing Reagan's Stragegic Defense Initiative (SDI, nicknamed "Star Wars") on the grounds that software engineering state-of-the-art was not sufficient to build such a system (it still isn't).  This article urges using, and teaching, disciplined development practices.

On a lighter note, here is another view of a good software development process:



This is taken from xkcd, which I follow regularly (together with Dilbert).

Sunday, January 2, 2011

It Looked Good When We Started

"Well, in our country," said Alice, still panting a little, "you'd generally get to somewhere else — if you run very fast for a long time, as we've been doing."
"A slow sort of country!" said the Queen. "Now, here, you see, it takes all the running you can do, to keep in the same place. If you want to get somewhere else, you must run at least twice as fast as that!"
— Lewis Carrol, Through the Looking-Glass, and What Alice Found There  

The Red Queen's race is often quoted in discussions about the increasing rate of change in our lives, which demands running just to stay in the same place.  This is particularly true in software development, where change is the only constant factor.  Any successful software system will need to be changed, and, if it can't, will fall by the roadside.  Change can be due to many factors, such as new legislation, new standards, new business opportunities, the need to support a larger volume of business, or the need to move to a new platform (for example, when an operating systems is no longer supported by the vendor).  The introduction of a software system into an environment that didn't have one before changes that environment; that change ripples to create new requirements.  Perhaps initially the system was only meant to replace manual work, as when using computers to track customer orders instead of doing it on paper.  But once the system is in place, users realize that it can do much more; for example, letting customers enter their own orders through the internet.  Adding that functionality may trigger the idea that customers may now be able to track the order fulfillment process through the internet.  Every change is the catalyst for another one.

There is a common misconception that software is easy to change.  It is certainly true that distributing software updates is much easier (and cheaper) than changing hardware.  However, making changes in software correctly while preserving previous functionality and without introducing new bugs is very difficult, as all experienced software developers know.  (In spite of that, they are often content to get their software more-or-less working, and rely on the users to discover problems and on future releases to fix them; see Programmers as Children.)

Obviously, it makes sense to prepare for future changes, so as to make the inevitable changes as easy to make as possible.  Some requirement changes, however, are beyond prediction, and many predictions that seem obvious at the time turn out to be wrong.  These unfortunate facts have far-reaching implications on all aspects of software development.  They mean that it is impossible to capture the precise system requirements, since these will change unpredictably.  They also mean that it is impossible to prepare for all future changes, since every design decision renders some future modifications easier while making others more difficult.  But they do not mean that we should just build for known requirements and ignore the possibility (or rather, inevitability) of future changes.

The Agile Development community takes an extreme view of our inability to predict future changes, and uses the rule "do the simplest thing that could possibly work" (abbreviated DTSTTCPW).  This rule is based on the assumption that keeping the code as simple as possible will make future changes easier; it also assumes the meticulous use of refactoring to keep the code as clear as possible while making changes.  (I have a lot to say about refactoring, but that will have to wait.)  However, there are some decisions that are very difficult to back out of, and I believe that most agilists will agree that planning ahead on these decisions does not violate DTSTTCPW.

One of the earliest decisions that must be made, and one that has a profound influence on all aspects of the development process, is about tooling, including the programming languages to be used.  Changing the language after a considerable amount of code has been written is very difficult.  Changing languages in a system that has been in operation for over twenty years and contains several million lines of code is almost impossible; this almost amounts to a complete rewrite of the system.  Much of the knowledge about many such systems has been lost, and the original developers are probably doing other things, possibly in other companies, and may even be retired.  Yet there are many large-scale critical systems that are over twenty and even thirty years old.

This received world attention in the 1990s, with the famous Year 2000 (or Y2K) Problem.  Programmers in the 1960s were worried about the size of their data on disk, since storage sizes were much much smaller in those days.  It seems incredible that today you can get a terabyte disk for well under $100; in those days you would be paying by the kilobyte.  So the decision to represent dates using just two digits to represent the year was very natural at the time.  In fact, it can be argued that it was the right decision for the time.  It was inconceivable that these systems would last until the year 2000; it was obvious that they will have to be replaced well before then.  Programmers kept to this design decision during the 1970s, 1980s, and, incredibly, even sometimes in the 1990s, although by then storage sizes have grown, prices came way down, and 2000 was not the far future any more.

To the amazament of the 1960s programmers, some of the systems they wrote then are still in operation today.  Naturally, these survivors contain some of the most critical infrastructure used by banks, insurance companies, and government bodies.  All of these had to be revamped before 2000 (actually, some even before 1998; for example, credit cards issued in 1998 expired in 2000).  This cost an incredible amount of money, and quite a few companies made their fortunes from Y2K remediation.  The fact that this effort was largely successful, and none of the doomsday predictions made before 2000 came to pass, should not detract from the importance of the problem and the necessity of fixing it.

Now switch points of view, and think twenty years into the future.  The code we are writing today is the legacy of the future; what kind of problems are we preparing for ourselves or the people who will be saddled with these systems after us?  I claim that we are being as incosiderate as the programmers of the 1960s, and with less justification.  After all, we have their lesson to learn from, whereas they were pioneers and had no past experience to draw on.

These days, we are building systems using a hodgepodge of languages and technologies, each of which is rapidly changing.  A typical large-scale project will mix code in Java for the business logic, with Javascript for the browser front end, SQL for the back-end database, and one or more scripting language for small tasks.  A legacy Cobol system may well be involved as well.  The Java code will be built on top of a number of different frameworks for security, persistency, presentation, web services, and so on.  Each of these will change at its own pace, necessitating constant updates to keep up with moving technologies.  How will this be manageable in twenty or thirty years?

Think of a brand new application written by a small startup company.  They are very concerned about time to market, but not really thinking twenty years into the future (after all, they want to make their exit in five years at the most).  And their application is quite small, really, just perfect for a scripting language such as Perl, Python, or Ruby on Rails.

If they fail, like most startups, no harm has been done.  But suppose their idea catches on, and they really make it.  They keep adding more and more features, and get more and more users.  Suddenly they have many millions of users, and their system doesn't scale any more.  Users can't connect to their site, transactions get dropped, and revenue is falling.  Something must be done immediately!  The dynamic scripting language they used was very convenient for getting something running quickly, because it didn't require the specification of static types, classes could be created on the fly, and so on.  But now that the application consists of 20 million lines of code, it is very difficult to understand without the missing information.

This scenario is not fictional; many successful internet companies went through such a phase.  In fact, even infrastructure tools went through such changes.  For example, consider PHP, one of the most successful web scripting languages today.  PHP 4 was downward compatible with PHP 3, although it had a number of serious problems, because it was deemed inappropriate to disrupt the work of tens of thousands of developers.  PHP 4 was wildly successful, with the result that when incompatible changes had to be made in PHP 5, hundreds of thousands of developers were affected, and adoption of PHP 5 was delayed.

I think that many times developers use the wrong tool for the job (see The Right Tool for the Job) because it looks like the right tool when development starts.  Changing circumstances then make it the wrong tool, but switching tools can be extremely costly.  With hindsight, it was obviously a mistake to base the whole application on a scripting language in the scenario described above, even though it looked like a good idea at the time.  Can we use foresight to avoid such mistakes in the future?

Monday, December 27, 2010

The Right Tool for the Job

No professional plumber will think of using a wrench instead of a hammer (although quite a few amateurs wouldn't think twice before doing so).  In fact, professionals avoid adjustable wrenches, using the appropriate fixed wrench for each task.

Developers are much less picky about the tools they use.  Take, for example, the C programming language.  It was designed for the development of the Unix operating system, and is well suited for that task.  It allows low-level access to the computer's resources, it can be efficiently compiled into efficient code, and requires very little run-time support while not being tied to any specific hardware platform.  In essence, it is a high level, machine independent, assembly language.  Of course, these properties, extremely desirable for writing system code, required the language designers to make various choices that make C extremely unsuitable for most other programming tasks.  For example, the desire for efficiency and free access to all memory led to unconstrained array access, without any runtime bounds checking.  This has enabled the infamous buffer-overrun attacks, so favored by writers of viruses and other malware.  In turn, these require extra care by application developers who use C or C++ (which, unfortunately, preserved this and many other undesirable properties of C), and necessitated the development of various tools for checking C/C++ programs for these kinds of errors.

Why did this happen?  There are several reasons why programmers don't always use the right tool for the job.  One is the generality of our tools.  Usually, the same application can be written using any general-purpose language.  One may be particularly well-suited for the task, but, with more effort, others can be used.  Another reason is the steep learning curve associated with our tools.  It takes a lot of time to learn all the intricacies of a programming language (or other tool), and a programmer who mastered one will be reluctant to start from scratch with another language just to perform one task.  Obviously, the developers of the Unix kernel were masters of the C language (and influenced or even participated in its evolution).  It was natural for them to write all other parts of the system, including user applications, in the same language.  The great success of Unix and C created a large body of programmers whose hammer was C, and all applications their nail.  As a result, the first really popular object-oriented language, C++, was built as an extension to C, inheriting all the properties that make it unsuitable for user-application development.

It took a long time until a successor appeared, in the form of Java, which rejected many of the assumptions underlying C and C++, and removed many of its undesirable properties.  (Of course, we are still stuck with the awful syntax, which is responsible for a whole set of problems by itself.)  First and foremost, Java challenged the assumption that the language must be compilable into extremely efficient code; this is in fact wrong for most application development work.  Yes, inner loops in database management systems, communications, cryptography, and similar software must be heavily optimized.  But it is much more important for business (and desktop) applications to be free from security vulnerabilities than for them to be a little faster.  In addition, development expenses, which include developer time as well as the cost of tools that attempt to find security vulnerabilities and other problems, often outweigh the benefits of application speed.  Of course, loss of customer trust as a result of security breaches can be devastating.

We saw two reasons why developers don't always use the right tool for the job: the overlap between tools due to their generality, and the difficulty of learning a new one.  These have a corollary, resulting from the nature of organizations.  While it is good to have experts in various kinds of technologies and tools, it is important for a cohesive organization to have a common set of tools, a lingua franca that everybody is familiar with, for the main part of the development work.  This is important for good communications, and also makes integration easier.  But it also leads to the use of the least common denominator, the language and tools that can support most of the work, even if they are not the best for any single purpose.  Thus I find myself these days programming in Java, although it may not be my first choice for the kinds of systems I develop.

There are more reasons why developers don't use the best tool for the job, and many other tools worth discussing.  But these will have to wait for later posts.

P.S. See the excellent book, C Traps and Pitfalls, for a list of syntactic and semantic issues you must be aware of if you develop in C or a derivative language.

Tuesday, December 21, 2010

Children Programmers

No high-school graduate expects to get a job as a physicist, even after getting top grades in the advanced placement physics exams.  But I know of many high-school students who make money programming, and who think of themselves as professional programmers.  No large or knowledgeable company will use such child labor, but there are still many small businesses and other clients who don't understand what really makes a professional developer, and are glad to save some money this way.

If this is a small one-time job that doesn't require maintenance, it may even be successful.  But if it becomes necessary to make changes, especially after the programmer has graduated from high school and moved elsewhere, the client often needs to scrap the application and start from scratch, having learned something the hard way.

This is not the children's fault.  They really don't understand what being a professional programmer is about.  In fact, this is not always discussed explicitly in the university computer-science curriculum.  Yes, there are courses on software engineering as well as project-based courses, and conscientious professors require extensive tests and documentation even in other courses.  But there are many courses, with specific requirements in each, and it is very difficult to create a truly large project that really requires good software engineering practices in the undergraduate curriculum.

Without such projects, the exercise is self-defeating.  Students will do whatever is required to get a grade, which often means creating an impression of using the practices required of them.  But they will realize that this isn't really necessary for the project itself.  As a result, they will feel that software engineering is another of those things best forgotten after the exam.  Hopefully, they will recall what they learned when they start working for real, and encounter the problems that make disciplined development a necessity.  I still occasionally meet former undergraduate students who tell me that now that they work in industry they finally understand what I was talking about.

Being professional is actually much more than following some software engineering process.  It is taking responsibility for your work: pursuing quality, avoiding questionable practices, refusing to release dangerous products.  It is taking responsibility for yourself: keeping yourself educated, continuously learning, being honest with your co-workers, employer, and the public in general.  All these things can't be taught in one course, in the same way that children can't be educated to be responsible citizens in one year.  Both require lifelong learning, and teaching is best done by example.  And with so many adults behaving like children (see "Programmers as Children"), how can we expect the children to behave like adults?

Thursday, December 16, 2010

Programmers as Children

Freud is quoted as saying that the postponement of gratification is the hallmark of maturity.  Developers are put the the test very often, since the computer can supply instant feedback on our programs - just run it and see what happens.  And, unfortunately, developers often fail this test.  It is much easier to try it out than to think it out!

When I started programming, up to the beginning of my student days, we could only use punched cards for our programs.  (Professors and other privileged people could use "terminals"!)  You had to punch your cards, then put the shiny deck on the dispatcher's table.  This high priest of the computer would be reading a newspaper, and although he would notice you staring at him, would ignore you completely.  Trying to hint to him that he should put your deck in the card reader would only make him slower.  Eventually, he would perform this operation, and then you had to wait for him to fetch the fan-folded paper from the big noisy line printer, separate it into individual printouts, then put it in your box.  And then you would find out that you forgot a comma.

The whole process would take about half an hour of turnaround time (for a few seconds of actual computation).  This made us very careful when submitting a job.  And, since there is only so many times you can go for coffee in one day, we would spend the turnaround time running the program in our heads: trying to figure out what can go wrong with it and fixing it on paper.

I will be the first to admit that checking for syntax errors is best left to the computer.  It is a waste of a developer's time to have to scan his code for them.  But time devoted to thinking about a program is time well spent.  As an undergraduate student, I wasn't thinking about formally proving my programs correct, and I didn't have the formal training for it, but in essence that is what I was trying to do.  And the result was that I found many logic problems before ever submitting the deck.

In my last year of undergraduate studies at Tel Aviv University, I took a compilation course and had to write a small compiler as a course project.  I decided to write it using a language made for the purpose: SNOBOL4.  Unfortunately, Tel Aviv U didn't have a SNOBOL4 compiler (probably because there wasn't one for the CDC 6600).  But I was taking a graduate course at the Weizmann Institute in Rehovot, and had an account on their IBM machine, which had the SPITBOL compiler for SNOBOL4.  Being reduced to the use of public transportation, I only visited Rehovot once a week, to attend my course.  So even though I had access to a 3270 terminal there and had almost instant turnaround time, I had to spend a week just staring at my listings between sessions.  I used that time to think about my compiler, and as a result finished debugging the whole thing in just two sessions at the terminal.  All told, I spent less time on the project that I would have if I had daily access to a terminal.  (Having a computer all my own to do my debugging on was totally unthinkable at the time.)

I'm ashamed to admit that I now find myself hitting "run" to see what happens, without thinking things through.  If it fails, I have learned something with little investment.  But what if it "works"?  Can I be convinced that my last change really eliminated the last bug, and I can go on to work on other things?  I still need to think carefully and convince myself that it will work on all other cases.

Testing is a useful way of increasing your confidence in your code.  But testing is not a replacement for thinking about the logic of your program!  What's more, writing good tests is a process that requires time and thought on its own.  And, like mental reasoning about programs, it is a process that many developers neglect.  But, more on that some other time.  I've got some code I need to think about.

Wednesday, December 15, 2010

My Dusty Deck

I started programming when I was about twelve, and I have enjoyed it ever since.  I was lucky to have spent many years in academia, so I was my own master.  Even when I consulted for a data-security company, I was responsible for security- and performance-critical code, and was never pressured to compromise on quality.

Sadly, most developers aren't that fortunate.  The results are all around us, from the daily annoyances on personal computers to lost lives and property.  (For some non-bedtime reading about the latter, see Computer-Related Risks by Peter Neumann.)

Can we do something about this?  There are many factors involved in producing high-quality maintainable software.  There are technical, psychological, and educational issues, and no one can claim to cover them all.  The Dusty Deck is my attempt to present my own point of view, shaped by my own experience in developing software and teaching computer science.

I am fascinated by good tools.  I want to have the best tools possible to do my job, and I get very frustrated when a tool doesn't understand what I want to do and gets in the way instead of helping me do it faster and better.  Because I know what tools I want to have for my own use, most of my research is focused on the creation of tools I can believe in.  In pursuing this goal, I spent four years at the Programmer's Apprentice project in MIT's AI lab, where I learned many things that I used in later research, most notably the plan calculus representation of programs.  (For more information, see The Programmer's Apprentice by Rich and Waters.)  Work with students and colleagues over the years led me into the field of static analysis of programs, which I am continuing now at IBM Research.

Over the years, I have been exposed to many languages, development environments, platforms, operating systems, and frameworks.  I started with Fortran on punched cards on a CDC 6600, went on to Pascal in the Introduction to Computer Science course at Tel Aviv University, played with many other languages (Prolog, APL, various assembly languages), and used Lisp on the Lisp Machine at MIT (happy days!).  C became my language for low-level work, and Java for higher-level programming, and various scripting languages for small tasks.  During all this work I developed some strong views, likes and dislikes, which I will share here.

I have taught core courses based on the books Structure and Interpretation of Computer Programs by Abelson and Sussman and Object-Oriented Software Construction by Meyer, and using Scheme, Eiffel, and Java (although the specific language was never a goal in itself).  For several years I have also been serving on the Israel Ministry of Education's Computer Science Curriculum Committee.  These experiences shaped my thinking about education.

To start with something controversial, in the next post I'll discuss the relationships between programmers and children.  Let me know what you think!

P.S. If you aren't familiar with the term "dusty deck," see its definition in the Jargon File.