Coding With IntentionHow to write well-defined and easy-to-maintain codeKenneth ReillyBlockedUnblockFollowFollowingApr 27Screenshot of a popular modern IDE — Visual Studio CodeWith hundreds of programming languages available to choose from today, the modern developer has an incredible amount of flexibility in designing and implementing software.
There is every type of syntax imaginable, ranging from low level code for raw power, to the more human-readable for easier development.
There is generally a tradeoff between these factors when it comes to choosing the right tool for the job, and within each language there are limitless ways to approach solving a problem.
While some languages are strongly-typed and require specific definitions of things like variable and function return types, others are more dynamic and forgiving in nature.
Regardless of the type system your language of choice implements, code that is explicitly written is often easier to parse, read, and debug.
What is explicit programming?Explicit programming is the process of producing source code in which all of the inputs, outputs, operations, and exceptions are explicitly defined using the best features available within the current language and environment.
This approach provides some advantages over it’s opposite, implicit programming, in which the meaning of everything is “implied” and left up to the compiler or interpreter, other developers, or anything else to determine the intended operation of each section of the program, often in near realtime.
While implicit programming does provide some flexibility in allowing the developer to express intent in many different ways with less constraints, there are major advantages to explicit programming:The computer running your application will have a better picture of what you are trying to accomplish.
For example, defining a variable ahead-of-time, with the correctly associated type, allows the machine that’s running or interpreting your code to allocate memory, tune the garbage collector, and do other cool things beneficial to your program.
Other people — such as testers and developers who read your code in the future — will also have a better idea of what the program is supposed to do and how it should work.
As in the case above, the next person to work on the program can clearly see the type and context of each variable.
When reading over tens of thousands of lines of code (or more) to maintain some huge project, these small details make a huge difference.
These are big advantages over having to figure out what every single variable means and why, and what to do when types don’t line up and type coercion is required, all of which start adding up fast when repeated billions of times.
It may appear time-saving at first to not have to initialize variables in languages which don’t require it and find other shortcuts.
However, consider that in these cases, this person is saving one or two seconds of effort and forcing everyone and everything to make up for it later, guessing at what the code means and hoping it doesn’t fail when some exception occurs, or when someone eventually changes it — introducing new bugs that now have additional wasted cost of debugging and refactoring.
The real time-saver is the minor up-front investment of a few seconds here and there, to bulletproof the software and its related tooling, documentation, and other systems.
Determine your optionsThe features available to the developer for producing well-defined and easy to read code will vary widely depending on the environment, from rough shell scripts and piecemeal architecture, to the extremely powerful realtime debugging tools available in Visual Studio Code and other modern editors.
In programming, a type system is a set of rules that assigns a property called type to various parts of a computer program.
This effectively defines to what degree the developer can or cannot illustrate the exact intended operation of each piece of the program.
Of the various rules within a type system, two of the most important are these:Type Safety: the extent to which a language prevents type errorsType Expression: whether types are implied, or explicitly definedFor information about the type systems used by some common languages, see this article: Comparison of programming languages by type system.
Define as much as possibleIf you have a choice between two options, one of which saves you a few seconds now but means reduced performance and additional debug time for someone later, don’t be lazy.
Put in the extra three seconds now and save someone — possibly even yourself — wasted time and effort later figuring out how something was supposed to work and why it suddenly broke one day.
Something is easy to read and understand the moment you are writing it, of course, since in your tunnel vision you are writing this one snippet of code that fits perfectly within your own memory and makes perfect sense to you.
Multiply that by thousands of components, each connected in a variety of different ways depending on who built it or when, and then imagine yourself wading through that a year or so later, trying to fix some random bug at the 11th hour before a major release.
Then it becomes more clear as to why it’s important to define how a program should work and leave as little open to interpretation as possible.
This is how standard computer systems actually work: with exact known parameters and rigid error handling to ensure that nothing blows up when things go wrong.
And go wrong they do, very often.
Build things that make senseDefining the intended behavior of your code in a manner that is obvious to both machines and humans is a straightforward process:Put serious thought into what you are going to make, and why, before you set out to actually implement it.
Spend some time at the whiteboard, or with a pen and notepad, and make sure your ideas make sense and pass logic tests left and right, inside and out.
If you don’t do this now, everyone else will do it for you along the way, which is not going to be fun for you.
Define the parameters of what you are making, including the variables you need, what types they are, how to name them, what kinds of abstractions or other features to use, and so forth.
If your team or project is using Test-Driven Development, this is an opportune time to scribble these into test cases, which you can later polish into a solid application test suite.
For projects involving relational databases, ensure that you are following standard conventions for naming, type definition, normalization, indexing, and other best practices for database design.
Place complex data operations inside well-named database functions and reuse them easily from within your application source code.
Do the same with SQL views to avoid having to pepper your application with massive redundant queries.
Utilize the abstraction features available within your language to at least create de-coupling layers between the bulk of your business logic and the underlying nuts and bolts of your application.
Avoid situations, for example, in which main entry point for an API is literally one step away from a direct database call (or worse, in the same code block).
This seems trivial but again, across hundreds of main API components, refactoring something you copied 275 times instead of abstracting once becomes a huge nightmare that could have easily been avoided head-on.
Practice writing self-documenting code: name things in a way that both adheres to language best practices and gives a clue to future readers as to what they mean.
For example, try to avoid random or cryptic names for variables, functions, or anything else, instead ensuring that every name gives obvious clues as to what it represents, and more importantly, why.
Discover any major weaknesses concerning the language used in the development of each part of your application, and use this info wisely to create utility classes and methods for yourself and/or choose a solid framework that ensures predictable behavior by dealing with these issues.
A good example of this is the type comparison table for PHP which illustrates some non-standard behavior, or the various frameworks available that can help mitigate these issues.
ConclusionThere are many ways someone can go about solving a problem, especially one in which there are plenty of examples available where others have found many different ways of achieving basically the same thing, all of which are viable options.
Find the languages and tools that work for you and allow you to create what you want to the best of your ability, and then master the features available within that environment.
This allows you to build software that will still run great and be easy to work with years or even decades later.
Good design is timeless.
It does not care about industry buzzwords, trends, emerging technologies, or anything else for that matter.
Things which work well and look great always will, which is why modern designers use very old concepts such as the golden ratio, which artists such as da Vinci used extensively to great success due to it’s widespread occurrence in nature.
Define what you want something to do and how you want it to work, and then set about constructing it in such a manner that leaves absolutely no guesswork to the reader, whether that reader is a machine running your program or another programmer who has to work on it months or years down the road.
Your clients, associates, and probably your future self will all thank you for it.
.. More details