Your IDE helps if you help the IDE - example

András Tóth - Nov 24 '21 - - Dev Community

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 or typescript 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() { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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, /* ... */];
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

👍

PROs and CONs

PROs:

  • Our IDE and typescript 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 our IDE 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
Enter fullscreen mode Exit fullscreen mode

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, /* ... */];
Enter fullscreen mode Exit fullscreen mode

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.

🙃

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .