I’ve got some comment for you
Łukasz Pawłowski
Posted on April 3, 2019
Code’s comments are always overlooked subject. Many times developers add them chaotically, ignore or misuse them. Bad comments can be harmful, while good one can be a very useful tool. In this article I want to summarize most important things, which I discussed with other developers in our company. I also want to present my personal approach for commenting code.
TLDR
You should use comments in few situations:
- use annotations and dock blocks to create documentation of your API
- explain unintuitive code solutions — say why, not how
- explain complex algorithms — mostly say why, not how. If situation required, add links for materials which explain algorithm in depth
- describe configuration options
Sometimes comments will be better and more accurate documentation, than any documentation created separately. Good comments will help you get back to your old code and can be useful for other people to understand your code.
You can use comment as tool for your development:
- add to-do list
- make a comment about technical debt
- add annotations inside functions, to get better autocompletion and code’s support in your IDE
You can even push them into code repository if you have WIP commit/branch. Remember to remove such comments before sending Merge Request or before adding your code to master branch.
Do not add too many comments. This can be indicator of problems or bad practices. Many times you can find some better equivalent. Here are a few examples of situation, which you should avoid:
- do not use comments for discussion — use slack or other tools instead
- do not use comments for Code Review — there are better solutions e.g. discussions on Merge Request in gitlab, Jetbrains Upsource etc.
- do not describe all variables and each step of your code — name your variables/functions/classes correctly and use type hints
- do not use comments to divide your code — divide code into multiple functions/classes/files
- do not describe each use case and each step of function — if you have multiple use case write multiple tests which will be better documentation than big wall of text in comment
A completely different topic is how to use comments. Comments, like code, should be consistent across whole project. Discuss them with your team and create guidelines. Decide when you use comments and how to format them. Remember to create comments during coding not after. When you use any bundlers or preprocessors, add steps with minimalization and comment removement e.g. when your parse sass to css, or during bundling js with webpack.
Comments as documentation
As developer you probably heard that “Your code should be self explanatory”. It is true, but it does not mean that you should not use comments. They can significantly increase the intelligibility of the code. You should use them as documentation for our code. Let me explain how I use it.
In most IDE when you generate file from templates, IDE will automatically add comments. That comments describe who, when and how generated file. While I prefer to change templates and remove comments, it’s good idea to add some general description of what file contains. Such high view of your files, can significantly speed up code analysis. Description should shortly explain what file contain and what is purpose of included elements. In most cases it does not matter who created file and when, because multiple users will change it later and original code can be long time forgotten. If you use any control version system, you will have this information there. On the other hand, when you build some library and create distribution file, it is still a good idea to include license at the beginning of file with information about author.
In our company we expect to add comment block to most of classes and functions. Such comments take form of annotations and short description. While many times it is enough to read code, in most cases developer does not have time to get through dozens of code’s lines to find out what specific argument in function is used for. It is good to have explanation of each argument in function: what is it and what are accepted values. We also add information about returning value and short description what functions do, what are side effects of function, what error it can throw etc. We can argue that part of this is covered by code itself e.g. by type hints, but sometimes you accept multiple type of value, return multiple type of values (which should not happen), or you throw many different exceptions in many places in code and comments help to organized this information. Here is simple example in php:
/**
* function validate if user has specific premissions
* @param User $user user for whom we check permissions
* @param string[]|Permission[] $perms names of permissions or permissions objects to validate
* @param bool $atLeastOne if true - user can have at least one permission, if false - user must have all permissions
* @return bool true if has permissions
* @throws ObjectDoesNotExistException if permission does not exist in the system
*/
function checkParmission(User $user, array $perms, bool $atLeastOne = true): bool { ... }
For classes, we expect at least short description and list of all public methods and public fields. Such short description of class will help you use new elements and will be good documentation of your API. There are also many situations when you have hidden methods or fields. You can use magick methods which hide it before user, or you create model to keep data which are hidrated automatically in it from DB. Good example are Laravel Eloquent Models. Here is simple example of Laravel Eloquent Model:
* Class which represent user model
*
* @property int $id user id - autoincremental
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $email
* @property Role $role
* @property int $role_id
* @property int $status status of user: 0 - deleted; 1 - active; 2 - not verified
*
*/
class User extends Illuminate\Database\Eloquent\Model {...}
Many times you will need to build some documentation of your code. Writing separate documents and keeping it up to date is huge time consuming task. Properly commented code will be much better in this case, because it will be updated automatically during refactoring, fixing bugs and building new things. Also it will be easier to use — you can immediately check what is what and how to use it, without finding correct spot in docs. If you need to create user readable docs, you can use tools like PHPDocumentor, JSDocs, DocFX or JavaDoc. They will create web page describing your API, based on your comments.
In context of documentation, it is also very important to present your configuration properly, that users/developers understand all available configuration options. It is always great idea to add some good README, but there is no better solution, than commenting configuration in configuration example. You can add all available options with default values and short descriptions. Comment out all options which are not required, but do not remove them from example file. I believe this is only place where code in comment make sense. Here is part of defualt jest configuration file as an example:
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\Łukasz\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
...
}
Comments as explanation
We discussed documentation with annotations, but what’s with comments which explain how something work, or what something does? First of all, do not describe what next lines of code will do (this make sense only in doc block at the beginning of function/class). Instead, describe why you use specific values, algorithm or some specific solution. This can save you insane amount of time, when you need to change something or you are debugging code. Sometimes you use specific solution, which is not intuitive or seems to be little odd, but from some specific reason it must stay that way e.g. you write some js for app which support old browsers and you need to add little hack to make it work with old IE. If you do not comment it properly, you would lose time to understand or remind yoursef, why specific part was done that way. Sometimes, to avoid potential errors, you will decide to not change specific part of code. In worst-case scenario, someone will not see sense of specific solution and will change it later or remove it, what will breakdown the app.
The situation is a little bit different, when you are using complex algorithm in your code. It would be helpful to describe algorithm and add some links for future readings and deeper understanding of a problem.
Such approach is useful mostly for code maintainers and ourselves. For sure, you know what you are doing now, but if you go back to code after few months, would you remember it? You go this, but will others know why specific value was used, what use case you cover?
At the same time, do not add too much descriptions and explanations. Comments should shortly explain why something is done, what class or function can be used for, but does not need to explain details of implementation. You should not describe each line of your code or even each block of your code. There are some exceptions. If you create complex library reused by thousands, it would be nice to add little more comments, to help user understand what is going on. If you are working on specific app, you do not need it, explain only specific situation and add documentation of the code.
Comments as developer’s tool
You already know that comments are useful for documentation purposes and help to explain complex situation to avoid adding new bugs. You can also think about it as your tool, which support you in building app during development. There are three main areas where you can use comments.
First, you can use comments in developer’s tools. Many IDE support code hinting. Most of hints and autocompletes are based on annotations mentioned above. Additionally many linters, code sniffers, test tools use comment to change default behavior. E.g. you can select specific part of code which should not be tested or linted, you can also change rules for specific part of code. Here is example of ESLint exclusion and rule changes:
/* eslint-disable */
thisIsNotCheckedByLint();
/* eslint-enable */
/* eslint eqeqeq: 2 */
$thisLineReturnESLintError == true;
Second useful tip is to use comments to plan your code. Many times when I code something, I start from building skeleton of specific thing. It includes folders and files with interfaces, abstract classes, classes etc. Methods and fields are declared in each class. Type hint and annotations comments are added, to declare what each method should get, return and probably throw. Inside each class, I put comments for each step, which will be written in code. Such skeleton present flow of data and some logic with comments inside code. Later I exchange comments with correct code. Here is simple example:
class TimeReport {
constructor(projectId) {
this.projectId = projectId;
}
generateReport() {
this.initData();
this.analizeData();
this.getFile();
}
initData() {
//find project in DB if notexist throw ObjectNotExistsError
//find all stages and corresponding tasks for project
//get schedule for project
}
analizeData() {
// foreach task calculate spent time and compare to estimation
// foreach stage sort tasks by status, count them
// foreach stage check timeline and verify if stage is closed
// calculate when stage ended (if ended) or can end based on task's statuses and estimations
// sum up how many hours we are after/before schedule
}
getFile() {
//get file template and propagate data
//create PDFFile
//encode file to base64
//return base64
}
}
Third situation occur when I work on code and I see the need to change or add something later. E.g. some functionality is not done yet, but I know it will be added later. Comments not only indicate which work must be done, but also adds information about technical debt. In this situation I use tag TODO in comment. It is also good idea to use them to indicate your state of mind when you take break during development e.g. when you leaving office or you going on lunch break. Such comments can help you to get back into code after break.
There is one very important thing about using comments as a tool — use them locally. It is fine if you push it to git for WIP branch, but remove them from final version of your code by resolving issues. Only thing which can stay are rare TODOs. Your code should not include any WIP comments.
Comments as code smell and bad practice
Now you know what you can get from using comments and when you should use them. When do not use comments? There are a lot of situations which you will find during code reviews or checking someone’s code (or even your own). Here are few examples where you can use better solutions than comments:
- Do not use comments for discussions. There are much better places to discuss something — arrange meeting, talk during Code Review, use Slack etc. If you need to discuss some part of code, there are really good tools e.g. git lab has nice CR tools for Merge Requests.
- Comments are not versioning system! Many times I stumbled upon old code, which was left as a comment by developer. Use versioning system and push multiple commits. Committed code will not be lost.
- Do not divide code into sections using comments. If you need such things, you have bad code. If it is config/translation file, divid it into smaller files. If it is one big class or function, also split it into smaller functions/classes.
- Do not repeat yourself. This make no sense. Here is example of what I’m talking about:
//Here I set avatar for user
$user->avatar = $avatar;
- Do not explain each variable. If you need to do this, change its name. If you need to explain specific lines, what they do, refactor code. Correct naming and better code will decrease number of comments. Here is simple example of code before refactoring:
const articles = (new Date(user.membershipEndDate) > new Date() && user.membershipLevel >= User.GOLD_LEVEL) ?
await db.execute('SELECT * FROM articles WHERE published = 1') //IF USER HAS ACTIVE GOLD MEMBERSHIP RETURN ALL ARTICLES
: await db.execute('SELECT * FROM articles WHERE published = 1 AND public = 1') //ELSE RETURN ONLY PUBLIC ARTICLES
And after
class User {
...
get isActiveGoldMember() {
const now = new Date();
const membershipEnd = new Date(this.membershipEndDate);
const isGoldMember = this.membershipLevel >= User.GOLD_LEVEL;
return membershipEnd > now && isGoldMember;
}
}
--------------------------------------------------------
let query = 'SELECT * FROM articles WHERE published = 1';
if (!user.isActiveGoldMember) {
query += ' AND public = 1';
}
const articles = await db.execute(query);
- Use type hint for your methods/functions and add annotations comments, this will save you lot of inline comments. If you need inline comment with annotations, that mean you did something wrong. Here is example of this what I am talking about:
/** @var User $user **/
$user = app->auth()->user();
/** @var Company $sompany **/
$company = $user->getCompany();
- Sometimes you need to describe what code do, when there are multiple dependencies or complex logic. It is better to cut loose dependencies and think if your code does not need refactoring. If you need too many explanations for it, you should rewrite part of the code.
- Use comments to describe your API, but do not describe all possible use cases. It is much better to provide detailed use cases within tests, because it will explain your code much better than big text wall (with abstract descriptions).
- Clean after yourself, do not leave development comments, which supposed to help you during development. If you have some comments left, most likely you did not finished task or you created technical debt.
- Put all imports on top of your file, use namespaces etc. This also can save some comments.
Comments as guideline’s subject
Next important thing is how to use comments — they should be understandable and consistent. User should know what comment is used for, only by looking at it. First of all, you should discuss commenting strategy in specific project. Decide what kind of comments you use and when. Define how to format each comment type. Keep it consistent through entire project. Here are few things that can be useful when you first approach problem:
- In most cases inline comments are code smell.
- Use block comments. They should be at the beginning of file, before classes and functions as documentation. One-line comments can be used for short tags like TODO, or simple descriptions inside functions.
- Define order of annotations to keep it consistent. For classes I start from description, then methods and fields are presented. For functions/methods I also start from description, then list arguments, returns and at the end all errors/exceptions thrown by function.
- Remember to use correct intents. Comment should be intended the same way as code which it comments. E.g.
/**
* Here is comment for Class
*/
class MyClass
{
/**
* Here is comment for my method
*/
public function myMethod() {}
}
- Put comment before commented code in separate line. Do not add empty line between comment and comented code. Put it before comment. E.g.:
//this is good place to comment why I use console.log()
console.log(serviceResponse)
console.log(oops)
//this is bad place to comment console.log(oops)
console.log(ouch)
- When you use doc block start with /**. In each new line add * at the beginning of comments line. E.g.:
/**
* This is
* multiline
* doc block
*
* @param string $text
* @return bool
*/
- Make your comment professional — no stupid jokes, no controversial words etc. Make sure you use correct spelling. Use the same language in whole comments in code — I use English.
- Do not nests comments e.g.:
/**
* So you should not comment this comment
* // but you do
* /* And it is wrong */
*/
At the end it is worth mentioned two additional tips. First of all try to create comments while you code, not after you finish. This way they will be most accurate and fresh. Secondary — when you use preprocessors, compilers or bundlers, check if there is some option to minimize code and remove comments before creating distribution file.
Summary
It is worth to remember, that comments are very useful when you work on big code base, which you and other developers maintain. Use them as documentation for your code. You can even build some html docs from comments. Use them as tool during development. Remember at the same time, that code is still more important than comments. It is tool, not target.
When you are using too many comments — readability goes down, file size grows etc. They supposed to help you, not slows you down. You should not have too many comments which explain step by step how something work, you should not have explanation for each function call, variable etc. If there are too many of them, it means that there is something wrong with your code.
I hope this reading was useful for you and help you rethink comments in your code.
Posted on April 3, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024
November 25, 2024