Normalization functions
Sometimes when coding, we discover a few different representations of the same data.
It can be tempting to write a function to combine these different representations.
Example:
function normalizeSku(sku: string|number) {
if (typeof(sku) == 'number') {
return "#" + sku.toString();
}
const _sku = parseInt(sku);
return "#" + parseInt(sku);
}This solves the problem in the short term, but in the long term, it increases sloppy contracts in the code.
A better solution is to find the original source of the data and make the representation uniform, even if it means changing code or data outside of the current code base.
Mapping as code instead of maps
It can be tempting to add a new condition when the user mentions a new edge case or new specificity to something. The result looks as follows:
function getMinimalOrderQuantity(brand: BRAND) {
if (brand == 'brand1') {
return 20;
}
if (brand == 'brand2') {
return 25;
}
return 20;
}Another variant of that which would be uglier would be to not even define getMinimalOrderQuantity and instead have that logic laid out and repeated in other functions.
The long time result is that the code increases in complexity, with more code branches over time. It becomes difficult to reason about code and the number of cases to test explodes.
It is usually much more future-proof to lay out these type of values as constant maps:
const mapBrandToMinimalOrderQuantity = {
brand1: 20,
brand2: 25,
default: 20
};
function getMinimalOrderQuantity(brand: BRAND) {
if (Object.keys(mapBrandToMinimalOrderQuantity).includes(brand)) {
return mapBrandToMinimalOrderQuantity[brand];
}
return mapBrandToMinimalOrderQuantity._default;
}Doing so lays out the business values plainly in code. If we were to store these values elsewhere, such as a database or UI, the refactor would be trivial. getMinimalOrderQuantity could remain mostly the same. If a value changed, we can track it in git easily. There are also just two code branches now, and it won’t scale up as we add brands, getMinimalOrderQuantity can remain the same.
The key here was to decouple business logic from business data.
In practice
After working with humans and a few AIs, I realize that these 2 patterns tend to accumulate in layers, making the software a complete buggy mudball. So don’t be the one adding a new instance of these.
Code splitting
Requirements pile up, applications grow from prototype to monster codebase. It is your responsibility to code split as you go so that we don’t end up with a huge index.js with all application logic or componentA.ts with 100 sub components. Any file above 1000 lines is a red flag.
This is especially true in an age where agents edit code and the longer the files, the more tokens are consumed.
Hard types
It’s normal to start an application in a simple HTML format. But quickly enough, it’s better to be proactive, introduce types (typescript setup + rewrite), a solid backend, etc.