Code that resists

Kellan Elliott-McCrea, on the way towards an understanding of technical debt, catalogs the ways we end up with code that resists our efforts to change it:

Therefore the second common meaning of “technical debt” is the features of the codebase we encounter in our work that make it resist change. Examples of features that can make a codebase resist change include: poor modularization, poor documentation or poor test coverage. Just as easily though an abundance of modularization (and complexity) or an abundance documentation, and tests encoding the now the incorrect old behavior can apply a strong downward pressure on change.

A little discussed and poorly understood design goal for code is disposability. Given change, what design patterns can we follow that allow us to quickly expunge incorrect behavior from our codebase? Interestingly it is a much more tractable metric for measuring as opposed to more popular criteria like “elegance”. (a post for another day)

Put that in your thinker. Does something like Strategy or Adapter let you throw out whole classes when they prove unnecessary? Or is that so only when you luck out and chose the exact right axes of disposability? Does a microservice really let you discard codebases wholesale? Can maps and functions free you from intertwingled state and behavior or does it move the resistance somewhere else?

Grumpy, opinionated answers: possibly! Even more possibly! Meh. Very meh.

Three part method

I find methods/functions decomposed into three parts really satisfying. Consider a typical xUnit test:

def test_grants_new_role
  # setup
  user = make_user
  new_role = make_new_role
  
  # behavior under test
  user.add_role(new_role)
  
  # assert results
  assert_equal [new_role], user.roles
end

Lately I’ve been structuring Rails controller similarly:

def create
  # Extract inputs/parameters from HTTP request
  person_params = params.require(:person).permit(:name, :age)

  # Invoke behavior encapsulated in a Plain(ish) Ruby object somewhere
  user = UserService.create_user(person_params)
  
  # Check the result and make some HTTP output
  if user.persisted?
    redirect_to user_path(user.id)
  else
    @user = user
    render :new
  end
end

Clojure even has the let form which encourages this style:

; annotated from clj-http
; https://github.com/dakrone/clj-http/blob/master/src/clj_http/util.clj
(defn gzip
  "Returns a gzip'd version of the given byte array."
  [b]
  (when b
    ; set the table
    (let [baos (ByteArrayOutputStream.)
          gos  (GZIPOutputStream. baos)]
     
      ; do the work and clean up
      (IOUtils/copy (ByteArrayInputStream. b) gos)
      (.close gos)

      ; produce a result
      (.toByteArray baos))))

I don’t think there’s anything inherently wrong if a method or function isn’t organized this way. But when I read code structured this way, it feels less like a bunch of random logic and more like a cohesive unit that someone put time into thinking through how someone might try to understand it later. The Rule of Three rules everything around us.

Of all the pop culture beefs going on at the time of this writing (Meek vs. Drake, BoB vs. Neil deGrasse Tyson, Trump vs. Everyone), my favorite is now Tim O’Reilly vs. Paul Graham on income inequality.

When a startup doesn’t have an underlying business model that will eventually produce real revenues and profits, and the only way for its founders to get rich is to sell to another company or to investors, you have to ask yourself whether that startup is really just a financial instrument, not that dissimilar to the CDOs of the 2008 financial crisis — a way of extracting value from the economy without actually creating it.

This has always bugged me in particular. So few startups have an idea beyond “get smart people together, maybe make something, hope that selling the team ends up profitable”. We need a much better word for “speculative technology-focused company funded by speculation”.

Threaded discussions: nope nope nope

Pet peeve #73: threaded discussions. You may have seen it in a Usenet reader or perhaps even your email. It may seem like a great way to manage a long conversation with multiple ideas and lines of discussion. OK, that’s fine, I think you’re wrong and looking at this a little too technically but it’s not forcing that perspective on anyone else so fine.

I get peeved when its suggested that conversational tools like Twitter or Slack should implement threaded messages. Nope. You have now failed my secret test, please disembark from the pragmatic train.

If a conversation requires threading, that conversation has already gone way off the rails.

Two people talking about one thing and another two people talking about another thing in the same conversation is the definition of talking past each other. Why should our software enable that?

If an email or chat ends up covering two important topics, e.g. whether to use solid or liquid fuel on a rocket and what color to paint the rocket, it was poorly written in the first place. A reasonable person can easily jump in and say “let’s talk about the fuel now and we can figure out the color later”.

Bottom line: I think people can and should handle breaking off side discussions on their own instead of trying to push weird hierarchy on participants.

The future of programming is design, teaching, and empathy

The Future Programming Manifesto starts with this header:

Inessential complexity is the root of all evil

OK, I’m on board!

We should measure complexity as the cumulative cognitive effort to learn a technology from novice all the way to expert. One simple surrogate measure is the size of the documentation.

Perhaps we could describe the complexity of a technology in “bookshelves”? For example, in my second internship I met a CleearCase administrator whose office bookcase had one shelf devoted to SunOS, one shelf to Oracle, and the final shelf dedicated to ClearCase itself. How many bookcases for Ruby, Rails, JS, CSS, a database, and all the other stuff you need to know to put a CRUD app in your browser (not even deploy it to the web!)

  • Maintaining compatibility increases complexity.
  • Technical debt increases complexity.
  • Most R&D is incremental: it adds features and tools and layers. Simplification requires that we throw things away.
  • Computer Science rejects simplification as a result because it is subjective.
  • The Curse of Knowledge: experts are blind to the complexity they have laboriously mastered.
  • Rewarding programmers for their ability to handle complexity selects for those who love it.
  • Our gold-rush economy encourages greed and haste.

A weird thing about programmer is that those that rant endlessly about someone else’s complexity, layers, and haste are almost completely blind to the complexity, layers, and haste they make in an effort to set the world just so.

We should work for end-users disenfranchised by lack of programming expertise. We should concentrate on their modest but ubiquitous needs rather than the high-end specialized problems addressed by most R&D. We should take inspiration from end-user tools like spreadsheets and HyperCard. We should avoid the trap of designing for ourselves.

What if more of programming was accessible as data manipulation (cf. spreadsheets, data files, JSX templates) instead of as logic and behavior (i.e. almost every programming language)?

We are doing Design: using experience and judgement to make complex tradeoffs in order to satisfy qualitative human needs.

This reminds me of Developer Experience. “Developer experience” is a weird word right now, but it’s becoming table stakes for success. It’s a design discipline. It’s considering the form and function of code. It’s the opposite of attempting to learn C ;)

Long story short: we’re gonna need more empathy, more design skills, and more teaching skills to reach the next level of great programming languages and tools.

What if large open source projects appointed a community manager to handle things like codes of conduct and social spaces? Anecdotally, those who make large projects are often the worst at actually running a community. Even volunteer projects need management. Flat organizations will always be dominated by ad-hoc in-group politics. The internet we’ve created thus far is allowing terrible people to outpace good people by a long shot.

Versioning an API is a river delta of pain

Slight rant: versioning a (REST) API inflicts upon you a confluence of factors that will lead to pain no matter what you do.

You’re going to need to version things, which opens you to bikeshedding which True Scotsman approach to REST versioning you’ll use. Once you’ve expended tons of effort on how clients should specify which version they want (i.e. once you’ve just barely started), now you need to figure out how to make that work in your code. Which, after you’re done parsing the HTTP request (the easy part!), is almost certainly going to lead to some unruly layer(s) of indirection. At which point you’re going to hate life and never want to introduce another version ever again. And you won’t even be close to finished.

I hope that, between JSON API and GraphQL, letting the client specify what they want ends up proving way better than relying on the server to carefully (or possibly carelessly) hand craft just the right data for the client.