Saturday, January 15, 2022

Good vs. Bad

A good technology helps you solve your problems.

A bad technology is one that misleads you into thinking it has solved the problems, but instead, its side effects are actually creating other problems that now you also have to solve.

A good technology works as expected. No matter what the circumstances are, you should be able to predict what it is going to do, and it will do exactly that. You can rely on it to behave as expected.

A bad technology sometimes does what you think it will do, but sometimes it does very strange or unexpected things. You can’t predict it in advance, it is basically random from your perspective. You are caught by surprise often when using it, shock and disappointment follow.

A good technology is deterministic, at least in its ability to arrive at an expected outcome. You know what it is going to do, so you can figure out how it will accomplish those goals. If it is distributed or parallel, there may be a little bit of uncertainty with the finishing order of some intermediate operations, but it will correctly get to the desired outcomes.

A bad technology is non-deterministic. It may complete the work successfully or it may not. It may be restartable if there was a problem, or it may have left the system in a bad state that requires some manual interference. You pretty much don’t have any certainty of success when using it.

A good technology is obvious to use. You may need a bit of coaching about what it solves at a higher level, but after that, you can use it to do all of the things that you think it should be able to accomplish.

A bad technology needs lots of additional help. It may have strange abstractions, twisted behaviors, rude side-effects, etc. It needs really super-strong documentation, which is exceptionally hard to write, and quite rare to find. If you are confused while using it, that isn’t a particularly good sign. If it is really a brand new revolutionary technology that actually does present a new and innovative paradigm never seen before, that conceptual shift needs its own documentation, but once you’ve understood that, more documentation should not be necessary for any specific instance.

A good technology is effectively transparent. You see it, you understand what it does, you can figure out what is happening deep in its encapsulation since at some level it is pretty obvious. You can string together a bunch of good technologies to accomplish larger goals, it’s fairly clear how you would do this if you already know the individual components.

A bad technology is opaque. It’s a mystery. It does things behind the scenes that you won’t guess or be able to reason about. It hides stuff from you, that you need to know about it. You can’t string bad technologies together easily, they tend to fight with each other. Using the technology is just painful.

A good technology is malleable. Small changes have noticeable but contained effects. Everything is localized and the scope of changes is obvious. You can guess the overall impact.

A bad technology is brittle. A little change causes huge and unexpected differences in behavior. You have to be terrified with any type of change, you can’t use size as a measure of impact. One day it works, the next it is broken for unknown reasons.

A good technology is smooth. You decide you need it, it fits in easily and you are ready to start utilizing it for your goals. It’s isn’t a big deal to add it, and there isn’t a steep learning curve.

A bad technology has a lot of friction. It’s hard to get it set up, it’s hard to deal with it, and it’s even harder to accomplish any type of work reliably on a schedule. You might get some traction, but you pay for every inch that you get. It’s slow and plodding and acts as a time sinkhole.

Monday, January 3, 2022

Readability

In order to be useful, code needs to be readable.

If it’s not readable, then as things change, it is difficult to update. That has the unwanted side-effect of freezing the code in place with all of its defects. This leaves 2 bad choices: either spend a lot of effort to rewrite the code or just blindly build on top. Any code built on top or around defective code inherits its undesirable qualities.

So, unreadable code might suffice to meet a tight deadline, but its intrinsic tech debt will continue to grow.

You can reflector unreadable code into something better, but it is actually a lot less effort to just not make it unreadable in the first place. Some programmers wrongly assume that it is faster to create unreadable code, that any work to prevent that is extra and slows down the effort, but oddly unless the initial typing effort is perfect (which is so rarely the case) fixing the time spent the first round of syntactic and semantic flaws will quickly negate any perceived gains. So it is usually slower to write unreadable code and it only gets worse as it stays around in the codebase.

The most obvious factor in readability is naming. People generally believe this to be hard, but the names of most things in code come either from the problem domain or from the technical one. If naming is complicated it is usually a huge warning sign that either sufficient analysis and/or design has been skipped. That is if you don’t know what you are writing, then, of course, you don’t know what the pieces should be called, but you are also just taking a random shot at being able to craft the ‘correct’ piece of code, which usually ends up wasting a lot more time as you keep having to fiddle with it.

If you know the names of the data and functions involved in the code, the next big issue is structuring the code correctly. The easy problems in programming generally involve getting data from one location and sending it to another one, so the structure of the code is rather trivial. It gets a little more difficult if you also handle any errors properly, but there are plenty of straightforward paradigms for properly arranging this type of behavior. Some system-level code, like interpreters and operating systems, requires non-intuitive structures in order to keep the code organized, but they are well documented and there are plenty of resources available to learn the proper techniques. You just have to do the research first, before starting.

The biggest problem in structuring code is reuse. You might have dozens of screens that are each easy to write, but all pretty much share the same underlying code. It is an ‘epic’ waste of time to just write each one in its own silo. The code is hopelessly redundant, but the testing is also long and painful. So, the smart thing to do is write some underlying base code that contains the bulk of the work, then trivialize each different screen on top of it.

That reuse pattern also occurs when managing persistent data, doing imports and exports from other systems, and when keeping some other system synchronized. The super-difficult problem however is not just reusing the code, but rather doing it in a way that result is actually far more readable than just a large bunch of brute force silos. That can happen though if the levels between the parts of the code are not random, but rather they properly encapsulate different types of complexity for the different pieces.

When you encounter code that is well structured, you tend not to notice the care and effort that went into the workmanship. Instead, the code just seems rather obvious, it flows quite easily.

And that brings us to the most difficult aspect of readability. If you write some code, you will think that it is readable as you are working on it. But it still might be filled with a lot of weird, awkward, and eclectic stuff that other people would find difficult to understand quickly. That is, readability may be considered subjective when it comes to your own work, but as more people look at the code it becomes considerably more objective. So, the real measure of readability is in trying to make it quickly understandable by the largest possible audience. You really want to present the code in ways that make it inclusive to as many people as possible.

That means picking a few simple patterns that are applied consistently across all of the code. That means trying hard to not just throw in as many dependencies as possible. It means augmenting the code with any type of comments the reader needs in order to see the code the way you saw it when you wrote it. It means spending some time and effort trying to look at the code and see it the way other people will see it.

There are still people out there that believe that creating an obfuscated spaghetti codebase is job security, but that’s actually a very foolish belief. You don’t want to get stuck spending the rest of your life struggling with the mess you created, so it’s far better to create things that you can hand off to others. That helps with getting better work in the current company, but it also helps in creating lots of other opportunities in other organizations as well. Nobody wants to work with a difficult programmer again, which means that each bad experience is dwindling the possible market of new ones, rather than increasing it. In that sense, it’s not only better to write readable code now, it is also going to help you a lot in the future even if you switch jobs.

It’s also more fun to work on a well-written readable codebase. If there is a huge amount of redundant code, then it becomes increasingly likely that any given little code change will cause nasty unexpected side-effects somewhere else. So, it’s scary and super stressful to make changes. And since there is never enough time and effort put into testing, a lot of bugs seriously hurts the whole project. Thus making good changes becomes harder and harder, until the project is nearly frozen in place and everyone is angry.

Contrast that with a project where you can make a lot of good enhancements with very little stress. If the code has a lot of reuse, you can use the tools in the IDE to quickly visually check that the changes will not have unintentional side effects. A small amount of effort, but a huge gain.

As well, one little change may fix a bunch of related issues all over the system, so it looks like people have put in a lot of care and effort. So, it’s way less stressful, a little bit of extra work, but to the users, it looks like the project is moving forward really quickly now. And there are plenty of battle-tested components around that you can rely on to speed up the development, so you get much better quality at a faster pace. Instead of falling farther and farther behind in the development, you can start pursuing deeper and more meaningful changes, so people are excited and happy about the progress. It’s fun to work on, and you feel good about it.

Ultimately it’s not that hard to make code readable and it’s not that much “extra” effort. If the project starts off that way, the results will be far better. Skipping this work may get you to an early deadline slightly faster, but as the costs build up it will continually cripple the work. It’s harmful and crazy to intentionally write bad code, it’s insane that we keep encouraging it. Code isn’t disposable, it takes a lot of time and effort to get it working correctly, so it’s far better to write less code that is of good quality than it is to just spew out lots of junk code. We don’t need more code, we just need better code now.