Thursday, March 14, 2024

Software Development Decisions

A good decision in a software development project is one that moves you at least one step closer to getting the work completed with the necessary quality.

A bad decision is one where you don’t get a step forward or you trade off a half step forward for one or more steps backward.

Even a fairly small software development project includes millions and millions of decisions. Some decisions are technical, dealing with the creation or running of the work. Some are usability, impacting how the system will solve real problems for real people.

A well running software development project mostly makes good decisions. You would look at the output of the project and have few complaints about their choices.

A poor software development project has long strings of very poor choices, usually compounding into rather substandard output. The code is a mess, the config is fragmented, the interfaces are awkward, the data is broken, etc. It is a whole lot of choices that make you ask ‘Why?’

If you look at the project and cannot tell if the choices were good or bad then you are not qualified to rate the work. If you cannot rate it, you have no idea whether the project is going well or not. If you don't know, then any sort of decision you make about the work and inject into the project is more likely to be harmful than helpful.

Which is to say if you do not immediately know if a decision is right or wrong, then you should push that decision to someone who definitely does know and then live with their choices. They may not be good, depending on the person you choose, but your chances of doing any better are far less.

In a project where nobody knows enough to make good decisions, it is highly unlikely that it will end well. So, at bare minimum, you can't rush the project. People will have to be allowed to make bad decisions, then figure out the consequences of those mistakes and then undo the previous effort and replace it all with a better choice. It will slow down a project by 10x or worse. If you try to compress that, the bad decisions will become frozen into the effort, start to pile up, and then it will take even longer.

That is, if you do not have anybody to make good decisions and you are still in a rush, the circumstances will always get way worse. It’s like trying to run to the store, but you don’t know where the store is, so you keep erratically changing directions, hoping to get lucky. You probably won’t make it to the store and if you do it will certainly have taken way longer than necessary.

If there is a string of poor choices, you have to address why they happened. Insanity is doing the same things over and over again, expecting the results to change. They will not change on their own.

Thursday, March 7, 2024

Ratcheting

You know the final version will be very complicated. But you need to get going. It is way too long to lay out a full and complete low or medium level design. You’ll just have to wing it.

The best idea is to rough-in the structure and layers first.

Take the simplest case that is reflective of the others. Not a “trivial” special case, but something fairly common, but not too ugly. Skip the messy bits.

Code the overall structure. End to end, but not fully fleshed out.

Then take something it is not yet doing and fill in more details. Not all of them, just more. If there are little bugs, fix them immediately but do it correctly. If it means refactoring stuff underneath, do it now. Do not cheat the game, as it will hurt later if you do.

Then just keep that up, moving around, making it all a little more detailed, a little more complicated. Keep making sure that what’s there always works really well. Build on that.

Ratchet up step by step. Small focus changes, fix any bugs large or small. Make sure the core is always strong. Sprinkle in more and more complexity.

This is not the fastest way to code. It causes a lot of refactoring. It requires consistency. You need to be diligent and picky. You might cycle dozens of times, depending on the final complexity, but that gives you lots of chances to edit it carefully. The code has to be neat and tidy. This is the opposite of throw away code.

Although it takes longer, I usually find that since the quality is far better, the testing and bugs get hugely reduced, usually saving more time than lost.

Thursday, February 29, 2024

Coding

Major points:
  1. Coding is always slow
  2. Coding produces both code & bugs
  3. The code always needs to be edited, the first version is just roughed in.
  4. Do not use disposable code in industrial strength projects.
The primary goal is to produce a minimal amount of readable code.

You want the code to be as small as possible, it is easier to deal with. Larger codebases are worse, not better.

You want the code to be as readable as possible so it is easier to edit. If it is a choice between small or readability, readability wins. If it is a choice between readable or performance, readability wins.

You can always fix readable code later. But it must be reable first, and remain readable afterwards.

You don’t want the code to be redundant, cause you’ll always forget to change all of the different manifestations of it for the same bug. Redundancies are bugs or potential bugs. Changes to redundant code cause the code to drift apart.

You need the codebase to be tightly organized so that it is easier to find and fix the problems. You can accidentally waste more time fixing bugs, than in coding/refactoring, so you need to optimize for that.

There should be one and only one place to put each line of code. All of the code should be in its one place. If there are lots of different places where you can put the code, you are disorganized.

The author is not the only one who needs to reread the code. Others will have to read it as well. Good code will be read by a lot of people.

Because you didn’t just magically get it right the first time, you and other people will have to go over the code, again and again, in order to make it better. Code doesn’t get written, it evolves.

The fiction that layers are bad is poor advice. Layers are the main way you keep the code organized. Without them, the code is just one huge flat mess. That is far worse, it is totally unreadable.

Layers can be abused, there can be too many of them. But not having any at all is worse. It is easier to remove a layer than add one.

A good function is specific to some part of the computation. It is general. It does the one thing that it says it does, nothing more. Sub-level detail processing is below it, in other functions. High-level flow is above it. Once you know what it does, and trust that it does exactly and only that, then you can ignore it, which makes life easier.

All data should come into the code from somewhere else. It should never be hardcoded in the code, it should not be hardcoded when passed down to the code. Thus all strings, integers, constants, etc. are suspect. The best code has zero hardcoded values.

If you need to do a bunch of steps each time for a common action, wrap the steps. The fewer things you call the better your code is. If you rely on remembering that all things have to always be done together, either you’ll forget or someone else who never knew will do it incorrectly. Either way, it is now a bug, and it may not be an obvious one, so it will waste time.

If you need to move around some data, all together. Wrap the data, in a struct, object, whatever the language supports. Composite variables are far better than lots of independent variables.

If you need to decompose the data (aka parse) to use it somewhere, decompose it once and only once. Keep it decomposed in a struct, object, etc. move it around as a composite.

If the call for some library/technology/etc. is messy, wrap it. Wrapping is a form of encapsulation, it helps to avoid bugs and reduce complexity.

If there are strange lines of code that are nonintuitive or don’t make sense, wrap them. At minimum, it gives you a chance to name it appropriately; at maximum, it leaves just one place to change it later.

Too many functions are way better than too few. If you have to get it wrong, create a billion functions. They force you to have to find reasonable names for the parts of work you are doing. If you don’t know how to name a function, then you don’t understand what you are doing. If you have too many functions it is easy to compact them. If you have too few, you are screwed.

Don’t use language features if you don’t understand them. The goal of coding for a system is not to learn new technology, it is to write industrial-strength code that withstands the test of time. If you want to play, good, but don’t do it in a real project, do it in little demos (which can be as messy as you want).

Do not pack lines. Saving yourself a few lines of code, but packing together a whole bunch of mechanics, just hides the mechanics and misguides you as to the amount of code you have. Separate out each and every line of code, it doesn’t take any real time and it lays out the mess in its full ugliness. If the mess is ugly fix that, don’t hide it.

Never do the same thing in a system in two or more different ways. You need to do something, do it one way and only one way, wrap it in a function, and reuse it in all other instances. This cuts down on complexity. By a huge amount. It cuts down on code, thus it cuts down on bugs.

Build up the mechanics to work at a higher level. That is, if you need an id to get to a user, and the user to get to their profile, then you should have a FindUser(id) which is supplied to the call FindProfile(user). Build up reusable pieces, don’t code down into stuff.

Thursday, February 22, 2024

Self-Inflicted Pain

The difference between regular programmers and 10x programmers is not typing speed. In some cases, it is not even knowledge.

It is that 10x programmers are aware of and strongly avoid self-inflicted injuries while coding.

That is, they tend to avoid shortcuts and work far smarter than harder. They don’t tolerate a mess, they don’t flail at their work.

They need some code, they think first before coding, they code what they need, and then they refine it rapidly until it works. Then they leverage that code, over and over again, to save crazy large amounts of time. This is why their output is so high.

If you watch other programmers, they jump in too fast. They don’t fully understand what they are doing. The code gets messier and messier. Debugging it sinks through massive effort. Then they abandon that work and do it all over again for the next part that is similar. They burn through time in all the wrong places.

All of these are self-inflicted injuries.

Writing code when you only half understand what it should do will go badly. It’s not that you should be able to predict the future, but rather that given your knowledge today it should span the code you write. If there is something you don’t understand, figure that out before starting to code. If you have to change the code later because things change, that is okay. But if you are coding beyond your current knowledge it will go badly and eat through time.

Trying to fix crappy code is a waste of time. Clean it up first, then fix it. If the code doesn’t clearly articulate what it was supposed to do, then any perceived bug may be predicated on top of a whole lot of other bugs. Foundations matter.

So, when debugging, unless it is some crazy emergency patch, you find the first bug you encounter and correct that first. Then the next one. Then the next one. You keep that up until you finally find and fix the bug you were looking for. Yes, it takes way longer to fix that bug, but not really, as you are saving yourself a lot of time down the road. Those other bugs were going to catch up with you eventually.

If you see bad names, you fix those. If you see disorganization, you fix it, or at a minimum write it down to be fixed later. If you see extra variables you get rid of them. If you see redundant functions, you switch to only using one instance. If you see poorly structured code or bad error handling, you fix that. If you see a schema or modeling problem, you either fix it now or write it down to fix it later. The things you wrote down to fix later, you actually fix them later.

The crap you ignore will always come back to haunt you. The time you saved by not dealing with it today is tiny compared to the time you will lose by allowing these problems to build up and get worse. You do not save time by wobbling through the code, fixing it at random. Those higher-level fixes get invalidated by lower-level changes, so they are a waste of time and energy.

And then, the biggest part. Once you have some good code that mostly does what you want it to do, you leverage that. That is, putting minimal effort into highly redundant code is as slow as molasses. Putting a lot of effort into a piece of code that you can use over and over again is exponentially faster. Why keep solving the same lower-level problems again and again, when instead you can lift yourself up and solve increasingly higher-level problems at faster speeds? That is the 10x secret.

If you have to solve the same basic problems again and again, it is self-inflicted. If you lose higher-level work because of lower-level fixes, it is self-inflicted. If you have to do spooky things on top of broken code, it is often self-inflicted. If you get lost or confused in your own code, it is self-inflicted. If you want to be better and faster at coding and to have less stress in your job, stop injuring yourself, it isn’t helping.

Thursday, February 15, 2024

A Rose by Any Other Name

Naming is hard. Very hard. Possibly the hardest part about building software.

And it only gets harder as the size of the codebase grows, since there are far more naming collisions. Code scales very, very badly. Do not make it worse than it has to be.

This is why naming things correctly is such a fundamental skill for all programmers.

Coding itself is oddly the second most important skill. If you write good code but bury it under a misleading name, then it doesn’t exist. You haven’t done your job. Eventually, you’ll forget where you put it. Other people can’t even find it. Tools like fancy IDEs do not save you from that fate.

There are no one-size-fits-all naming conventions that always work correctly. More pointedly there can never be such a convention. Naming is not mindless, you have to think long and hard about it. You cannot avoid thinking about it.

The good news is that the more time you spend trying to find good names, the easier it gets. It’s a skill that takes forever to master, but at least you can learn to not do it badly.

There are some basic naming rules of thumb:

First is that a name should never, ever be misleading. If the name is wrong, it is as bad a name as possible. If someone reads it and comes to the wrong conclusion, then it is the author's fault. When you name something you have to understand what that thing is and give it the best possible name.

Second is that the name should be self-describing. That is, when someone reads the name, they should arrive at the right conclusion. The variable should hold the data they expect. The function should do what it says. The repo that holds a given codebase should be obvious.

“Most people never see the names I use in my code …”

No, they do see them. All of them.

And if they see them and they are poor or even bad, they will recommend that your code gets rewritten. They will throw away your work. Nothing else you did matters. If the code is unreadable, it will not survive. If it doesn’t survive, you aren't particularly good at your job. It’s pretty simple.

Occasionally, some really awful code does get frozen way deep in a ball of mud. But that unfortunate situation is not justification for you being bad at your job. Really, it isn’t.

Third, don’t put litter into your names. Made up acronyms, strange ‘pre’ or ‘post’ text. Long and stupid names are not helping. Stop typing in long crazy names, spend some time to thinking about it. Find short reasonable names that are both descriptive and correct.

Fourth, don’t put in irrelevant or temporary stuff in there either. If some unrelated thing in an organization changes and now the name is either wrong or needs to be changed, you did it wrong. Names should be nearly timeless. Only if the nature of the problem changes, should they need changing, and you should do that right away. Names that used to be correct suck.

Names are important. They form the basis of readability, and unreadable code is just an irritant. If you were asked to really write some code, you need to really write it properly. If it takes longer, too bad. Good naming only slows you down until you get better at it. You need to be better at it.