How AI tools are democratizing software development

matt
aiproductivityprogramming

I've been working on the comments system for my blog in the past week. Comments systems are complicated; there's not only what the user experiences but there's also entire sub-systems and metadata dedicated to managing comments. Think: moderation systems.

As far as moderation approaches go mine is pretty pragmatic. I have a moderation queue that shows the parent comment, OPs comment, and any replies in a collapsible container. I can leverage that the contextualize the comment for discourse, but I can also preview the GPs comment for further contextualization.

I used the word simple, but what I'm talking about is two sets of APIs: one for users and one for admins (me). Next, I do some database tomfoolery with reverse relationships to display child and parent comments. A preview:

{
  "items": [
    {
      "id": 0,
      "author": {
        "id": 0,
        "username": "string"
      },
      "content": "string",
      "post": {
        "id": 0,
        "title": "string",
        "content": "string",
        "created_at": "2024-10-13T18:17:04.363Z",
        "updated_at": "2024-10-13T18:17:04.364Z",
        "published": "2024-10-13T18:17:04.364Z",
        "author_id": 0,
        "slug": "string"
      },
      "children": [
        {
          "id": 0,
          "author": {
            "id": 0,
            "username": "string"
          },
          "content": "string",
          "post": {
            "id": 0,
            "title": "string",
            "content": "string",
            "created_at": "2024-10-13T18:17:04.364Z",
            "updated_at": "2024-10-13T18:17:04.364Z",
            "published": "2024-10-13T18:17:04.364Z",
            "author_id": 0,
            "slug": "string"
          },
          "children": ["string"],
          "visible": true,
          "created_at": "2024-10-13T18:17:04.364Z",
          "updated_at": "2024-10-13T18:17:04.364Z",
          "reviewed": true,
          "note": "string"
        }
      ],
      "parent": {
        "id": 0,
        "author": {
          "id": 0,
          "username": "string"
        },
        "content": "string",
        "post": {
          "id": 0,
          "title": "string",
          "content": "string",
          "created_at": "2024-10-13T18:17:04.364Z",
          "updated_at": "2024-10-13T18:17:04.364Z",
          "published": "2024-10-13T18:17:04.364Z",
          "author_id": 0,
          "slug": "string"
        },
        "parent": "string",
        "visible": true,
        "created_at": "2024-10-13T18:17:04.364Z",
        "updated_at": "2024-10-13T18:17:04.364Z",
        "reviewed": true,
        "note": "string"
      },
      "visible": true,
      "created_at": "2024-10-13T18:17:04.364Z",
      "updated_at": "2024-10-13T18:17:04.364Z",
      "reviewed": true,
      "note": "string"
    }
  ],
  "count": 0
}

Notice the parent and child relationships have to avoid some circular dependencies, but are optimized for nested relationships. As in all things that are cool I'm using some tricks to produce what looks like a very comprehensive comment system like fetching only top level comments and sorting server side for visibility. All in all, from my tests, even with thousands of comments the system continues to perform pretty quickly.

Now for confession time and the starting in on the overall point of this post: I built most of this on nights and weekends with GitHub Copilot.

AI as a productivity tool

LLMs get shopped as this thing that's going to kill peoples jobs. It reminds me of the discourse around idempotent automation and immutability over a decade ago.

"This one neat trick your sysadmins don't want you to know about will reduce your ops overhead drastically!"

-- Some consultant, probably

The gist is that you didn't need rooms of people managing servers like they were their pets. Instead, we'd manage flocks. What this opened up was a greater focus on the meta of the flock rather than dedicating individual focus to each member of the flock. We knew the flocks size, the cost of each member, and since all members were clones we knew a lot about the characteristics about each member. The philosophy of immutable infrastructure was sold as a lot of things but at its core it was a productivity shift for software operations

Immutable infrastructure was just one philosophy of many that were cannibalized by an overarching movement called "shift left". We, as in industry, focused on making optimal use of the time between when a software engineer puts their fingers on a keyboard and when your change goes live to users. Much of that did indeed require a skills adaptation, but the industry went through many changes including the Great Migration to the Cloud™. Why? Turns out calling an API to spin up infrastructure is just a lot faster and (generally, but not always) less frustrating than filing a ticket for some person that works in a basement.

Fear not though; the basement workers in this story became people like me. I also worked in a NOC, fulfilling tickets and responding to broken things nearly full time; now I work on software through the lens of reliability and developer productivity.

Neither here nor there but... it's funny how some people constantly seek to remove other people from the equation of their business and those people just reinvent themselves.

I wanted to spend some time comparing the present to the past because I feel it's pretty relevant to remind folks we've been here before. Now, back to Copilot. Copilot is a very capable LLM based on ChatGPT's line of products. Of course, it gets things wrong and does dumb things, but if you know how to prompt it then it can be quite capable. That's to say, Copilot isn't even the most capable for what I'm tasking it with; there are newer LLMs that are optimized for generating entire codebases that when put in the hands of a capable, well-seasoned programmer could knock out the most rote tasks of a codebase much quicker than Copilot does for me.

When I use Copilot actively I target it at two things:

  1. rote tasks that I'd colloquialize as the "busy work" of programming
  2. highly complex, small surface area problem solving

Number one is pretty self-explanatory to most programmers, but for the non-programmers I'm talking about things like REST API scaffolding. If I've built a user system, RBAC, and logging system then I need to wire each up in every occurance of the CRUD parts of my API. When I build new APIs Copilot goes to work scaffolding that CRUD API and including the components I mentioned. There's some ways to optimize this through prompting and keeping relevant code open in tabs, which Copilot will source from.

One of the examples I like to use is that I needed to write some various date formatting functions for Typescript on top of Javascript's native Date class. I wanted a function that displays the dates you see in these posts but I wasn't super keen on using a date library given that the OpenAPI generators I use the native Date class. Copilot came up with this:

export function formatDate(date: Date | null | undefined): string {
  if (!date) {
    return ''
  }

  // change date to the user's local timezone
  const offset = new Date().getTimezoneOffset()
  const localDate = new Date(date.getTime() - offset * 60000)

  const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
  const months = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ]

  const dayOfWeek = daysOfWeek[localDate.getDay()]
  const month = months[localDate.getMonth()]
  const day = localDate.getDate()
  const year = localDate.getFullYear()
  const hours = String(localDate.getHours()).padStart(2, '0')
  const minutes = String(localDate.getMinutes()).padStart(2, '0')

  // Function to get the correct ordinal suffix
  function getOrdinalSuffix(day: number): string {
    if (day > 3 && day < 21) return 'th' // Handles 11th, 12th, 13th
    switch (day % 10) {
      case 1:
        return 'st'
      case 2:
        return 'nd'
      case 3:
        return 'rd'
      default:
        return 'th'
    }
  }

  const dayWithSuffix = day + getOrdinalSuffix(day)

  // store a value with the timezone
  const timezone = localDate.getTimezoneOffset() / 60

  return `${dayOfWeek}, ${month} the ${dayWithSuffix}, ${year} @ ${hours}:${minutes} (UTC${timezone >= 0 ? '+' : '-'}${timezone})`
}

The comments are largely added by me to guide Copilot into doing the right thing. While some may scoff at this, this is code that I can understand well but on my own would largely never write. In the past I would've reached for a date library and would've handled changing types back and forth.

If I'm starting to sound like a Copilot shill then let me be clear: I don't think this set of constructs is special to Copilot. I've used Anthropic's models to achieve (mostly) the same result in other applications.

Tooling is primacy

As a wood worker I understand a fundamental truth to wood working: tools, and the quality of them, do not guarantee the quality of the product but they can influence the speed to produce a quality product. It's the logic that was at the heart of Industrial Revolution and nearly a century on forward we have not escaped that logic. Company build entire Internal Developer Platforms (IDPs) to speed programmers along to fitting into their tech stack so that they can focus on the business logic that saves or makes the company money. That's to say at some point once a corporation has achieved peak product-to-market fit the difference between it and enterprise becomes efficiency and reliability.

Another analog near and dear to my heart is the story of indie game developers and the explosion of the market we've experienced in the last five years. Platforms like Steam, GOG, and Epic have driven the availability of these games but the tools developers use to build games have also began to find quite a nice middle ground where more developers can do the things they need to produce a good game in a much quicker time than they used to. I'm talking about comprehensive tooling and engine bundles like Godot, Unity, and Unreal engines. There's multitudes of other tooling and engine options I could list here that aim to solve deeper productivity problems than the formers solve like easing the burden to interfacing with multiple languages and other problems that slow programmers down.

For my last analog I present the case that Go and Rust, while being a neater abstraction on top of closer-to-the-metal languages, really achieved most of their adoption and adoration through their bundled toolings capabilties. Go and Rust's cross-compilation, built-in formatters, and package management utilities were tools that developers built affinities for because it reduced the complexity of everything that leads up to packaging software for distribution.

The democratization of software development

Large enterprises built in the early 2000s have traditionally maintained their competitive advantage through acquisition and vast engineering teams. While companies like Google successfully combine internal innovation with strategic acquisitions, the traditional model requires significant resources and complex organizational structures.

The enterprise software development process involves coordinating across multiple teams, systems, and stakeholders. As companies grow, they naturally develop more complex social and technical interfaces. Leaders spend considerable time building relationships and alignment between teams before individual engineers can focus on core development work. This organizational complexity, while necessary at scale, can slow down the development process.

Here's where AI tools create new opportunities: smaller, more agile teams now have access to productivity multipliers that were previously unavailable. Just as the industrial revolution democratized manufacturing, and modern game engines democratized game development, AI-powered development tools are making complex software development accessible to smaller teams.

My prediction for the future is that companies with smaller, more nimble structures will leverage these tools to build increasingly sophisticated products. Rather than displacing large organizations entirely, this creates a more diverse ecosystem where different organizational structures can thrive in their respective niches. We're seeing the emergence of a new category of highly productive small teams, similar to how indie game developers found success alongside major studios.

Back to top