Commenting, Code Structure and Tricks By albro

Commenting, Code Structure and Tricks

In this post I want to talk about commenting for codes as well as code formatting. Usually, novice developers think that these two items are not so important in code readability and cleanliness, but the truth is that both items can make our code more readable or give it a big blow. So without further ado I will begin.

Categorizing bad comments

If I want to summarize the issue of comments, I will tell you that it is better to avoid them. Comments are usually a bad thing and hurt the readability of your code, but there are very few exceptions that we want to talk about. Most of the time, there are newbies who emphasize on commenting, and that's because they don't master coding. When you are a new developer, you have trouble reading and understanding the code, so you like to comment each and every line so that later you can read and understand them in simple language, but for someone who is good at coding. Most of the comments are internal and additional information. Why? If in a part of the code we sent a POST request to an API to register a new post, do we need a comment? The code itself specifies what it is doing, and the comment you write will only repeat itself. Consider the following example:

class Database {
private dbDriver: any; // the database engine to which we connect



lodaDatabaseDriver(driver: string) {
if (driver === 'sql') {
// connect to sql driver if it is set to sql
this.dbDriver = sqlDriver;
} else {
// otherwise, connect to Mongo
this.dbDriver = mongoDriver;
}
}


connect() {
this.dbDriver.connect(); // this may fail and throw an error
}
}

As it is clear in the code above, all the explanations are completely redundant and unnecessary. For example, in the lodaDatabaseDriver method, we explained that if the driver was set to SQL, we should connect to MySQL, but why? Why have we written such information as a comment? Isn't this the nature of if statements? So what is the need for additional explanations?

The next category of bad comments are those that separate different parts of the code. Consider the following example:

// ***************
// GLOBALS
// ***************
let sqlDriver: any;
let mongoDbDriver: any;



// ***************
// CLASSES
// ***************
//

I've used two large sections of comments with asterisks here to say that in the first section I'm using some globals and in the second section I'm looking for a class definition. There is no need for such a comment because anyone can understand this issue with a simple look and see the code failure in different areas. In this example, how can someone who knows how to program not be able to recognize the definition of classes? If you are a professional developer but still feel that you may need to break files, your file is probably too big and needs to be split into smaller modules. Files that are too long usually cause too much clutter, so you feel the need to comment out the various boundaries.

The third category of bad comments are misleading comments! For example, consider the following code:

// ***************
// GLOBALS
// ***************
let sqlDriver: any;
let mongoDbDriver: any;



// ***************
// CLASSES
// ***************
// Acts as an adapter, connecting models to various database engines (SQL, MongoDB)
class Database {
private dbDriver: any; // the database engine to which we connect



loadDatabaseDriver(driver: string) {
if (driver === 'sql') {
// Connect to the SQL Driver if "driver" is set to SQL
this.dbDriver = sqlDriver;
} else {
// Otherwise, connect to MongoDB
this.dbDriver = mongoDbDriver;
}
}



connect() {
this.dbDriver.connect(); // This may fail and throw an error
}



insertData(data: any) {
this.dbDriver.insert(data); // updates a user
}

At the end of this code, we have a method named insertData, and by looking at the code inside it, we realize that its task is to register a new user, while the comment says updates a user, which is misleading. Naturally, the update and insert operations are not the same because the update operation is performed only when something is already registered and exists. If the codes are a bit more complex, such a comment will cause other developers to misunderstand.

The fourth category of bad comments are comments that disable part of the code. Many novice developers disable too much code by commenting it, causing code clutter. There is no problem with simple and temporary commenting of codes, but our discussion is about permanent and not temporary comments. For example, if you want to disable a part of the code for testing or if you want to disable a part of the code to edit it later, there is no problem. On the other hand, if you are afraid that you might need this code later but never remove it until the end of the project development, you have done something wrong. Today, we use version control programs like Git, which can easily revert any deleted or edited parts, so there is no need to use comments, but you should have regular commits with short time intervals. In particular, in the case of such comments, you should just remove that part of the code and then commit the change.

Good comments category

As I said, most of the comments are bad and are written due to incompetence or lack of experience, but there are also comments that are good and should be written. The first category of good comments are those that contain legal and copyright information, for example:

// (c) @albro / Hive Blockchain
// Created in 2023



let sqlDriver: any;
let mongoDbDriver: any;



class DatabaseAdapter {
private dbEngine: any;



loadDatabaseDriver(engine: string) {
if (engine === 'sql') {
this.dbEngine = sqlDriver;
} else {
this.dbEngine = mongoDbDriver;
}
}

This category of comments specifies legal restrictions and the owner of the work and is always written at the beginning of the file and before all the codes, so they have no interference with the readability of the codes. In many cases, if you work in big companies, they force you to write such comments, and this is not a matter of taste at all.

The second category of good comments are those that explain a problem that cannot be conveyed using a good name choice. A simple example of these comments are comments that explain Regex or Regular Expressions. Consider the following example:

// accepts [text]@[text].[text], i.e. it simply requires an "@" and a dot
const emailRegex = /\S+@\S+\.\S+/;

Choosing the name emailRegex tells us that this code is a Regex for validating emails, but validating emails is not a single process. For example, a particular website may only support Google, Microsoft, and Yahoo domains and want to reject all other emails. There are different rules for email validation and there are several different ways to do this, so you should write the description next to the Regex. In the example above, all we have done is check the following structure:

stroing . string @ string

Of course, our Regex in this example is relatively simple so experienced developers can easily read it, but in many cases the Regex can get very long. Consider the following examples of Regex:

Email Validation: /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,5})$/
Password Validation: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/g
Hex Code: /#?([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})/g
Removing HTML Tags: /(<script(\s|\S)*?<\/script>)|(<style(\s|\S)*?<\/style>)|((html comment removed: (\s|\S)*?))|(<\/?(\s|\S)*?>)/g

As you can see, Regex can get very, very complicated and adding comments is definitely necessary. The comment you write for the Regex should not be simply "email validation" but you should explain the structure of the Regex; Exactly what structure does it accept (like the comment I added above for our own example).

The third category of good comments are those that contain a special warning. This category of comments explains a specific part of the code that may cause incompatibility with some systems. Consider the following example:

// Only works in browser environment
localStorage.setItem('albro', 'test@test.com');

This comment says "only works in browser environment" and it's a very good comment because it gives us some very important information. localStorage only works in the browser, so if someone puts our code in their server (Node.js) regardless of this issue, their program will crash and the server will stop.

The fourth category of good comments are todo comments. Todo comments are comments that are used to specify future programs. Consider the following example:

// (c) @albro / Hive Blockchain
// Created in 2023



let sqlDriver: any;
let mongoDbDriver: any;



class DatabaseAdapter {
private dbEngine: any;



loadDatabaseDriver(engine: string) {
if (engine === 'sql') {
this.dbEngine = sqlDriver;
} else {
this.dbEngine = mongoDbDriver;
}
}



connect() {
this.dbEngine.tryConnect();
}



insertData(data: any) {
this.dbEngine.insert(data);
}



findOne(id: string) {
// Todo: Needs to be implemented
}
}

We have a function called findOne whose job is to find a specific row or document from the database, but writing this method completely takes some time and we don't have time to do it right now. If we leave it without writing anything, we may forget or consider the incomplete code inside a half-written function as complete code, so we use a comment to add that this part needs to be completed and is still incomplete. Is. Of course, there are various plugins for vscode and other editors that allow you to highlight Todo comments by accessing them in a special way.

The last category of good comments are those known as Document String. This issue depends on your programming language, but such comments are usually used to add documentation (descriptions and documentation of a code, module, package, etc.). For example, in Python, we have DocString. These comments are special descriptions that are recognized by IDEs and displayed to the user who uses your library. A simple example of Python language and how to use Docstrings is as follows:

def my_function():
'''Demonstrates triple double quotes
docstrings and does nothing really.'''



return None



print("Using __doc__:")
print(my_function.__doc__)



print("Using help:")
help(my_function)

The result of executing this code will be as follows:

Using __doc__:
Demonstrates triple double quotes
docstrings and does nothing really.
Using help:
Help on function my_function in module __main__:



my_function()
Demonstrates triple double quotes
docstrings and does nothing really.

If you are familiar with Python language, you know that this feature is very useful and almost all developers favor it.

What is Code Formatting?

Code formatting is a process in which we determine how to display and place the codes in different lines. Unlike comments, which mostly affect the readability of the code, code formatting will make our codes more readable and, in other words, convey the meaning of our codes to the reader. We can divide the subject of code formatting into the following two categories:

  • Vertical Formatting: Vertical formatting is related to the code structure in the vertical axis. For example, the number of empty lines between lines of code and the grouping of codes are included in this category of formatting.
  • Horizontal Formatting: Horizontal formatting is related to the code structure in one line (horizontal). For example, indentation and the length of each line are included in this category of formatting.

Code formatting rules in these two fields are themselves divided into two other fields: general formatting rules and programming language specific formatting rules.

Vertical Formatting

There is a general rule about vertical formatting; Your code should read vertically like a book and not fragmented. By fragmented, I mean that to understand one part of the code, we have to go to another part of the code and get stuck in a strange cycle. Just as when reading a book, you start from page one and reach the end of page one, your codes should be written in the same way as much as possible. what does it mean? Some codes are written in such a way that we have to read the end of the code first and then go to the middle of the code and then go to another file and then go back to the beginning of the code. If a book starts on page 5 and ends on page 1, what will happen to the reader? Therefore, your codes should have a vertical trend without "jumping". To achieve such a simple and uniform process as described, you must follow a few simple rules that I will explain in this section.

If your file contains several concepts, it is better to split it into several smaller files. what does it mean? If you have several classes in one file, each class is related to a specific part of the program, you should probably split that file into smaller files. For example, if my js file manages the database and then is also responsible for loading multiple pages, it's time to split the file. Even sometimes, each file has only one specific concept, but the file size becomes too large, in these cases, it is wise to split the file. Remember that there is no set rule for the number of lines allowed in a file, and it's up to you. A general rule of thumb for file splitting is that when working with classes, each class should be defined in a separate file.

The second rule is to separate concepts from each other using spacing. I mean the concepts of communication between code components. For example, the following code is a simple code to store data on disk:

const path = require('path');
const fs = require('fs');



class DiskStorage {
constructor(storageDirectory) {
this.storagePath = path.join(__dirname, storageDirectory);
this.setupStorageDirectory();
}



setupStorageDirectory() {
if (!fs.existsSync(this.storagePath)) {
this.createStorageDirectory();
} else {
console.log('Directory exists already.');
}
}



createStorageDirectory() {
fs.mkdir(this.storagePath, this.handleOperationCompletion);
}



insertFileWithData(fileName, data) {
if (!fs.existsSync(this.storagePath)) {
console.log("The storage directory hasn't been created yet.");
return;
}
const filePath = path.join(this.storagePath, fileName);
fs.writeFile(filePath, data, this.handleOperationCompletion);
}



handleOperationCompletion(error) {
if (error) {
this.handleFileSystemError(error);
} else {
console.log('Operation completed.');
}
}



handleFileSystemError(error) {
if (error) {
console.log('Something went wrong - the operation did not complete.');
console.log(error);
}
}
}



const logStorage = new DiskStorage('logs');
const userStorage = new DiskStorage('users');



setTimeout(function () {
logStorage.insertFileWithData('2023-07-1.txt', 'A first demo log entry.');
logStorage.insertFileWithData('2023-08-2.txt', 'A second demo log entry.');
userStorage.insertFileWithData('albro.txt', '@albro blog');
userStorage.insertFileWithData('peakd.txt', '@peakd blog');
}, 1500);

In the code above, it is easy to see that the dependent components of the code are together and separate from each other. For example, there are two import commands at the beginning of the file and then they are separated from the class definition with a blank space (Enter key). Also, the methods in this class are separated from each other because each method is a separate concept. If we want to write only the first few lines without spaces, the code becomes very annoying to read:

const path = require('path');
const fs = require('fs');
class DiskStorage {
constructor(storageDirectory) {
this.storagePath = path.join(__dirname, storageDirectory);
this.setupStorageDirectory();
}
setupStorageDirectory() {
if (!fs.existsSync(this.storagePath)) {
this.createStorageDirectory();
} else {
console.log('Directory exists already.');
}
}
createStorageDirectory() {
fs.mkdir(this.storagePath, this.handleOperationCompletion);
}

As you can see, reading this code is much more difficult than reading the first code and makes the reader dizzy. I want you to look at this code and extract more points from it.