Category Archives: Software Development

Can non-programmers use Cursor?

I wrote Can non-programmers make applications with AI? last month. TL;DR: Yes. But, I hadn’t used Cursor yet. Now, I’m pretty sure that to use Cursor well on a real project, it helps to know some programming. But, if you do, it’s way more useful than it would be to an expert, which is saying something, because I find it very useful.

As an expert, my coding session today was maybe 2x faster for the same code. But, a non-programmer would have taken weeks to do what I did (if they could even do it). I think they have a chance to get close with prompts—I almost did, and they would try harder.

For what I needed to do today, in the first 5 minutes, Cursor did a good first pass. I fixed its syntax errors and the result “worked”. It looked terrible (this was implementing drag drop in a React app)—it took me a couple of hours to get it exactly how I liked it and then polish the code. But, getting me started quickly gave me a ton of momentum, and then I had time to make it exactly how I wanted it.

For a less skilled programmer to do this task, I think the first five minutes goes the same way. I know from experience, that it’s easier for me to just fix little problems, but I think it could be done with prompts. Then, the rounds of successive improvement were helped by autocomplete, but I initiated all of it. I relied on my knowledge of CSS and React to fix issues. I haven’t had good experience with the LLM’s for this—they can’t “see” the problem in the browser yet, and all of my problems were UI nitpicks and complicated Drag/Drop issues (not a static render I could screenshot or easily describe). All of the different modes of Cursor LLM integration have strengths for different uses—but some rely more on your ability than others.

From my use, it feels like knowing some programming is required. But, if it took a less skilled person from 2 weeks to 1-2 days, that’s more like 10x for them. What’s more, I go from 20x faster than them to 4x for this task, and they have more to improve, where my gains are asymptotic.

Three Days of Cursor

I tried out Cursor three days ago. But, before I did, I really did try to give GitHub Edits a chance. I used it for a few things, and it was more trouble than it was worth. I mentioned this to a friend at lunch on Tuesday and he asked why I hadn’t tried Cursor yet. I said I was worried it would interfere with my setup too much, but he confirmed that it reads your VSCode settings and extensions and (for him) just works. I use extensions as Tech Debt Detectors, so they are important to me.

So, the next day, I gave it a try. At 3:15 on Wednesday, I started the download. By 3:20, it was installed and working as I expected. I wrote a prompt to do the thing I needed to do next and its change was perfect. It was 3:27.

My next request did not go as well, but it was a complicated one involving a package I wanted to try. It would not install properly, and neither I nor Cursor could figure it out.

Since then, I’ve been using Cursor a lot. There are three main ways it’s better than GitHub CoPilot.

  1. The Generation from chats is much better than the GH equivalent.
  2. The autocomplete doesn’t require me to place the cursor. It anticipates what I am going to do and offers changes in different parts of the file. Sometimes all at once.
  3. There’s a hotkey for inline generation that works well too. I had been doing this with comments, but this is better because it just lets me type (without interruptions) and it knows I want it to start a generation when I’m done.

For all of these features, I feel very much like I am still programming. I am sequencing the work. It feels like it’s reading my mind since I do know exactly how to do what I am asking, but it’s saving a lot of typing.

This is helped by my app’s code being very regular. There’s only one way to do DB code, one way to wrap it in GQL, one way to call it. My UI is regular. The code has established patterns, and I put example files in the context before I prompt (and mention that I want it done like those files).

The main way it helps me though, is to keep me in flow. I am not constantly juggling files and typing out simple things. I say I want a table with an id, name, and a specific relation, and it knows my id is a UUID, and how I typically name relations. I say I want a cross table and it knows to set up the relations between the tables and the new one (in the way I have done it before). It intuits that I want cascading deletes without me asking. It’s just a lot of little things that save time and let me move on to something else.

How to Increase Story Points Per Sprint

Story points are not a basis for measuring and improving productivity because points are just an abstract measure of time. That’s why I said you should Be Skeptical of Points-based Productivity Claims. Any systematic productivity improvement would decrease the points for a future story, and the velocity would go back to a steady state.

If you had a way to compare stories over time to each other, you might see improvement in an individual story. If you literally are doing the same story over and over—like, for example, a set of recurring tasks—then, you could absolutely see its points decrease over time if you automate it. So, one way to get more done per sprint is to automate repetitive tasks, but once you do that, the points assigned to that task will go down and the velocity will not change. But the thing to look at is the comparison of this story in your sprint with its past incarnations, not velocity.

This is why I Estimate Using Time, not points. They are the same thing, but everyone understands time, and no one understands points. If you reread the first two paragraphs, but substitute “hours” instead of “points”, then they are obviously correct. If you automate a task then the number of hours it takes goes down—no one disagrees with that. If I use CoPilot to get systematic productivity, then my estimate of the number of hours it takes to do something will go down. Points obscure this.

There is a problem with using time (which is also true with points, but it’s just glossed over), which is the difference between work time and calendar time. If you estimate with work time, and report that, you will confuse people, because the only time that matters is calendar time. But, Story Points are often a proxy for work time, so I’ll assume that’s what it is for you too.

Given all of that, it’s easy to see how to increase the number of points done in a sprint. You need to either create time or decrease the difference between work time and calendar time. If you do that, you will find more stories get pulled into a sprint and get done, which will show up in velocity because it probably won’t cause the points for a particular card to go down, because most teams estimate points using work time.

So, how do you create time? Easy. Hire more developers. Despite what you have heard, you can add developers to a team to get more done, as long as they don’t need to communicate with each other.

To reduce the difference between work time and calendar time, you need to concentrate on everything that’s not sprint work. The main culprit is unnecessary meetings, but another is long waits for feedback. There are lots of times developers might need to wait, but waiting for code reviews is probably the biggest one because it happens to everyone all of the time. To work on that, read: If code reviews take too long, do this first.

Add Developers to a Team Without Lowering Productivity

According to Brook’s Law (from Mythical Man Month [affiliate link]), adding software developers to a late project makes it later because the number of communication lines increases rapidly with each new person. A team with N developers has N*(N-1)/2 communication lines, so a team of two has one line between them, but a team of 20 has 190 lines, one for each pair. If we accept this, there are several caveats that mean the law might not apply to you.

It should be clear that this won’t apply to a very small team. If you have two developers, then adding two more will help. The number of communication lines goes from one to six, but the amount of time that can be spent working on the project doubles. Compare this to adding two people to a twenty person team, where you might add more than forty communication lines and only 10% more work time. Also, a team of two have probably not created a very large codebase yet, so ramp-up time is not as bad as what it takes to learn what a team of twenty could create.

Another way the law doesn’t apply is if your developers don’t need to talk to each other. If they can work completely independently, then you are not adding communication lines. That also works if they only need to talk to a small subset.

You can also increase the effectiveness of a given team (before adding anyone) if you can remove lines of communication. This does not mean “better communication” (like documentation), which only reduces the cost of a line, not the number of lines. Reducing the cost of lines is good, but will be overwhelmed by combinatorial explosion of new lines if you don’t work to remove them.

Removing a line of communication only happens if the communication isn’t necessary. The model here is to think of that part of the team as external. If I add AWS to my project, I expect a lot of productivity gains, but I don’t add in communication lines to every developer that built it. A lot of teams move to architectures like micro-services to reduce lines of communication, which is supposedly how AWS came into existence in the first place. That’s one way, but you can get there with clean API’s in libraries too. Whatever you may think of external library dependencies (I think of them as instant tech debt), they can make us more productive (just not for free). If you added developers to make a library for you, you would get the same benefit if they could stay as independent.

Once you have independent sub-teams, they can add developers because they are small. At Trello, when I was on the iOS team, I relied on the Trello backend API, but I didn’t need to communicate much with the developers that worked on it, follow their daily PR flow, or go to their meetings. As a principal engineer, I did read their RFC’s, but most of my team didn’t need to. When the backend team grew, my productivity wasn’t affected. Our team was small, so we doubled in size without causing the backend developers any trouble. Our lines of communication increased, but it was manageable.

Independence wasn’t only at the code level. Teams at Trello didn’t have a shared development process. Many teams did sprints, but they didn’t need to sync with each other. Teams that worked on the same deployable had to communicate more, but it was more hub and spoke rather than every possible pair. One way we did this was to have a role called a “feature lead” who was a focal point of communication. They would have a line to each developer on their project, but could help the rest of the team members stay independent from each other.

So, to add developers with less communication overhead, create small, independent teams. To the extent that there are dependencies between them, try to concentrate that in a few people, rather than spread the effect to everyone.

Why ChatGPT Works Better for Newbies than StackOverflow

On StackOverflow, it’s common for newbie questions to get downvoted, closed, and draw comments making fun of the questioner. To be fair, many of these questions are not good and show that the asker didn’t follow the guidelines. Even so, StackOverflow is often hostile to new programmers. To be honest, I’m surprised that ChatGPT didn’t somehow learn this bad behavior.

ChatGPT answers are sometimes totally wrong, and they will be even more wrong for the way newbies ask questions. If they weren’t, StackOverflow wouldn’t have had to ban answers generated from chatbots. But, I still think ChatGPT a better experience because it’s fast and synchronous. This allows it to be iterative. Of course, this doesn’t help if the suggested code can’t be used.

If I were StackOverflow, I might consider how LLMs could be used to help newbies ask a better question that gets answered by humans if the LLM can’t answer. Let the user iterate privately, and then have the LLM propose a question based on a system prompt that understands StackOverflow guidelines. Normally, I’d expect the LLM to be able to answer at this point, but I just ran into a problem yesterday where it kept hallucinating an API that was slightly wrong. This kind of thing happens often in ChatGPT for me. In a lot of cases, I could guess the real API or search for it in the docs, but a newer programmer might not be able to do that.

What Would Drumeo Style Coding Tutorials Look Like

I’ve watched dozens of Drum videos even though I don’t play drums and have no interest in learning how. But, I am interested in their teaching style. If you haven’t seen it, Drumeo is a website with drum lessons, charts, drumless tracks (for you to practice with), and suggestions for how to drum songs based on your level. It’s this last kind of lesson, shown in “The Five Levels” series, that should be emulated in other disciplines.

In a “The Five Levels” video, someone from Drumeo plays a song with an iconic drum track. Then they show you how to play a beginner version—sometimes with just one stick, or maybe a stick and a pedal. You are mostly keeping the beat. Each level progresses by incorporating a new skill and a new part of the drum set. By the fifth level, you are playing what the original drummer played. This idea works for coding lessons as well—in fact, it’s how I learned.

My first two programming lessons (in middle-school) were essentially like this. In the first one, we were given the code to draw ASCII art on the screen, but we added the data for exactly what we wanted to draw. Then we had to figure out how to add animation to it. In the second lesson, we were given a working slot machine game, and we were asked to alter it so that we always won. In both lessons, we started with a working system.

For both drummers and programmers, this is what the work really is—adding something, not building from scratch. In my career, I started something significant from scratch a few times a year at most, but I altered something that otherwise worked nearly every day. It’s baffling that the vast majority of tutorials start from an empty directory and teach you how to do something programmers barely ever do.

How to be avoid being replaced by AI

Right now, I am an independent software developer, and so I own all of the code I create. So, of course, I want it to be as efficient as possible to write that code, and I use AI to do that. I get all of the benefit. I am not replaced—I am freed up to do other things.

All of my consulting income comes from work that originated from my network, cultivated from over 30 years of delivering on my commitments. Some of it is because I am one of only of few people that understand a codebase (that I helped create and partially own). There are very few people that could replace me in this work, even with AI. Even if AI were able to 100% reproduce everything I say and write, the client wouldn’t know how to judge it because their judgement of me is almost entirely based on past experience.

It’s not just AI—there are many people who are smarter than me, with more experience, who work harder, and have better judgement. But if they are completely unknown to my clients, they couldn’t replace me.

Of course, I realize that this isn’t something that one could just replicate immediately, but if you are building a software engineering career for the next few decades, I recommend cultivating a network that trusts you based on your past performance and owning as much of your own work as possible. When you do that, all of the benefits of efficiency flow to you.

Thoughts on Tool Making

In my career, about two-thirds of that time I was making something more like a tool than a solution. For Droplets and Atalasoft, it was developer SDKs and for Trello/Atlassian, it was a productivity tool. Our customers used our software to build things. I feel like I did my best work during those times and enjoyed it the most. So, one of the things I am trying to do in 2025 is get back into tool making.

My inspiration is what I’ve called the Great Works of Software and the Great Works of Software Poetry. All of the works I cited in those articles were tools that the authors were using to make something else. I also noticed (after the fact) that 5 of the 7 works I cited were made to help publish writing: TeX, HTML, VisiCalc (for business cases), Markdown, and WikiWikiWeb. Other “great works” like Postscript, PDF, and Alan Kay’s Dynabook concept are also along these lines.

So, perhaps I should be looking at my own publishing aspirations. I did that a couple of years ago when I made Page-o-Mat to make a custom journal. All of the updates to it have been driven by what I wanted in my journals. I had to make it because I felt true friction with existing tools. I could have made my journal in a word processor, but the repetitive nature of the journal pages calls for a programming language.

The pattern of creating a programming language for creating documents seems to come up often. Aside from coming up with words, all of the rest is a pain and ripe for automation, from layout to distribution. Coming up with words is also a pain, but that’s a part I want to do.

Write Test Plans for Every PR (and do them)

On the ticket/issue/item attached to your PR, write out the steps to see the change in the application your PR introduces. Even better, record yourself doing them. Not just the happy path, but also show the types of errors that can happen and how the application shows them.

I wish I did this more when I worked on a team, but when I did, everything went smoother through code review and QA. Not because it helped the reviewer or QA engineer—I’m sure it did.

Mostly it helped because I made sure I did all of the steps in the test plan before I posted the PR. Also, writing down a test plan put me more in a tester’s mindset where I was trying to think of things to check and finding problems before I submitted my work.

With all of those things out of the way, the reviewer and QA engineer could concentrate on bigger picture things to check.

How to Find Bugs in a Code Review

I have written that the primary reason to review code is knowledge sharing, not finding bugs, and that the way to use code reviews to prevent bugs is to reject PRs that aren’t obviously correct. But what should you do if you really want to try to find bugs during review?

Run the code—preferably in a debugger from a test.

The best tip I got from Steve Maguire’s Writing Solid Code [affiliate link] was to step through my code in a debugger as I was writing it. The original version of this book (1993) came out before xUnit based unit-testing took off, so I don’t remember him mentioning them. But, I think a unit-test is a better way to do this.

If you don’t have a test that puts you at the line of code you want to inspect, then driving the code manually while stepping through the code in a debugger is a good-enough option. Of course, not having appropriate tests is probably a good enough reason to send the PR back, but if that’s not your team’s culture, and you want to find the problems manually during review, at least use a debugger.

It’s more likely you’ll see problems if you are actively engaging with running code. You will be anticipating what is going to happen as you step through, and if the code behaves differently, you’ll notice. You’ll either find a problem or learn something.