Thursday, June 29, 2023

Variants

A variant is a behavior that is marketed to act one way, but in fact, behaves differently,

Chaos Enhancer

Anything that injects non-determinism into the software usage.

An example is a list of the last five documents you opened. Seems to be great if you want to reopen something right away, but if you rely on it and the document you need falls to number six, it is a problem. A small list is very different from providing a searchable history. Suggested items are another example, as are some auto-completion features. Use at your own risk.

Invisible Feedback

When you do something but there is no feedback for a long time, so you are left wondering whether it worked or not.

Dynamic Scrolling

An early attribute of browsers was that they would render dynamically as the page came in. That was because the medium was very slow, so some people thought that partially rendered documents were a reasonable preview. But now in the middle of page rendering the vertical or even horizontal scroll position changes erratically as the Ajax calls trigger. So the user starts to read something, but it moves. They fix that, try to read again, and it moves again.

Deceptive Ease

So, the software takes care of things for you, right up until it doesn’t, then you are screwed. The programmers coded some easy parts but then bailed on actually solving any real problems.

Convient Disorganizer

A feature that pretends to help you keep things organized, but is actually structured to funnel you into just throwing everything into a disorganized lump. You initially rely on the feature only to discover later that it made things worse.

Retrograde Change

A change that looks on the surface like it was an improvement, but really a whole lot of other useful stuff was quietly stripped away. Common with software rewrites. The new version claims to be better, but it ain’t.

Incomplete Feature

A feature such as being able to manage some data, but part of the functionality is missing, like not being able to delete stuff. To really implement a complex feature, you need to implement all of the functionality. It’s not a real feature unless it is complete.

Feature Swamp

A software tool that has every possible feature under the sun, but they're all so badly thrown together that they are impossible to find and use appropriately. It can do anything, but you can’t figure out how, because it is lost in the swamp.

Blunder Search

An organizational feature that entirely relies on searching, except that the searching itself is really crude and totally untrustworthy. Blunder searches have often replaced far better ways of keeping things organized, probably because someone incorrectly thinks they are cool.

Unintentional Disconnection

When a device built to steal focus successfully disconnects the user from the here and now. A person talking on a cell phone while waiting in line (the conversation is no longer private). Someone lost in a mobile app while in a crowd (lack of awareness).

Useless Distraction

Anything that forcefully grabs attention or pops up while you are trying to do something else. Dancing baloney was an earlier, crude predecessor. If the user is in the middle of doing something they should be able to do it.

Money Trap

Any software that tries to give away just enough free stuff so that later it can extract a lot of money. It’s not altruism, it’s just a shallow, deceptive, marketing tactic. Any sort of slimy pricing surprises should be viewed negatively. If they claim it is free, or won't tell you how much it costs, you should avoid it. It's a trap.

Upsell Hell

Similar to a money trap, where there are landmines everywhere that you can inadvertently select that will try to trick you into some upsell that you don't need.

Summary

Variants mislead users. They are similar to lying. It’s always been very bad in software to show the user any wrong information, but it is equally bad to do some form of bait and switch. We should stop allowing these types of behaviors to get added into the code.

Thursday, June 22, 2023

Control

Control issues play out in multiple ways in large software projects.

The most obvious is that the nontechnical stakeholders sometimes seek to control the development focus, but as I’ve often said that can lead to chaos.

The other issue is that for everything you put into production if you don't directly control it then when it runs into a problem your options are extremely limited.

For example, if you rely on some small open-source library and it has a bug with a fairly large impact, you will probably not be able to convince the authors to release a patch right away. If you fork, then your version will drift away from theirs. So, often it is better to find another similar library, or just write a replacement. Finding another library might be faster, but you haven’t actually fixed the problem, you’ve just pushed it away again.

It is never a wise choice to assume that there won’t be bugs. Code is still written by humans, bugs will always happen and any testing to catch ‘all’ of them is prohibitively expensive. If you write your own code you also have to assume there will be bugs, but you now have the means now to correct the problem and get it back into production.

That is, with the library you can use it, but you have no real control over it. If it is your code, you have control; we’ll at least if you have someone around that understands the code.

Now, while that works for small or core functionality, it does not work for large technology issues. For instance, you always need to persist some data. But writing reliable persistence is quite tricky, it involves a lot of deep understanding of both technology and theory. So, where you might not want to be dependent on little libraries, you would be insane to try and write your own database. It’s too complicated, you’re too likely to get it wrong, and it takes a huge amount of time for that code to mature. Unless controlling persistence is a ‘core’ part of what you are doing — e.g. you’re a software company and need your own database product — it is not reasonable to try.

The general rule for coding then is that you should always write your own stuff, but only if you can. And it’s that second part which is really tricky. If you have to build a large system that shows tangible results in a six month window, the amount of time you have to write code is incredibly limited. You need to pick carefully. And you need to accept that you may have to lean on less desirable dependencies for the first phase of the work. That is, you include a library now, but with the specific intention to replace it later, when time becomes available.

If you can get into that mindset, that each and every dependency you have could be a candidate for replacement someday, it will help. As time allows, you will get rid of the low-hanging fruit — the trivial libraries — and use that as an excuse to also properly refactor the code around them. Then as the work grows it also evolves and matures.

That type of effort then isn’t just about writing your own code but rather it is really about increasing the amount of control you have over what you are releasing. If you want the project to be stable, you have to control enough of what goes out that you can make it stable.

The corollary is you always want to be increasing control; over everything, if possible. If you don’t, the project will become increasingly out of control until it shakes apart. You want to be successful, you want to deliver good solutions that really help your users. Being in control isn’t fun, but it is what allows you to deliver.

Thursday, June 15, 2023

Take the Long way Home

Good programmers have good habits.

Sometimes they develop these habits on their own, and sometimes they pick up best practices from other good programmers or even the industry.

Their habits are never arbitrary. They do what they do, for a reason, even if they can’t explain it fully.

One super great habit is to always take the longer, slower, right way whenever possible.

It’s a simple habit. If you have a choice between taking a quick shortcut or some way slower, but rather obviously more correct way, then you train yourself to take the long way home.

However, on those really bad days, when everything is going crazy and there are serious problems that need help right away, you can take the shortcut. But taking the shortcut is the exception, not the rule.

If you have time, you take the long way home.

Why?

Shortcuts always have tricky side effects and unexpected consequences. Take enough of them and everything goes to hell. So, you want to use them liberally. Know about them, be willing to use them, but save them for the moment that you really, really, really need them.

If you do this, it will cut down on technical debt. Not by a little, but by a huge amount. And in those moments where you do need a shortcut and you do deliberately kick up the technical debt, it was an emergency, so you’ll at least get the most credit for it, even if you do end up having to redo the work later.

Thursday, June 8, 2023

Fix the Real Problem

The road to spaghetti code is paved with a thousand sloppy fixes.

The value in a variable is wrong, so someone fiddles with it close by to where they found the problem. From a stack perspective, this is way up in the call tree, so we’ll call this high. Somewhere buried down in the calls there are other places where the bug could be fixed. We’ll call the one a farthest down, low. Fixing a bug high, instead of low, is a mistake.

If you are going to fix the code, you need to fix it at its lowest possible location, not the highest one.

Why?

If the code is used in only one place, it doesn’t make a difference where you fix it, high or low. But, if you fix it high, and later someone else reuses the same call, which is what you want, then they’ll have to fix the bug too.

If the code is used in multiple places, and you fix one instance of it only, then the other places are still bugs. If you fix each one independently, you have wasted a lot of time.

With high fixes, if there are a lot of them, the usage of the call varies all over the place. It is hard to clean up, and it is hard to refactor. If this occurs once the mess is not too bad, but if this keeps reoccurring then it is a huge mess.

Once it is a huge mess, people often get frustrated and start siloing the calls. When they do that, they are relying on duplication of code to get beyond their own disorganization, but while it may seem to be a good idea initially, it will only get worse.

Fix the bug as low as possible, always. If that changes a few of the calls, then fix those too. Do it properly and there will be far fewer problems going forward. Do it poorly, and the code will explode into spaghetti, really quickly. Or as they used to say “a stitch in time saves nine”. Works for both clothing and code 🙂

Thursday, June 1, 2023

Clever and Over-engineered

You need some code. Not having enough weakens the solution, but too much of it is hard to handle. 

Sometimes, what is necessary to solve a group of problems is fairly simple. Routine even. But if the developers do the routine thing, it will be boring. Very boring. So, in many cases, they decide to do something clever or over-the-top to avoid being bored.

Clever is usually small. A crazy idiom or a neat syntax trick. When wrapped nicely it is mostly okay, but if done recklessly, everywhere, it kills the readability. Good job insurance for the clever person, but wantonly destructive for everyone else.

Over-engineering is at the middle level or higher. Such as using an advanced paradigm that doesn’t provide any benefits, or putting in excessive industrial strength that is not necessary. Another example is modeling systems for impossible possibilities. All of these will significantly bump up the complexity, making it all far harder to wrangle. Sure it isn’t a bug fest, but it is usually too stiff, too slow, and too inflexible.

Another variation is dependency madness. The desire to throw in as many partially used small libraries as possible, primarily for the sake of learning and getting experience with all of them. They aren’t needed and they choke up the effort, making it harder in the future because they decay quickly.

Silos and fragmentation should be included here too. It’s boring to read and try to understand someone else’s code. So avoiding that by rewriting it, is really just going back to the start again. Silos come from hiding your stuff away from other core stuff, with onion architecture being a common variation. Fragmentation is just throwing code and data anywhere without caring.

The last variation is Rubik’s cube addiction. Solving lots of little problems as fast as possible and then just tossing them over your shoulder into a growing pile. The act of organizing these little bits may be tedious, but a big pile of stuff is pretty useless.

All of these bad habits are about staving off boredom. They are all negative, they always hurt the work, contribute to tech debt, and only get worse with time.

What would be better if you are bored is to build things that are sophisticated, not just complex. E.g. solve more of the user's issues, in better ways, with simpler interfaces. That’s not boring either and it's far more positive.