Skip to content

On the “advances” in web development since 1995

The first “web application” I worked on was written in a late version of WebObjects, version 4.5. An HTTP request was handled by an “adaptor” layer that chose a controller based on the parameters of the request, you could call this routing if you like. The controller accesses the data model, any relevant application logic, and binds the results to the user interface. That user interface could be an HTML and JavaScript document for presentation in a browser, or it could be a structured document destined for consumption by a remote computer program. The structured document format would probably have looked something like this:

{
  "first_name": "Graham";
  "last_name": "Lee";
}

The data can be bound on properties in objects called “components” that represent different elements in an HTML document, like images, anchors, paragraphs and so on.

Different frameworks for interfacing with various data storage technologies, such as relational databases, were available.

And basically nothing – apart from the cost – has changed since then. A MEAN application works in the same way, but with the “advance” that it makes it easier to support single-page applications so you can send a few megabytes of transpiled bundle over an unreliable mobile data network to run on a slow, battery-operated smartphone processor instead of in the server. And the data storage interfaces have been expanded to include things that don’t work as well as the relational databases. Other “advances” include a complicated collection of transpilers and packagers that mean every project has its own value of “JavaScript”.

It’s not like anything has particularly got much easier, either. For example, a particular issue I’ve had to deal with in a React application – binding a presentation component to properties of an element in a collection – is exactly as difficult now as it was when WebObjects introduced WORepetition in 1995. Before we get to the point where it becomes easier, we’ll give up on React, and Node, and move on to whatever comes after. Then give up on that.

In software engineering, if something isn’t broke, fix it ’til it is.

When Object-Oriented Programming Isn’t

A problem I was investigating today led me to a two-line Ruby method much like this:

class App
  # ...
  def write_file_if_configured
    file_writer = FileWriter.new(@configuration.options)
    file_writer.write if file_writer.can_write?
  end
end

This method definitely looks nice and object-oriented, and satisfies many code quality rules: it’s shorter than 10 lines, contains no branches, no Boolean parameters (unless there are any hiding in that options object?), indeed no parameters at all.

It also conforms to the Law of Demeter: it calls a method on one of its fields, it creates a local object and calls methods on that objects, and it doesn’t send messages to any other object.

In fact there’s significant Feature Envy in this method. The method is much more interested in the FileWriter than in its own class, only passing along some data. Moreover, it’s not merely using the writer, it’s creating it too.

That means that there’s no way that this method can take advantage of polymorphism. The behaviour, all invoked on the second line, can only be performed by an instance of FileWriter, the classglobal variable invoked on the first line.

FileWriter has no independent existence, and therefore is not really an object. It’s a procedure (one that uses @configuration.options to discover whether a file can be written, and write that file if so), that’s been split into three procedure calls that must be invoked in sequence. There’s no encapsulation, because nothing is hidden: creation and use are all right there in one place. The App class is not open to extension because the extension point (the choice of writer object) is tightly coupled to the behaviour.

Unsurprisingly that makes this code harder to “reason about” (a fancy phrase meaning “grok”) than it could otherwise be, and that with no additional benefit coming from encapsulation or polymorphism. Once again, the failure of object-oriented programming is discovered to be that it hasn’t been tried.

Two ways of thinking

I’ve used this idea in conversations for years, and can’t find a post on it, which I find surprising but there you go. There are, broadly speaking, two different ways to look at programming languages. And I think that these mean two different ways to select programming languages, which are asymmetric. However, they can lead to the same choice, but viewed in different ways: if you use one of those languages then you need to understand that these different views exist to understand decisions made by programmers working in those languages.

On the one hand, the Language Lawyer seeks out languages with plenty of esoterica and specifics, in order to become proficient at recalling those esoterica and demonstrating their correct application. The Language Lawyer loves that reference and value types in swift are actually called class and struct, or that the difference between class and struct in C++ is the default member visibility, and that neither is actually related to the words class or struct. They are delighted both to know and to explain that in Perl 5, @var, \var and $var refer to the same variable but are accessed in different contexts and what they evaluate to in those contexts. They are excited to explain that technically the admonition that you must always return a value from a non-void function in C is not true, because since C99 if you have a function called main that returns control without returning a value, the compiler will implicitly return 0.

The Language Lawyer seeks out a language with such esoterica. But learning it is complex and time-consuming, so they probably don’t change particularly often. In fact, changing language may be deleterious, because then other people will know the esoterica that they get caught out on. Imagine knowing how the function overloading rules in C++ work, then trying something similar in Java and finding that you’re wrong. And that somebody else knows that. The shame! Once their language is chosen, the Language Lawyer will form heuristic rules of which language feature to use when solving which problem.

Standing in the other corner, the Lazy Evaluator wants to avoid those edge cases, because it makes them think about the programming language, not the software problem. They’d much prefer to have an environment in which there’s as much as one way to express their solution, then worry about how to express their solution using that one tool. The Lazy Evaluator loves Ruby because Everything Is An Object (and they love Io more because in Ruby there are also classes). The Lazy Evaluator is delighted to know that in Lisp, Everything Is A List. They are excited to be programming in Haskell, where Everything Is A Function.

Both of these people can be happy with the same programming language, though clearly this will be for different reasons, and their different thought processes will bring them into conflict. The Lazy Evaluator will be happy enough using the third-most common C-family language, C++– (full name: “C++98 but never use exceptions or templates, avoid multiple inheritance, and [never/always] mark methods as virtual”). The Language Lawyer will enjoy demonstrating const-correctness and the difference between l-, r-, pr-, gl- and x-values, but the two will come to blows over whether the Lazy Evaluator is denying the full elegant expressiveness of the language by proscribing exceptions.

Similarly, the Lazy Evaluator can look at JavaScript, see the simple classless object model with dynamic dispatch remembered from Self or Io, even notice that Functions Are Objects Too and work in that Everything Is An Object paradigm. The Language Lawyer can be confident that at some point, all of the weird coercion behaviour, the Which this Is That this Anyway question, and the You Need That Flag With This Polyfill To Get That Language Feature issues will come up. But the Lazy Evaluator will notice that That Flag also enables class syntax and arrow functions, we already have prototypes and function functions, and disagreement will ensue.

So in what way are those ways of thinking asymmetric? Invoking Hickey’s Corollary, you can never be recomplecting. It’s easier to compromise on C++ But Without Exceptions than it is to compromise on Lisp But We’ll Add Other Atom Types. You can choose JavaScript But Never Use this, you can’t choose Haskell But With Type-Escaping Side Effects.

This isn’t really about programming languages, it’s about any form of model or abstraction. Languages are a great example though. I think it’s a mindset thing. Your task is to solve this problem, using this tool. Are you focusing on the bit that’s about the problem, or the bit that’s about the tool? Both are needed.

If Object-Oriented Programming were announced today

Here’s an idea: the current backlash against OOP is actually because people aren’t doing OOP, they’re doing whatever they were doing before OOP. But they’re calling it OOP, because the people who were promoting OOP wanted them to believe that they were already doing OOP.

Why is that? Because the people who were promoting OOP wanted to sell their things. They were doing this in the 1980s to 1990s, when you could still expect developers to spend thousands of dollars on tools and libraries. “Here’s a thing that’s completely unlike what you’re already doing” is not as good a sales pitch as “ride the latest wave without boiling the ocean”. Object-Oriented principles were then hidden by the “Object Technology” companies – the StepStones, NeXTs, OTIs, OMGs – who wanted to make OOP accessible in order to sell their Object Technology.

That’s not the world of 2017. Nowadays, developer environments, libraries, deployment platforms etc are largely open source and free or very cheap, so rather than make an idea seem accessible, people try to make it seem important. We see that with the current wave (third wave? I’m not sure) of functional programming evangelism, where people are definitely trying to show that they are “functionaller than thou” rather than that you already know this stuff. Throw up a github or an npm that uses monad, pointfree or homoiconic without any indication of shame, and you’re functionalling right. Demonstrating that it’s already possible to curry methods in Objective-C is not the right way to functional.

If OOP were introduced into this world, you’d probably have a more dogmatic, “purer” representation of it than OOP as popularly practised today. You might even have OOP in Java.

One thing that makes me think that is that, well, it’s happened and it’s true. Multiple times. As previously explored here, protocol-oriented programming is just polymorphism under another name, and polymorphism will be found in a big-letters heading in any OOP book (in Barbara Liskov’s “Program Development in Java”, it’s chapter 8, “Polymorphic Abstractions”. In Bertrand Meyer’s “Touch of Class”, section 16.2, “Polymorphism”.

Similarly, what are microservices other than independent programs that maintain their own data and the operations on those data, performing those operations in response to receiving messages? What is a router at the front of a microservice but the message dispatch handler, picking a method implementation by examining the content of a selector?

Open Source: because I got mine, so fuck you

The Free Software movement has at its core the idea that people have the freedom to use, study, share, and improve the software on their computers. The modern developer “ecosystem” has co-opted this to create a two-tier society: a developer has the freedom to use, study, share, and improve the tools and libraries that developer puts to use in creating software that must be accepted as-is, and used only in the ways permitted in the Terms of Service and End User Licence Agreement. I’ve got mine, so fuck you.

Your web application is probably split, broadly speaking, into a front-end bit that runs in the browser and a back-end bit that runs on somebody else’s computer.

The back-end bit is on somebody else’s computer, so you chose not to adopt the Affero GPL and don’t need to spill any precious freedom on the consumers of your service. The fact that you’re using Node.js (non-copyleft Free Software), a billion node modules (all most likely non-copyleft Free Software), losing your customer’s data in MongoDB (copyleft Free Software), and deploying on GNU/Linux (copyleft Free Software) with Docker (non-copyleft Free Software) and Kubernetes (non-copyleft Free Software) got you what you wanted quickly and cheaply, but there’s no need to permit anyone else access to those freedoms.

The front-end bit is on your customers’ computers, so you definitely don’t want to accidentally spill any precious freedom there! Even though you used a gallon of polyfills and shims (non-copyleft Free Software) and Angular (non-copyleft Free Software) to get what you wanted quickly and cheaply, there’s no need to permit anyone else access to those freedoms.

And of course you edited all that JavaScript with VSCode (non-copyleft Free Software).

The fact that the browser used to run that JavaScript might itself be Free Software is immaterial. The freedom to not have any freedom is not freedom.

That’s true in the mobile world too. Your free software compilers and runtimes and libraries all go to build opaque blobs that must be run as-is on somebody else’s phone, whether or not that phone has a kernel that’s Free Software. We got the bits to build our platforms and our apps quickly and cheaply, thanks to Free Software. We’ve got ours, fuck you.

But, you argue, this is all immaterial. People who aren’t developers, well, they aren’t developers, they can’t change software, why does any of this matter? Because, as all the crime dramas attest, people need motive, MO, and opportunity. Remove the freedom and the opportunity is removed: maybe someone would go out and learn a bit of programming if they had a problem they wanted to solve and the opportunity to solve it. Or maybe they’d go out and find a gigging coder, or a student who wanted a side project, or one of us pros who needs to keep their activity chart green in order to stay employable. Maybe we would benefit more from our jobs as people who change software, if there were more opportunities to change software.

The current division of software freedom into the haves and the have nots is arbitrary, artificial, and unnecessary.

Technical debt and jury service

We have the idea that in addition to the product development backlogs for our teams, there’s an engineering backlog where technical debt paydown, process/tooling improvements, and other sitewide engineering concerns get recorded. Working on them is done in time that is, by definition, taken away from the product backlogs (because of Sustainable Pace).

A colleague recently described the time spent on the engineering backlog as a “tax”, which is an interesting analogy. A pejorative interpretation is that, like a tax, centralised engineering work is a cost imposed that takes away from realising more value on my direct projects.

A positive spin is that taxes go toward funding the commons: no one of us can afford to build a road between our house and the office, but having roads connecting all the houses to all the offices has strategic benefit to society as a whole (higher productivity, lower unemployment, more opportunities) so if we all pay in a fraction of the cost of a road we can all have a road. Similarly, one product team might grind to a halt if they spend all of their time on the new CD pipeline infrastructure, but all teams will benefit if they all chip in a bit.

This version of the analogy implies that there might be, like the treasury, a central agency deciding how to spend the common wealth. Somebody needs to decide how much tax everyone should pay, what to do with dissenters (is it OK if your product team focuses on its sprint for a fortnight and doesn’t do any of the engineering backlog?), whether to accept overpayments, and what those tax dollars should go on.

Only it’s not tax dollars, it’s tax hours. In this sense, a better analogy is conscription (I originally thought of the Anglo-Saxon fyrd, maybe jury service or non-military national service is a less aggressive way to consider this). Taxation means that I give all of my work time to Wealth Wizards but give a chunk of my money to the government. Conscription means that I don’t get to give all of my time to my employer: some of it has to go to the commons. Maybe Jonathan and Rebecca can’t give any time to their product teams this week because they’ve been “called up” to the engineering backlog effort.

That seems like a useful analogy for these tasks. I can think about what resources are available for products or “the commons”, because I can think about whether someone is working on “the day job” or has been conscripted. Maybe it doesn’t make sense for everybody to have equal likelihood of being “called up”, in the same way that it’s easier for students to get out of jury service than for full-time employees.

The worst phrase in software marketing

“Rewritten from the ground up”.

Please. Your old version mostly worked, except for those few corner cases that I’d learned how to work around. Now I don’t know whether the stuff that did work does work now, and I don’t know that I’ll find that stuff in the same place any more.

There’s a reason that old, crufty code in the core of your application was old and crufty. It is old because it works, well enough to pay the programmers who work on maintaining it and building the new things. It is crufty because the problem you’re trying to solve was not perfectly understood, is still not perfectly understood, and is evolving.

Your old, crufty code contains everything you’ve learned about your problem and your customers. And now you’re telling me that you’ve thrown it away.

Culture Smell

A phrase I used in a discussion today. Developers are familiar with “code smells”, aspects of a codebase that aren’t necessarily wrong but do make you take a deeper look. By analogy, a culture smell surprising, but not necessarily wrong, behaviour on a team that should make you wonder what motivations lead to that behaviour, and what to change to remove or redirect those motivations.

It’s easy to get the wrong idea about culture though. Familiar with the developer concept of pure functions, some organisations seem to operate on the belief that by defining some principles and printing motivational posters, they can ensure conformant behaviour. Or that “the culture” is a concrete device that employees buy into.

Culture is the more slowly-varying norms established by the interactions between group members. You can guide, and lead, and of course admonish or eject, but you can’t control. As a result, more culture signals are “smells” than problems: what you see may not look right, but you have to explore more.

Literate Programming with LibreOffice

This post comes in the form of an OpenDocumentFormat document containing a program that can extract programs from ODF documents, including the program contained in this document.

Prototypical object-oriented programming

Some people think that the notion of classes is intrinsic to object-oriented programming. Bertrand Meyer even wrote a textbook about OOP called A Touch of Class. But back in the 1980s, Alan Borning and others were trying to teach object-oriented programming using the Smalltalk system, ostensibly designed to make simulation in computer programmers accessible to children. What they found was that classes are hard.

You’re not allowed to think about how your thing works before you’ve gone a level of abstraction up and told the computer all about the essence of thing-ness, what it is that’s common to all things and sets them apart from other ideas. And while you’re at it, you could well need to think about the metaclass, the essence of essence-of-thing-ness.

So Borning asked the reasonable question: why not just get rid of classes?. Rather than say what all things are like, let me describe the thing I want to think about.

But what happens when I need a different thing? Two options present themselves: both represent the idea that this thing is like that thing, except for some specific properties. One option is that I just create a clone of the first object. I now have two identical things, I make the changes that distinguish the second from the first, and now I can use my two, distinct things.

The disadvantage of that is that there’s no link between those two objects, so I have nowhere to put any shared behaviour. Imagine that I’m writing the HR software for a Silicon Valley startup. Initially there’s just one employee, the founder, and rather than think about the concept of Employee-ness and create the class of all employees, I just represent the founder as an object and get on with writing the application. Now the company hires a second employee, and being a Silicon Valley startup they hire someone who’s almost identical to the founder with just a couple of differences. Rather than duplicating the founder and changing the relevant properties, I create a new object that just contains the specific attributes that make this employee different, and link it to the founder object by saying that the founder is the prototype of the other employee.

Any message received by employee #2, if not understood, is delegated to the original employee, the founder. Later, I add a new feature to the Silicon Valley HR application: an employee can issue a statement apologising if anybody got offended. By putting this feature on the first employee, the other employee(s) also get that behaviour.

This simplified approach to beahvioural inheritance in object-oriented programming has been implemented a few times. It’s worth exploring, if you haven’t already.