Effective Comments and JavaDoc

Learn what makes for useful and effective comments, and some scenarios where we shouldn't use comments at all
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a maid character
Ryan McCombe
Ryan McCombe
Posted

We have previously looked at how we can add comments to our code using // and /* */.

Whilst knowing how to comment is useful, there is a more nuanced topic, around knowing when to comment.

Commenting for Documentation using JavaDoc

When other developers (or our future selves) want to get an idea what the classes can do, we're generally going to explore in one of two ways:

  1. Check the header file
  2. Use intellisense / code completion

We can infer quite a lot in this way. The header files are going to list all the functions and their method signatures.

Intellisense is going to find our public variables and functions, and let us know what arguments they expect.

Screenshot showing an intellisense tooltip

This is useful, but semetimes won't have all the information we might need. Lets look at this function, for example:

bool TakeDamage(float Percent);

What is the bool that this function returns? And what is the Percent parameter? Is it the percentage of the character's maximum health? Or current health? Or something else?

We can improve intellisense and our documentation further by adding comments to our header file. By adding a simple comment above the method signature, our code can be described in plain english.

 /* Take a percentage of the character's maximum
  * health as damage.  Returns true if the damage
  * was Lethal
 */
 bool TakeDamage(float Percent){};

Most IDEs will also show this comment when we use our classes, or hover over calls to our function:

Intellisense showing a comment

Comments can be even more powerful when we follow a specific pattern. If we format our comments in a structured style, other systems will be able to read and understand our comments at a programmatic level.

Take this style of commenting, for example:

/**
 * Take damage based on the character's maximum health.
 * @param Percent - The percentage of the character's maximum health to inflict as damage
 * @return Whether or not the damage was lethal
 */
bool TakeDamage(float Percent);

If we now view our tooltip in our editor, it is likely to have been formatted in a more structured way.

Intellisense showing a js-doc formatted comment

This style of comment is called JavaDoc. It features many more properties than those demonstrated here. JavaDoc comments can include things like like:

  • links to websites
  • references to other blocks of code
  • flags to inform users that the function is deprecated and they should probably use something else

And much more.

When we create comments in a specific manner, it can be read and understood not just by humans, but also by other tools. As we've seen, this includes our IDE, but there are many more use cases.

For example, Doxygen can use comments written in this style to automatically create documentation websites for our projects.

Sometimes we want to write comments above lines of code, but to not have those comments be interpreted as anything more than a simple comment.

Typically, a comment that uses three slashes - /// - will not appear in our editor tooltips.

/// This comment will not appear in tooltips
int Number { 4 };

Commenting for Clarity

Another situation where we should consider adding comments is if our implementation is complex, or "unusual". If we think another developer will struggle to understand why a section of code was set up in a specif way, it can be helpful to add comments to it:

void TrackEvent(Event& Event) {
  // This event type crashes our analytics service
  // Temporarily ignoring it while we investigate
  if (Event.Type == EventTypes::StartLevel) {
    return;
  }

  Send(Event);
}

Another good place to add comments is to code that is simply too complex for most people to understand. An explanatory comment, often with a link to provide more information, can be very helpful.

// This is an implementation of the Fast Inverse Square Root algorithm
// See https://en.wikipedia.org/wiki/Fast_inverse_square_root
float InvSqrt(float number) {
	const float y {
    std::bit_cast<float>(
      0x5f3759df - (std::bit_cast<std::uint32_t>(number) >> 1)
    )
  };
	return y * (1.5f - (number * 0.5f * y * y));
}

Self-Documenting Code: Avoiding the need for Comments

Whilst comments are often useful, they can often be misused as a way to mitigate messy code.

Ideally, our code should require as few comments as possible. We should strive to make our code readable, rather than having messy code and explanatory comments.

If code is complex or messy, our first instinct should be to refactor it rather than explain it with comments.

This goal is sometimes referred to as creating self documenting code.

Self documenting code has at least two advantages over comments:

  • Comments can fall out of date. Often people will update code and forget to update the associated comments.
  • The process of refactoring code to be "self documenting" almost always improves the code to make it more maintainable.

The rest of this section outlines the 3 most common ways we can replace comments with simply better code.

Avoiding Comments by Renaming or Adding Variables

Often, a comment that describes what a variable does can be replaced by simply improving the variable name. Before:

// The aggression level
int Level { 0 };

After:

int AggressionLevel { 0 };

Additionally, we often have literals in our code that warrant an explanatory comment. These literals are sometimes called "magic strings" or "magic numbers" - seemingly arbitrary values that have some special properties.

// Unicode character for a smiling emoji
Chat.Send("U+1F600");

// This URL returns our special offers
HTTP::Get("https://192.63.27.92");

We can make that relationship concrete and self-documenting by just creating variables.

string SmilingEmoji { "U+1F600" };
Chat.Send(SmilingEmoji);

string SpecialOffers { "https://192.63.27.92" }
HTTP::Get(SpecialOffers);

Avoiding Comments by Introducing Meaningful Types or Aliases

Another common source of comments is documenting properties associated with types. We might have a variable, or a set of variables that have some semantic meaning that is not immediately clear.

Instead of needing comments to document that, we can often use new types. Before:

// x, y and z positions in the world
float x { 0 };
float y { 0 };
float z { 0 };

After:

struct Position { float x; float y; float z; }

Position WorldPosition { 0, 0, 0 };

Enum types are often useful for this purpose, too. Before:

// 0 is friendly, 1 is neutral, 2 is hostile
int AggressionLevel { 0 };

After:

enum class Aggression { Friendly, Neutral, Hostile };
auto AggressionLevel { Aggression::Friendly };

Where an existing type does not exactly match our requirements, we may be tempted to reuse it with some comments:

// Returns ability damage as a std::pair
// first is damage, second is crit chance
std::pair<int, float> GetDamageValues();

Instead, we should consider just introduce a new, self-documenting type:

struct AbilityDamage { int Damage, float CritChance };
AbilityDamage GetDamageValues();

Even if a more generic type does match our requirements, we sometimes feel a comment is warranted to specifically explain what that type contains:

// This vector only contains the abilities that have already been learned
std::vector<Ability> GetAbilities();

Instead, we can just do this with a type alias:

using LearnedAbilities = std::vector<Ability>;
LearnedAbilities GetAbilities();

Avoiding Comments by Decomposing Problems

When we create a complex function, composed of multiple parts, our first instinct is often to describe what is going on in that function by introducing a lot of comments.

That might look something like this:

bool Attack(Character* Target) {
  // Make sure we have a target, and it is valid to attack it
  if (!Target) return false;
  if (Target.Hostility == Aggression::Friendly) return false;

  // Target is attackable, but we need to make sure it is in range
  float [x1, y1, z1] = MyPosition;
  float [x2, y2, z2] = Target->Position;
  float x = x1 - x2;
  float y = y1 - y2;
  float z = z1 - z2;

  // The square root of this values will be the distance
  float Distance { (x*x) + (y*y) + (z*z) };

  if (sqrt(Distance) <= 10) {
    // Attack was successful
    Target->TakeDamage();
    return true;
  }
  // Attack was unsuccessful
  return false;
}

Instead, we should first check if the complexity can be reduced, typically by decomposing it into separate functions.

Breaking a big problem into smaller functions makes our code simpler, and gives us the opportunity to replace our comments with descriptive function names instead.

As an additional benefit, quite often these smaller, more generic functions can be useful in multiple places. This allows us to reuse them to make future work, and reducing the amount of code we have to write and maintain.

For the above function, decomposing it into smaller functions might look something like this:

bool Attack(Character* Target) {
  if (!isValidTarget(Target)) return false;

  Target->TakeDamage();
  return true;
}

private:
bool isValidTarget(const Character* Target) {
  if (!Target) return false;
  if (Target.Hostility == Aggression::Friendly)
    return false;
  if (CalculateDistance(Target->Position) > 10)
    return false;
  return true;
}

float CalculateDistance(Position TargetPosition) {
  float [x1, y1, z1] = MyPosition;
  float [x2, y2, z2] = TargetPosition;
  float x = x1 - x2;
  float y = y1 - y2;
  float z = z1 - z2;
  
  return sqrt((x*x) + (y*y) + (z*z));
}

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Clean Code
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Automatic Code Formatting

Learn how we can automatically format our code, and use the industry standard .clang-format file to control how our code is laid out.
3D art showing a maid character
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved