All my best programming tips
Published: 2019-06-19 16:14:49

What follows is a running list of all the programming tips I can think of that are worth sharing.

Many of these tips may seem so obvious they go without saying, but every single one of these tips is here because I've seen programmers fail to take advantage of these tips on multiple occasions, even very experienced programmers.

I've divided the tips into two sections: development tips and debugging tips. I've arranged them in no particular order.

## Development tips

### Articulate the goal

Before you sit down and start typing, clearly articulate in writing what it is you're trying to accomplish. This will help you avoid going down random paths which will dilute your productivity. It will also help you work in small, complete units of work.

### Break big jobs into small jobs

Some job seem so big and nebulous as to be intractable. For example, if I'm tasked with creating a schedule that contains recurring appointments, where do I start with that? It will probably make my job easier if I start with a smaller job, for example, thinking of how exactly to specify and codify a recurrence rule.

### Keep everything working all the time

Refactoring is the act of improving the structure of a program without altering the behavior of the program. I've seen a lot of occasions where a programmer will attempt to perform a refactoring and fail not to alter the behavior of the code they're trying to refactor. They end up having to throw away their work and start over fresh.

If in the course of refactoring I break or change some existing functionality, then I somehow have to find my way back to making the code behave as it originally did. This is hard.

Alternatively, if I take care never to let the code stray from its original behavior, I never have to worry about bringing back anything that got altered. This is relatively easy. I just have to work in very small units and regression-test at the end of each small change.

### Continuously deliver

Continuous delivery is the practice of always having your program in a deployable state.

Let's say I start working on a project on January 1st which has a launch date of July 1st. But then, unexpectedly, leadership comes to me on March 1st and says the launch date is now March 10th and we're going to go live with whatever we have by then.

If I've been practicing continuous delivery, launching on March 10th is no big deal. At the end of each week (and the end of each day and perhaps even each hour) I've made sure that my application is 100% _complete_ even if it might not be 100% _finished_.

### Work on one thing at a time

It's better to be all the way done with half your tasks than to be halfway done with all your tasks.

If I'm 100% done with 50% of 8 features, I can deploy four features. If I'm 50% done with 100% of 8 features, I can deploy zero features.

Also, open work costs mental bandwidth. Even if you believe it's just as fast to jump between tasks as it is to focus on just one thing at a time, it's more mentally expensive to have 5 balls in the air than just one.

### Use automated tests

All code eventually gets tested, it's just a question of [when and how and by whom](https://www.codewithjason.com/code-eventually-gets-tested-just-question/).

It's much cheaper and faster for a bug to get caught by an automated test during development than for a feature to get sent to a QA person and then kicked back due to the bug.

[Testing helps](https://www.codewithjason.com/five-benefits-automated-testing/) prevent the introduction of new bugs, helps prevent regressions, helps improve code design, helps enable refactoring, and helps aid the understandability of the codebase by serving as documentation.

### Use clear names for things

When a [variable name is unclear](https://www.codewithjason.com/variable-name-anti-patterns/) due to being a misnomer or when it's unclear due to being abbreviated to obfuscation, it adds unnecessary mental friction. 

Don't abbreviate variable names, with the exception of universally-understood abbreviations (e.g. SSN or PIN) or hyperlocal temp variables (e.g. `records.each do { |r| puts r }`).

A good rule for naming things: call things what they are.

### Don't prematurely optimize

There's a saying attributed to Kent Beck: "Make it work, then make it right, then make it fast."

For the vast majority of the work I do, I never even need to get to the "make it fast" step. Unless you're working on high-visibility features for a high-profile consumer app, performance bottlenecks probably won't emerge until weeks or months after the feature is initially deployed.

### Only write a little code at a time

I have a saying that I repeat to myself: "Never underestimate your ability to screw stuff up." I've often been amazed by how often a small change that will "definitely" work doesn't work.

Only work in tiny increments. Test what you're working on after each change, preferably using automated tests.

### Use atomic commits

An [atomic commit](https://www.codewithjason.com/atomic-commits-testing/) is a commit that's only "about" one thing.

Atomic commits are valuable for at least two reasons. One, if you need to roll back a commit because it introduces a bug, you won't have to also roll back other, unrelated (and perfectly good) code that was mixed into the same commit. Two, atomic commits make it easier to pinpoint the introduction of a bug than non-atomic commits.

A separate but related idea is to use small commits, which is just a specific application of "Only write a little code at a time".

### Don't allow yourself to stay stuck

[Being stuck](https://www.codewithjason.com/stuck-programming-problem-tactics-will-get-unstuck-99-time/) is about the worst state to be in. Going down the wrong path is (counterintuitively) more productive than standing still. Going down the wrong path stirs up new thoughts and new information; standing still doesn't.

If you can't think of a good way to move forward, move forward in a bad way. You can always revert your work later, especially if you're using small, frequent, atomic commits.

If you can't think of a way to keep moving, try to precisely articulate what exactly it is you're stuck on and why you're stuck on it. Sometimes that very act helps you get unstuck.

If you think the reason you're stuck is a lack of some necessary piece of knowledge, do some Google searches. Read some relevant blog posts and book passages. Post some forum questions. Ask some people in some Slack groups (and if you're not in any technical Slack groups, join some). Go for a walk and then come back and try again. If all else fails, set the problem aside and work on something else instead. But whatever you do, don't just sit there and think.

## Debugging tips

### Articulate the problem

If you can precisely articulate what the problem is, you've already gone a good ways toward the solution. Conversely, if you can't articulate exactly what the problem is, your situation is almost hopeless.

### Don't fall into logical fallacies

One of the biggest dangers in debugging is the danger of tricking yourself into thinking you know something you don't actually know.

If in the course of your investigation you uncover a rock solid truth, write it down on your list of things you know to be true.

If on the other hand you uncover something that's _seems_ to be true but you don't have enough evidence to be 100% sure that it's true, don't write it down as a truth. It's okay to write it down as a hypothesis, but if you confuse hypotheses with facts then you're liable to get confused and waste some time.

### Favor isolation over reason

I know of two ways to identify the cause of a bug. One is that I can study the code and perform experiments and investigations until I've pinpointed the line of code where the problem lies. The other method is that I can isolate the bug. The later is usually many times quicker.

One of my favorite ways to isolate a bug is to use `git bisect`. Once I've found the commit that introduced the bug, it's often pretty plain to see what part of the commit introduced the bug. If it's not, I'll usually negate the offending code by doing a `git revert --no-commit` the offending commit and then re-introduce the offending code tiny bit by tiny bit until I've found the culprit. This methodology almost never fails to work.

### Favor googling over thinking

Mental energy is a precious resource. Don't waste mental energy over problems other people have already solved, especially if solving that particular problem does little or nothing to enhance your skills.

If you run a command and get a cryptic error, don't sit and squint at it. Copy and paste the error message into Google.

Thinking for yourself of course has its time and place. Understanding things deeply has its time and place too. Exercise good judgment over when you should pause and understand what you're seeing versus when you should plow through the problem as effortlessly as possible so you can get on with your planned work.

### Learn to formulate good search queries for error messages

Let's say I run a command and get the following error: `/Users/jasonswett/.rvm/gems/ruby-2.5.1/gems/rspec-core-3.8.0/lib/rspec/core/reporter.rb:229:in require': cannot load such file -- rspec/core/profiler (LoadError)`

Which part of the error should I google?

If I paste the whole thing into Google, the `/Users/jasonswett/.rvm/gems/ruby-2.5.1/gems/rspec-core-3.8.0`, which is unique to my computer, my Ruby version and my `rspec-core` version, narrows my search so much that I don't even get a single result.

At the other extreme, `require': cannot load such file` is far too broad. Lots of different error messages could contain that text. The sensible part of the error message to copy/paste is `require': cannot load such file -- rspec/core/profiler (LoadError)`. It's specific enough to have hope of matching other similar problems, but not so specific that it won't return any results.

### Learn to formulate good search queries in general

I'm not sure how to articulate what makes a good search query but here's my attempt: a query that's as short as it can possibly be while still containing all the relevant search terms.

### Don't give too much credence to the error message

Whenever I get an error message, I pretty much never pay attention to what it says, at least not as the first step.

When I get an error message, I look at the line number and file. Then I go to that line number and look to see if there's anything obviously wrong. Probably about 90% of the time there is.

For the other 10% of the time, I'll go back to the error message and read it to see if it offers any clues. If it does, I'll make a change to see if that fixes the problem. If the error is so cryptic that I don't know what to do, I'll google the error message.

### Make the terminal window big

If I had a dollar for every time I helped a student who was trying to debug a problem using a terminal window the size of a Post-It note, I'd be a rich man. Maximize the window so you can actually see what you're doing.

### Close unneeded tabs

Open tabs cost mental energy. Between the cost of re-finding the page later and the cost of being mentally cluttered with all those tabs open, it's way cheaper just to re-find the page later. On average I myself usually have about two tabs open at any given time.