Programming languages are the least usable, but most powerful human-computer interfaces ever invented

Really? I often think this when I’m in the middle of deciphering some cryptic error message, debugging somme silent failure, or figuring out the right parameter to send to some poorly documented function. I tweeted this exact phrase last week while banging my head against a completely inscrutable error message in PHP. But is it really the case that programming languages aren’t usable?

Yes and no (as with all declarative statements in tweets). But I do think that only some of these flaws are fundamental. Take, for example, Jakob Nielsen’s classic usability heuristics, one rough characterization of usability. One of the most prominent problems in user interfaces is a lack of visibility of system status: usable interfaces should provide clear, timely feedback about the how user input is interpreted so that users know what state a system is in and decide what to do next. When you write a program, there is often a massive gulf between the instructions one writes and the later effects of those instructions on program output and the world. In fact, even a simple program can branch in so many ways that some execution paths are never even observed by the programmer who wrote the instructions, but only by whoever later executes it. Talk about delayed feedback! There are whole bodies of literature on reproducibility, testing, and debugging that try to bridge this disconnect between command and action, better exposing exactly how a program will and will not behave when executed. At best, these tools provide information that guide programmers toward understanding, but this understanding will always require substantial effort, because of the inherent complexity in program execution that a person must comprehend to take action.

Another popular heuristic is Neilsen’s “match between system and the real world”: the system should use concepts, phrases, and metaphors that are familiar to the user. There’s really nothing more in opposition to this design principle than requiring a programmer to speak only in terms that a computer can reliably and predictably interpret. But need to express ideas in computational terms is really inherent to what programming languages are. There are some ways that this can be improved through good naming of identifiers, choice of language paradigm, and a selection of language constructs that reflect the domain that someone is trying to code against. In fact, you might consider the evolution of programming languages to be a slow but deliberate effort to define semantics that better model the abstractions found in the world. We’ll always, however, be expressing things in computational terms and not the messy, ambiguous terms of human thought.

Programming languages fail to satisfy many other heuristics, but can be made significantly more usable with tools. For example, error prevention and error actionability can often be met through careful language and API design. In fact, some might argue that what programming languages researchers are actually doing when they contribute new abstractions, semantics, and formalisms is trying to minimize errors and maximize error comprehensibility. Static type checking, for example, is fundamentally about providing concrete, actionable feedback sooner rather than later. This is very much a usability goal. Similarly, Nielsen’s “recognition rather than recall” heuristic has been met not through language design, but carefully designed and evolved features like autocomplete, syntax highlighting, source file outlines, class hierarchy views, links to callers and callees in documentation, and so on.

There are other usability heuristics for which programming languages might even surpass the usability of their graphical user interfaces. For example, what user interface better supports undo, redo, and cancel than programming languages? With modern text editors and version control, what change can’t be undone, redone, or canceled, at least during design time? Our best programming languages are also perhaps the most consistent, flexible, minimalist, and beautiful user interfaces that exist. These are design principles that most graphical user interfaces struggle to even approach, as they often have to make sacrifices in these dimensions to achieve a better fit with the messy reality of the world.

So while programming languages might lack usability along some dimensions, but with considerable effort in careful tool design, they can approach the usability of graphical interfaces (partly through the use of graphical user interfaces themselves). In fact, there’s been a resurgence of research inventing precisely these kinds of tools (some by me). These usability improvements can greatly increase the accessibility, learnability, and user efficiency of languages (even if they only ever approach the usability of graphical user interfaces).

Now to the second part of my claim: are programming languages really the most “powerful” user interfaces ever invented? This of course depends on what we mean by power. They are certainly the most expressive user interfaces we have, in that we can create more with them than we can with any other user interface (Photoshop is expressive, but we can’t make Photoshop with Photoshop). They might also be the most powerful interfaces in a political sense: the infrastructure we can create with them can shape the very structure of human communication, government, commerce, and cultural production.

But if by power we mean the ability to directly facilitate a particular human goal, there are many tasks for which programming languages are a terrible tool. Sending emails, having a video chat, playing a game, reading the news, managing a to do list, etc. are activities best supported by applications explicitly designed around these activities and their related concepts, not the lower level abstractions of programming languages (or even APIs). In this way, they are probably the least powerful kind of user interface, since they really only facilitate the creation of directly useful human tools.

If there’s any truth to the title of this post, its the implied idea that programming languages are just another type of human-computer interface and the rich and varied design space of user interface paradigms. This has some fun implications. For example, programmers are users too, and they deserve all of the same careful consideration that we give non-programmers using non-programming interfaces. This also means that programming languages researchers are really studying user interface design, like HCI researchers do. There aren’t two fields we might find more dissimilar in method or culture, but their questions and the phenomena they concern are actually remarkably aligned.

For those of you who know me and my work, none of these should be surprising claims. All of my research begins with the premise that programming languages are user interfaces. It’s why, despite the fact that I principally study code, coding, and coders, I pursued a Ph.D. in HCI and not PL, software engineering, or some other disciplined concerned with these phenomena. Programming languages are and will forever be to me, the most fascinating kind of user interface to study and reinvent.

16 thoughts on “Programming languages are the least usable, but most powerful human-computer interfaces ever invented

  1. I’m a UX designer and your post resonates really strongly with me.

    In my current role I have had the remarkable opportunity to participate in the design of a new programming language which led to me having the epiphany that programming languages are a tremendous UX design challenge.

    I wrote about my experiences here:

    You might find the view from the other side interesting.



    • Anything that takes user input and provides output is a human-computer interface. Programming languages and the tools that support them (compilers, runtimes, debuggers, etc.) do this by definition. The difference between a programming language and a graphical user interface really comes down to a few key differences:

      The speed with which output is provided. Programs can execute in the future, whereas GUIs always execute now.
      The degree to which the output is associated with the input. GUIs work hard to make it clear what a program does in response to user input. Programs do very little, hence the need for debuggers.

  2. Recently, I’ve been toying with Operational Transformation. I’ve seen it primarily in the context of collaborative text or data-structure editors, but I haven’t seen it discussed in the context of making a program’s running history both visible and mutable – the idea being one could look at a program’s “actions” as modifying some memory that is tracked via OT. I wonder what it would be for the system to be run under itself bootstrapped so its own operational history can be viewed and modified.

  3. “When you write a pro­gram, there is often a mas­sive gulf between the instruc­tions one writes and the later effects of those instruc­tions on pro­gram out­put and the world. In fact, even a sim­ple pro­gram can branch in so many ways that some exe­cu­tion paths are never even observed by the pro­gram­mer who wrote the instruc­tions, but only by who­ever later exe­cutes it. Talk about delayed feed­back!”

    You might be interested in Jonathan Edwards’ Subtext language. The latest work seems more focused on type systems, but the demo that made a big splash a couple years ago was all about showing all possible paths through a “function”.

  4. Interesting post. I hadn’t thought about programming languages framed as a HCI problem before.

    Some of the complexity of programming languages seems inherent in the medium, right? After all, computers are about the most alien things that humans are forced to routinely communicate with. (At least until we solve that pesky natural language problem and can just tell machines to “do what I want.”)

    But even that’s sorta misleading because it’s pretty obvious on a day-to-day basis as a developer that a lot of the tools we’re using were not designed with comfort in mind. I’m thinking here of ghc, Haskell’s compiler. Debugging can feel like deciphering French. (And in OCaml, debugging really *is* deciphering French.)

  5. Yes, there are many open questions about how to teach programming languages, particularly to novices. It is a challenging problem for many reasons. First, a class of newcomers various dramatically in both their prior experience and affinity. If you treat everybody the same, you will probably end up boring 10%, teaching 40% and losing 50%. Second, early programming concepts build on each other. If you don’t quite understand variables, you probably will be lost on iteration. Once you are behind, catching up is extraordinarily hard as the class moves on. Third, without fully getting something, you often can’t make noticeable progress. If I am a mediocre writer, I can still churn out something that looks (on the surface) like what a good writer crafts. If I don’t get a programming concept, the code does not work and it looks like a mess. I’m in a privileged environment where I only teach a small group of students (4-6) at a time. The hitch is that they are masters students that have not previously taken programming classes. Many fear the class and part of my job is convincing them that they can do it. I’ve seen the lows (I can’t do this at all and everybody else can) and the highs (I finally got it, these problems are easy now). Emotional support is necessary. Fourth, when a new concept is introduced, it can shake the foundations of existing concepts. I’ve often seen a person really master a concept in one context and completely fall apart when the next step forward is introduced. Fifth, teachers have a hard time relating to students. I’ve been programming for twenty years; I can solve the problems that I assign in typing speed. They are all trivial. I could not even tell you what was hard about them. Having now taught the class for three years and working with people on a one-to-one basis, I now better understand what makes these problems difficult for them and how I can better support them: creating better problem sets, preparing exercises that allow people to hone a particular skill (e.g., identifying objects and messages), diagnosing which misconception a student might have, etc. Without that intense experience, I would be a much worse teacher. So, the question of “how to teach languages” is necessarily linked to the question of “how do we teach teachers to teach languages.” All of that said, I still think that language design can be a critical piece of the solution.

  6. Nice post. I’ve been thinking a lot about the same topic. One element to consider is that usability changes over time. I’m an expert Smalltalk programmer. Smalltalk is about as simple a language as you can conceptual get (everything is done through message passing, everything is an object, strong dynamic typing, etc.). It is a wonderfully usable environment for me as an expert programmer. I can load libraries, dynamically explore them, debug code to understand it, etc. For me, its features (dynamic environment, dynamic typing, the tight integration between IDE and language) are great. They make it easier to write polymorphic code, create object-oriented code (beyond programming with objects), easily find and fix bugs, and implement unit tests. I’ve been teaching beginning programmers for three years. For my convenience, I’m teaching Smalltalk. It allows me to create little Microworlds for them to use and I truly like the concepts that are embedded into the language (message passing, closures, dynamic environments, etc.). But, it is not easy for them. They would probably benefit from static typing and compiler type checking. It takes a while to understand classes. It takes even longer to understand stacks. A debugger is initially inaccessible. All the things that make it great for me make it tough for them. In contrast, a compiler that tells you were there is a type error is useful for them and kind of a pain for me. It would be nice to have a programming environment that could change over time (e.g., dropping static typing, adding a full debugger) smoothly.

    I appreciate the work on Gidget as providing a nice scaffolded platform to enable incremental progress but the language features still stay the same. That said, Python is a great choice for a language that allows pretty accessible simple programming but also more expert programming. I still think Smalltalk (and particularly its integrated IDE) is better for writing object-oriented code, but Python is a good compromise language. Can we do better than “one size fits all” to have a language that changes over time? Greenfoot – Alice – BlueJ – Eclipse is a relatively nice progression of environment for Java but I think Java is conceptually limited. Can we create a beginner language that morphs into Haskell or Smalltalk over time?

    • These are great questions Jeff. What we really need is a much better knowledge of how to teach languages. If we can do that more effectively and efficiently, the question of which language first becomes moot, because we could just teach everyone multiple languages, focusing on how they vary in their strengths and weaknesses. I think with the right tools and the right pedagogy, this becomes feasible.

Leave a Reply
