Naming things (= variables, properties, functions, methods, classes) correctly and in an understandable way if an extremely important part of writing clean code.
Be descriptive, names have one simple purpose: They should describe what's stored in a variable or property or what a function or method does. Or what kind of object will be created when instantiating a class.
Exception: Boolean variables, contants, functions and methods are better named as a Yes/No question
- isUserValid = True
- bool inputIsValid() {}
In most situations, you should avoid generic names like handle(), process(), data, item etc. It doesn't matter if you prefer fetch...(), get...(), retrieve...() or any other term but you should be consistent!
Examples:
You could think that comments help with code readability. In reality, the opposite is often the case though.
Proper code formatting (i.e. keeping lines short, adding blank lines etc.) on the other hand helps a lot with reading and understanding code.
Bad comments | Good comments |
---|---|
Dividers & Markers | Legal Information |
Redundant Information | "Required" Explanations |
Commented Out Code | Warnings |
Misleading Comments | To do Notes |
Functions and methods (I don't differentiate here) are the meat of any code we write. All our code is part of some function or method after all. And we use functions to call other functions, build re-usable functionalities and more.
Functions are made up of three main parts:
- Their name
- Their parameters (if any)
- Their body
Besides the number of paramters, the function body should also be kept small. In order to be small, functions should just do one thing. Exactly one thing.
Example (large login function, no abtraction):
function login(email, password) {
if (!email.includes('@') || password.length < 7) {
throw new Error('Invalid input!');
}
const existingUser = database.find('users', 'email', '==', email);
if (!existingUser) {
throw new Error('Could not find a user for the provided email.');
}
if (existingUser.password === password) {
// create a session
} else {
throw new Error('Invalid credentials!');
} }
Example (small function, but is doing one thing?):
function login(email, password) {
validateUserInput(email, password);
verifyCredentials(email, password);
createSession();
}
A function is considered to do just one thing if all operations in the function body are on the same level of abstraction and one level below the function name.
No matter which kind of application you're building - you will most likely also use control structures in your code: if statements, for loops, maybe also while loops or switch-case statements.
But control structures can also lead to bad or suboptimal code and hence play an important role when it comes to writing clean code.
This is a simple one. It can make sense to use positive wording in your if checks instead of negative wording.
if isEmpty(blogContent):
// throw error
if not hasContent(blogContent):
// throw error
let avoid negative checks
if isNotEmpty(blogContent):
// continue
}
if not isNotEmpty(blogContent) :
// throw error (?)
}
This is very important! You should absolutely avoid deeply nested control structures since such code is highly unreadable, hard to maintain and also often error-prone. dirty code
function messageUser(user, message) {
if (user) {
if (message) {
if (user.acceptsMessages) {
const success = user.sendMessage(message);
if (success) {
console.log('Message sent!');
}
} }
} }
Guards are a great concept! Often, you can extract a nested if check and move it right to the start of a function to fail fast if some condition is (not) met and only continue with the rest of the code otherwise.
function messageUser(user, message) {
if (!user || !message || !user.acceptsMessages) {
return;
}
user.sendMessage(message);
if (success) {
console.log('Message sent!');
}
}
We already learned that splitting functions and keeping functions small is important. Applying this knowledge is always great, it also helps with removing deeply nested control structures.
Errors are another nice way of getting rid of redundant if checks. They allow us to utilize mechanisms built into the programming language to handle problems in the place where they should be handled (and cause them in the place where they should be caused...).
Sometimes, you end up with duplicated if statements and duplicated checks just because the code inside of these statements differs slightly.