Writing code comments

SkillDev tools

Use whenever writing or reviewing a code comment anywhere in this repo (lib, ext, spec, sig, docs, CI config, etc.) – e.g. "add a comment", "explain this in a comment", "is this comment worth keeping". Enforces dd-trace-rb comment-noise conventions.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Writing code comments skill

What this skill tells your AI

The instructions your AI receives, as published by datadog/dd-trace-rb in .claude/skills/write-comment/SKILL.md and read by ahel’s review.

Code comments

Default to no comment: a comment must earn its place by saying something the code cannot.

Exception: @public_api docstrings follow normal YARD conventions (@param, @return, @raise, @example, @see) — they're shipped customer docs (docs/PublicApi.md). The method body still follows the rules below. E.g.:

# Returns the baggage for the current trace.
#
# If there is no active trace, a new one is created.
#
# @return [Datadog::Tracing::Distributed::Baggage] The baggage for the current trace.
# @public_api
def baggage

Write a comment only when:

  • It explains why, not what: a non-obvious tradeoff, a workaround for an upstream bug (link it), a perf choice that looks wrong but isn't. E.g.:
    # We use a custom random number generator because we want no interference
    # with the default one. Using the default prng, we could break code that
    # would rely on srand/rand sequences.
    def self.id_rng
      @id_rng ||= Random.new
    end
    
  • It warns of a real hazard: ordering constraints, thread/async safety, mutation of a shared value, a caller invariant that isn't type-enforced. E.g.:
    # Supports synchronous code flow *only*. Usage across
    # multiple threads will result in incorrect relationships.
    # For async support, a {Datadog::Tracing::TraceOperation} should be employed
    # per execution context (e.g. Thread, etc.)
    class TraceOperation
    
  • Cites an external source (spec, RFC, ticket, formula, algorithm) — never an internal-only one (wiki, Slack, JIRA, incident), even by name. Only cite what a non-Datadog reader can look up. E.g.:
    # Golden ratio constant for optimal distribution.
    # @see https://en.wikipedia.org/wiki/Hash_function#Fibonacci_hashing
    DEFAULT_KNUTH_FACTOR = 11400714819323198485
    
  • It documents a struct/instance field or other declaration that has no body to narrate: its valid states, invariants, or concurrency rules aren't derivable from the declaration itself. E.g.:
    // Accessed under the GVL for most functions EXCEPT on_gvl_waiting(),
    // which writes to it without the GVL.
    long gvl_waiting_at;
    

NEVER write:

  • Narration of nearby code — the comment just restates in prose what a block, loop, or call already does. E.g.:
    # increment counter
    counter += 1
    
    Or narrating a whole method:
    # Gets the value of the header with the given name.
    def get(header_name)
      @env[Header.to_rack_header(header_name)]
    end
    
  • Section banners and scaffolding. E.g.:
    # ---- Helpers ----
    # Step 1: parse input
    # Step 2: validate
    
  • Repetition of what a good name already says. Delete the comment, or rename if the identifier is the weak link.
  • Type restatements in prose (non-public surfaces). Type it in sig/ instead (write-rbs skill). E.g.:
    # @param [TraceOperation] trace
    # @return [Boolean]
    def match?(trace)
    
  • Duplicates of a nearby docstring or README. State it once, in the most discoverable place.
  • The same comment copy-pasted across multiple sites in one diff. Consolidate into one place and reference it.
  • Narration of the change or of your own process. That belongs in the commit message or PR description. E.g.:
    # Added error handling
    # Now using the new API
    
  • Comments on obvious imports, getters/setters, or trivial one-line wrappers.

Ratio check before finishing: if a diff has more than roughly one comment per 15 lines of new code, or if any comment would still be true after deleting the code it narrates, cut comments until that stops being the case. Exempt declaration-only blocks (struct/instance field docs with no accompanying logic) from the ratio — density there doesn't imply narration.

When editing existing files: don't add comments to code you merely moved or reformatted. Don't remove comments outside your change's scope unless they're now wrong — that's unrelated churn. Comments on code you're actually touching still follow the rules above.

Prefer over commenting: a clearer name, an extracted well-named function, a named constant instead of a literal, or a test that demonstrates the behavior.

Removing existing comments

Applies only to reviewing/deleting existing comments, not writing new ones:

  • Fix the named defect, don't just delete. Rename the identifier (repetition), relocate to sig/ (type restatement), add a shared reference (copy-paste) — apply it, or verify the destination already carries the information (e.g. the .rbs actually declares the type), before removing.
  • A deletable comment can contain a keeper. Judge multi-line comments per line: delete the narration, keep an embedded hazard/why/citation line.
  • A tag and its wrapped continuation are one unit. Remove both together, then re-read the docstring for a complete, consistent signature.
  • Bulk sweeps hide repeated defects. One file or module per commit, so a systematic mistake surfaces on file one.

Style

Always be terse. One line beats a paragraph; a fragment beats a full sentence. E.g.:

# Workaround for JRuby not supporting Process.fork.

not:

# This is a workaround that we need because JRuby does not support
# the Process.fork method, which is used elsewhere in this codebase.

Signals

GitHub stars
416
Forks
417
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
write-comment
Source
github.com/datadog/dd-trace-rb