Comments #
Comments are one of the simplest features in any programming language, yet also one of the most frequently misused. A bad comment is more dangerous than no comment at all — it misleads readers and creates contradictions when the code changes but the comment doesn’t. Dart has three kinds of comments with different purposes: one for quick inline explanations, one for disabling blocks of code, and one that’s processed by tooling to generate formal documentation. Knowing when to use each is the skill that separates maintainable code from code that becomes a burden on the team.
Why Comments Matter — and When They Don’t #
Before diving into the syntax, it’s important to understand the basic philosophy: good code should already be self-documenting. Good variable names, function names, and code structure should explain what is happening. Comments exist to explain why — the context, design decisions, and trade-offs that the code itself can’t express.
// ANTI-PATTERN: a comment that only repeats the code — adds no value
int i = 0; // set i to 0
i++; // increment i by 1
return i; // return i
// CORRECT: a comment that explains WHY, not WHAT
// Start at 0 because the external API uses zero-based page indices,
// but the UI displays from page 1 — the conversion happens here.
int halaman = 0;
halaman++;
return halaman;
// ANTI-PATTERN: compensation comments for bad names
// This function calculates x based on y and z
double hitung(double y, double z) {
return y * z * 0.1;
}
// CORRECT: expressive names remove the need for explanatory comments
double hitungDiskon(double hargaAsli, double persentaseDiskon) {
return hargaAsli * persentaseDiskon * 0.01;
}
The rule of thumb: if you feel the need to write a comment explaining what a line of code does, consider first whether that code could be rewritten with better names. Use comments to explain why a particular decision was made — something the code can’t express.
flowchart TD
A{Is this comment necessary?} -- Explains WHAT the code does --> B{Can the code be\\nwritten more clearly?}
B -- Yes --> C[Refactor names/structure\\nDon't write a comment]
B -- No --> D[Write a single-line comment //]
A -- Explains WHY a\\ndecision was made --> E[Write a // comment]
A -- Documents a\\npublic API --> F[Write a DartDoc comment ///]
A -- Temporarily disables\\ncode --> G[Use /* */ multi-line]Single-Line Comments (//)
#
Single-line comments are the most frequently used type. They start with // and apply until the end of the line — anything after // is ignored by the compiler. Dart also supports placing comments at the end of a line of code (inline comments), which is useful for short explanations tightly bound to one particular statement.
// Comment on its own line — for an explanation before a block of code
void prosesOrder(Order order) {
// Validation happens before touching the database so no partial
// data is stored if validation fails halfway through.
validasiOrder(order);
// Save to the database
simpanOrder(order);
kirimEmail(order.email); // Notify the customer after the order is saved
}
When to Use Single-Line Comments #
// ✓ Explaining a non-obvious decision
// Use ceiling instead of floor so users don't feel "shortchanged"
// when the count doesn't divide evenly.
int jumlahHalaman = (totalItem / itemPerHalaman).ceil();
// ✓ Marking areas that need special attention
// TODO: Replace with a cache once the /v2/produk endpoint is available
List<Produk> produk = await apiLama.ambilProduk();
// ✓ Explaining magic number values
const int batasRetry = 3; // Industry standard for HTTP retries
const int timeoutDetik = 30; // Maximum limit from the contract SLA
// ✗ Don't: noise comments without any new information
String nama = 'Budi'; // nama = 'Budi'
int umur = 25; // umur is 25
TODO, FIXME, and HACK Comments #
Dart (and editors like VS Code) recognize several conventional prefixes that can be highlighted specially:
// TODO: Implement pagination before releasing to production
List<Produk> semuaProduk = await repository.ambilSemua();
// FIXME: There's a race condition when two requests arrive at the same time —
// a mutex or atomic operation is needed here.
stok -= jumlahPesan;
// HACK: Workaround for a bug in the third-party library version 2.1.0
// Remove this once they ship a patch — see issue #4521
response = response.copyWith(headers: {'Content-Type': 'application/json'});
// DEPRECATED: Use hitungDiskonV2() which already supports tiered discounts
double hitungDiskon(double harga) => harga * 0.1;
VS Code with the Todo Tree or Better Comments extension can visualize allTODOs andFIXMEs in a project in a single panel, so nothing slips through during code review.
Multi-line Comments (/* */)
#
Multi-line comments wrap all text between /* and */ — covering anything from a single line to hundreds of lines. Unlike some other languages, Dart supports nested block comments — meaning /* */ can nest inside other /* */ blocks. This is very useful when you need to temporarily disable a block of code that already contains multi-line comments.
/*
This is a standard multi-line comment.
It can span as many lines as needed.
Dart also supports nested block comments like this:
/*
This is a nested comment — valid in Dart, not valid in C/Java.
*/
Back to the outer comment.
*/
Main Use: Temporarily Disabling Code #
The most practical use of /* */ is disabling a block of code while debugging or experimenting, without having to delete it:
void main() {
var data = ambilData();
/*
// Old code being compared for performance:
var hasil = prosesLambat(data);
print('Old method: ${stopwatch.elapsedMilliseconds}ms');
*/
// New method being tested:
var hasil = prosesCepat(data);
print('New method: ${stopwatch.elapsedMilliseconds}ms');
}
Because Dart supports nested comments, disabling a block that already contains /* */ doesn’t cause an error:
/*
This block is temporarily disabled for A/B testing.
Inside it there's another multi-line comment:
/*
Notes about the previous algorithm.
*/
And this is still part of the outer comment — valid in Dart.
*/
// ANTI-PATTERN: using /* */ to document functions/classes
/*
Calculates the area of a circle.
Parameter: radius — the circle's radius.
*/
double hitungLuasLingkaran(double radius) {
return 3.14159 * radius * radius;
}
// This comment won't be processed by dart doc — it won't appear in the docs
// CORRECT: use /// for documentation that gets processed by tooling
/// Calculates the area of a circle from the given radius.
///
/// [radius] is the circle's radius in meters.
/// Returns the area in square meters.
double hitungLuasLingkaran(double radius) {
return 3.14159 * radius * radius;
}
| Comment Type | Processed by DartDoc | Nested | Best for |
|---|---|---|---|
// | ✗ | N/A | Inline explanations, TODO/FIXME |
/* */ | ✗ | ✓ | Temporarily disabling blocks of code |
/// | ✓ | ✗ | Public API documentation |
/** */ | ✓ | ✓ | Public API documentation (alternative) |
Documentation Comments (///)
#
Documentation comments are the most powerful of the three Dart features, but also the least fully utilized. Every line starts with ///, and all consecutive /// comments immediately before a declaration are processed by dart doc to generate HTML documentation pages. Editors like VS Code and IntelliJ also show these comments as tooltips when you hover over a symbol.
/// Calculates the discount value from the original price and percentage.
///
/// This function rounds up (ceiling) so the discount result
/// never exceeds the expected value.
///
/// Example usage:
/// ```dart
/// double diskon = hitungDiskon(150000, 20);
/// print(diskon); // 30000.0
/// ```
///
/// Parameters:
/// - [hargaAsli] — the price before discount, must be positive
/// - [persentase] — the discount value in percent (0–100)
///
/// Throws [ArgumentError] if [hargaAsli] or [persentase] is negative.
/// Returns the discount value in the same unit as [hargaAsli].
double hitungDiskon(double hargaAsli, double persentase) {
if (hargaAsli < 0) throw ArgumentError('hargaAsli cannot be negative');
if (persentase < 0 || persentase > 100) {
throw ArgumentError('persentase must be between 0 and 100');
}
return hargaAsli * persentase / 100;
}
Documenting Classes #
Class documentation should explain the purpose of the class and when it’s used — not its internal implementation. Public properties and methods are each documented separately:
/// Represents a product in the store catalog.
///
/// [Produk] stores basic product information and provides
/// methods for common operations like checking stock availability
/// and calculating prices after discounts.
///
/// Example usage:
/// ```dart
/// var produk = Produk(
/// id: 'P001',
/// nama: 'Gaming Laptop',
/// harga: 15000000,
/// stok: 5,
/// );
///
/// if (produk.tersedia) {
/// print('Price after discount: ${produk.hargaSetelahDiskon(10)}');
/// }
/// ```
class Produk {
/// Unique product identifier in the system.
///
/// Format: capital letter followed by digits, e.g. `P001`, `PRD-042`.
final String id;
/// Product name displayed to customers.
String nama;
/// Product price in Rupiah, excluding tax.
double harga;
/// Number of units available in the warehouse.
///
/// Negative values are not allowed — use [tambahStok] and
/// [kurangiStok] to modify it with automatic validation.
int stok;
/// Creates a new [Produk] instance.
///
/// [id] and [nama] are required. [stok] defaults to 0 if not provided.
Produk({
required this.id,
required this.nama,
required this.harga,
this.stok = 0,
});
/// Returns `true` if the product is still available (stok > 0).
bool get tersedia => stok > 0;
/// Calculates the price after a discount.
///
/// [diskonPersen] is the discount percentage (0–100).
/// Throws [ArgumentError] if the discount value is outside the valid range.
double hargaSetelahDiskon(double diskonPersen) {
if (diskonPersen < 0 || diskonPersen > 100) {
throw ArgumentError('diskonPersen must be between 0 and 100');
}
return harga * (1 - diskonPersen / 100);
}
/// Adds [jumlah] to the product's stock.
///
/// Throws [ArgumentError] if [jumlah] is not positive.
void tambahStok(int jumlah) {
if (jumlah <= 0) throw ArgumentError('jumlah must be greater than 0');
stok += jumlah;
}
}
Documenting Enums #
Enums can and should also be documented — especially when the values aren’t self-explanatory:
/// The processing status of an order from creation to completion.
///
/// Normal status flow: [menunggu] → [diproses] → [dikirim] → [selesai].
/// The [dibatalkan] status can happen from [menunggu] or [diproses].
enum StatusOrder {
/// Order just created, not yet processed by the warehouse team.
menunggu,
/// Order is being prepared in the warehouse.
diproses,
/// Order has been handed to the courier, in transit.
dikirim,
/// Order has been received by the customer and the transaction is complete.
selesai,
/// Order canceled — either by the customer or the system.
dibatalkan,
}
DartDoc Tags #
/// comments support several special tags that render differently in the HTML documentation output. These tags make the documentation much more structured and scannable.
Symbol References with [namaSimbol]
#
Square brackets create automatic links to other Dart symbols in the documentation:
/// Fetches a product by ID from the repository.
///
/// Use [ProdukRepository.ambilSemua] if you need the full list.
/// Returns a [Produk] if found, or `null` if it doesn't exist.
///
/// See also [CacheService] for recommended caching strategies.
Future<Produk?> ambilProduk(String id);
Code Blocks in Documentation #
Code examples inside documentation use triple backticks with a language label — these render as syntax-highlighted code blocks in the HTML output:
/// Formats a number as Rupiah currency.
///
/// Example:
/// ```dart
/// print(formatRupiah(150000)); // Rp 150.000
/// print(formatRupiah(1500000.5)); // Rp 1.500.001 (rounded up)
/// ```
///
/// For international formats, use [NumberFormat] from the `intl` package.
String formatRupiah(double jumlah) {
// implementation
}
@param, @returns, @throws Tags (Alternative Style)
#
Besides the free descriptive format, DartDoc also recognizes several Javadoc-style tags:
/// Divides two numbers.
///
/// @param pembilang the number being divided
/// @param penyebut the divisor, must not be zero
/// @returns the division result as a double
/// @throws [ArgumentError] if penyebut is zero
double bagi(double pembilang, double penyebut) {
if (penyebut == 0) throw ArgumentError('penyebut cannot be zero');
return pembilang / penyebut;
}
However, the descriptive style with [namaParameter] in the text is more common in the Dart ecosystem because it’s easier to read in source code:
/// Divides two numbers.
///
/// [pembilang] is the number you want to divide.
/// [penyebut] must not be zero — throws [ArgumentError] if zero.
/// Returns the division result as a [double].
double bagi(double pembilang, double penyebut) {
if (penyebut == 0) throw ArgumentError('penyebut cannot be zero');
return pembilang / penyebut;
}
@deprecated — Marking APIs That Will Be Removed
#
/// Calculates a simple discount.
///
/// **Deprecated:** Use [hitungDiskonV2] which supports tiered discounts.
/// This function will be removed in version 3.0.0.
@Deprecated('Use hitungDiskonV2() — will be removed in v3.0.0')
double hitungDiskon(double harga) => harga * 0.1;
/// Calculates a discount with tiered discount support.
///
/// Replaces [hitungDiskon] with additional capabilities:
/// discounts can differ per product category.
double hitungDiskonV2(double harga, {double persentase = 10}) {
return harga * persentase / 100;
}
Generating Documentation with dart doc
#
Once you’ve written good /// comments, generating HTML documentation is just one command:
# Run from the project root directory
dart doc
# Documentation is generated in the doc/api/ folder
# Open it in the browser:
open doc/api/index.html # macOS
xdg-open doc/api/index.html # Linux
start doc/api/index.html # Windows
The generated output structure:
doc/
└── api/
├── index.html ← main page
├── produk/ ← one folder per library
│ ├── Produk-class.html
│ ├── StatusOrder.html
│ └── ...
└── ...
dart doc Configuration #
You can configure the dart doc output via a dartdoc_options.yaml file at the project root:
dartdoc:
# Additional pages (changelog, contributing guide, etc.)
categories:
"Guide":
markdown: doc/panduan.md
name: "Usage Guide"
# Exclude certain files/folders from documentation
exclude:
- "lib/src/internal/**"
# Package name shown in the output
name: "MyApp API Documentation"
# See all available options
dart doc --help
Comment Anti-Patterns to Avoid #
A bad comment is more dangerous than no comment at all. Here are the patterns you’ll encounter most often and how to fix them.
Lying Comments #
Comments that don’t sync with the code are the root of many hard-to-trace bugs:
// ANTI-PATTERN: comment no longer matches the code after refactoring
// Calculates a 10% discount
double diskon = harga * 0.15; // code was changed to 15% but the comment wasn't updated
// CORRECT: if the logic is clear from the code, remove the comment entirely
double diskon = harga * PERSENTASE_DISKON; // the constant name is clear enough
Comments Disabling Code for Too Long #
// ANTI-PATTERN: old code that "might be needed later" commented out for years
// var hasil = metodeLama(input);
// print('debug: $hasil');
// if (hasil > threshold) return;
var hasil = metodeBar(input);
// CORRECT: delete unused code — version control (Git) keeps the history
// If you're afraid of losing old code, add a Git tag or a commit note
var hasil = metodeBaru(input);
Over-documenting Internals #
// ANTI-PATTERN: documenting private implementation details that change often
class _KeranjangBelanjaState extends State<KeranjangBelanja> {
/// Internal list that stores items in the cart.
/// Initialized as an empty list and filled when the user adds products.
/// Must not be accessed directly from outside the class.
List<Item> _items = []; // private, no DartDoc needed
// CORRECT: DartDoc documentation is only for public APIs
// Private variables only need // if a comment is really necessary
List<Item> _items = []; // local cache, synced with CartRepository
}
// ANTI-PATTERN: big comment blocks as C-style "file headers"
//===========================================================
// File: produk_repository.dart
// Author: Budi Santoso
// Date: 2024-01-15
// Description: Repository for product CRUD operations
// Version: 1.0.0
//===========================================================
// CORRECT: this information already lives in Git history and pubspec.yaml
// Start directly with code or class DartDoc
When to Write Comments, When Not To #
A practical guide for deciding whether a comment is worth writing:
WRITE A COMMENT when:
✓ The code does something counter-intuitive — explain why
✓ There's a workaround for an external library bug — include the issue number
✓ There's a debated design decision — record the reasoning
✓ There's a non-obvious constraint — e.g. "must be called after init()"
✓ You're documenting a public API — use ///
✓ There are TODO/FIXMEs that need follow-up
DON'T WRITE A COMMENT when:
✗ The code already reads clearly — function/variable names are enough
✗ The comment only repeats what the code does (noise)
✗ The comment explains code that should be refactored
✗ Documenting private properties/methods with DartDoc
✗ File header comments with information that's in Git
Summary #
- Three kinds of Dart comments —
//for inline explanations,/* */for temporarily disabling blocks of code, and///for API documentation processed bydart doc.- Comments explain WHY, not WHAT — if you need to explain what the code does, that’s a signal the code needs refactoring, not a comment.
///is a team investment — documentation comments appear as tooltips in the editor and are generated into HTML pages bydart doc. Every public API must be documented.- The
[namaSimbol]tag inside///produces automatic links between symbols in the HTML documentation — use it to build an interconnected documentation web.- Nested block comments
/* /* */ */are valid in Dart, unlike C/Java — useful when disabling code that already contains multi-line comments.@Deprecatedcombined with///is the standard way to mark APIs that will be removed while guiding users to the alternative.- A comment that isn’t updated is more dangerous than no comment — a lying comment misleads readers and hides bugs.
- Delete commented-out code — version control (Git) keeps the history. Dead commented code only adds noise and confuses readers.
dart docruns from the project root and generates HTML documentation in thedoc/api/folder — one command for professional documentation.