Programmers write code.
They need complicated tools to do this properly.
They should have fast machines with lots of resources. Anyone who thinks stiffing their programmers with underpowered machines is a good idea is mistaken. You’re just wasting your own money if the coders can’t be their jobs effectively. Money spent on hardware for coders is always well spent.
Development machines have access to a lot of tools. They need these tools to be able to do their jobs efficiently. By inefficient, I don’t mean it will take 20% longer, I mean it will be a multiplier like 3x or worse. If programmers can download the tools they need, as they need them, they can flow through the work and get the job done. Delays and disruptions are crazy expensive.
There is no absolute way to predict all of the tools. The software industry is too vast and the range of issues is too volatile. You can’t just make a list of the tools. It doesn’t work that way, it has never worked that way, and anytime someone has tried to enforce such a restrictive viewpoint, it hurt development efforts, crushed morale and resulted in programmers jumping ship. When that happens, it can take a very long time for a company to recover, as their reputation amongst the programming community tends to take a rather massive hit.
There are two ways to deal with the programmers getting in outside tools. Both are probably necessary.
The first is to segregate them on their own network. In that way, if one of their tools is problematic, the scope of the damage is controlled.
The other is to set up the network so that only some of the programmers can bring in outside tools. You make it a senior responsibility, but still one that is handled by the programmers. A nonprogrammer may reject using some tools simply because they do not understand them, so it needs to be someone who has the correct experience and knowledge to be able to make an informed decision.
As the toolset increases, they should be kept internally somewhere. That is, most programmers when they need a new tool or a version of the tool, should go to the internal tool repository and get it from there. It should be easy and convenient to get tools from there, and after a while, most of the major tools will be there. There should also be some documentation to list out the ‘best’ version of any tool that the coders should use.
If you do all of those things correctly, then the developers can have minimum constraints on them as to how they use their machines. That is, they have admin rights, and any preventive software is minimized.
A clear sign that an organization does not understand “programming” is when they insist that programmers aren’t administrators of their own machines. Getting programmers mixed up with power users is a common, but really bad mistake. Programmers should program. Programming involves messing with stuff. Messing with stuff can be dangerous. If you stop them from messing with stuff, you cripple their ability to program, and their output tends to degrade to unusable quality. That is, either you empower people to do their jobs, or you accept that their jobs will not be done poorly. You can’t have it both ways.
Fully disempowered programmers are just typists. You don’t need typists, you need programmers to build you stuff that is good enough to solve your problems.
A poor worker may blame their tools, but a gifted worker without reasonable tools is a waste.
Software is a static list of instructions, which we are constantly changing.
Thursday, August 17, 2023
Thursday, August 10, 2023
Complexity and Principles
In the midst of overwhelming complexity, the best option is to pull up slightly.
As we are human, things can get far too messy and complicated, so even if we are keeping up with it, the sheer size of the scope will cause us to be rampantly inconsistent. Those inconsistencies themselves are more extra complexity that is getting added as a byproduct of our not being able to cope with the intrinsic complexity. So it is getting worse.
While the ‘devil’ is in the details, trying to deal with the details at their lowest level is a losing game. So, instead, we want to go a little higher and look for patterns that lay out across similar but different cases.
Instead of getting caught up in the abstract nature of patterns though, we can think of these overarching structural constraints as ‘principles’. Then the higher-level principles should constrain the lower-level details.
Then you might have a great sea of details, but you have a fairly small set of principles above that keep the details in place. Then it becomes easy to fix things. If some specific special case violates the higher principle, you adjust it accordingly. Then to be consistent, you don’t have to remember all of the different cases, just the binding principles. With both, you have a way of moving forward and improving the work, without having to exceed your cognitive limits.
If at some point the principles themselves become too much, then we just do the same thing again and go up another level to meta-principles, to ensure that our principles are aligned and consistent.
Now this is not the same as trying to find top-down abstractions.
This is a bottom-up approach, where you start with the details first. When you see enough of them, you try to align their similarities into principles. The key part though is that you recognize and accept that the different groups of details should share common patterns, and so it is worthwhile looking for those patterns.
But it is worth noting that if the principles are not obvious, then creatively overlying fake patterns on top is not good. If there are no obvious patterns, then there are no principles that bind things together. We don’t want principles for the sake of having principles, that would just be extra complexity, we want them for the sake of simplifying stuff and making it consistent to allow us to reduce complexity. So insanely complicated principles are not actually principles, they are just artificial meta-details, that in themselves should be dropped.
In that way, along with ‘encapsulation’ we can tackle overwhelming complexity without getting completely overwhelmed. You cannot fix complicated things by ignoring their nature with over-simplified approaches, that will usually make it worse. If something is complex, you first have to accept that it is complex. You have to deal with it as a complex entity and work within those boundaries.
As we are human, things can get far too messy and complicated, so even if we are keeping up with it, the sheer size of the scope will cause us to be rampantly inconsistent. Those inconsistencies themselves are more extra complexity that is getting added as a byproduct of our not being able to cope with the intrinsic complexity. So it is getting worse.
While the ‘devil’ is in the details, trying to deal with the details at their lowest level is a losing game. So, instead, we want to go a little higher and look for patterns that lay out across similar but different cases.
Instead of getting caught up in the abstract nature of patterns though, we can think of these overarching structural constraints as ‘principles’. Then the higher-level principles should constrain the lower-level details.
Then you might have a great sea of details, but you have a fairly small set of principles above that keep the details in place. Then it becomes easy to fix things. If some specific special case violates the higher principle, you adjust it accordingly. Then to be consistent, you don’t have to remember all of the different cases, just the binding principles. With both, you have a way of moving forward and improving the work, without having to exceed your cognitive limits.
If at some point the principles themselves become too much, then we just do the same thing again and go up another level to meta-principles, to ensure that our principles are aligned and consistent.
Now this is not the same as trying to find top-down abstractions.
This is a bottom-up approach, where you start with the details first. When you see enough of them, you try to align their similarities into principles. The key part though is that you recognize and accept that the different groups of details should share common patterns, and so it is worthwhile looking for those patterns.
But it is worth noting that if the principles are not obvious, then creatively overlying fake patterns on top is not good. If there are no obvious patterns, then there are no principles that bind things together. We don’t want principles for the sake of having principles, that would just be extra complexity, we want them for the sake of simplifying stuff and making it consistent to allow us to reduce complexity. So insanely complicated principles are not actually principles, they are just artificial meta-details, that in themselves should be dropped.
In that way, along with ‘encapsulation’ we can tackle overwhelming complexity without getting completely overwhelmed. You cannot fix complicated things by ignoring their nature with over-simplified approaches, that will usually make it worse. If something is complex, you first have to accept that it is complex. You have to deal with it as a complex entity and work within those boundaries.
Thursday, August 3, 2023
Usablity Tradeoff
Software graphical user interfaces are funny things.
For anything you’d like to do there is a whole spectrum of possible interfaces. On each screen, you can have various different types of modes. You can overlay all sorts of navigational paradigms on top. There are plenty of different widgets and lots of different styling tools. When you add up all of the permutations the possibilities feel endless.
But they are not.
For one thing, user interfaces are highly subject to current trends. A new interface either fits in nicely with the ones around it or it clashes. Its alignment may be at the higher levels, often referred to as ‘look’ and ‘feel’, or it can do it at the lower levels with how it uses the underlying widgets.
So, one rather counter-intuitive property of interfaces is that if they are easy to program, then they are hard to use. It’s not a strict tradeoff, but it is pretty close to it.
If you hire a graphic designer, they will make your interface look great.
If you don’t, it is incredibly difficult to make it look great and most people aren’t naturally gifted at doing that, it takes experience. Ugly interfaces increase the cognitive load of the user, they basically have to expend energy to ignore how annoying the thing is. It doesn’t ‘look’ great.
The ‘feel’ part is similar, usually the domain of a UX expert. Really it is how the navigation maps back to the user’s workflow. Obviously, poor or awkward mappings make it harder for the user to get around and do what they need to do. Instead of it being intuitive, they have to constantly remember that for ‘this interface’ they do some weird steps. So again it is an extra cognitive load for the users.
Interfaces with poor ‘feel’ also usually end up needing a lot of training as well. The users can’t just sit down and do what they need to do, instead, it is always a puzzle of some sort. That makes the project longer too.
For any large interface, a lot of work goes into initially setting up the UX, but as more and more functionality gets added, there is also a lot of work in keeping it relevant.
Then we get down to widgets.
Pretty much every platform provides the same basic ones. Some platforms provide even more. They all have an appropriate use. For example, if there is a password widget, then using it instead of a text widget is preferable.
And there are collected paradigms like dynamic trees. Lots of frameworks have some tree support, but they usually only work for small static trees, huge dynamic trees are a lot of work to get correctly implemented. Retrofitting paging back into trees and lists can be painful.
Layouts with widgets are often tricky as well. You can use simple ones. but they don’t look appealing. A good layout that matches a strong graphic design is often a complicated nest of different containers and layout managers, sometimes 3 or even 4 levels deep.
Now it is far easier for a programmer to ignore the graphic design and UX requirements, and toss the widgets around instead of using them properly. As well, it is extraordinarily difficult to sort out the UX mapping issues, as it always requires some foresight into how the application will grow.
So if you were going to produce a really slick interface, it is actually a huge amount of work. If you skimp on that work, it’s not that hard to wire up some widgets that do some stuff, but it will never make the users happy. So, it is a pretty direct tradeoff, just a little different because, for graphic design and UX, you would hire outside help, so it’s not harder in those cases for the programmers, it is just more expensive for the project.
For anything you’d like to do there is a whole spectrum of possible interfaces. On each screen, you can have various different types of modes. You can overlay all sorts of navigational paradigms on top. There are plenty of different widgets and lots of different styling tools. When you add up all of the permutations the possibilities feel endless.
But they are not.
For one thing, user interfaces are highly subject to current trends. A new interface either fits in nicely with the ones around it or it clashes. Its alignment may be at the higher levels, often referred to as ‘look’ and ‘feel’, or it can do it at the lower levels with how it uses the underlying widgets.
So, one rather counter-intuitive property of interfaces is that if they are easy to program, then they are hard to use. It’s not a strict tradeoff, but it is pretty close to it.
If you hire a graphic designer, they will make your interface look great.
If you don’t, it is incredibly difficult to make it look great and most people aren’t naturally gifted at doing that, it takes experience. Ugly interfaces increase the cognitive load of the user, they basically have to expend energy to ignore how annoying the thing is. It doesn’t ‘look’ great.
The ‘feel’ part is similar, usually the domain of a UX expert. Really it is how the navigation maps back to the user’s workflow. Obviously, poor or awkward mappings make it harder for the user to get around and do what they need to do. Instead of it being intuitive, they have to constantly remember that for ‘this interface’ they do some weird steps. So again it is an extra cognitive load for the users.
Interfaces with poor ‘feel’ also usually end up needing a lot of training as well. The users can’t just sit down and do what they need to do, instead, it is always a puzzle of some sort. That makes the project longer too.
For any large interface, a lot of work goes into initially setting up the UX, but as more and more functionality gets added, there is also a lot of work in keeping it relevant.
Then we get down to widgets.
Pretty much every platform provides the same basic ones. Some platforms provide even more. They all have an appropriate use. For example, if there is a password widget, then using it instead of a text widget is preferable.
And there are collected paradigms like dynamic trees. Lots of frameworks have some tree support, but they usually only work for small static trees, huge dynamic trees are a lot of work to get correctly implemented. Retrofitting paging back into trees and lists can be painful.
Layouts with widgets are often tricky as well. You can use simple ones. but they don’t look appealing. A good layout that matches a strong graphic design is often a complicated nest of different containers and layout managers, sometimes 3 or even 4 levels deep.
Now it is far easier for a programmer to ignore the graphic design and UX requirements, and toss the widgets around instead of using them properly. As well, it is extraordinarily difficult to sort out the UX mapping issues, as it always requires some foresight into how the application will grow.
So if you were going to produce a really slick interface, it is actually a huge amount of work. If you skimp on that work, it’s not that hard to wire up some widgets that do some stuff, but it will never make the users happy. So, it is a pretty direct tradeoff, just a little different because, for graphic design and UX, you would hire outside help, so it’s not harder in those cases for the programmers, it is just more expensive for the project.
Thursday, July 27, 2023
Unknowns
An all too common problem in software is that you now have to do some work, but for you, it is a brand new task. You've never done anything like this before.
So, right out of the gate, there are a lot of unknowns.
One way to handle this is to dive in directly. Just go straight into the things you have to do and thrash your way through the problems one by one.
In the midst of doing that though, you’ll probably have to make a lot of decisions. Setup, configuration, conventions, organization, etc. But more often than not the correct decision is not the most intuitive one. They are often quite different.
If you’ve been around for a long long time, your intuition has probably been honed by plenty of similar experiences, but if you are relatively new to this type of work, it is not. The odds are that your intuition will lead you astray.
Some people think that they are saving time by just experimenting. Try a bunch of stuff and if it seems to work, then push it out.
But the better approach has always been to stand on the shoulders of giants. That is, in the same situation, you bounce out quickly and see what other people have done. Not in-depth research, but enough light reading that you can now make a better choice.
Do not make up your own way of doing things. Whatever you make up will end up costing you more time than you would have spent doing a bit of reading.
After you read stuff, you might not choose to follow it, and you certainly should never choose to follow it blindly. Instead, you might just build on what you’ve learned, and use that to improve how you do things.
If you rely on random guesses, then more often than not you will be wrong.
As you get more experienced, you’ll probably find that you dive blindly in far less often. You’re more likely to do some initial reading, look around for great references, and try to get some depth to your understanding first.
The more you know upfront, the better off things will go. You obviously don’t have time to know everything, so you do have to pick and choose, but more knowledge is always better than less knowledge. And getting knowledge from more experienced people is always far better than getting knowledge from overconfident ones.
So, right out of the gate, there are a lot of unknowns.
One way to handle this is to dive in directly. Just go straight into the things you have to do and thrash your way through the problems one by one.
In the midst of doing that though, you’ll probably have to make a lot of decisions. Setup, configuration, conventions, organization, etc. But more often than not the correct decision is not the most intuitive one. They are often quite different.
If you’ve been around for a long long time, your intuition has probably been honed by plenty of similar experiences, but if you are relatively new to this type of work, it is not. The odds are that your intuition will lead you astray.
Some people think that they are saving time by just experimenting. Try a bunch of stuff and if it seems to work, then push it out.
But the better approach has always been to stand on the shoulders of giants. That is, in the same situation, you bounce out quickly and see what other people have done. Not in-depth research, but enough light reading that you can now make a better choice.
Do not make up your own way of doing things. Whatever you make up will end up costing you more time than you would have spent doing a bit of reading.
After you read stuff, you might not choose to follow it, and you certainly should never choose to follow it blindly. Instead, you might just build on what you’ve learned, and use that to improve how you do things.
If you rely on random guesses, then more often than not you will be wrong.
As you get more experienced, you’ll probably find that you dive blindly in far less often. You’re more likely to do some initial reading, look around for great references, and try to get some depth to your understanding first.
The more you know upfront, the better off things will go. You obviously don’t have time to know everything, so you do have to pick and choose, but more knowledge is always better than less knowledge. And getting knowledge from more experienced people is always far better than getting knowledge from overconfident ones.
Thursday, July 20, 2023
Git Outta Here
I’m certainly not in love with Git. I’ve used far too many other types of nicer repos over the decades, but if you are going to use it, you need to use it properly.
At the high level, you have code that you packaged and released. Sometimes you need to just make a simple fix to that ‘exact’ code.
You need to ensure though that the code you are working on is exactly and only the code that you released. If your work habits or environment are messy, that is a difficult task fraught with a lot of uncertainty. But it does not have to be that way, and it should not be like that. If that happened, it is entirely self-inflicted.
So, a key development task is to ensure that that never happens. That is the primary reason for using a repo. Used correctly, most of them will guarantee that you know and can get a hold of the code that you released.
There are several different families of keeping the code organized, so most of the repo tools support a huge number of options. While that is great, it also opens up a hole for people to wantonly mix and match functionality, which is really bad. You can use a repo like Git to make your life easier, but you can also use it to make life a lot harder.
My advice is don’t mix and match. Pick one of the proper ways to use the repo first, learn it deeply, and then only when you really understand, tweak it carefully. For Git, Atlassian produced git-flow. You don’t have to use their tools, but you should strictly follow the branching model and do it carefully and properly.
For people who think that that sounds like more work, it isn’t. It follows the adage “a stitch in time saves nine”. That is for a little more effort up front, you’ll save yourself a whack load of time later; when you actually have less time than now.
Mixing and matching any sort of optionality is often called ‘going against the grain’, which comes from woodworking. If you sand with the grain of the wood, it will be easier and the results will be better. If you use the development tools properly, within a particular family of usage, they will work far better.
Ensuring that you get the exact code that you released earlier is part of another high-level principle, which is to never, ever, ever release untested code. If you haven’t tested it, you can not release it.
If some code does get released, it is because it was tested first. There are legendary examples of people causing huge problems because they violated this. But it is fundamental to every software release, large and small, critical or otherwise. It is the one rule that you should never violate. And if you violate it, it should not be accidental, but rather for some scary reason you needed to take that risk, and you were aware of the risk that you were taking. Accidental release or rollback of code is so easy to avoid, that any sort of excuse is lame.
If you can ensure that the code you released is easily available, then you can ensure that the code you are testing is also easily available. Your testing environment is akin to production. It is just another place you release stuff. In fact, every environment is just another place to release stuff, including development ones. The only difference with development is that people will change things directly, then retest.
If you get those types of guarantees then your repo tool is doing its job, working correctly for you. If you are not getting those types of guarantees, you need to fix your environments, they are broken. The mess is just making your life harder for no reason.
At the high level, you have code that you packaged and released. Sometimes you need to just make a simple fix to that ‘exact’ code.
You need to ensure though that the code you are working on is exactly and only the code that you released. If your work habits or environment are messy, that is a difficult task fraught with a lot of uncertainty. But it does not have to be that way, and it should not be like that. If that happened, it is entirely self-inflicted.
So, a key development task is to ensure that that never happens. That is the primary reason for using a repo. Used correctly, most of them will guarantee that you know and can get a hold of the code that you released.
There are several different families of keeping the code organized, so most of the repo tools support a huge number of options. While that is great, it also opens up a hole for people to wantonly mix and match functionality, which is really bad. You can use a repo like Git to make your life easier, but you can also use it to make life a lot harder.
My advice is don’t mix and match. Pick one of the proper ways to use the repo first, learn it deeply, and then only when you really understand, tweak it carefully. For Git, Atlassian produced git-flow. You don’t have to use their tools, but you should strictly follow the branching model and do it carefully and properly.
For people who think that that sounds like more work, it isn’t. It follows the adage “a stitch in time saves nine”. That is for a little more effort up front, you’ll save yourself a whack load of time later; when you actually have less time than now.
Mixing and matching any sort of optionality is often called ‘going against the grain’, which comes from woodworking. If you sand with the grain of the wood, it will be easier and the results will be better. If you use the development tools properly, within a particular family of usage, they will work far better.
Ensuring that you get the exact code that you released earlier is part of another high-level principle, which is to never, ever, ever release untested code. If you haven’t tested it, you can not release it.
If some code does get released, it is because it was tested first. There are legendary examples of people causing huge problems because they violated this. But it is fundamental to every software release, large and small, critical or otherwise. It is the one rule that you should never violate. And if you violate it, it should not be accidental, but rather for some scary reason you needed to take that risk, and you were aware of the risk that you were taking. Accidental release or rollback of code is so easy to avoid, that any sort of excuse is lame.
If you can ensure that the code you released is easily available, then you can ensure that the code you are testing is also easily available. Your testing environment is akin to production. It is just another place you release stuff. In fact, every environment is just another place to release stuff, including development ones. The only difference with development is that people will change things directly, then retest.
If you get those types of guarantees then your repo tool is doing its job, working correctly for you. If you are not getting those types of guarantees, you need to fix your environments, they are broken. The mess is just making your life harder for no reason.
Subscribe to:
Posts (Atom)