DEV Community

loading...
Cover image for 7 Tips For Clean Code

7 Tips For Clean Code

Akash Shyam
UI/UX Designer | MERN Stack Developer | Freelancer
・3 min read

It's all about maintaining clean and readable code(which works of course). So, I wanted to share a few tips with you guys. Feel free to correct me or share more tips of your own in the comments to create a learning experience for all of us. Let's get to it then:

1. Comments, Comments and More Comments

I'm not the ideal developer whose code is understood by everyone and I'm betting you aren't either. Instead of explaining what exactly to do, I'm going to tell you what not to do. Let's look at some sample code:

// Created a constant variable "foo" and assigned a value of "bar"
const foo = "bar";
Enter fullscreen mode Exit fullscreen mode

2. Meaningful Names

You want to use those kinds of names where the reader goes, "Yes! I know exactly what this does".

// NEVER DO THIS
const abc = validateEmail('writecleancode@gmail.com')

// Good Job!
const isEmailValidated = validateEmail('writecleancode@gmail.com');
Enter fullscreen mode Exit fullscreen mode

3. Console Warns/Errors

This is a huge problem, so many times I've seen developers commit code with eslint warnings. Infact, a couple of months ago, I started working on an existing project. When I compiled the frontend, there were over 100 warnings by jsx-ally, eslint etc.

We can use husky along with lint-staged which will not let you commit code until you clear all the warnings and errors.

4. as unknown in Typescript

Typescript is smart, but sometimes, it just isn't smart enough! I've seen a lot of // @ts-ignores or as unknowns in typescript code. So instead of doing this:

const user = dog as unknown;
Enter fullscreen mode Exit fullscreen mode

do this:

const user dog as IUser
Enter fullscreen mode Exit fullscreen mode

Even though doing this is not advisable, at least you get some type safety.

5. Use Babel Instead of tsc

From version 7, Babel added support for TypeScript, which means you no longer need to use the TypeScript compiler i.e. tsc to build your project, but instead can just use Babel which simply strips your types from all TypeScript files and then emits the result as JavaScript.

This is not only much faster than tsc, especially in bigger projects, but also allows you to use the whole Babel ecosystem within your project. For example, it's great when you want to use react or javascript features which are still in stage 3.

For back-end projects, this means you can simplify your clunky file-watching scripts and just use babel-node to watch for changes.

6. Use SonarJS and Eslint

Eslint has many rules that enforce best practises and conventions and will also help to prevent bugs.

(TSLint is being deprecated in favor of typescript-eslint; the TSLint plugin SonarTS has been adopted and is now part of SonarJS).

In addition to ESLint’s features, SonarJS adds some complexity checks to your code, which are helpful to just code away and then break your methods into smaller pieces.

7. Opaque Types

I'm not going to explain, I'll just demonstrate this to you.

Imagine we are building a banking API.

// Account.ts

export type PaymentAmt = number;
export type Balance = number;
export type AccountNumberType = number;

function spend(accountNo: AccountNumberType, amount: PaymentAmt) {
  const account = getAccount(accountNo);
  account.balance -= amount;
}
Enter fullscreen mode Exit fullscreen mode
// controller.ts
import {spend} from "./accounting";

type Request = {
  body: {
    accountNumber: number,
    amount: number
  }
};

export function withdrawAmt(req: Request) {
  const {accountNumber, amount} = req.body;

  spend(amount, accountNumber);
}
Enter fullscreen mode Exit fullscreen mode

Did you spot the bug? If you didn't, look at the place where we are calling the spend() function. I've (intentionally) passed the amount before the accountNumber but typescript does not complain.

If you are wondering why this happens, this is because AccountNumberType and PaymentAmt are assignable to each other because both of them are of type number.

There is a long standing issue in the typescript repo about this. Until the typescript team does something about this, we can use the following hack

// Can be used with any type
type Opaque<K, T> = T & { __TYPE__: K };

type Uuid = Opaque<"Uuid", string>;
Enter fullscreen mode Exit fullscreen mode

The utility function Opaque simply defines a new type that, aside from a variable’s value, also stores a (unique) key, such as Uuid.

Conclusion

Thanks for reading! Check out my twitter where I (try) to post tips & tricks daily. Bye 🀟

Discussion (24)

Collapse
joelbonetr profile image
JoelBonetR • Edited

For the comments part It's not about commenting all declared vars, this is useless, the vars should have a syntactically understandable name.
If you end up with something like:

const optimusPrime = whatever;

You are doing something wrong and there's no lint that can help you here. The second point so, overrides the first.


Comments in functions and methods are good, at least to know what this function or method is supposed to do (super briefly), which args it requires and what it returns.

/*
 * Sums 2 numbers
 * num1, num2: Int
 * returns: Int
*/
function sum(num1, num2) {
  return num1 + num2;
}

There are many linters out there, all of them opinionated and require manual actions to adapt your code to it's opinionated rules. I would suggest to install and configure prettifier to your (the project) standard way to do the things (2 spaces, code line length and so on) so your code will be auto formatted on save.

I'm gonna take a try to this SonarJS to see how it works.


I see TypeScript as a dying tech. It was discussed tones of times so I'll place a little brief: it's a subset of JS (not a superset, it's something that compiles into js, so it cannot be a superset by definition) thus it obscures and limits what you can do with javascript while providing a fake peace of mind. Network calls, third party libs and so on have no way to communicate with TS. This is anyway, another discussion.


Finally the console warns and errors are intended for something. It's the same than saying hey, don't throw 404 error from backend when the route is not available, handle it on another way... hmm ok it can be done but errors are here by convention and cuz they help us find out what's going on on a certain situation.
You can, of course, log this errors -as long the execution didn't break apart- to a server instead, adding the required stack trace and so on but there are certain situations where this kind of console commands could be good.

It's not about console logging all your application steps, it's just handling the warning or error situations that you missed out or didn't even think possible to happen.

try {
  // do validated stuff
} catch(err) {
  // if something fails we'll get this message
  console.error(`Error: ${err.message() err.stack()}`;
  // of course as long as it does not break the execution flow we can do
  logger.send(err);
  // and send it to our logging server, but if this fails, we'll still available to replicate 
  the same issue and see the error through the console
}
Collapse
srikanth597 profile image
srikanth597

Ok, I may not be good JS dev as you,
Let me brief an example for you.

Accessing a prop on a nested object let's say obj.data.hasMyRecognitions which could always be array or object or array of objects I don't know them if I go with your Proper JS Road, I would most likely endup being atleast one error which any JS developer experience. i.e Can't read property of undefined.
We don't always want go and check that fix all over again.
In the end we all want time we want to spend on writing and debugging should be kept at minimum and send faster results.
I think TS is great, it definitely does improve DX.

Collapse
akashshyam profile image
Akash Shyam Author

Whoa! Controversial statement right there πŸ˜‚, I agree with the fact that network calls, third party libs cant communicate with TS. Even though we need to write some extra code, it helps a lot in the long run. It just boosts vs code autocomplete so much that it's become difficult coding in vanilla javascript now πŸ˜…. People who are new to the codebase can figure out types of arguments/variables/functions etimmediately.

You know what, I'm going to post about this next time and discuss with you guys!

I see TypeScript as a dying tech. It was discussed tones of times so I'll place a little brief: it's a subset of JS (not a superset, it's something that compiles into js, so it cannot be a superset by definition) thus it obscures and limits what you can do with javascript while providing a fake peace of mind. Network calls, third party libs and so on have no way to communicate with TS. This is anyway, another discussion.

Collapse
joelbonetr profile image
JoelBonetR

Hahaha it could be funny or a mess, let's see how this discussion ends :P

For the intellisense (autocompletion) you can use plugins for that as you are probably using for TS, either way the types are most of time not so important and not required on JS as long as you can create your own classes and types as well and you can ensure the data is of the type you want or need on any data flow.
You can of course, simply check types where's implicitly needed with typeof so no breaking benefits of having TS. As i said, you simply cannot do a single thing with TS that Is not available in JS while adding some throwbacks as the mentioned above so... I can see a clear #1 winner here πŸ˜‚πŸ˜‚πŸ˜‚

Thread Thread
akashshyam profile image
Akash Shyam Author

But typescript offers some extra functionality that javascript does not have like Readonly variables, private/public/protected properties and methods in classes etc etc.

In the end, it all comes down to preference

Thread Thread
joelbonetr profile image
JoelBonetR • Edited

As I said, there's NOTHING that you can do using TS that can't be implemented using JS.
stackoverflow.com/questions/242840...

For instance, there are things that you cannot do with TS that definitely are available on vanilla JS :D

Apart from that using a feature just because it's here is most of time a bad analysis of what do you really need to reach a given software solution, and anything that you feel missing or unexperienced/lazy to develop by your own, there will be a babel plugin or whatever that cover that need hahaha

Thread Thread
akashshyam profile image
Akash Shyam Author

Instead of continuing this discussion, here check out all my points and views here.

Thread Thread
joelbonetr profile image
JoelBonetR

finished my text and commented on the proper discussion :D

Thread Thread
akashshyam profile image
Akash Shyam Author

Lol πŸ˜‚

Collapse
benstigsen profile image
Benjamin Stigsen

While I do agree that comments are very useful, I do not consider it to be one of the best examples:

// Created a constant variable "foo" and assigned a value of "bar"
const foo = "bar";
Enter fullscreen mode Exit fullscreen mode

Here it is describing what is happening which is quite self-explanatory. I think it would be more useful to describe why the variable is created by describing how it's going to be used.

// Variable "foo" to get details from the user "baz" later
const foo = "bar";
Enter fullscreen mode Exit fullscreen mode

Or something like that.

Collapse
akashshyam profile image
Akash Shyam Author

The piece of code you gave consists of "How to not comment code"... TLDR; don't comment every line of code, comment for specific code blocks can't be understood with just the function/variable names.

Collapse
benstigsen profile image
Benjamin Stigsen

Woops, my bad, yeah then we have the same opinion on that.
I didn't see the "not" in:

I'm going to tell you what not to do

Collapse
mhmxs profile image
Richard Kovacs

Personally I hate comments at all :) If i have to write comment it means that the code is not straight forward enough. So simplify first and then comment WHY instead of WHAT if the code below is a workaround, hidden performance tuning or something not stems from the code.

Collapse
akashshyam profile image
Akash Shyam Author

Sometimes we just can't help it. While using a 3rd party library, it might have confusing names or methods which can't be understood without referring to the docs.

Collapse
mhmxs profile image
Richard Kovacs

You are right, but docs and comments are two different things. Function needs some documentation to understand the reason. But leaving comments everywhere doesn't make sense.

Thread Thread
akashshyam profile image
Akash Shyam Author

Exactly, It is exactly what I mentioned in the post

Collapse
pradeepradyumna profile image
Pradeep Pradyumna

Good article!

Point 1 & 2 sometimes go hand in hand.
For brevity if methods/ classes are named such that they are very much self explanatory, then comments won't be necessary. For instance, if a method name says ValidatePhoneNumber(), then I refrain from writing a comment on top of it, saying // Validate phone number.

Collapse
akashshyam profile image
Akash Shyam Author

Thanks mate! I agree with you completely, which is why I gave an example regarding how not to comment.

Collapse
patoezequiel profile image
Patricio Hondagneu Roig

In general I think comments are a good idea when they define the purpose of something or metadata about how to use them (go JSDoc) or when logic is inherently complex and chunks of code are better understood with a text explanation.

The opaque type helper is a new for me, thanks

Collapse
abhinav1217 profile image
Abhinav Kulshreshtha

Under Opaque Types section, what would the Opaque implementation of the buggy example looks like?

Collapse
davids89 profile image
David

About tip number 1. I think if you code is clean enough, you don't need comments.

Collapse
akashshyam profile image
Akash Shyam Author

Sometimes, some code blocks will be quite complex and comments will help another dev to scan through the code quickly.

Collapse
jonrandy profile image
Jon Randy • Edited

Look again, the code shows an example of what not to do

Collapse
kenethsimon profile image
kennyLFC

Using eslint helps me write clean code