What happens when you ban syntax?

Photo by Mohammad Rahmani on Unsplash
Let's actually write some TypeScript code! For this first "programming-specific" post, I've added some new features to this site to execute TypeScript on the front-end. This blog post specifically is around writing code. This took a while to add only because I haven't been focused on getting this feature in, but now that it finally is in, this should open up more possibilities that I hope to leverage in the future.
JavaScript is expressive, but this freedom can become a problem
JavaScript is an expressive language allowing you to do things multiple different ways. Personally, I believe a lot of these ways just shouldn't be used. Instead, I recommend simplifying what syntax and patterns are used to write less common, but code that ends up more readable and easier to go through. The main focus with the code in this post is minimizing context, so what you need to think and care about and where you need to care about it are all minimized with these patterns.
To accomplish this, let's ban some of the core syntax that you could use. This will mean we will self-enforce some rules that will prevent us from using specific types of syntax. This of course limits what options are available, but this should lead to cleaner code, assuming we can still accomplish the same thing.
So without further ado, let's get started!
I will refer to the following code as just "JavaScript", but will actually be writing and running TypeScript. The difference won't matter much here; just keep in mind that the written code is TypeScript, but most of the concepts will also apply to JavaScript as well.
The goal
The goal of these is to specifically focus on one problem, a problem that is less important when it comes to AI writing the code, but way more important when it comes to debugging it.
It's context—minimizing what your brain has to think about when debugging becomes one of the most, if not the most, important aspects of code written largely by AI. The goal of the code isn't "elegance" or separation of concerns; instead, it's how to understand it and review it.
Examples
Nested conditionals means nested context
Let's look at a code block. This code block implements a validation function for a user type. Let's start with code that is straightforward, but written obtusely with nested conditionals.
This code works, but the comments point out part of the problem, where the logic returns false is largely "far away" from the actual conditional.
One way to tackle this is by moving to the early return pattern, which is well suited for this admittedly contrived situation.
Early returns allow for removal of nested logic and removal of syntax such as else or else if. This also minimizes how much context you need as you go deeper into the code, as the further down you go the more "niche" of a situation you are in.
Let's move on to more complex examples, and jump back a bit to variables themselves.
Variables and you
You can declare a variable multiple ways in JavaScript:
var- the old waylet- the new wayconst- the other new way
However, const enforces some clear constraints and provides some context based on just how it's declared: this value, whatever it may be, will not be re-assigned. Objects or references could be mutated, but the reference itself won't change. Because of this, we can decide to only use const, and ban var and let as much as possible. This makes some situations that might be done with a let only be done with alternative syntax if you can only use const. This is intended, as it forces the code to be structured away from mutation and re-assignment, forcing you to "split up" your code in a more distinct intentional way.
Relying entirely on const has some side effects, primarily forcing code into sub-functions, ternaries or my personal favorite, an immediately invoked function expression (IIFE). This syntax can trip people up initially, but practically it's very "functional" in execution where it's immediately clear that the value of the const variable being assigned is defined in-line with a small chunk of localized code calculated in this function directly. Of course, having a dedicated sub-function is more natural a majority of the time, but for small one-offs, or things like in-lining some logic within JSX I think it makes more sense when read top to bottom, compared to defining a function "ahead" of time then using it later, which requires remembering this context from somewhere else in the file.
This example works, but notice how the conditionals in the middle of the function muddle the overall code as multiple things are happening between the default type declarations, the logic to setup default colors and the actual creation of the object.
There is also the TypeScript requirement that the favoriteColor variable is initialized to a value; omitting an initial value causes TypeScript to flag the return object later. This can be worked around a number of ways, but notice how setting red is done because it's the default fallback, which could also be removed.
Let's rewrite this function with const and a ternary as the initial refactor.
This also works, but the nested ternary is a bit hard to read. This also has a fallback case that isn't even possible since undefined will never actually be passed unless the input type is wrong, which results in the fallback case somehow being defined twice as red, but never actually possibly passed normally, or if it somehow is used you have to maintain two "fallback cases" that are the furthest apart.
Finally, let's rewrite this with an IIFE, which allows us to control the nesting and flow of logic for just the section of code we want. This could be replicated with another helper function, but I try to avoid that for one-off logic, as that moves the code away from where it is used. An IIFE keeps things localized: as long as you don't get intimidated by its syntax, it's a great way to localize context. Because it is still a function, we also get the ability to utilize early returns to minimize extra nesting or "else if" chaining. This results in the IIFE syntax being somewhat surprising for those not used to the syntax, but the code in the function is about as straight forward as you could write it, using basic syntax with very clear top-to-bottom execution.
This final version is my favorite: outside of introducing some extra syntax, the logic is isolated to where it's used, and the code can expand with new use cases more easily. The ternary approach is similar, but depending on your auto-formatter, it can result in harder-to-follow nesting because a ternary forces an "either/or" branch every time.
With this example, we introduce functional syntax, but get to remove let, extra nesting, else, and else if while avoiding nested ternaries. I also intentionally didn't use switch here, as if the goal is to minimize syntax, I believe you just don't need or want a switch statement a majority of the time. Even in cases where a switch is most optimal, such as utilizing the "drop through" behavior when you don't use break, I'd consider it more niche and generally just avoid it for simplicity's sake.
Wrapping Up
Self-imposing constraints on what syntax you allow yourself to reach for might seem counter-intuitive in a language as flexible as JavaScript. But the goal isn't about writing clever one-liner, it's minimizing cognitive context when reading, debugging, and reviewing code.
By banning a few familiar constructs, you can force yourself to write code more effectively, and hopefully have an easier time debugging later.
Of course with AI writing more and more code for us, this sort of guidance (and post) might be less and less applicable. That said, I wanted to get this post out as I've had this one in the background cooking for nearly a year. I just never was able to get it out due to the complexities of getting the typescript parser and sandbox working, as it essentially "hand rolls" parts of the monaco editor just to work in this blog setting.
Well its out, and I hope to leverage and build on the editor more in the future, or at least write more about code itself!
Until next time, keep learning, keep growing and keep asking questions!