I have written previously about a clean code angle I rarely read about: writing code with the available coding tools in mind.
This is a change in viewpoint.
Instead of scraping together the easiest to write code possible you think about how much the coding tools (particularly your
IDE
ortypescript
transpiler) are going to be able to make use of it.
If this sounds alien to you I have found recently a seemingly innocent thing that can actually lead you to problems.
Let's say you want to start up a koa
server using routing-controllers
library. You have controllers
, middlewares
, interceptors
...
Also you have two options: add these functions as an array or add the directories and createKoaServer
function will look it up for you.
Now let's say you have an errorInterceptor
.
// 'interceptors/error-interceptor/index.js'
export function errorInterceptor() { /* ... */ }
And then you go with the option to specify the directories:
import * as path from 'path';
import { createKoaServer } from 'routing-controllers';
const app = createKoaServer({
controllers: [path.join(__dirname, 'controllers/**/index.js')],
middlewares: [path.join(__dirname, 'middlewares/**/index.js')],
interceptors: [path.join(__dirname, 'interceptors/**/index.js')]
});
export { app };
We really went future-proof by loading all middlewares
, controllers
and interceptors
right out of their damn directories?
All fine, but later some other dev would come tracking down a problem: "Maybe errorInterceptor
is not running?" 🤔.
So they would go and use Find Usages
option in their IDE
.
...And it says "No usages". Then they search for errorInterceptor
and only find the definition.
"Well then, it seems it really was dead code! Let's delete it then!"
And then all hell breaks loose.
Or you don't delete it, you might be annoyed to have one file in one folder and you find it wasteful, so you just rename index.js
-> error-interceptor.js
and move it up one folder to interceptors/error-interceptor.js
...
All these will break things and you won't know it until you press Run
button.
The Pros and Cons
PRO: We do not need to think about ever adding a new middleware/controller or cleaning it up.
CONS: Your IDE will say none of these are ever used, the typescript
transpiler won't help you if you ever use the wrong format and so on... You have essentially turned off all automatic safe-guards. Only tests can save you.
Personally I find any pre-runtime checks very useful and if I have two solutions I would prefer the one that has more of them.
Runtime is a "weird quantum state" where we do not know what is there until we get there: the more we push into this realm that less our coding tools will help us find the landmines before we step on them.
What if we take the other option and pass the items ourselves?
The error-interceptor/index.js
file stays where it is and - as an effort to preserve the "future-proof"-ness of the previous solution - let's create a new file in the interceptors
folder:
// `interceptors/index.js`
import { errorInterceptor } from './errorInterceptor/.';
import { otherInterceptor } from './otherInterceptor/.';
export const interceptors = [errorInterceptor, otherInterceptor, /* ... */];
Somewhere else in the file that creates the server:
import { createKoaServer } from 'routing-controllers';
import { interceptors } from './interceptors/.';
const app = createKoaServer({
interceptors: interceptors,
// controllers: controllers,
// middlewares: middlewares
});
export { app };
👍
PROs and CONs
PROs:
- Our
IDE
andtypescript
will now know about the usages of our functions - It can warn us when we delete/refactor
- If we move around the
controllers
/middlewares
/injectors
we can use ourIDE
to update the paths for us...
CONs:
- you might need to type a little (but not too much)
I think there is a clear winner here.
"But what if I have 50+ controllers?"
In this case you also want to group them I guess into smaller folders, let's say:
\controllers
\user-controllers
\login
\logout
\...
\index.js
\product-controllers
\add
\remove
\...
\index.js
index.js
In this solution user-controllers\index.js
loads, groups into an array an exports all of the controllers from the folders:
// controllers/user-controllers/index.js
import { login } from './login';]
// ...
export const userControllers = [login, logout, /* ... */];
// ------
// /controllers/index.js
import { userControllers } from './user-controllers';
import { productControllers } from './product-controllers';
export const controllers = [...userControllers, ...productControllers, /* ... */];
Finally where we create the server can stay simple while we boxed away really long imports/exports in neat, easily readable files.
Summary
When you have options to choose from you should choose the solution that works better with the coding tools you have: you get back what you feed them. Feed them love and care and they are going to save you when you need them, feed them negligence and they won't help you.
🙃