Defeating Hydra: The Growing Problem of Complexity in Web Developmentby@brbs
1,661 reads
1,661 reads

Defeating Hydra: The Growing Problem of Complexity in Web Development

by Tyler BerbertJuly 15th, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

As you add pieces to an interconnected system, it gets more complex, and fast. The bigger the gap between the pace at which you build and how you address your complexity, the more trouble you’re creating for your future self. The programming language I use the most is called TypeScript, which was built on a better-known language called JavaScript. TypeScript is now both a “frontend” and “backend’s” language. The vast majority of web developers need to be multilingual by default.

People Mentioned

Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Defeating Hydra: The Growing Problem of Complexity in Web Development
Tyler Berbert HackerNoon profile picture

Look at the picture below. Look at how many more dots are on the right than on the left. Then look at how many more lines are on the right than on the left.

As you add pieces to an interconnected system, it gets more complex, and fast.

Conventional wisdom in the tech industry is that you manage complexity like weeds in a garden. You put aside a fixed amount of time (let’s say a couple days) at a fixed interval (let’s say every month) to do some documentation here, some code refactoring there.

Many don’t do that. Many want to, but are so busy churning out new features that they end up treating their product’s complexity the way one treats Boo, that Mario villain who slyly follows you while your back is turned and stops moving when you look at him. In the real world, many settle for “build really fast and sometimes acknowledge that complexity exists.”

Deep down, in places they don’t talk about at parties, every engineer knows these approaches are broken.

Software complexity is like a hydra, a monster from Greek myth that grows two new heads each time you cut one off. The more you build, the more it grows.

Making your team build faster to make up for the slowdowns that complexity can bring is the opposite of what you should do, and exactly what many teams do. In fact, the bigger the gap between the pace at which you build and the pace at which you address your complexity, the more trouble you’re creating for your future self.

The hydra’s heads generally attack from three places:

  • The languages and frameworks you use
  • The libraries and apps you use
  • The product you’re building

The languages and frameworks you use

The programming language I use the most is called TypeScript, which was built on a better-known language called JavaScript.

TypeScript is now both a “frontend” and “backend” language. This means, in a nutshell, that you can use it not just on devices to make app screens and webpages show up, but also on the faraway computers, called the servers, that handle the app’s data and send the right data to each user’s device.

Being able to use one language for most of your job is nice. Being able to use one language for all of your job is pretty much impossible. The vast majority of web developers need to be multilingual by default.

To be a frontend engineer, you can’t just know some flavor of JavaScript. You need HTML and CSS. HTML lets you decide what goes where — a heading here, an image there, a block of text yonder. CSS is how you style it with colors and fonts and such. JavaScript adds the logic and data. When I click on something, what happens? When I ask the server for some data and it arrives, how does it get handed off to the HTML?

Most backend engineers know more than one language too, in large part because so many more exist, and because they have such different strengths. Many were around long before the web was, like C++ and Java. Some are newer but still largely meant for other disciplines, like Python for data science. And completely separate from whichever of these language(s) you use, there are also the ones you specifically need for database querying, be it SQL or MongoDB or whatever else.

Especially for those languages that weren’t built for backend web development at all (and for some that were), tools called “frameworks” have sprung up to make backend web development easier. There’s Django and Flask for Python, NestJS and Express for Typescript, Rails for Ruby, and a lot more.

A lot of impressive frontend frameworks have sprung up, too, that make Javascript easier to use with HTML and CSS, and more generally, that make it easier to build big web apps. React, Vue, Angular, and Svelte are a few big ones. They can have major philosophical differences — React, for example, favors a “top down” approach where data is handled and dispersed from some central source, while Vue is friendlier to the idea of user actions and events causing data to “bubble up” in a tree of components.

Going further, each framework has an entire ecosystem around it. React, for example, has a spinoff called Preact that’s smaller and faster, and a related framework called React Native for building mobile apps. They also tend to have constellations of specific third-party libraries that work especially well with that framework.

Which brings us to…

The libraries and apps you use

This section is going to be a lot of lists. This is on purpose. It’s a shock-and-awe tactic.

There are a ton of things a codebase might (and should) want to “outsource” to an open-source library, if that library has already done the job well. Here’s a small subset of examples from the Javascript world alone:

  • Date formatting (Dayjs)
  • Creating forms for users to fill out (Final Form)
  • Validating different types of form info like emails, phone numbers, etc. (Yup)
  • Running automated tests to save time when doing quality assurance (Jest)
  • Giving specific users of your app the permission to do specific things (CASL)
  • Letting your frontend and backend communicate more easily (GraphQL)
  • Letting your backend and database communicate more easily (TypeORM)
  • Combining your app’s many files, which you separated for readability, into much bigger files that work better when parsed by actual web browsers (Webpack)

When a library for something is particularly hard to build, you often have to integrate with another service entirely, sometimes paying for it, and usually getting some kind of web dashboard on the side. These also tend to cover pretty universal needs, like:

  • Hosting your app (Heroku)
  • Payment processing (Stripe)
  • Letting people log in and out of your app (Auth0)
  • Hosting landing pages for your app (Webflow)
  • Hosting random files your app needs to access (Amazon S3)
  • Making sure new code doesn’t break your app (CircleCI)
  • Logging the errors your site has (Sentry)
  • Logging what actions your users are taking (Mixpanel)

And then there are libraries from both categories — open-source and paid — that are much more domain-specific, depending on the needs of your company, like:

  • Rendering charts and graphs (D3)
  • Allowing drawing and image processing (Fabric)
  • Sending emails (Sendgrid)
  • Sending texts and making calls (Twilio)
  • Hosting chat services (Pusher)
  • Managing customers and customer support tickets (Salesforce)
  • Workforce management (Humanity)
  • Medical record integration (AllScripts)
  • Rendering math text (MathJax)
  • Hosting alternative databases for data that doesn’t do well in rows and columns, like time-series data (InfluxDB)

And then, of course, there are tools that don’t require a library that works with your codebase, but still involve adding some kind of product to your workflow that you have to get familiar with:

  • Data dashboards (Looker, Tableau)
  • Product management (Jira, Asana)
  • Documentation (Slab, Google Docs)

And finally, you have your apps. Every day, when I open my computer, there are at least ten apps from my launch bar that get opened without a second thought. I have to use them all pretty regularly. There’s:

  • VS Code, for writing code
  • Chrome, for testing code and looking stuff up
  • Postman, for testing backend work without needing the frontend
  • Figma, for looking at the designs of anything visual I’m building
  • Slack, for communicating over text
  • Zoom, for communicating over video
  • Datagrip, for inspecting data in my database
  • GitHub, for looking at changes over time in the codebase
  • A text editor, for random notes, planning things out, and drafting long messages
  • Terminal, for starting up the app I’m working on, switching between projects, and committing my work

As numerous as these tools are, they’re almost always well-documented — your end users are the developers using them, after all.

Inside companies, there’s less incentive for this. The code itself, and most of the nooks and crannies of how your product works, aren’t what you’re showing to the user. It’s the “happy path,” the core experience of your product. But the code itself, and its many edge cases, take up a huge percentage of your developers’ time.

This discrepancy leads to whole new cans of worms. The next section is the big kahuna — it produces the most hydra heads by far.

The product you’re building

If you’ve read the book “The Mythical Man Month” by Fred Brooks, you already have an idea of the gist of this section. (If you haven’t, and if you work in a trade where teams of people do knowledge work, give it a read.)

The premise, which I’ve seen validated too many times, is this: when you double the number of people working on a software project, you do not cut the time it takes to finish that project in half. Dream on.

With each person you add, there’s extra communication you need to do regarding how the system works (or should work), and extra planning regarding when things should get worked on relative to each other and by whom. This takes time.

Beyond writing the code that builds the thing, you need the following pieces in place, at a minimum:

  • A process. Are you “Agile,” as they say? Or do you “waterfall” instead? What kinds of meetings do you have where you decide what to build, how to build it, and how long it will take? Who does the work for each piece below, and when?

  • Reasons for building stuff. This can be data from app usage or results of user testing. If you’re constantly going on hunches, you’ll find yourself scrapping work, wasting money, and demoralizing your people. It’s hard to be sure of that, and it’s hard to get right — here’s good general rule:

  • Specs. For a team to build something, the requirements of that thing need to be thought through. The internal consistency and external consequences of those requirements need to be considered. Then they need to be written down and set in stone for others. Changing the finish line after you start the race causes chaos and makes the end result sloppy, and often so sloppy that it needs to be redone. An ounce of prevention is worth a pound of cure.

  • Testing. You need to know how, when you finish building something, everyone will come to agree that the finish line has been crossed. This means checklists that people can run through. This means automated tests that your machines can run through, both to save people time and to help ensure that you didn’t break old stuff, which inevitably happens in complex systems.

  • Design systems. You should have some kind of design system that doesn’t let engineers make user interface and user experience decisions while they’re writing code, for they will get those decisions horribly wrong.

  • Documentation. Important pieces of the product, important decisions made in your project’s past, and important ways in which your product is modeled and represented and stored in your codebase and database — all of these should be written down as much as possible. The shortest pen is better than the longest memory. New engineers should also get some kind of documentation about how to start building things for your product, and on the tools and frameworks and libraries you use.

Virtually no one overestimates how much time it will save to be methodical about these. On the contrary, most of us underestimate that all the time.

Is the fault in our tools?

Clearly there’s a dizzying array of tools and processes you now need to understand in order to make software that’s creative and profitable and better than alternatives. This, as mentioned at the beginning, is where real complexity comes from.

Some of this increasing complexity, it’s fair to say, is out of teams’ control. Software is eating the world, and that complexity has to live somewhere.

Many software ecosystems, especially Javascript, are understood by those within them to be bloated and crowded, if incredibly versatile. When presented with the statement “building Javascript apps is overly complex right now,” an astonishingly low percentage of developers — 34.4% — say they disagree. (Note that this percentage is also among people who like JavaScript enough to take a survey about it.)

That number is improving modestly over time, thanks to awesome work by awesome people building awesome tools. You might say “But wait! You said those tools can create even more opportunities for complexity!”

They can. But not because of the tools themselves. It’s our understanding of them and of how we use them.

The tools are incredible. Through them, a staggering amount of complexity has already been wrangled for us. Others have packaged up so much math and logic and physics and electrical engineering and computer science over the centuries, and especially over the last few decades, that you can now literally type some characters into some text files and pop out apps like magic.

These tools are not hammers or screwdrivers, and it’s a mistake to think we can just cobble them together in increasingly complex ways and leave it at that.

Brains process information like “how to ride a bike” or “how to build a chair” (this is called “implicit memory”) fundamentally differently from information like “the capital of North Dakota” or “the name of that script you use to fill your database with dummy data” (“explicit memory”).

Every product, app, and library you use as a developer is a little language unto itself, with new challenges for your explicit memory. New vocabularies of jargon. New lists of functions to remember (or at least remember that they exist). The things in each of these categories are also typically nested — built on top of other tools — and they evolve and get updated frequently in ways that hammers don’t.

The constant recall and lookup of these changing facts means there’s no real upper limit to the demands that they can impose on your brain’s cognitive load. Chronically bumping up against the limits of your cognitive load is a powerful source of stress, and a lot of companies have very smart people fine-tuning the amount you experience while using their apps to make sure this doesn’t happen.

Teams can do this for themselves, too. Much still rides on the way you manage, or fail to manage, the complexity that you build upon and create for yourself. What’s within a team’s control, then, is not the nature of software tools, but the nature of the product that it’s building, and the ways in which you marshal different resources to build it.

The solutions

Solutions? You still want solutions? Ugh. Come back later. Tired from explaining the problems.

Okay, obviously not. But it’s also tempting to skip solutions because they’re so infuriatingly, maddeningly simple. Most people could throw out uninformed suggestions that, if actually enacted, would beat the status quo.

Anyway, fine. Here.

  • Hire more people, not just to build, but to wrangle complexity
  • Spend increasing amounts of time on product quality, not quantity

That’s it. It’s really just up to teams whether they want to do it or not.

The “increasing” part is annoying, but important. A fixed amount won’t cut it. As you build, you need to allocate more time and resources to reducing what complexity you can, and documenting what you can’t.

Remember that the biggest productivity gains come from laying groundwork for others to walk on, whether that’s documenting complicated code, streamlining the ways developers test it, or anything in between. That’s why people build useful software in the first place.

Too many teams understand this perfectly when it comes to selling easy-to-use software to their customers, and not at all when it comes to making their own product easier, or sometimes even possible, for their own team members to work on effectively.

Many companies want to have their cake and eat it too. They want to build on top of, and make money from, the many ways in which others have wrangled complexity for them. They very rarely want to worry about wrangling it themselves. They want to build, build, build. They want to be feature factories. (Read this thread for a great deep dive into why that’s not the only approach startups should consider.)

Again, the more of a feature factory you are, and the faster your complexity grows without being managed, the more damaging it becomes. And the harder it is to stay in front of it. And the less feasible it is, after a while, to even get in front of it.

It’s a sneaky, vicious cycle, and the concrete symptoms are ugly. Projects start blowing through their time estimates. Unnecessary work is done. Necessary work is redone. Team communication suffers. Employees burn out.

Improving each individual employee’s ability to handle complexity, even if that’s a good goal to have, is not a substitute for the real solutions. Your product’s internal complexity will still catch up to people’s cognitive load, even if they’re very smart.

You have to strengthen your team’s collective ability to wrangle any part of the product’s complexity. For a product to be worked on by a team, where people come and go, and frequently communicate with each other, you have to make the product itself, and the many ways people work on it, inherently less complex and easier to understand.

The people who can do this legwork — document, understand, and wrangle complexity — are out there. Many people, as crazy as it sounds, actually thrive on it.

There are some roles for it already, with job titles like “Technical Product Manager,” and luckily they’re growing more common, but they’re not common enough. Instead, practically all of the complexity I introduced at each level of this post is currently distributed across a mishmash of engineers, product managers, and designers, rather than being owned in full-time roles.

The collective need for changing this fact can manifest itself in many places — retrospective meetings, Slack discussions, 1:1s with managers, and offhand comments during daily standups, to name a few.

When it does, don’t let it be swept under the rug. Don’t let people make excuses for it or kick the can down the road. Take note and take action. If you do so, you won’t just keep the creeping vines of complexity at bay. You’ll build better features, and you’ll build them more quickly, and you’ll build them with a much, much happier team.