An assortment of random axioms that I try to think about when doing software development work. The reason why I hold to these usually falls into one of these reasons:
- Making the most efficient use of my time
- Minimal need to maintain the code (hopefully, maybe)
- Ease of maintaining the code
- Ease of reading and understanding what the code does
- Finding the best way to make efficient software, without tramping on the above
Regarding the software development process
The priorities of development
I stick to these priorities:
Making the app easy to maintain
- If the app isn't easy to maintain, then it makes everything harder to do. And this is a very broad category, covering things like what tools I use, designing, and implementation details
Making the app do what it's supposed to
- If the app doesn't do what it's supposed to do, then why bother using it?
- Making the app perform as best as it can
Now some people who aren't developers looking into this may go "how come performance isn't your priority? Shouldn't you always make the app perform fast?" In particular say for games. To me, it doesn't matter if the game can get 300 FPS while looking good on potato hardware, if it crashes all the time, has bugs, or isn't, you know, fun, then I couldn't care less because it's not doing what it's supposed to do. And if it's a nightmare to maintain, then good luck fixing the problems.
Find the tools that you can work with, but only find as many as necessary
Having an excellent set of tools does wonders for productive software development. The tools need to be ones that you can work with depending on your preference. But only get the ones that you need and no more. This creates noise in your toolchain and whenever you setup your development environment, the fewer tools you need to install and configure, the faster you can get to doing the actual work. And before you think about adding a tool to your toolbox in a more "permanent" fashion, think about why you need it, how has it helped you, and how often does it help you.
For example, the bare minimum toolset I would l like for a given project is:
- A text editor with code syntax highlighting and line number viewing on the side. Bonus points if it checks syntax and for issues, but that's not necessary
- A diff tool to see the differences between versions of source code and possibly merge them
- A version control tool of some sort.
Some people may want more. Some people may want less. And some people use the bare minimum for these (if they do it). For example, The 8-Bit Guy for Planet X3 development uses a basic text editor (akin to Notepad) for coding and his "version control" is copying and pasting the project folder once in a while. He doesn't mention a diff tool, but he likely hasn't had a need for one.
Regarding software design
Figure out what needs to be done first
The app has a purpose, figure out what that purpose is and what needs to be done to achieve it first. If you don't know what the purpose of the app is, then what's the point in cranking out code for it?
Design from the top-down, but build from the bottom-up
Do you design a skyscraper by first figuring out what kind of foundation you want before working on the shape you want it to have or do you design the skyscraper from the shape that you want it to have, then work your way down to the foundation? Or to put it more closer to geekiness, if you want to build a smartphone, do you first design the phone, its features, and what you'd like it to do, then work your way down to what kind of hardware it should have, or do you decide what hardware it should have, then work on the design of the phone, its features, etc.?
Granted you could go either way, but the problem with designing from the bottom-up is the bottom houses details aren't oriented to solving the problem of designing the thing in question. Those details are oriented to implementing it. Using the phone example, if you find out too early the hardware doesn't actually work the way you thought it did, then you'll be at square one. If you design from the top-down first, you end up with a concept that is independent of the implementation so that if you find out the hardware doesn't actually work, then the amount of work thrown away is relatively smaller.
So where does the "build from the bottom-up" come from? In a lot of cases, applications need a foundation and infrastructure to work with. By building from the bottom up, you create a platform in which makes developing the rest of the application easier. Using the smartphone example, once you're done designing and selecting the hardware, you need to first build the hardware, then, generally speaking, build the firmware, OS, and drivers before you can start working on the application itself.
Another way of saying this as well from say a web app point of view, build from the back-end to the front end.
Design principles are fine to learn, but should be understood before putting them to practice
A design principle in software is always good to learn, but you shouldn't rush out and put it to practice before understanding what it's doing and how it would fit in your application.
Design (and coding) Principles
The following principles I like to follow:
- Keep it simple, stupid (KISS): The simpler the pieces of code are, the easier it is to work with.
- Don't repeat yourself (DRY): If you are copying and pasting ANY amount, you should probably see if it fits better in its own function
- You ain't gonna need it (YAGNI): Don't do things you don't need until you actually need it.
- Loose coupling: Do not have one file, class, etc. heavily depend on another. That is, I should be able to remove this file, class, etc. without needing extensive modifications in other places of code.
- Single responsibility principle: A feature or a small subset of a feature should be contained within a single file, class, etc.
Regarding software implementation
Code is written for humans, not machines
It may be tempting to believe that the whole point of source code is to describe to a computer what you want it to do (and it'll do it exactly). However, that's not 100% true. Source code is describing what a computer should do in a way that humans know what the computer is doing.
Code should always be consistent, even when you don't agree with the style
Everyone has their reasons for styling the code. But what's important is not the style of code, but that it's the same style across the board.
Code should self-documenting
Every function, variable, class, etc. name needs to be meaningful. And not just meaningful by description, but context. For example all variables should be nouns, because they are a thing. All function names should start with a verb, because they are doing things.
Comments should be used sparingly and the only time I think it should be appropriate to use comments with no questions asked are:
- Separating sections of code
- Describing files at the header
- At the top of function definitions to describe them
- If something non-obvious needs to be communicated, like why an innocuous piece of code needs to be where it is
Integrating someone else's solution is fine, if you understand what it does
Let's face it, a lot of coding is basically copying and pasting someone else's code. But there's a difference in sticking a piece of code in your software, seeing it work, and going on your merry way than taking this piece of code, analyzing it, and seeing what it's doing, and how it affects your code.
Build and test often
I always feel there's this stigma that if you have to constantly build and test out your application, you're probably not a good developer (This is probably self-inflicted, I never actually read anything about this). But to me, building and testing often means catching bugs and figuring out problems earlier than later. And when you change large swathes of code and there's a problem... well good luck finding what did it.
Work on one feature before moving onto another
If the application has a lot of features or the work is updating a lot of features, focus on one feature before going to another. This will reduce mental load and allow concentrating on getting that one feature more or less perfected. If the feature is dependent on another one that isn't developed yet, provide dummy data or something.
Break things up if possible, but don't break them down too far
Breaking things up helps understand the code piecemeal, but don't break it up so far that you're always jumping around in the code.
Reinventing the wheel is fine for simple things, but don't reinvent it again
Basically, if you can hammer out a library or set of utilities for something simple, do it. And reuse it.
While I'm sure in Python there are CSV reading libraries, building one from scratch is easy and I'm not hunting one down to get the features I want. And when I do build it, I should keep it around so I'm not building it again.
Make the application predictable
One of the biggest time savers for source code, both for the computer running it and the person reading it is making the application as predictable as possible. You can do this by trying to eliminate conditionals as much as possible.
What I mean by a conditional is any if-statement or any loop. The less of these you have, the easier it is not only for the computer to process (predictable code is happy code), it's easier for developers to read and understand. To put another perspective on it: for every conditional you have, you double the complexity. Examples of eliminating conditionals:
If you are initializing a variable and it has to be within certain bounds, find a way to make it impossible to go out of bounds in the first place. For example, if you are generating a random number and it needs to be within certain bounds, contain how the random number is generated in the first place so you don't have to check after the fact.
Using a single variable to maintain state, rather than checking a bunch of variables to determine what to do next. If you catch yourself writing code that's always checking the same variables, it would be faster to have any change to those variables update a single "state" variable, then depending on what "state" its in, determine what to do next. Then when it comes to debugging a problem, you'll only have one thing to check: what was the last state it was in? And it's much easier to do a search on when the state changes to that particular value than say a dozen variables and finding the right combination that blows things up (I've had to deal with this before)
Use switch-case statements when possible. If you need guidance on when you should use them, this answer on StackExchange helps. The compiler or interpreter will then find the best way to turn this into an cleanly executable bit of code https://www.eventhelix.com/RealtimeMantra/Basics/CToAssemblyTranslation3.htm is an example of how switch-case statements are handled in C.
- Avoid if-statements in loops if possible. An example of this is if you need to print out values, say the prime factorization of a number. This requires printing out an * after each value, but you don't want to print an * at the end of the last one. You could, in the loop that does the factorization, check if the value is going to be the last one and if it's false, print out an *. Or you can solve the first factor then in the loop, always print a * first thing.
It boils down to this: make your code predictable. Deterministic. That is for any input, strive to make it run the same exact code.
Don't prematurely optimize
Premature optimization is trying to be "smart" with code for the sake of performance at the cost of readability. Premature optimization also just adds additional work. You shouldn't be caring about the performance unless it's too slow. And if you need to care about performance, benchmark and profile the application to see where the heaviest cost is, then work on optimizing that. This reduces the amount of work you need to do.
It's okay to do the naive thing to prove the design works
This ties in to "Don't prematurely optimize", but if you want to make sure the design works, do the naive solution just to prove what you're doing works. Once it does work, give it a once over for improvements if it doesn't take much effort.