RizTech Academy logo
RizTech Academy
Best Practices: Code Others Can ReadLesson 3 of 525 min

Comments that say why, and Javadoc that earns its place

Most comments are a smell. A few are the most valuable lines in a file. Knowing which you are writing is the whole skill.

The rule

Code says what. Comments say why.

If a comment restates the code, it is noise that will go stale and start lying. If it explains a decision the code cannot express, it is the only place that information exists anywhere.

// increment i
i++;

// add the delivery to the list
deliveries.add(delivery);

/** Gets the customer. */
public String getCustomer() { return customer; }

Three comments, zero information. Worse than blank lines, because a reader spends attention on them.

// CopyOnWriteArrayList, not ArrayList: a listener that unsubscribes while we
// are iterating would otherwise throw ConcurrentModificationException — and
// that is a real thing listeners do.
private final List<DeliveryListener> listeners = new CopyOnWriteArrayList<>();

That one is worth more than the line it describes. Nothing in new CopyOnWriteArrayList<>() says why it is not an ArrayList, and without the comment the next person "simplifies" it and the bug returns six months later.

The comments worth writing

Why this and not the obvious alternative. The example above. Anywhere the code looks odd and is deliberate.

// Six digits and never a leading zero: no Indian pincode starts with 0.
// "\\d{6}" looks right and quietly accepts 012345.
if (!pincode.matches("[1-9]\\d{5}")) {

A warning about consequences.

// After the commit, never inside it. A transaction can be rolled back; an SMS
// cannot be unsent.
await this.notifications.orderStatusChanged(...);

Something non-obvious about the domain. The reader may know Java perfectly and not know the business.

// Cash on delivery is a real order the moment it is made — nothing else has to
// happen before the shop starts packing.

A pointer to the decision record.

// See docs/decisions/0013 — stock is claimed with a conditional UPDATE.

A deliberate limitation, so the next person does not think it is a bug.

// Only the last attempt is kept. A full history is the next thing to add if
// support ever needs it, and nobody has asked.

The comments to delete

Restatements. Anything that would change with a rename.

Commented-out code. Git remembers. A block of dead code sitting in a file raises a question — is this coming back? does it still work? — and answers nothing. Delete it.

Change logs in the file.

// Modified by Rahul 12/03 - added pincode
// Modified by Priya 18/03 - fixed bug

git log and git blame do this properly, and these always drift out of date.

Divider art.

//======================================
// GETTERS AND SETTERS
//======================================

If a class needs signposts to navigate, it is too big. Fix that instead.

TODO with no name and no date. A TODO from 2019 with no author is not a task, it is litter. Either do it, or write a ticket and reference it.

Javadoc

Javadoc is a different job from an inline comment: it documents a contract for somebody who will never read the body.

Write it for anything public that another class calls. Do not write it for private methods where the name is enough.

/**
 * The bill for one customer in one month.
 *
 * @param customer the subscriber's name, exactly as recorded on deliveries
 * @param tiffins  how many tiffins were delivered; must be positive
 * @return the amount in paise, never negative
 * @throws IllegalArgumentException if no subscriber is registered with that name
 */
public long billPaise(String customer, int tiffins) {

What makes that useful is everything the signature cannot say: that the name must match exactly, that tiffins must be positive, that the unit is paise, that an unregistered name throws rather than returning zero.

Compare:

/**
 * Bills the paise.
 * @param customer the customer
 * @param tiffins the tiffins
 * @return the paise
 */

That is the signature retyped. It passes a "has Javadoc" check and helps nobody — and this is exactly what happens when a team mandates Javadoc coverage as a number.

The tags worth using

Tag For
@param what the argument means, and what is not allowed
@return what comes back, including "never null" or "may be empty"
@throws each exception and the condition causing it
@see a genuinely related type
@deprecated and always say what to use instead
{@code ...} inline code, so generics do not break the HTML

{@code List<String>} matters more than it looks: written bare, the <String> is parsed as an HTML tag and vanishes from the rendered documentation.

What "never null" buys

The single most useful sentence in a Javadoc comment is whether a return value can be null. It is the question every caller has, and the alternative to answering it is every caller writing a defensive null check they may not need — which is the next lesson.

Self-documenting code is not an excuse

"Good code needs no comments" is half true. Good code removes the need for comments that explain what. It does nothing about why.

No amount of renaming communicates "we tried the obvious thing and it deadlocked under load". That fact exists nowhere except in a comment, in somebody's memory, or in an incident report nobody will find.

The test: if the next person would be tempted to simplify this and reintroduce a bug, write the comment.

Check your work

The rule: code says what, comments say why.

Why a restating comment is worse than none: it costs attention and goes stale, and a stale comment is a lie.

The kinds worth writing: why not the obvious alternative, a consequence warning, domain knowledge, a pointer to a decision record, a deliberate limitation.

Why commented-out code goes: git remembers, and the block only raises questions.

Why change-log comments go: git blame does it properly and never drifts.

What divider comments mean: the class is too big.

What Javadoc is for: a contract for somebody who will never read the body.

What makes Javadoc useful: everything the signature cannot say — units, constraints, "never null", what throws.

Why {@code} matters: generics in bare text are eaten as HTML.

The limit of self-documenting code: renaming cannot express "the obvious version deadlocked".

Practice

  1. Find a comment in your capstone restating its code. Delete it and check nothing was lost.
  2. Find a piece of code that looks odd and is deliberate. Write the why-comment.
  3. Search for commented-out code in any project you have. Delete it, and note the reluctance.
  4. Write Javadoc for one public method including a constraint the signature cannot express.
  5. Write deliberately useless Javadoc that retypes the signature. Compare.
  6. Put List<String> in Javadoc without {@code}, generate the docs, and look at the output.
  7. Find a public method returning something that might be null. Document it.
  8. Find a TODO older than a year in any codebase. Decide its fate.
  9. Take the CopyOnWriteArrayList comment from module 12, remove it, and imagine a new developer "simplifying" the line.
  10. Write the comment you wish had been in the last confusing code you read.

Next: nulls, validation, and failing fast.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship