This blog article is based on a talk presented at ESUG 2023, in which we report on the challenges and and insights we have experienced in teaching moldable development to newcomers.
See the original article on the feenk blog.
Moldable development is a new way of programming. In many ways it looks like programming as we are used to it, but it actually entails fundamentally different ways of thinking about programming, and new patterns of programming.
Moldable Development is a way of programming in which you build custom tools for each problem. In this way the system becomes explainable, and thus supports decision making for a range of stakeholders. Let’s look at a simple example.
Let’s take an easy domain that we can easily relate to, namely the ESUG website. Here we are inspecting the cloned repo of the ESUG website.
We can browse the pages of the website, and see the contents of a page.
From the website inspector we also see all the links, possibly missing internal links, reachable pages, as well as an overall map of root pages (blue), reachable pages (green) and unreachable pages (red).
We can also check the status of links using a background process. Finally, we can search for pages or links by title or content.
So what have we seen? We have domain objects representing a website, the web pages and the links. We have custom views for each domain object allowing us to explore the information that interests us, and to navigate to other objects. We also have custom actions to open a web browser or to start an analysis, and we have a custom search to query lists of pages and links.
Each of these custom tools is implemented in just a few lines of code in a method of the domain object concerned annotated with a dedicated pragma. For example, here is the code for the Pages
view (we Alt+click on the view to see the code).
Now we get to the key question:
How hard is it to teach people to build their own explainable system for their domain of interest?
Since Moldable Development is a way to develop explainable systems, rather than focusing on technology, it makes sense to start from the patterns that we observe when building such systems.
Moldable development patterns express best practices in the process of molding software to make it explainable. Let’s have a look a few of these patterns.
This is perhaps the hardest pattern to learn. Where would you rather be when you are developing code? Staring at the source code, or viewing the live object that you are working on?
Here we see at the top a typical Coder view of a class where we can browse and edit the methods. But this is actually the least interesting view we can have because each method is seen divorced from the context in which it is used and interacts with other methods.
From the very first day that we start to program, we learn to write some code in a text editor, and we compile it and run it. It always takes a few steps to see the end result.
But why not reverse this? Below here we see a moldable object, that is, a live instance of the PillarWebsite object that we can interact with.
While it can be cumbersome to navigate from the code view to see its effect, it is usually easy to navigate from the live object to its code. Suppose for, for example, that we want to extend the overview with new attributes. We can directly navigate to the code and adapt it.
From the moldable object we can experiment with new features, extract methods and examples or tests, and immediately see the effect.
Although sometimes you may have the luxury to work on a greenfield project, most projects start from some existing data and code. When we load the data into our environment, we will obtain the default views for that data. Here, for example, we have loaded the cloned ESUG website repo and are inspecting the contents.
We obtain a completely generic view of the folders and files that tells us nothing about the domain. We would like to turn this into a proper domain object that tells us interesting things about itself. As a first step, then, we can wrap the data into a dedicated object.
When we do this, at first glance the result appears to be even worse, as now we just get a generic Raw view of the new object.
But now we have the possibility to explore it, add behavior, and add new views. After some iterations we obtain the view we have seen earlier.
When you program in a conventional IDE, the code is strictly disconnected from any live instance. Experimenting and exploring the state of the running program is only possible by setting breakpoints and running the code in the debugger, or by writing additional dedicated test code.
Another way is to start coding from an Inspector on a live, moldable object. This should remind you of the Smalltalk practice of coding in the debugger, but the difference is that you can code in an inspector without having to get a debugger instance.
Here we see a live instance of our simple EsugWebsiteWrapper with a Playground opened at the bottom.
Why is this interesting?
Because the playground is bound to the context of the object, unlike code that we write in a normal code editor. For instance, the variable repoDir
is bound to that slot of the live instance. We can experiment with code in the playground, and when we see something we like, we can copy the code to a method, or even directly apply an extract method refactoring.
How do you know when to create a custom view?
This is another fundamental pattern. The idea is that, whenever you find yourself navigating to get to some interesting objects, you should turn that navigation path into a custom view so you can get to that information directly.
Let’s look at a trivial example: as we explore the Esug website model, we navigate to the pages using a Playground snippet, and then we can navigate to individual pages.
Instead we’d like the list of pages view to be visible directly in the website object. We can inspect that view to see that it is implemented as a #gtItemsFor:
method in the class SequenceableCollection
. We could copy-paste the code and later adapt it, but as a first quick step we can simply define a forwarding view from the website to the collection.
We just open a Coder view and add the view that forwards to the existing view of the pages collection. The new view is instantly available.
We have just seen some of the most basic moldable development patterns. They are described in further detail in the GT Book, together with several other patterns.
Teaching Moldable Development is challenging not only because there are the patterns to learn, but also a fair bit of technology to get acquainted with. Here are a few things that can ease the learning process.
GT comes with a live knowledge base of notebook pages documenting the system itself as well as numerous case studies. For example, here is a page describing how to interact with the GitHub REST API as a case study in Moldable Development.
The GT book not only offers users documentation to read and live tutorials to try out, but also serves as a concrete example for users to start their own projects from their personal database of notes.
Discord can be a great platform for community building, however it must be well-curated. It’s critical not only to make people feel welcome and encouraged to ask any kind of questions, but their questions need to be taken seriously, and answered reasonably quickly.
One interesting pattern in community building is to teach people when they share screenshots to capture an entire window showing the flows between snippets of code and views of interest. A well-designed screenshot goes a long way to telling a story or explaining an issue.
Some people love to watch long, meandering videos at double speed, but I’m not one of them. A short, compact video can be a very effective way to explain and demonstrate a complex topic, but it also requires considerable planning. It can, however, take more time to prepare a seven minute video than one that runs over an hour.
Let me round up with a couple of the biggest challenges we have encountered in trying to explain moldable development.
People hate change, or perhaps rather it’s the case that it’s extremely hard for people to change habits once they have become ingrained. I personally took a long time to get into the habit of starting to code from live objects rather than heading to a code editor first. The code editor is a comfortable place to start from, even if it is not very useful for exploring implementation options or understanding existing code.
What helps here best is live mentoring on a real project. When you see over and over again the benefits of coding from live objects, you begin to build up a Pavlovian instinct to stay away from the code editor as your starting point. Demos and videos also help, but they are not as effective as the first-hand experience.
The Glamorous Toolkit contains many pieces of technology that fit together to support moldable development. People like to put things into boxes, so they tend to focus on the first bit of technology that captures their imagination, and so risk to lose the big picture. For example, visualization is an important component, but it is a mistake to equate moldable development with visualization.
The same can be said for the live notebooks, the language workbench, or the pervasive use of examples.
In the end, what is important to convey is that Moldable Development is a process in which you ask questions about a software system, explore it, and build lots of small, custom tools to answer those questions and make the system explainable.
Moldable Development is a new way of programming that takes time to learn, but that in the end boils down to a set of learnable patterns.
]]>My first experience with gaps between expectations and reality in Software Engineering took place in 1972 when I started to learn to program. In this case the gap was simply the time between designing a program and actually seeing it run. I was a tenth-grade high-school student in Toronto when I was invited to the University of Waterloo for a week of Mathematics and a bit of programming.
My first programming experience was with APL, a live programming language for manipulating matrices and arrays. We were taught by the amazing Prof. Lee J. Dickey, and we programmed interactively on a timesharing system, getting immediate feedback.
APL is facetiously known as a “write-only” language, due to its highly compact notation using Greek letters as array manipulation operators. Here we a see a one-line APL program to generate prime numbers. I remember writing a similar one-line APL prime sieve that looked something like this one.
[Sources: APL wiki, Wikimedia commons]
As an amusing parenthesis, I saw this sign displayed in the Open Air museum in Reykjavik in 2015. It looks fine, except the “typewriter” was not a typewriter at all.
The typist at the kitchen table | IBM 2741 |
The so-called typewriter was actually an IBM 2741 used to program in APL. It is exactly the same kind of terminal I first used in 1972, including the APL typeball with Greek characters. I somehow doubt it was used to type letters in a kitchen in Iceland.
In the Autumn of 1972, the Toronto Board of Education introduced the first high school computer programming courses. We coded our programs with pencil on optical cards, like the one you see here.
We would hand our decks to the teacher, and get our printed output three days later. Debugging was not a pleasant experience. After my first experience with interactive APL, I felt I had moved back decades in time.
Later in the semester one of my classmates discovered that anyone at all could walk in off the street and use the keypunches and the high-speed job stream at the University of Toronto. After that we did all our homework at UofT.
Here we see a “Wardley map”, a diagram for business strategy that maps components with respect to user need.
At the top we see the need for getting our programs running. The other nodes are components that contribute to that need. From bottom to top components provide more value to the user, and from left to right they evolve from genesis, through prototypes, to products, and all the way to commodities.
The grey lines show which components contribute to others higher up. So, optical mark cards are sent by courier and contribute to getting the code running. The red arrows indicate evolution of components towards products and commodities. Greyed-out components are those from the past. The dark red arrows point to new components that emerge from the evolution.
In this case we see that the source code medium evolves from optical mark cards via punch cards to interactive terminals, shortening the feedback loop. Similarly, getting the program to run evolves from sending it by courier to just hitting the “enter” key.
What’s interesting, however, is that when the technology is sufficiently evolved, new opportunities arise that could not be imagined before. In this case, since the feedback loop is so short with live programming, we can experiment in ways that were not possible before. Since this form of experimentation is a new thing, it appears towards the left of the map, so can also be expected to evolve.
After high school, I studied Mathematics at Waterloo, and later switched to do a Masters and PhD in Computer Science at UofT. There I experienced the enormous gap between conceptual models and implementation.
The task for my MSc thesis was, together with another Masters student, to build a workflow system, called TLA, on top of another prototype of an electronic Office Forms System. OFS was itself built as a layer on top of MRS, the Micro-Relational System, one of the first implementations of a relation database management system for Unix.
I was very excited about the project, and had a clear idea of what to do, before even looking at the code. When I opened the box, however, I had trouble mapping the concepts to the C code. Essentially the problem was that I was looking for the domain concepts in the code, but I could not find the objects.
I then had an epiphany when I saw the August 1981 special issue of Byte magazine dedicated to Smalltalk, a brand-new object-oriented language and system. This was exactly what I was looking for!
August 1981 Byte special issue on Smalltalk | Introducing the Smalltalk-80 System |
Object-oriented programming was born in Norway in 1962, where Ole-Johan Dahl and Kristen Nygaard struggled to implement simulation software using ALGOL, an early procedural language.
[Source: Journal of Object Technology]
They invented objects, classes and inheritance to model real-world simulations as a thin layer on top of ALGOL. The resulting language was called Simula, which was standardized in 1967. Roughly 20 years later Bjarne Stroustrup, an experienced Simula programmer, started adding a similar thin layer to C, called “C with Classes”, and later renamed “C++”.
Also during the 1960s, an American computer scientist, Alan Kay, noticed that computer hardware was getting smaller and faster at an astonishing rate. He predicted that, somewhere down the line, you would be able to hold a computer in your hand, providing access to an encyclopedia of multimedia data. He figured that it would only be possible to build the software for such a so-called “Dynabook” using a fully object-oriented language and system. He convinced Xerox PARC to fund the development of not just the language, but the entire system down to the metal. The Smalltalk team indeed built not only the Smalltalk language and VM technology, but also the first graphical workstations and windowing systems.
I read about this and proposed to my supervisor at UofT, Prof. Dennis Tsichritzis, that to build our advanced electronic office systems we needed an object-oriented language and system. Since Smalltalk wasn’t available for our hardware, we started research into building our own language and system. Dennis told me, “Grab a couple of MSc students and go implement an object-oriented system.” It was not that easy, but we did manage to come up with something interesting.
Object-oriented programming made it possible to trace concepts from requirements through design down to implementation. This certainly made it easier to develop software systems, but what is interesting is that this enabled long-term growth. Not only development, but maintenance and evolution of such systems was facilitated by making it easier to navigate from requirements to code.
Whereas in the 1980s most of the resistance to OO technology came from concerns about performance (after all, every message to an object entails at least one additional indirection), by the 1990s machines were fast enough that developer productivity was seen as being more important. But what is “productivity”?
This great photo shows Margaret Hamilton, NASA’s lead software engineer for the Apollo Program, standing next to printouts of all the code produced by her team for the 1969 moon landing.
[Source: Wikipedia commons]
But software productivity clearly isn’t just producing more code in the same amount of time, otherwise refactoring would be seen as negative productivity! The trick is to produce more value for the customer, whether this is more code or less.
In the 1990s, the mantra was “Objects are not enough”. But if so, what else was needed? This photo shows Ralph Johnson, Erich Gamma, Richard Helm and John Vlissides, the “Gang of Four” who produced the first “Design Patterns” book. All four were also experienced in developing object-oriented frameworks based on reusable architectures and software components. They noticed that the same design ideas appeared in all the systems they had developed independently, and figured something interesting was going on.
[Source: Medium]
Object-oriented programming was so successful that, by the mid 1990s, there were already many, very large OO systems. Despite the increased ease of evolution, many of these older OO systems started to show the typical symptoms of legacy systems predicted by Lehman and Belady’s laws of software evolution.
We started a European project called FAMOOS in 1996 with industrial partners Nokia and Daimler-Benz to reengineer legacy OO software towards cleaner, more scalable component-based frameworks.
Our strategy was to recover lost design knowledge by reverse engineering and analyzing the legacy software, and then migrating it step-by-step towards a newer and cleaner architecture.
In the process of analyzing dozens of diverse OO software systems at Daimler and Nokia, we discovered numerous reverse and reengineering patterns that could be applied in multiple contexts. For example, the pattern Interview during demo helps a stakeholder you are interviewing to focus attention on concrete features and usage scenarios as you are trying to gain an initial understanding of a software system. This is far more effective than watching a slideshow or scribbling on a whiteboard. One of the key outcomes of this work was the open-source book, Object-Oriented Reengineering Patterns.
Design patterns are valuable not only as a way to teach software developers about best practices, but they also offer a way to raise conversations about design to higher levels. If I say to you, “Do we need a Singleton here?”, you should immediately understand what’s at stake.
Frameworks and components also raise the level of conversation. Instead of dealing with many unconnected applications, we deal with a Software Product Line of applications sharing a common code base.
Once upon a time (and also today), testing was a manual process.
This great photo shows the ENIAC, one of the very first digital computers, completed in 1945. As you can imagine, testing was a slow, laborious process of rewiring. Before automation, manual testing of software systems could be as painful as rewiring the ENIAC.
[Source: National Archives]
Automated testing only started to become popular with the introduction of Kent Beck’s SUnit framework for Smalltalk, and the subsequent port of SUnit by Kent and Erich Gamma to Java.
During the FAMOOS project we were concerned about a particular project we were analyzing, and for which we could find no test cases. When we asked, we were told, “Oh, but we have very extensive test cases!” “Great!” we said, “Can we see them?” They then brought us a big book of test cases that some poor person had to manually step through every time the tests were run.
The transition from manual to automated testing not only shortened the feedback loop from coding to validating that predefined test cases would pass, but it enabled something new.
With high test coverage, it was now possible to introduce radical changes to the design of a software system, and to subsequently ensure that everything that worked before was still working. The obvious downside was the need to manually write the test cases. In any case, you only had to write an automated test once, whereas a manual test had to be evaluated each time by hand.
The open question is where we can go with generated tests. So far these mostly just identify cases where the system fails, but there are some exceptions. For example, property-based testing can test business logic. When generated tests are ready to replace manually-written tests, there will certainly be new opportunities that can be hard to imagine now.
Although there were many other exciting advances in the 1990s, let us skip ahead.
Earlier we saw the “execution gap” between design and execution of a running program. The deployment gap concerns the gap between having a running and validated system and deploying it with end users. Until the introduction of DevOps, this could be a slow and painful manual process.
Although DevOps clearly served to automate and close the gap between development and deployment, what is interesting is the new opportunities it created.
In particular, with DevOps it is possible to experimentally introduce new features for selected groups of users to monitor and assess their impact on business. Without DevOps, the very idea is laughable.
Let us move to the present day. We know that unfettered growth of software systems leads to legacy issues. Very large software systems don’t scale well for decision making because we don’t, and can’t, understand them. Let’s take a look for some of the reasons.
Mainstream IDEs are glorified text editors
All mainstream IDEs are pretty much the same: they focus on editing source code.
Why is this a problem? First of all, putting a text editor at the center of the IDE presupposes that you are generally either reading or writing code. But in fact we only spend a small part of our time writing code.
Reading code does not scale
On the other hand, reading code for very large systems does not scale. You simply cannot read hundreds of thousands of lines of code, let alone many millions of LOC.
Lightweight tools are more effective than reading code
One of the key reengineering patterns is Study the Exceptional Entities, which helps you to learn quickly about potential problems in a software system by asking questions about outliers, such as very large classes, or classes with many fields but little behavior. Lightweight tools and metrics can help you to gain insight into a complex system.
In this system complexity view, we map the numbers of attributes, methods and lines of codes to the width, height and color of classes in a hierarchy. This immediately highlights classes with abnormally many lines of code, or lots of data with little behavior.
While such tools are useful early when investigating a system, they are still generic. To support solving concrete problems, tools have to take the context of those problems into account. The key question is whether such tools can be cheaply produced to answer specific questions about a given software system.
Code is disconnected from the running application
There is a huge gap between software source code and a running application. In a classical development environment you stare at source code, modify it, then run the code to see the effect in the running application. When that fails, you go back to staring at the code.
Perhaps you will have a test case or a breakpoint that will land you in a debugger to explore the live object, but from there you can only go back to playing with the source code. Worse, if you start from the running application, you may struggle to answer questions such as, “Where is this feature implemented?”
Q&A tools lack context, so give unreliable results
When we are at a loss, and no colleague is nearby to lend a hand, we often turn to Google and friends, which sends us to other well-known Q&A sites. The problem with the answers we find on these sites is that they lack our specific context, so we can waste considerable time on non-trivial questions to assess their relevance to our problem.
Although Stack Overflow clearly suffers from this problem, statistically-generated answers such as those provided by ChatGPT are no better, and in some ways worse, because of the high level of confidence (or hubris?) displayed by the answers.
A typical Stack Overflow answer | A typical ChatGPT answer |
These screenshots show answers I obtained from Stack Overflow and ChatGPT to actual questions I had about GitHub pages. In both cases I struggled to get the information relevant for my context. Worse, the answer from ChatGPT looked authoritative, but in fact was fanciful and inaccurate.
The fact that we nevertheless tend to search for answers outside the system shows that there is a crisis that is not being addressed.
Plugins are hard to find, hard to build, and hard to compose
Instead of leaving the IDE to find answers to questions we have about our software base, it would be so much nicer if we could extend the IDE with the tools we need. Luckily there exist plugins.
Unluckily plugins are (in my experience) not pleasant to implement, it is hard to find any that are truly helpful, and they pretty much never play nicely with other plugins. Finally, plugins don’t know anything about your context, so like Stack Overflow and ChatGPT, the likelihood that they will help you for your problem is slim.
The key issue is that software is so contextual that it is not possible to predict the specific questions that will arise in developing and evolving it. (We’ll see some examples shortly.) As a consequence, generic tools and plugins will always fail to be useful at some point. Instead, we need to be able to cheaply and quickly build or adapt tools to answer our specific, contextual questions.
The IDE is more than a text editor. For a software system to be explainable, it must be explorable.
For example, to understand the slideshow implementation of the talk this blog post is based on, we don’t start from the source code but from a live instance. We can navigate from the slideshow to its slides, to the Wardley maps, or even to the source code, if we need to.
In this screenshot, we create an instance of the BATbern50Slideshow
class, and inspect it in the Glamorous Toolkit (AKA GT), an open-source, moldable environment for authoring explainable systems.
We can explore, for example, the Slide methods view to see the methods in order, and for each method we see the Slide view, the source code, or other views.
We can similarly explore any kind of system to understand it. Here we explore the git repository of the slideshow’s source code. We can explore the changes, the commits, the packages, and from each entity navigate further to deepen our understanding.
It doesn’t matter whether the domain is that of slideshows, git repositories, computer games, social media data, or life insurance policies. In any of these cases, we can navigate through the network of domain objects to answer our questions, or to navigate from the objects to the code, rather than the other way around.
To make systems explainable, you need to be able to add cheap, composable tools, such as views. Understanding doesn’t arise simply from navigating, but from being able to navigate efficiently and effectively to the answers you need. This implies that you need to be able to easily and cheaply define your own plugins to mold the IDE to your needs.
For each of the custom navigational views we have seen, a few lines of code are all that are needed to define them. We can ALT-click on the tabs to see the code. For example, the Slide deck view is extremely short, and leverages the fact that all the parts are composable.
All the views we have seen are examples of custom tools added to a moldable IDE. In addition to custom views, there are several other ways the IDE can be molded, such as by adding custom actions, search capabilities, and quality advices.
The Metrics view of each extension shows that they are generally small and cheap to implement. For example, there are nearly 3000 inspector views in the standard GT image, and they average under 12 LOC.
Lifeware is a company that provides software infrastructure and services for life insurance companies. Lifeware has a very large number of test cases for their life insurance SPL. These tests are run on up to 960 processors, or “workers”, on a cluster of AWS machines, each with between 16-64 processors. Each worker runs a number of test tasks. The goal is to run all the tests within 8 minutes, but because tests can take varying lengths of time to complete, this goal is not so trivial to reach.
The moldable development strategy is to ask questions of the live system, and where the answers are not immediately evident, to introduce custom views to answer those questions. By creating a few custom views, we can get some insight into what’s going on in the live system.
In this lightweight visualization we see that a number of workers, i.e., the long grey bars near the bottom, are taking twice as long, but why?
Here we highlight in red those tasks that took longer for the same worker than an earlier task. This tells us that there are scheduling issues, and not just with the slowest workers.
Since the machines in our cluster are not all the same, we ask if the scheduling issues are related to particular machines. Here we inspect the execution timelines of all the workers on a given machine. We see that just a few machines are having problems meeting the deadline. Is there something special about these machines?
We dive into a particular machine to see what’s going on. We can see clearly that only one worker (second from the bottom at the right) is having scheduling issues, so it’s clearly not an issue of the specific machine.
Are there issues with the memory usage? We inspect the total VM memory consumption and free memory per worker. We see nothing too unusual.
Finally we can dive into the individual tasks. Here we can verify that indeed the problem is just with the scheduling. By updating the historical data of the time taken to run each test, we can better schedule the test runs and reduce the delays.
We were able to perform this analysis because we could turn every question into a cheap and lightweight tool that would let us transform the execution data into an explorable model of the test runs.
At the bottom here we see a transition from fixed and rigid IDE tools to moldable tools that can be easily and cheaply extended with new behavior. I have only shown you some custom inspector views, but the idea applies just as well for search tools, quality advices, and even customizable debuggers.
At the next level we see how moldable tools are leveraged by moving from manual inspection to custom queries that we plug into the tools. We saw this when we browsed the slides of a slideshow or the recent changes of a git repo. Now, instead of manually cobbling together a view, we can easily obtain a view using the molded tools.
We can have “inside-out conversations” that emerge from exploring the system itself, instead of classical “outside-in” conversations that treat the software as a black box with inputs and outputs. This then allows us to spot risks and opportunities that are observable only inside a system, and to support quick decision making not only for developers, but also business stakeholders.
What lessons can we draw from all this?
Programming is Modeling
First, I would say, that “programming is modeling”.
This is not a new observation, but it can be interpreted in several different ways. I found the live programming approach of APL far more exhilarating than the plodding punchcard model of programming supported by FORTRAN, but both languages suffered from a very limited view of what a program is. It was just as hard to build a clean conceptual model in APL as it was in FORTRAN.
Object-oriented programming changed that by allowing not only the design but the domain model to be reflected in the code.
I am reminded of Bertrand Meyer’s observation that he could never understand the fascination with modeling notations and CASE tools when everything is there in the code. Then one day it hit him: “Bubbles and arrows don’t crash!”
In contrast to model-driven approaches, instead of generating code from models, perhaps we are better off generating views from the code.
Programming is Understanding
We can go further, however. Kristen Nygaard, one of the inventors of Simula, the first OO language, was apparently fond of saying “To program is to understand.” I would interpret that today as meaning that software is more than just source code. It can and should be seen as a living system can expresses knowledge about itself.
I would like to propose a new mantra, namely: “The software wants to talk to you.”
This means that instead of letting the IDE lock up software inside the prison of a text editor, it should enable the exploration, querying and analysis of live systems. Then, instead of having to head to Google, Stack Overflow or ChatGPT to answer questions about our software, we should be able to answer the questions we have with the help of the systems themselves.
﹌﹌﹌﹌﹌
This blog post is based on an invited talk I gave at the 50th anniversary Berner Architekten Treffen on June 9, 2023.
]]>Apparently I’m not the only one. In the immortal words of Edsger Dijkstra: “Object-oriented programming is an exceptionally bad idea which could only have originated in California.”
Well, I’m not normally one to complain, but I think it is time to step back and take a serious look at what is wrong with OOP. In this spirit, I have prepared a modest list of Ten Things I Hate About Object-Oriented Programming.
What is the object-oriented paradigm anyway? Can we get a straight story on this? I have heard so many different versions of this that I really don’t know myself what it is.
If we go back to the origins of Smalltalk, we encounter the mantra, “Everything is an object”. Except variables. And packages. And primitives. And numbers and classes are also not really objects, and so on. Clearly “Everything is an object” cannot be the essence of the paradigm.
What is fundamental to OOP? Peter Wegner once proposed that objects + classes + inheritance were essential to object-oriented languages. Every programming language, however, supports these features differently, and they may not even support them as built-in features at all, so that is also clearly not the paradigm of OOP.
Others argue convincingly that OOP is really about Encapsulation, Data Abstraction and Information Hiding. The problem is that some sources will tell you that these are just different words for the same concepts. Yet other sources tell us that the three are fundamentally different in subtle ways.
Since the mid-eighties, several myths have been propagated about OOP. One of these is the Myth of Reuse, which says that OOP makes you more productive because instead of developing your code from scratch, you can just inherit from existing code and extend it. The other is the Myth of Design, which implies that analysis, design and implementation follow seamlessly from one another because it’s objects all the way down. Obviously neither of these candidates could really be the OO paradigm.
Let’s look at other paradigms which offer a particular way to solve programming problems. Procedural programming is often described as programs = data + algorithms. Logic programming says programs = facts + rules. Functional programming might be programs = functions + functions. This suggest that OOP means programs = objects + messages. Nice try, but this misses the point, I think.
For me the point of OOP is that it isn’t a paradigm like procedural, logic or functional programming. Instead, OOP says “for every problem you should design your own paradigm”. In other words, the OO paradigm really is: Programming is Modeling
Another thing I hate is the way that everybody loves to hate the other guy’s programming language. We like to divide the world into curly brackets vs square brackets vs round brackets.
Here are some of the nice things that people have said about some of our favorite OOPLs:
“C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do, it blows away your whole leg.”
It was Bjarne Stroustrup who said that, so that’s ok, I guess.
“Actually I made up the term ‘object-oriented’, and I can tell you I did not have C++ in mind.” — Alan Kay
“There are only two things wrong with C++: The initial concept and the implementation.” — Bertrand Meyer
“Within C++, there is a much smaller and cleaner language struggling to get out.” — Bjarne Stroustrup
“C++ is history repeated as tragedy. Java is history repeated as farce.” — Scott McKay
“Java, the best argument for Smalltalk since C++.” — Frank Winkler
“If Java had true garbage collection, most programs would delete themselves upon execution.” — Robert Sewell
But perhaps the best blanket condemnation is the following:
“There are only two kinds of languages: the ones people complain about and the ones nobody uses.” — Bjarne Stroustrup
Classes drive me crazy. That might seem strange, so let me explain why.
Clearly classes should be great. Our brain excels at classifying everything around us. So it seems natural to classify everything in OO programs too.
However, in the real world, there are only objects. Classes exist only in our minds. Can you give me a single real-world example of class that is a true, physical entity? No, I didn’t think so.
Now, here’s the problem. Have you ever considered why it is so much harder to understand OO programs than procedural ones?
Well, in procedural programs procedures call other procedures. Procedural source code shows us … procedures calling other procedures. That’s nice and easy, isn’t it?
In OO programs, objects send messages to other objects. OO source code shows us … classes inheriting from classes. Oops. There is a complete disconnect in OOP between the source code and the runtime entities. Our tools don’t help us because our IDEs show us classes, not objects.
I think that’s probably why Smalltalkers like to program in the debugger. The debugger lets us get our hands on the running objects and program them directly.
Here is my message for tool designers: please give us an IDE that shows us objects instead of classes!
To be fair, I hate methods too.
As we have all learned, methods in good OO programs should be short and sweet. Lots of little methods are good for development, understanding, reuse, and so on. Well, what’s the problem with that?
Well, consider that we actually spend more time reading OO code than writing it. This is what is known as productivity. Instead of spending many hours writing a lot of code to add some new functionality, we only have to write a few lines of code to get the new functionality in there, but we spend many hours trying to figure out which few lines of code to write!
One of the reasons it takes us so long is that we spend much of our time bouncing back and forth between … lots of little methods.
This is sometimes known as the Lost in Space syndrome. It has been reported since the early days of OOP. To quote Adele Goldberg, “In Smalltalk, everything happens somewhere else.”
I believe that the code-oriented view of today’s IDEs is largely to blame — given that OO code does not accurately reflect the running application, the IDE gets in our way instead of helping us to bridge the gap. Another reason I believe that Smalltalkers like to develop in the debugger is that it lets them clearly see which objects are communicating with which other objects. I am guessing that one of the reasons that Test-Driven Development is popular is that it also exposes object interactions during development.
It is not OOP that is broken — we just haven’t figured out (after over 40 years) how best to develop with it. We need to ask ourselves: Why should the source code be the dominant view in the IDE?
I want an IDE that lets me jump from the running application to the code and back again. (For a demonstration of this idea, have a look at the Seaside web development platform which allows you to navigate directly from a running web application to the editable source code.)
OK, I admit it. I am an impatient guy, and I hate having to say everything twice. Types force me to do that.
I’m sure some of you are thinking — “Oh, how could you program in an untyped language. You could never be sure your code is correct.”
Of course there is no such thing as an “untyped” programming language — there are just statically and dynamically typed ones. Static types just prevent you from writing certain kinds of code. There is nothing wrong with that, in principle.
There are several problems, however, with types as we know them. First of all they tend to lead to a false sense of security. Just because your Java program compiles does not mean it has no errors (even type errors).
Second of all, and much more evil, is that type systems assume the world is consistent, but it isn’t! This makes it harder to write certain useful kinds of programs (especially reflective ones). Type systems cannot deal well with the fact that programs change, and that different bits of complex systems may not be consistent.
Finally, type systems don’t cope well with the fact that there are different useful notions of types. There is no one type system to rule them all. Recall the pain we experienced to extend Java with generics. These days there are many interesting and useful type systems being developed, but we cannot extend Java to accommodate them all. Gilad Bracha has proposed that type systems should not only be optional, in the sense that we should be able to run programs even if the type system is unhappy, but that they should be pluggable, meaning that we can plug multiple type systems into different parts of our programs. We need to take this proposal seriously and explore how our languages and development tools can be more easily adapted to diverse type systems.
“Change is inevitable — except from a vending machine.” — Robert C. Gallagher
We all hate change, right? So, if everyone hates change, why do we all complain when things don’t get better? We know that useful programs must change, or they degrade over time.
(Incidentally, you know the difference between hardware and software? Hardware degrades if you don’t maintain it.)
Given that real programs must change, you would think that languages and their IDEs would support this. I challenge you, however, to name a single programming language mechanism that supports change. Those mechanisms that do deal with change restrict and control it rather than enable it.
The world is not consistent, but we can cope with that just fine. Context is a great tool for managing change and inconsistency. We are perfectly comfortable adapting our expectations and our behavior in our daily lives depending on the context in which we find ourselves, but the programs we write break immediately if their context changes.
I want to see context as a first-class concept in OO languages and IDEs. Both source code and running software should be able to adapt to changing context. I believe that many design patterns and idioms (such as visitors, and dependency injection) are simply artifacts of the lack of support for context, and would disappear if context were available as a first-class construct.
Patterns. Can’t live with ’em, can’t live without ’em.
Every single design pattern makes your design more complicated.
Visitors. I rest my case.
“All methodologies are based on fear.” — Kent Beck
Evidently some of my students follow the Chuck Norris school of Agile Development:
“Chuck Norris pairs alone.”
“Chuck Norris doesn’t do iterative development. It’s right the first time, every time.”
“Chuck Norris doesn’t do documentation. He stares down the code until it tells him everything he wants to know.”
Bertrand Meyer tells this story about always wondering why diagrammatic modeling languages were always so popular, until one day it hit him: “Bubbles don’t crash.” I believe his point is that OO languages are modeling languages. (AKA “All you need is code”)
There similarly appears to be something fundamentally wrong with model-driven development as it is usually understood — instead of generating code from models, the model should be the code.
By analogy, when FORTRAN was invented, it was sold as a high-level language from which source code would be generated. Nowadays we think of the high-level languages as being the source code.
I like to think that one day, when we grow up, perhaps we will think of the model as being the source code.
Finally, I hate the catchphrase: “Objects are not enough. We need …” Over the years we have needed frameworks, components, aspects, services (which, curiously, seems to bring us back to procedural programming!).
Given the fact that objects clearly never were enough, isn’t it odd that they have served us so well over all these years?
25 years ago we did not expect object-oriented programming to last as a “new” phenomenon for so long. We thought that OO conferences like ECOOP, OOPSLA and TOOLS would last for 4 or 5 years and then fade into the mainstream. It is too soon to dismiss OOP as just being part of the mainstream. Obviously we cannot feel passionately about something that does not interest us. The fact that academic and industrial research is still continuing suggests that there is something deep and important going on that we do not yet fully understand.
OOP is about taming complexity through modeling, but we have not mastered this yet, possibly because we have difficulty distinguishing real and accidental complexity.
I believe that to make further progress we must focus on change and how OOP can facilitate change. After all these years, we are still in the early days of OOP and understanding what it has to offer us.
Oscar Nierstrasz
Banquet speech given at ECOOP 2010. Maribor, June 24, 2010.
First published on The JOT Blog. DOI: 10.5381/jot.2010.9.5.e1
]]>