A handy standard specification proposal for writing very useful comments directed at anyone reading software source code with aided understanding as the primary objective.
- Title: Reader-Tagged Comments Specification
- Author(s): Ifeora Okechukwu Patrick
- Team: self
- Reviewer(s): Eze Sunday Eze, Pascal Oraizu
- Created on: 21st May, 2025
- Last updated: 17th November, 2025
Copyright (c) Ifeora Okechukwu Patrick (2025). All Rights Reserved.
Software source code comments exist for every programming language. They are usually ignored by the compilers/interpreters of these languages. However, they can be very helpful to humans who read them provided the comments contain useful information and are not the size of a poem or bible chapter.
Most non-standard comments encountered in the wild hardly contain useful information and are placed in locations within a source file that encourage ignoring them.
This document exists to suggest a standard for creating and managing source code comments in a sane and meaningful manner that specifically aids any person reading any software source code (where the comments described herein are used) to quickly and accurately become versed in the what, why and how of the source code.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119.
For a very long time, software engineers and programmers alike have had a very messy and weird relationship with comments. Some believe that source code comments are a nuisance and should be avoided always. I say source code comments can be adorable again and more importantly useful.
It would be very remarkable if code could speak for itself 100% of the time and never need to answer certain questions after a human had read through it. However, that has not been my experience and certainly not the experience of many software engineers i have encountered. So, code needs to be augumented with additional information that helps answer questions that could potentially go unanswered.
By setting up a few guidelines, it is possible to create comments that other programmers or software engineers would want to read and it starts by tagging comments with a short one-word name (at the beginning on the comment) depicting the reason why that comment exists.
If there's no real reason for the comment to exists, then it SHOULD NOT have a tag. And if it SHOULD NOT have a tag, then it SHOULD NOT exist.
The only requirement (or assumption) is the existence and employment of any programming language that supports comments.
There SHALL be only 9 distinct comment tags which will be put forward as a standard in this document:
@SMELL@USAGE@HINT@NOTE@TODO@FIXME@SHIM@CHECK@INFO
Hence they SHALL be known as Reader-tags and the comments created with them SHALL be known as Reader-tagged Comments. Also, these tags are programming language agnostic.
Reader-tagged Comments can very well be single-line comments or multi-line comments.
We will go over each of them in due course.
Among software engineers, there exists an unwritten common non-standard ways for tagging comments such as:
/* FIXME: ... */
/* TODO: ... */
// Note that ..."""
FIXME: ....
TODO: ...
""" # TODO: ...
# FIXME: ...As well as well-documented, common standards such as:
/**
* This function does something...
*
* @param array $list
*
* @see https://example.com/my/docs
* @final since version 1.2.x.
*
* @return string|null
*/ /**
* Some description here.
*
* @see java.lang.Object
* @return This will return {@link #toString() string} response.
*/ """Return a foobang
Optional plotz says to frobnicate the bizbaz first.
""" /* eslint-disable */
const confirmCausePropertyIsMissing = () => {
var testError = new Error(
"test message",
{ cause: "[test cause]" }
);
return !('cause' in testError);
};
/* eslint-enable */All the well-documented standard comments exist for a specific purpose - either to help communicate with a linter (static analyser) or to help generate useful documentation for public APIs as well as protected/private API within source code files.
There are also non-standard comments in certain languages like Rust. For example, the SAFETY comment (which is a comment that highlights a code smell in making use of unsafe code in Rust.
The Rust code snippet below is taken as an excerpt from a portion of the Linux Kernel (written in Rust)
// SAFETY: A `NodeDeath` is never inserted into the death list
// of any node other than its owner, so it is either in this
// death list or in no death list.
unsafe { node_inner.death_list.remove(self) };Now, Reader-tagged comments are different. They exist primarily to aid readability of any codebase. It is RECOMMENDED (but not required) to place them inline between code sections that traverse multiple lexical scopes or on the line preceding the program statement they refer to in any lexical scope.
No standard yet exists for the purpose of aiding the understanding of source code being read by someone other than maybe the author. In fact, the author may need help understanding the code written by them later.
There's a saying that is quite popular among software engineers or programmers
Good code contains few comments. Great code has zero comments
I must say that for many years as a software engineering novice i believed this until i did some thinking and research on the matter (especially since i always felt the need time and time again to include why i was writing code a certain way). I found out that based on my experience thus far and my research, there was no truth to such a saying at all.
In other words, Great code can and does contain comments.
If you have been building/writing software code for a considerable amount of time like i have, i bet you have been in a situation where you came back to a codebase you authored 3-6 months ago and cannot pick up where you left off. The logic in some (or most) source files feel strange to you as if someone else wrote them (yet git-blame says clearly that you wrote it - 😁).
Presently, you proceed to spend an hour or more re-reading the entire codebase all over just to understand what it is doing and where you left it at.
What if you left little summaries and notes in specific place within the source file to remind you ?
However, these summaries SHOULD be short, to-the-point and brisk. Comments in general SHOULD NOT be more than one tiny paragraph contain either 3 simple sentences separated by punctuation or 1 compound sentence.
There are several reasons why humans (i.e. software engineers or programmers) will skip reading comments in some source code or through out a codebase.
They are as follows:
- It doesn't contain a summary of vital information necessary for code comprehension.
- It is very lengthy and/or far away from the code/logic block it refers to.
- It is explaining more of what the code is doing and less of why the code exists the way it does and/or how it operates.
- It contains information that is vastly unrelated to the piece of code closest to it.
- It is ambigous (i.e. It contains slangs or abbreviated words that conceal meaning - badly written).
Knowing these reasons can lead us to a list of requirements for well-formed reader-tagged comments.
- Comments SHOULD contain a summary of necessary information (where "necessary" is denoted by relevance to the contents of the piece of code).
- Comments SHOULD NOT be lengthy for any reason. Always prefer summaries.
- Comments SHOULD contain explainations of the why more than what relating to any piece of source code.
- Comments MUST NOT be ambigous in semantics andd expression.
- Comments MAY contain hyperlinks to further link to more detailed imformation (Again, always prefer summaries).
- Comments SHOULD NOT contain unrelated information whatsoever.
- Comments SHOULD be updated first before the source code they refer to is updated.
Finally, comments are NOT a must in a good number of cases. The semantics of source code can be kept clear, without comments in such cases:
- Turn comments explaining what each and every variable is supposed to communicate into the variable names.
- Turn comments explaining what methods or functions are doing into the metho and function names.
- Turn comments describing what blocks of code do for each source file into idiomatic expressions using syntax and source file name.
Comments SHOULD be useful all the time. Useful comments include:
- Overview of modules
- Interface/API ddescriptions
- Explanation why a certain approach was taken/used and why a more obvious one were found not to work out.
Also, sometimes for the first two, a test case with a working example is usually better than useful comments and stays up-to-date too.
A Python programming language example (👇🏾)
Now, we can all agree that this:
x = x + 1 # Compensate for border (...describing why)is better than this:
x = x + 1 # Increment x (...describing the obvious)A C programming language example (👇🏾)
In the same vein, this:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *my_strdup(const char *s) {
if (s == NULL) {
return NULL;
}
size_t len = strlen(s) + 1;
char *new_str = (char *)malloc(len);
if (new_str == NULL) {
return NULL;
}
memcpy(new_str, s, len);
return new_str;
}
int main() {
char *src = "This is a string.";
char *dest = my_strdup(src);
if (dest == NULL) {
perror("malloc failed");
return 1;
}
printf("Duplicated string: %s\n", dest);
/*
@NOTE:
`my_strdup(...)` function above allocates
memory on heap
*/
free(dest);
return 0;
}is better than this:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *my_strdup(const char *src) {
if (src == NULL) {
return NULL;
}
size_t len = strlen(src) + 1;
char *new_str = (char *)malloc(len);
if (new_str == NULL) {
return NULL;
}
memcpy(new_str, src, len);
return new_str;
}
int main() {
char *src = "This is a string.";
char *dest = my_strdup(src);
if (dest == NULL) {
perror("malloc failed");
return 1;
}
printf("Duplicated string: %s\n", dest);
free(dest); // Free the allocated memory (...describing what again)
return 0;
}A Rust programming language example (👇🏾)
Finally, this:
// @TODO [Create a Rust Macro for this]: Make sure this `vec!` has at least one element
let kafka_brokers = vec["kafka:9092", "kafka2:9092"];
if kafka_brokers.is_empty() {
return Err("At least one Kafka broker is required");
}is way better than this:
let kafka_brokers = vec["kafka:9092", "kafka2:9092"];
/* @HINT: Check if kafka brokers vector is empty (... useless comment stating the obvious) */
if kafka_brokers.is_empty() {
return Err("At least one Kafka broker is required");
}When writing Reader-tagged comments, it is important to put yourself in the shoes of the reader. How would you want to or prefer to be informed ? Sometimes, it is hard to fully see yourself as a future reader of the source. code unless you have been in the situation i described earlier in Section 9.
Yet, if there are many comments all saying one thing or another, without being prefixed with any Reader-tags, it might be hard for any reader to decide if the comment is worth reading or not.
This is why it is RECOMMENDED to include Reader-tags as prefixes to comments within any source file like so:
Example 1 (👇🏾)
/* @HINT:
A structured concurrency primitive similar
> to `sync.WaitGroup` in Golang for this
> experiemntal javascript test-runner
*/
/* @CHECK: https://go.dev/src/sync/waitgroup.go */
/* @NOTE:
`sync.addToWait(..)` This signals to
> the test-runner to wait until a
> test calls `sync.releaseFromWait(..)`.
`sync.addToWait(..)` is important
> especially when the test-runner
> is running multiple tests in
> parallel.
*/
/* @USAGE: `sync.addToWait(..)` MUST be the first statement in every test case callback like this one */
sync.addToWait('@kyc');Example 2 (👇🏾)
namespace Palie\Relations;
/*
$sql = DB::connection()->getQueryGrammar()->makeRawSql(
'SELECT * FROM users WHERE email = ?',
'foo@example.com',
);
*/
use Illuminate\Database\Eloquent\Relations\HasMany;
/* @CHECK: https://github.com/laravel/framework/issues/54867#issuecomment-2694103119 */
class BinaryHasMany extends HasMany {
public function addEagerConstraints(array $models) {
$keys = $this->getEagerModelKeys($models);
if ($this->parent->getKeyType() === 'binary') {
$keys = array_map(function($key) {
/* @NOTE:
each `$key` is already binary here from `->getRawOriginal(..)`
> called within `->getEagerModelKeys(..)` above
*/
return $key;
}, $keys);
}
$whereIn = $this->whereInMethod($this->parent, $this->localKey);
if (!empty($keys)) {
$this->whereInEager($whereIn, $this->foreignKey, $keys, $this->getRelationQuery());
}
}
protected function getEagerModelKeys(array $models) {
$keys = [];
foreach ($models as $model) {
if (!is_null($value = $model->getRawOriginal($this->localKey))) {
/* @HINT: Fetch raw binary value before casting */
$keys[] = $value;
}
}
return array_values(array_unique($keys));
}
}This is a code snippet (below) from an actual open-source projects' source file that benefits from the @HINT comment tag.
/**
* @param StorageQueryTask $queryTask
* @throws Exception
*
* @return mixed
*/
final public function handle(StorageQueryTask $queryTask) {
$result = null;
$processingError = null;
$noResult = true;
$hasError = false;
/* @INFO:
Using the template method pattern to ensure each handler
> doesn't forget to call the next handler in the chain.
*/
try {
/* @NOTE:
that the manager for the storage query task handlers already
> sets up each handler to have a reference to the next handler
> in the chain of handlers prior.
*/
try {
$result = $this->beginProcessing(
$this->migrateQueryTask($queryTask)
);
$noResult = false;
return $result;
} catch (Exception $error) {
/* @HINT:
If `->skipHandlerProcessing(..)` is called,
> then call the next handler in the chain.
*/
if ($error->getMessage() === $this->message
&& $this->nextHandler !== null) {
$result = $this->nextHandler->handle($queryTask);
$noResult = false;
return $result;
} else {
$hasError = true;
$processingError = $error;
throw $error;
}
} finally {
if (!$hasError) {
$this->finalizeProcessing(
$this->migrateQueryTask($queryTask),
$result
);
} else {
$this->finalizeProcessingWithError(
$queryTask,
$processingError
);
if ($noResult
&& $processingError->getMessage() === $this->message) {
/* @HINT:
If there's no result and we have an error, try to
> get a result from an alternate process
*/
$result = $this->alternateProcessing(
$this->migrateQueryTask($queryTask)
);
$noResult = false;
return $result;
}
}
}
} catch (Exception $error) {
/* @TODO [Debug Logs]: log error here in debug mode later on */
throw $error;
}
return $result;
}The @HINT tag is used for situations where we want to specify to the reader why code was written or the motivations for writing the code the way we have written it. It can also be that we want to offer the reader a hint for how to ingest/interprete the entirety of a code block (not just a line of code) so it makes sense quickly. The great thing about hints is that they can be used to leave messages to not just other readers but also for the erstwhile (i.e. former) author who is now a reader of his/her own code.
This is a code snippet (below) from an actual open-source project contribution that can benefit from the @NOTE comment tag.
/* @NOTE:
we don't try to include the "Local: " stuff
> unless we're pretty sure which URL it would be a good idea to try and visit.
> (even then, it's not 100% or anything. But at least with these checks, it's
> not wrong MOST of the time.)
*/
if (process.env.NODE_ENV !== 'production' &&
(!sails.config.ssl || _.isEqual(sails.config.ssl, {})) &&
!sails.config.http.serverOptions &&
!sails.config.explicitHost
) {
sails.log('Local : ' + chalk.underline('http://localhost:'+sails.config.port));
}The @NOTE tag is used for situations where we need to offer clarity on context. As we know context is important when conveying information about anything. When the context is missing, words (or syntax in this case) can be taken "out-of-context". So, providing that missing context for the reader at just the right time is vital.
This is a code snippet (below) from a test case an actual open-source projects' source file that benefits from the @SMELL comment tag.
test('should render `useBrowserScreenActivityStatusMonitor` hook and check proper setup and teardown', () => {
const { result, unmount } = renderHook(() =>
useBrowserScreenActivityStatusMonitor({
onPageNotActive: stubExtraCallback,
onPageNowActive: stubBasicCallback,
onStopped: stubObserverCallback,
})
)
/* @SMELL: Coupled to implementation; `setTimeout()` */
expect(setTimeout).toHaveBeenCalled()
/* @SMELL: Coupled to implementation; `setTimeout()` */
expect(setTimeout).toHaveBeenNthCalledWith(
2,
expect.any(Function),
(defaults.TimeoutDuration + durationPadding)
);
expect(stubObserverCallback).not.toHaveBeenCalled()
expect(stubBasicCallback).not.toHaveBeenCalled()
expect(stubExtraCallback).not.toHaveBeenCalled()
unmount();
expect(stubObserverCallback).toHaveBeenCalled();
})The @SMELL comment tag is used when there's a need to leave a message/reminder as the author of a piece of code. It serves to establish that there's potentially a code smell in the code that the reader (who could also be the author) is reading.
This is a code snippet (below) from a public projects' source file that can benefit from the @CHECK comment tag.
/**
* componentLoader:
*
*
* @param {AsyncFunction<[], { default: () => JSX.Element }>} lazyComponent
* @param {Number=} attemptsLeft
*
* @returns {Promise<*>}
*/
export function componentLoader<
M extends {
default: (injected?: {
queries: Record<string, UseQueryResult | null>;
}) => JSX.Element | null;
}
>(lazyComponent: () => Promise<M>, attemptsLeft = 3) {
return new Promise<M>((resolve, reject) => {
/* @CHECK: https://medium.com/@botfather/react-loading-chunk-failed-error-88d0bb75b406 */
lazyComponent()
.then(resolve)
.catch((error) => {
/* @HINT: let us retry after 500 milliseconds */
window.setTimeout(() => {
if (attemptsLeft === 1) {
reject(error);
return;
}
componentLoader(lazyComponent, attemptsLeft - 1).then(
resolve,
reject
);
}, 500);
});
});
}The @CHECK comment tag is used in situations where the reader might or could require additional information on a very niche or specific idea used in a piece of code that can be found in detail on the internet via a hyperlink.
This is a code snippet (below) from a code snippet that can benefit from the @USAGE comment tag.
"""
@USAGE: Fetch the HTML string via a HTTP GET request.
Parse the HTML string and print the tree nodes.
"""
response = requests.get(sys.argv[1])
body = response.text
rootNode = HTMLParser().parseHTMLString(body)
print_tree(rootNode)The @USAGE comment tag is used for occasions where we would like the reader to get a sense for how a public interface to some self-containedd logic is to be used in a real sense or a serious project.
This is a code snippet below from a simple bash script used to generate passwords for demo brute force attacks that makes use of the @INFO comment tag.
# @INFO: A very minimal yet specific `readarray`-like implementation using `read`.
# @INFO: Older versions of bash (v3 and below) do not support `readarray` or `mapfile` functions.
if ! type -t readintoarray >/dev/null; then
# @NOTE: Does NOT work with lines that contain double-quotes due to the use of `eval()` here.
# @NOTE: Ensures the use of glob patterns (e.g. *) without issues like reading directory file names.
readintoarray() {
local cmd opt t v=MAPFILE
while [ -n "$1" ]; do
case "$1" in
-h|--help) echo "minimal substitute readarray for older bash"; exit; ;;
-r) shift; opt="$opt -r"; ;;
-t) shift; t=1; ;;
-u)
shift;
if [ -n "$1" ]; then
opt="$opt -u $1";
shift
fi
;;
*)
if [[ "$1" =~ ^[A-Za-z_]+$ ]]; then
v="$1"
shift
else
echo -en "${C_BOLD}${C_RED}Error: ${C_RESET}Unknown option: '$1'\n" 1>&2
exit
fi
;;
esac
done
cmd="read $opt"
set -o noglob
eval "$v=()"
while IFS= eval "$cmd line"; do
line=$(echo "$line" | sed -e "s#\([\"\`\$]\)#\\\\\1#g" )
eval "${v}+=(\"$line\")"
done
eval "${v}+=(\"$line\")"
}
fiThe @INFO comment tag is used for occasions where it would be immesely beneficial to provide historical or circumstantial information (e.g. a deprecation notice or a choice of implementation) to help the reader understand why a piece of code exists at all or is written the way it is.
This is a code snippet below from a code expert for a TypeScript project and makes use of the @SHIM comment tag.
import { install } from "resize-observer"; /* @SHIM: #Browser API: ResizeObserver */
/* @NOTE: `navigator.clipboard` is undefined in Safari 12.1.x as well as the earlier versions
of other browsers like Chrome (Webkit), Firefox, Edge (EdgeHTML) */
/* @CHECK: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#clipboard_availability */
import "clipboard-polyfill/overwrite-globals"; /* @SHIM: #Browser API: Clipboard */
if (!window.ResizeObserver) {
/* @NOTE: This polyfill installation is for the Safari browser only */
/* @CHECK: https://stackoverflow.com/a/65832955 */
install();
}The @SHIM comment tag is used for situations where there's a clear need for a workaround, polyfill or hack because there aren't any viable alternatives to get the code to do what. it's supposed to do.
This is a code snippet below from a code snippet and makes use of the @TODO comment tag.
import { useEffect, useCallback, useRef, useState, useMemo } from "react";
import { Upload } from "tus-js-client";
enum UploadStatus {
IDLE = "idle",
UPLOADING = "uploading",
PAUSED = "paused",
RESUMED = "resumed",
COMPLETED = "completed",
ERROR = "error",
}
const useResumableFileUploader = ({
uploadEndpointUrl,
onBeforeUpload,
onProgressUpdate,
onErrorRaised,
onSuccessReached
}: {
uploadEndpointUrl: string,
onBeforeUpload<F extends File>(variable: F): void,
onProgressUpdate: (progress: number) => void,
onErrorRaised: (error: { message: string }) => void,
onSuccessReached<D = unknown>(data: D): void
}) => {
const uploadRef = useRef<Upload | null>(null);
const [uploadStatus, setUploadStatus] = useState<UploadStatus>(UploadStatus.IDLE);
const statuses = useMemo(() => {
const isUploadIdle = uploadStatus === UploadStatus.IDLE;
const canResetUpload = uploadStatus === UploadStatus.COMPLETED || uploadStatus === UploadStatus.ERROR || uploadStatus === UploadStatus.PAUSED;
const canResumeUpload = uploadStatus === UploadStatus.PAUSED;
const isUploadActive = uploadStatus === UploadStatus.UPLOADING || uploadStatus === UploadStatus.RESUMED;
const canAbortUpload = uploadStatus !== UploadStatus.ERROR && uploadStatus !== UploadStatus.PAUSED && uploadStatus !== UploadStatus.COMPLETED && uploadStatus !== UploadStatus.IDLE;
const canStartUpload = uploadStatus !== UploadStatus.UPLOADING && uploadStatus !== UploadStatus.PAUSED && uploadStatus !== UploadStatus.RESUMED;
return {
isUploadIdle,
canAbortUpload,
canPauseUpload: canAbortUpload,
isUploadActive,
canResumeUpload,
canResetUpload,
canStartUpload
} as const;
},[uploadStatus]);
const resetUploadStatus = useCallback(() => {
if (uploadStatus === UploadStatus.COMPLETED
|| uploadStatus === UploadStatus.ERROR
|| uploadStatus === UploadStatus.PAUSED) {
if (uploadStatus !== UploadStatus.PAUSED
&& Boolean(uploadRef.current)) {
uploadRef.current = null;
}
setUploadStatus(UploadStatus.IDLE);
}
}, [uploadStatus]);
useEffect(() => {
const onProgressChange = (e: CustomEventInit<{ progress: number }>) => {
onProgressUpdate(e.detail.progress);
};
const onErrorFound = (e: CustomEventInit<{ error: { message: string } }>) => {
onErrorRaised(e.detail.error);
};
const onSuccess = (e: CustomEventInit<unknown>) => {
onSuccessReached(e.detail);
};
/* @TODO:
Include a randomly-generated string into the custom event names to avoid
once instance of the ReactJS hook in one component clashing with another
instance in another component on the same page.
*/
window.addEventListener("_progress.change", onProgressChange, false);
window.addEventListener("_error.found", onErrorFound, false);
window.addEventListener("_success", onSuccess, false);
return () => {
window.removeEventListener("_progress.change", onProgressChange, false);
window.removeEventListener("_error.found", onErrorFound, false);
window.removeEventListener("_success", onSuccess, false);
};
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [uploadEndpointUrl]);
....The @TODO comment tag is used for situations where there's work to be one still yet not enough time to do it right away
The @FIXME comment tag is used for occasions where there's a need to fix code that is clearly broken or can be imporved.
One should be careful about putting comments on code that they know or understand little about. Also, A.I. can be used in code editors to enhance the experience programmer and/or software engineers have with comments.
The authors of this document would like to thank the following people for their contributions and support to make this a better specification: Eze Sunday Eze, Pascal Oraizu
Nil