...Wherein we explore the radical notion that programmers are people, and then look at what this might mean for deep tools, such as APIs, protocols, and programming languages.
My experience is that significant advances are triggered by radical notions. My current radical notion is this: Programmers are people.
Some will be skeptical, but I think that there is sufficient evidence to accept this, at least for the sake of argument. Briefly, though, I will point out that programmers are well known to sleep on occasion, and to smell badly if they do not bathe. Beyond these basic animal traits, we can also note that they use language (although often crypticly), a trait commonly accepted to be a hallmark of humans.
If we accept that programmers are humans, one primary and interesting consequence is that human factors issues can be properly applied towards the tools they use. I'm not talking here about IDEs, which have GUIs that are clearly subject to human factors analysis. (Answer: They mostly suck.) I am speaking about the more basic tools programmers use every minute they do their work: programming languages and APIs.
I first started to think about this when writing a paper on C++. In the first (and only) draft, I declared that a programming language is a user interface to the language's abstract programming model. I was surprised to discover that this view was not common, although it was easy to sell. It was easy to sell because most people quickly see the point: Users (in this case programmers) are interfacing with the programming model through the medium of the code text.
The striking thing was that it wasn't already widely accepted. I thought I was stating an obvious truism. But for many, I was stating a wholly new idea. Clearly the practice of programming languages does not look to human factors.
C++ is a grand example of this. As a small example, consider the following:
There are two ways to declare variables: The way used for function parameters and the way for everything else: foo(int a, int b) vs. int a, b;. One is comma separated, the other semicolon terminated. You cannot directly declare two variables to have the same type if and only if they are function parameters. Basic human factors says that an interface should have one way to do a single thing that always looks the same.
Is this earth-shattering? Hardly. It's just easy to describe in a paragraph. And it shows the basic premise at work.
More profoundly, consider garbage collection. One can argue up and down about the overhead, etc., but consider this: Two of the best selling C++ books are Scott Meyer's Effective C++ and More Effective C++. The single largest category of the 85 tips -- about one quarter of them -- is dealing with potential memory leaks. In a garbage collected environment, none of those tips need to be considered. Whatever other costs you consider, you must consider the bug rate when those hints are not used, or are used incorrectly. This is fundamentally a human factors problem: You can tell people how to avoid the whirling knives of the abattoir, or you can close the abattoir door.
I do not claim to be original in worrying about such issues: The classic Brian Kernighan1 paper "Why Pascal Is Not My Favorite Programming Language" does not say so, but many of its critiques are human factors based. But I think our lives as programmers could be a lot better if we took this seriously, actually understanding human factors and applying them, instead of just doing our best to exercise personal good taste.
Consider the following human factors principles (the numbers are only so I can refer back to them; these are in no particular order):
There should be one way things are done (whether or not it appears in multiple places). For example, the login dialog box should look the same, no matter where and why it pops up.
Similar things should be done similarly. This helps people use their learning from one place to work in another.
Different things should be clearly different.
Dangerous things should be impossible, or where necessary, clearly labeled as dangerous.
Default behavior should be harmless.
Users should be able to predict what the results of actions will be.
My first example fails (1) and (2). A C++ example of (3) (and (6)) might be implicit conversion. Without a large knowledge of the local environment (or a very good IDE) nobody can tell if return x + y (where x and y are objects) creates one new object, or three, or twenty. It all depends on how many conversions are necessary on y to make it usable in some operator+ method on x, and then how many more are required to turn the result into an object compatible with the return type. In one example I uncovered, the total number of objects created and discarded was 12. The difference between x + y and x + new Foo(new Bar(new Gogin(y))) is pretty important, and completely invisible.
Many of C++'s most egregious violations of human factors violate (5). Consider that a default copy constructor will be created for you if you do not provide one. This constructor will be simple (field-by-field copy), but also often enough wrong. You can stop it from giving you one by providing your own, but you must do this whether or not you want a copy constructor at all. If you don't want one, you declare one but make it private. In other words, the default is dangerous (and the workaround backhanded).
And compare that behavior to a violation of (6): One would expect that there would be a default overload for the == operator. Why? Because in almost every situation where the default copy constructor was valid, an analogous field-by-field comparison for equality would be valid as well. But you don't get a default operator==. And surely if you do provide one, it clearly obvious that 99.99% of the time, you can define x != y as !(x == y). This is far more likely to be correct than the default copy constructor, but in one place C++ is "helpful" and in the other place we must trust people to know Scott's Effective C++ tip that you should always override == and != as a pair. This is a rich source of bugs.
Note in addition that these two language choices take fundamentally different views of the human programmer. Is C++ a "tries to be helpful" language for default method implementations or a "conservative choice" language? What would you guess: Does C++ provide an automatic = (assignment) operator? In this and many other ways it is schizophrenic, and hence unpredictable.
I don't want to pick on C++. At least not exclusively. Smalltalk's unusual syntax contributed greatly to its demise. Java chose to keep C's "fall through" default for switch statements which is Just Plain WrongTM. Perl has so many ways to do the same thing that most Perl programmers commonly encounter chunks of Perl they cannot comprehend: each Perl programmer has a sub-dialect they understand, and much they've never seen. I could go on for a long time on languages alone, never mind APIs. And so could you.
No matter what, every language will have human factors problems. Tradeoffs are inevitable even with the best design, so you can always find one principle violated to support another. But I want us to be trading them off against each other, not ignoring them. Let us think of each other as human (well, with a few exceptions) and then design like it.
1 Originally my editor misattributed this to Dennis Ritchie. Unfortunately for me, I'm my own editor so it's still my bad.
Some of these human factors principles (4 and 6 in particular) are common arguments for functional programming languages (the removal of side effects, referential transparency, etc.). Yet, many programmers find functional programming distasteful and a little too safe. So where does this play into human factors?
Few programing languages are designed with these issues in mind. The fingers of one hand are probably way more than enough to number them.
Take a look at Python, http://www.python.org ; you might like it. (Don't let the indentation issue discourage yourself early on, chances are you'll like that one too at some point).
The design philosophy is aptly described in The Zen of Python by Tim Peters:
Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea.
Disclaimer: No, I don't consider Python a silver bullet. It's not the best tool for absolutely every job. It's just the most "human-friendly" programming language I know of.
The arguments are chosen wisely. Take a look at the book "Psychology of Everyday Objects" by Donald Normann for an indepth picture. Being a retriered professor for cognitive science, Donald had lots of time devoted to the way humans think and how they interact with their environment. His findings are very much like Ken's.
> The arguments are chosen wisely. Take a look at the book > "Psychology of Everyday Objects" by Donald Normann for an > indepth picture.
Interestingly enough, this is one of the three books on Object Oriented Design that I recommend. The other two being Tufte's The Visual Display of Quantitative Information and Robin Williams' The Non-Designer's Design Book (no, not that Robin Williams].
Notice that none of them are about[i/] OO design, it's just that I don't know of any OO design book that tells you how to care about the clarity of what you express. (Which is why, in my copious spare time, I'm writing a book on OO design.)
> The design philosophy is aptly described in The Zen of > Python by Tim Peters:
There is one thing I'd take issue with, though:
> If the implementation is hard to explain, it's a bad idea.
I'd say if the usage of the implementation is hard to explain, it's a bad idea. But I'm nearly fearless about designing usages that make the implementor's life hard (although the impossible I try to avoid). The ratio implementors to users should approach zero, so I care about them only as a secondary effect: If something makes it 100 times easier for the implementor and 1.1 times harder for the user, I might consider it (he exaggerates for effect, of course, mostly because there is no way to quantify here, but you get the idea.)
> Disclaimer: No, I don't consider Python a silver bullet. > It's not the best tool for absolutely every job. > It's just the most "human-friendly" programming language I > know of.
There is no silver bullet, neh? [ahref]www-inst.eecs.berkeley.edu/~maratb/readings/NoSilverBullet.html[/href]Fr ed Brooks says so[/a].
> > If the implementation is hard to explain, it's a bad > idea. > > I'd say if the usage of the implementation is hard > to explain, it's a bad idea. But I'm nearly fearless > about designing usages that make the implementor's life > hard (although the impossible I try to avoid). The ratio > implementors to users should approach zero, so I care > about them only as a secondary effect: If something makes > it 100 times easier for the implementor and 1.1 times > harder for the user, I might consider it (he exaggerates > for effect, of course, mostly because there is no way to > quantify here, but you get the idea.)
Tim's checklist, idealized as it is, allows no compromises here.
In essence he says "if it cannot be made simple for both the user and the implementor, you don't want to have it in the language". Python's implementation has a reputation of rock-solid stability, and this is likely very much due to consistent application of this variant of the KISS principle. Did it make Python a poor, crippled language feature-wise? You decide.
> There is one thing I'd take issue with, though: > > > If the implementation is hard to explain, it's a bad > idea. > > I'd say if the usage of the implementation is hard > to explain, it's a bad idea. But I'm nearly fearless > about designing usages that make the implementor's life > hard (although the impossible I try to avoid). The ratio > implementors to users should approach zero, so I care > about them only as a secondary effect: If something makes > it 100 times easier for the implementor and 1.1 times > harder for the user, I might consider it (he exaggerates > for effect, of course, mostly because there is no way to > quantify here, but you get the idea.)
I agree about your sentiment regarding implementors vs. users (althoug in Python's early life this wasn't so clear-cut). We have some things in Python that were excrucuatingly hard to implement right (e.g. the "yield" statement). The Zen rule applies to explaining the implementation, which pertains to the user: invariably, properties of the implementation end up affecting its usage, often through performance, or through specific limitations. Another application of the Zen rule is to avoid deviations between different implementations. The C++ standard, for example, has things that are so hard to implement that it takes years to get the first conforming compiler.
I dont think you gave adequate attention to programmer motives. Writing a simple, safe, and powerful programming language is easy compared to getting programmers to actually use it. If programmers are human, we should address programmer psychology.
Yes, simple lanuages often lead to better, more stable results. However its been my experience many programmers are not looking for the simplest solutions to problems; just solutions that work now. And all the better if the user of the code is forced to retain the author's services because the code is too complex for anyone else to understand.
While I whole heartedly agree with Ken's suggestion that programming languages should never expose dangerous interfaces when functionally equivalent alternatives exist. But I can recall a time when I thought it was "fun" to learn C++ syntax. Templates were cool and operating overloading was powerful and "elegant." I have also enountered my share of perl hackers who love perl for its power and cryptic. Its much harder to impress fellow programmers with knowledge of Java syntax than with knowledge of C++ or Perl syntax.
Yes, programmers are people, a product of natural selection, and as such they often have selfish desires. I think the tendency of some programmers to choose complex programming languages is related to their tendency to want to work on software that is challenging and "interesting" rather than merely useful. After all, the easier it becomes to program a computer, the less need society has for programmer experts.
Another design book I highly recommend is The Most Beautiful House in the World by Witold Rybczynski (which is also nothing about programming or software design at all), particularly for advocates of Big Design Up Front -- http://c2.com/cgi/wiki?BigDesignUpFront
What a riot. Complete with rotating knives...I would add, "Programmers are just as stupid as everyone else." That is, the intellectual effort required to use an API should be roughly equivalent to that required to understand a cooking recipe.
You speak of non-OO books which have valuable lessons. One book which taught me oodles about the importance of clarity and simplicity - in all human communication, including code - was written circa 1780: Lectures on Rhetoric and Belles Lettres, by Hugh Blair. It was interesting as well for another reason. In a section describing the basic structure of language, he described nouns as a forming a hierarchy of categories, and the text could have been taken verbatim as a description of OO inhertance. From 200 years ago. Made my jaw drop when I read it...One of those twilight zone moments...
> There's a minor error in your post: "Why Pascal is not..." > is by Brian W. Kernighan instead of DMR.
Not minor enough for me -- credit always goes where it's due, so I'll go back and edit the article to get it right (but, being the stickler I am, I'll footnote it so someone who laughed at me can still do so later.-)
> Another design book I highly recommend is The Most > Beautiful House in the World by Witold > Rybczynski (which is also nothing about programming or > software design at all)
Interesting -- I often find a lot to learn from architecture. I think it is an important connection that we use the same word for software and buildings, because the tradeoffs have the same flavors: Customer desires vs. your own aesthetics; What works vs. what would be beautiful; Timeframes, budgets, and other constraints. Architecture (both kinds) is a practice at the intersection of beauty and reality. (And like many kinds of intersections, there are a lot of accidents.)
So while we're here, The Not So Big House is a very nice book about architecture for people's actual needs instead of abstract stereotypes. Of course I see parallels, but it's a good thought provoker in any case.
Flat View: This topic has 44 replies
on 3 pages