Documentation has four types: Tutorials, How-To Guides, Reference Docs, and Explanations — each serves a different reader need.
A good comment explains WHY a decision was made, not WHAT the code does.
A great README shows a Quick Start that works in under 5 minutes.
JavaDoc generates a browsable API site from /* / comments — same format as the JDK.
Production insight: Outdated documentation causes real incidents — a dev following stale docs deployed a misconfigured service.
Biggest mistake: Writing comments that restate the code — teaches readers to skip all comments.
Plain-English First
Imagine you spend six months building an incredible LEGO city, then put it in a box with zero instructions. A year later, someone else opens that box — or even future-you opens it — and has absolutely no idea what piece goes where or why you made certain choices. Software documentation is the instruction manual for your code. It tells other developers (and your future self) what your program does, why it was built that way, and how to use or modify it safely without breaking everything.
Every developer has had this experience: you open a project someone else wrote, or a project you wrote six months ago, and you feel completely lost. There are no explanations, no comments, no README — just a wall of code staring back at you. This is one of the most expensive problems in the software industry. Studies consistently show that developers spend more time reading code than writing it, and without good documentation, that reading time multiplies painfully. Bad documentation (or none at all) costs companies real money in onboarding time, bugs introduced by misunderstood code, and features that get re-built because nobody knew they already existed.
Documentation solves the 'context gap' — the enormous difference between what the code does and what the next person needs to know to work with it confidently. Code tells a computer what to do. Documentation tells a human what the code does, why it does it that way, and what the edge cases are. Without it, every new team member has to reverse-engineer months of decisions from scratch. With it, a new developer can be productive in hours instead of weeks.
By the end of this article, you'll understand the different types of documentation (and when to use each one), how to write code comments that actually help instead of just restating the obvious, how to structure a README that makes people want to use your project, and the habits that separate developers who write maintainable code from those who create technical debt. You'll also see real, runnable code examples showing what good documentation looks like in practice.
The Four Types of Documentation Every Project Needs
Not all documentation is the same. Mixing them up is the number one reason docs become useless fast. Think of it like a car manual: there's a 'how to drive' section, a 'how to maintain it' section, a 'what each warning light means' section, and a 'technical specs' section. They serve completely different readers with completely different needs.
The four types are: Tutorials (learning-oriented — 'here's how to get started'), How-To Guides (task-oriented — 'here's how to do this specific thing'), Reference Docs (information-oriented — 'here's exactly what every function does'), and Explanations (understanding-oriented — 'here's why we made this architectural decision'). This framework comes from Divio's documentation system and it's the mental model used by major open-source projects like Django and Kubernetes.
Beginner developers usually only write one type — comments inside the code itself — and skip everything else. That's like publishing a car with only the warning-light section. The person who needs to get started has nothing to read. The framework developer who needs to understand a past decision is equally stuck. Match your documentation type to your reader's actual need in that moment.
UserAccountService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* UserAccountService — ReferenceDocumentationExample
*
* PURPOSE: Manages the full lifecycle of user accounts in the application.
* This includes creation, authentication, suspension, and deletion.
*
* DESIGNDECISION: We chose to keep authentication logic here (rather than a
* separate AuthService) because in our current scale, the two responsibilities
* are tightly coupled. If the app grows beyond 100k users, consider splitting them.
*
* DEPENDENCIES: Requires a live DatabaseConnection. SeeDatabaseConfig.java.
*/
publicclassUserAccountService {
privatefinalDatabaseConnection databaseConnection;
privatefinalPasswordHasher passwordHasher;
/**
* Creates a newUserAccountService.
*
* @param databaseConnection An active, non-null connection to the user database.
* @param passwordHasher The hashing strategy to use (e.g., BCrypt, Argon2).
* Never pass a plain-text or reversible hasher here.
*/
publicUserAccountService(DatabaseConnection databaseConnection, PasswordHasher passwordHasher) {
this.databaseConnection = databaseConnection;
this.passwordHasher = passwordHasher;
}
/**
* Registers a new user account.
*
* HOWITWORKS:
* 1. Validates that the email is unique in the database.
* 2. Hashes the password before storage (plain text is NEVER stored).
* 3. Persists the new user record and returns the generated user ID.
*
* @param email The user's email address. Must be unique. Cannot be null.
* @param password The raw password entered by the user. Min8 characters.
* @returnThe newly created user's unique ID (a positive long).
* @throwsDuplicateEmailExceptionIf that email already exists in the system.
* @throwsInvalidPasswordExceptionIf the password fails strength requirements.
*
* EXAMPLEUSAGE:
* long newUserId = accountService.registerUser("alice@example.com", "Str0ngP@ss!");
* System.out.println("Created user with ID: " + newUserId);
*/
publiclongregisterUser(String email, String password)
throwsDuplicateEmailException, InvalidPasswordException {
// Guard: never store an account if the email is already takenif (databaseConnection.emailExists(email)) {
thrownewDuplicateEmailException("Account already exists for: " + email);
}
// Guard: enforce password policy before doing any DB workif (password == null || password.length() < 8) {
thrownewInvalidPasswordException("Password must be at least 8 characters.");
}
// Hash the password — we store the hash, NEVER the original stringString hashedPassword = passwordHasher.hash(password);
// Persist the new user and return their auto-generated database IDreturn databaseConnection.insertNewUser(email, hashedPassword);
}
}
Output
// No console output — this is a service class.
// When registerUser() is called in a test or main method:
// InvalidPasswordException: Password must be at least 8 characters.
The Divio Rule:
Before you write any documentation, ask: 'Is this person learning, doing a task, looking something up, or trying to understand a decision?' That answer tells you exactly which type of doc to write. Mixing all four into one giant wall of text is why most documentation goes unread.
Production Insight
A team that only writes Reference docs (JavaDoc) but never Tutorials forces new hires to reverse-engineer the system from method signatures.
When a new feature is introduced without an Explanation doc, the rationale is lost within two sprints.
Rule: Every major feature needs at least one Explanation doc — or accept that future decisions will be made without context.
Key Takeaway
The four types of documentation serve four distinct reader needs.
Mixing them creates unusable walls of text.
Use the Divio framework — you'll write less and help more.
Which Documentation Type Should You Write?
IfReader needs to get started quickly
→
UseWrite a Tutorial — step-by-step with working examples.
IfReader needs to accomplish a specific task (e.g., reset a password)
→
UseWrite a How-To Guide — focused, goal-oriented steps.
IfReader needs to look up a function signature or config option
→
UseWrite Reference Docs — auto-generated from JavaDoc or similar.
IfReader needs to understand why an architectural decision was made
→
UseWrite an Explanation — narrative, context, trade-offs.
Writing Code Comments That Actually Help — Not Just Restate the Code
Here's the most common documentation mistake developers make: writing comments that simply repeat what the code already says in plain English. If I write int userAge = 25; // set userAge to 25, I've added zero value. The code already says that. I've just doubled the amount of text someone has to read.
Good comments answer 'WHY', not 'WHAT'. The code already tells you what it's doing — that's literally the code's job. Comments exist to explain the reasoning behind a decision, warn about a non-obvious gotcha, flag a workaround for a known bug, or describe the intention behind a complex algorithm. Think of it this way: if someone deletes your comment, do they lose any information that isn't already in the code? If not, the comment shouldn't exist.
There's also a middle ground: 'signpost' comments that break a long method into readable sections. These are fine in moderation. If a function is doing five steps, a one-line comment naming each step makes it scannable. But the goal is always to write code so clear that the comments are explaining strategy, not translating syntax. If you find yourself writing a lot of 'WHAT' comments, that's a signal the code itself needs to be refactored to be more readable.
PaymentProcessor.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
publicclassPaymentProcessor {
// BAD COMMENT STYLE — What not to do// ─────────────────────────────────────────────────────────────────publicdoublecalculateTotalBad(double itemPrice, int quantity, double taxRate) {
double subtotal = itemPrice * quantity; // multiply itemPrice by quantity
double taxAmount = subtotal * taxRate; // multiply subtotal by taxRate
double total = subtotal + taxAmount; // add subtotal and taxAmount
return total; // return total
}
// Every comment above is useless — the code already says exactly that.// GOOD COMMENT STYLE — What to aim for// ─────────────────────────────────────────────────────────────────publicdoublecalculateTotal(double itemPrice, int quantity, double taxRate) {
double subtotal = itemPrice * quantity;
// Tax is calculated on the subtotal, NOT on any already-discounted price.// This matches our legal obligation in jurisdictions where tax// applies before merchant discounts (see compliance doc: TAX-2024-03).double taxAmount = subtotal * taxRate;
return subtotal + taxAmount;
}
publicdoubleapplyEarlyBirdDiscount(double originalPrice, long purchaseTimestampMs) {
long currentTimeMs = System.currentTimeMillis();
long nineAm = getTodayNineAmTimestampMs();
long elevenAm = getTodayElevenAmTimestampMs();
// Only apply the discount during the 9-11am window.// WHY this check exists: marketing ran a campaign where the first// 2 hours of each day have a 10% discount. The check uses strict// less-than on elevenAm so a purchase at exactly 11:00:00.000 does// NOT qualify — product team confirmed this edge case on 2024-08-12.if (purchaseTimestampMs >= nineAm && purchaseTimestampMs < elevenAm) {
return originalPrice * 0.90; // 10% discount applied
}
return originalPrice;
}
// TODO(dev-team): Replace this stub with a real calendar utility in v2.1// Tracked in GitHub issue #447privatelonggetTodayNineAmTimestampMs() { return 0L; }
privatelonggetTodayElevenAmTimestampMs() { return 0L; }
}
Output
// This is a library class — no main method output.
// To test calculateTotal with itemPrice=50.0, quantity=3, taxRate=0.08:
//
// double result = processor.calculateTotal(50.0, 3, 0.08);
// System.out.println(result);
//
// Output:
// 162.0
//
// (subtotal = 150.0, taxAmount = 12.0, total = 162.0)
The Comment Test:
Before you commit a comment, ask: 'If someone deleted this comment, would they lose any information that isn't already visible in the code?' If the answer is no, delete the comment yourself. Your codebase will be cleaner and future readers won't develop 'comment blindness' — the habit of skipping all comments because they're usually just noise.
Production Insight
Comment blindness is real: when teams tolerate restating comments, devs stop reading all comments — even the valuable ones.
Your team wastes hours debugging because someone overlooked a vital but buried explanation.
Rule: Every comment must survive the delete test, or it becomes noise.
Key Takeaway
Comments justify decisions, not syntax.
If the code already says it, delete the comment.
Invest that saved time in explaining the non-obvious.
Writing a README That Makes People Actually Want to Use Your Project
The README is the front door of your project. It's the very first thing a new developer, a potential user, or an interviewer looks at. A blank README, or one that just says 'This is my project', is like a restaurant with a locked door and no sign. People will walk right past.
A great README has a clear structure. It starts with one sentence explaining what the project does and who it's for. Then it shows the fastest possible path to get something working — the 'Quick Start'. This is the most important section and the most commonly skipped one. Follow that with configuration options, how to run tests, and how to contribute. End with the license.
Think of your README reader as someone who has thirty seconds to decide if they're interested. If they can't get a working demo running in under five minutes, they'll move on. Every sentence in a README should either convince them the project solves their problem, or get them closer to running it. Remove anything that doesn't do one of those two things. README-driven development is even a real practice — some engineers write the README before the code, which forces them to think about the user experience upfront.
README.mdMARKDOWN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# TaskFlow — LightweightTaskQueueforJavaApplicationsTaskFlow lets you run background jobs in your Java app without setting up
RabbitMQ, Kafka, or any external broker. Everything runs in-process.
Perfectfor small-to-medium apps where you need async work but don't want
infrastructure overhead.
## Who is thisfor?
Developers building SpringBoot or plain Java apps who need to defer work
(like sending emails or resizing images) to a background thread.
---
## QuickStart (< 5 minutes)
### 1. Add the dependency
```xml
<dependency>
<groupId>io.thecodeforge</groupId>
<artifactId>taskflow</artifactId>
<version>1.2.0</version>
</dependency>
```
### 2. Define a job
```java
publicclassSendWelcomeEmailJobimplementsTaskFlowJob {
@Overridepublicvoidexecute(String userEmail) {
EmailClient.send(userEmail, "Welcome aboard!");
}
}
```
### 3. Enqueue it
```java
TaskQueue queue = newTaskQueue(workerThreadCount: 4);
queue.enqueue(newSendWelcomeEmailJob(), "alice@example.com");
```
That's it. The job runs in the background. Your main thread is free immediately.
---
## Configuration
| Option | Default | Description |
|-------------------|---------|------------------------------------------|
| workerThreadCount | 2 | Number of parallel background workers |
| maxQueueSize | 1000 | Jobs accepted before blocking callers |
| retryOnFailure | true | Retry a failed job up to 3 times |
---
## RunningTests
```bash
./mvnw test
```
All tests should pass in under 10 seconds on a standard laptop.
---
## KnownLimitations
- JobsdoNOT survive a JVM restart. For durability, use a persistent broker.
- Not suitable for jobs that take longer than 30minutes (no heartbeat support yet).
See issue #88for progress on this.
---
## ContributingPRs welcome. Please read CONTRIBUTING.md first — it's short, we promise.
## LicenseMIT — use it freely, attribution appreciated.
Output
// README files are rendered as formatted HTML on GitHub/GitLab.
// Plain text output when viewed in terminal:
//
// # TaskFlow — Lightweight Task Queue for Java Applications
// TaskFlow lets you run background jobs in your Java app...
// [rest of content renders as formatted text]
//
// On GitHub it renders with headers, code blocks, and a formatted table.
Watch Out — The 'Assumed Knowledge' Trap:
The single biggest README mistake is writing for yourself instead of a first-time user. You know your project inside out — your reader doesn't. Always include the full command to clone the repo, the exact command to install dependencies, and what a successful run looks like. Never write 'then set up the database' without explaining exactly how.
Production Insight
Your README is the first thing a new hire sees — if it lacks a Quick Start, they waste their first morning asking for help.
Open-source projects with a bad README lose ~80% of potential contributors at the first glance.
Rule: If you can't run the project in 5 minutes using only the README, your README is broken.
Key Takeaway
A README's job is to convert interest into usage.
Quick Start is the most important section.
Assume zero prior knowledge — show every command, every expected output.
JavaDoc and API Documentation — Making Your Code Self-Describing
If you're building something other developers will use — a library, an internal SDK, a shared utility — your public methods need formal API documentation. In Java, this is done with JavaDoc. JavaDoc comments sit directly above a class or method, start with /**, and use special tags like @param, @return, and @throws to document every input, output, and exception.
The key insight about JavaDoc is that it generates a beautiful HTML website from your comments automatically. Run javadoc on your source files and you get a browsable reference site — the same format as the official Java standard library docs. That's how Oracle documents the JDK and it's exactly the format your users already know how to read.
The rule for what needs JavaDoc is simple: every public method and class in code that other people will depend on must have it. Private methods can have regular comments. Package-private (default) methods depend on whether external contributors need to understand them. If in doubt, document it. The cost of writing a JavaDoc comment is thirty seconds. The cost of someone spending two hours reverse-engineering an undocumented method is very real.
TemperatureConverter.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* TemperatureConverter — Utilityclassfor converting between temperature scales.
*
* <p>SupportsCelsius, Fahrenheit, and Kelvin. All conversion methods are stateless
* and thread-safe — safe to call from multiple threads simultaneously.</p>
*
* <p>Thisclass intentionally has no constructor — use the static methods directly:
* <pre>
* double boilingInF = TemperatureConverter.celsiusToFahrenheit(100.0);
* </pre>
* </p>
*
* @author TheCodeForgeTeam
* @version 2.0
* @since 1.0
*/
publicclassTemperatureConverter {
// Private constructor prevents instantiation — this is a pure utility class.// All methods are static; there's no state to hold in an instance.privateTemperatureConverter() {}
/**
* Converts a temperature from Celsius to Fahrenheit.
*
* <p>Formula used: F = (C × 9/5) + 32</p>
*
* @param celsius The temperature in degrees Celsius.
* Valid range: -273.15 and above (absolute zero is the floor).
* @returnThe equivalent temperature in degrees Fahrenheit.
* @throwsIllegalArgumentExceptionif {@code celsius} is below absolute zero (-273.15°C).
*
* @see #fahrenheitToCelsius(double)
*/
publicstaticdoublecelsiusToFahrenheit(double celsius) {
if (celsius < -273.15) {
thrownewIllegalArgumentException(
"Temperature " + celsius + "°C is below absolute zero. That's physically impossible."
);
}
// The classic formula: multiply by 9/5 then add 32return (celsius * 9.0 / 5.0) + 32.0;
}
/**
* Converts a temperature from Fahrenheit to Celsius.
*
* <p>Formula used: C = (F − 32) × 5/9</p>
*
* @param fahrenheit The temperature in degrees Fahrenheit.
* Valid range: -459.67 and above (absolute zero in Fahrenheit).
* @returnThe equivalent temperature in degrees Celsius.
* @throwsIllegalArgumentExceptionif {@code fahrenheit} is below absolute zero (-459.67°F).
*/
publicstaticdoublefahrenheitToCelsius(double fahrenheit) {
if (fahrenheit < -459.67) {
thrownewIllegalArgumentException(
"Temperature " + fahrenheit + "°F is below absolute zero."
);
}
return (fahrenheit - 32.0) * 5.0 / 9.0;
}
/**
* Converts a temperature from Celsius to Kelvin.
*
* <p>Kelvin is the SI base unit for temperature. ZeroKelvin = absolute zero.
* There are no negative Kelvin values.</p>
*
* @param celsius The temperature in degrees Celsius (must be >= -273.15).
* @returnThe equivalent temperature in Kelvin (always >= 0).
* @throwsIllegalArgumentExceptionif {@code celsius} is below -273.15.
*/
publicstaticdoublecelsiusToKelvin(double celsius) {
if (celsius < -273.15) {
thrownewIllegalArgumentException(
"Cannot convert " + celsius + "°C to Kelvin: value is below absolute zero."
);
}
// Kelvin = Celsius + 273.15 (by definition of the Kelvin scale)return celsius + 273.15;
}
// Quick demo — run this class directly to see conversions in actionpublicstaticvoidmain(String[] args) {
double boilingPointCelsius = 100.0;
double bodyTempFahrenheit = 98.6;
double absoluteZeroCelsius = -273.15;
System.out.println("=== Temperature Converter Demo ===");
System.out.printf("%.1f°C in Fahrenheit = %.1f°F%n",
boilingPointCelsius,
celsiusToFahrenheit(boilingPointCelsius));
System.out.printf("%.1f°F in Celsius = %.2f°C%n",
bodyTempFahrenheit,
fahrenheitToCelsius(bodyTempFahrenheit));
System.out.printf("%.2f°C in Kelvin = %.2fK%n",
absoluteZeroCelsius,
celsiusToKelvin(absoluteZeroCelsius));
System.out.println("\nAttempting to convert below absolute zero...");
try {
celsiusToFahrenheit(-300.0); // This will throw
} catch (IllegalArgumentException exception) {
System.out.println("Caught expected error: " + exception.getMessage());
}
}
}
Output
=== Temperature Converter Demo ===
100.0°C in Fahrenheit = 212.0°F
98.6°F in Celsius = 37.00°C
-273.15°C in Kelvin = 0.00K
Attempting to convert below absolute zero...
Caught expected error: Temperature -300.0°C is below absolute zero. That's physically impossible.
Generate the Docs Site:
Run javadoc -d docs src/TemperatureConverter.java from your project root. Open docs/index.html in a browser and you'll see a full, professional API reference website generated entirely from those /*/ comments. This is exactly how the JDK itself is documented — same tool, same output format.
Production Insight
Undocumented public methods are a productivity black hole — each one costs a new team member ~30 minutes of reverse-engineering per use.
When a library ships without JavaDoc, support tickets pour in for basic usage questions.
Rule: Every public method must have JavaDoc with @param, @return, @throws — or accept the support tax.
Key Takeaway
JavaDoc is not optional for shared code — it generates the API reference site automatically.
Invest 30 seconds per method now, save hours of questions later.
The JDK docs are the gold standard — match that bar.
Making Documentation a Team Habit — How to Survive When You're Not the Only One Writing
Individual documentation effort decays fast. You write a great README on Monday. By Friday, a colleague merges a change that breaks it. By next month, the whole team ignores docs because they're always out of date. The fix isn't more discipline — it's changing the process so documentation stays alive automatically.
The two most effective patterns: treat doc updates as a required part of every code review, and automate freshness checks. A PR template with a 'Documentation updated?' checkbox works surprisingly well. Better yet, add a CI step that fails the build if a public method signature changes without a corresponding JavaDoc update. For README changes, use a linter that checks for broken links or missing Quick Start steps.
Another habit: schedule a monthly 'doc sprint' where the whole team picks a few files to audit and update. Rotate ownership so no single person becomes the documentation bottleneck. And write Architecture Decision Records (ADRs) for every non-trivial choice — they're the only way future team members will know why you chose PostgreSQL over MySQL.
.github/workflows/doc-checks.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: DocumentationChecks
on: [pull_request]
jobs:
check-javadoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: CheckJavaDoc completeness
run: |
# Findpublic methods without JavaDoc
missing=$(grep -r 'public ' src/main/java --include='*.java' | grep -v '@' | wc -l)
if [ "$missing" -gt 0 ]; then
echo "Found $missing public methods missing JavaDoc."
exit 1
fi
check-readme:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: CheckQuickStart is not outdated
run: |
# Simple check: ensure README contains the current project version
version=$(grep -oP 'version=".*?"' pom.xml | head -1 | tr -d '"' | cut -d= -f2)
grep -q "$version"README.md || { echo "README does not mention version $version"; exit 1; }
Output
// GitHub Actions workflow that runs on every PR.
// If it fails, the PR cannot merge — forcing documentation updates.
The Bus Factor and Documentation as Insurance
Bus factor = number of people who need to be hit by a bus before the project becomes impossible.
Without documentation, every key decision is locked in one person's head.
An ADR or inline explanation takes 10 minutes to write but saves days of reverse-engineering.
Treat documentation as insurance: you pay a small premium now to avoid a catastrophic loss later.
Production Insight
Teams without documentation habits suffer 3x longer onboarding — every new hire has to reverse-engineer the same undocumented decisions.
When the original author leaves, undocumented systems become 'legacy' that nobody dares to touch.
Rule: Documentation is not overhead — it's the cheapest insurance against knowledge loss. Pay the premium or pay the rebuild cost.
Key Takeaway
Documentation lives only when it's part of the team's workflow.
Automate checks in CI and make doc updates a review requirement.
The bus factor drops with every ADR written — protect your team's future self.
When Should You Update Documentation?
IfPR changes a public API signature (method name, parameters, return type, exceptions)
→
UseMandatory: Update JavaDoc and any related README sections in the same PR.
IfPR adds a new feature or configuration option
→
UseMandatory: Add a How-To Guide or update the Configuration table in the README.
IfPR fixes a bug that was documented incorrectly in a comment or JavaDoc
→
UseMandatory: Fix the incorrect doc in the same commit as the bug fix.
IfPR refactors internal code without changing public behaviour
→
UseOptional: Add an Explanation comment only if the refactor introduces a non-obvious approach.
● Production incidentPOST-MORTEMseverity: high
Outdated Documentation Causes Production Data Exposure
Symptom
A critical API endpoint started returning full user profiles without requiring authentication tokens. The incident was detected by an automated security scan flagged it as 'unauthenticated access'.
Assumption
The team assumed that because the README was authoritative and had not been removed, the configuration described was still safe to use. They trusted the written word over checking the actual code.
Root cause
The README still contained an old troubleshooting section that advised setting DEBUG=true in application.properties. A recent feature branch had inadvertently repurposed that flag to bypass authentication for internal testing, and the change was merged without updating the README.
Fix
1. Immediately reverted the commit that repurposed the DEBUG flag.
2. Removed the DEBUG section from the README and replaced it with a note that DEBUG is no longer used.
3. Added a PR template that requires 'Documentation updated?' checkbox for any config changes.
Key lesson
Old documentation is more dangerous than no documentation because it actively misleads.
Treat doc updates as a mandatory part of every PR — never ship a code change that alters behaviour without updating the corresponding docs.
Automate checks: use a CI step that fails a PR if a config property changes but the docs/ folder has no corresponding diff.
Production debug guideSystematic approach to detect and fix documentation drift3 entries
Symptom · 01
New hire follows the Quick Start and gets errors immediately
→
Fix
Check if the commands, versions, or environment variables match the current codebase. Update the Quick Start to match the actual setup steps.
Symptom · 02
A developer says 'I followed the docs and it broke production'
→
Fix
Audit the documentation against the current code. Use git blame on the doc file to find when it was last changed and what code changed around that time.
Symptom · 03
Users report that API examples in the docs don't match actual behaviour
→
Fix
Run the example code against a test instance. If it fails, update the examples. Consider moving examples to an automated test suite that generates doc snippets.
★ Documentation Drift Quick FixWhen docs diverge from code, use these commands to find and fix the gap fast.
README says one thing, code does another−
Immediate action
Compare last modified date of README.md vs the source file it describes.
Commands
git log --oneline --follow README.md | head -5
git log --oneline --follow src/main/java/io/thecodeforge/PaymentService.java | head -5
Fix now
Update README to match current code, or add a deprecation notice with 'Last updated: YYYY-MM-DD'.
JavaDoc method signature doesn't match actual throws+
Immediate action
Run javadoc and see if the generated docs show unexpected exceptions.
Update JavaDoc @throws to match the actual exception class and conditions.
Good Documentation vs Bad Documentation
Aspect
Good Documentation
Bad Documentation
Comments explain
WHY a decision was made
WHAT the next line does (restating code)
README has
Quick Start that works in 5 minutes
Vague 'Installation' section with missing steps
JavaDoc covers
Every @param, @return, @throws with meaning
Just a method name repeated as a sentence
Edge cases
Explicitly documented with examples
Completely absent — discovered by users as bugs
Tone
Written for a newcomer who knows nothing
Written for the author who knows everything
Kept up to date
Updated in the same PR as the code change
Last updated 2 years ago, actively misleading
Design decisions
Recorded in ADRs or inline explanations
Lost forever — only the original author knows
Key takeaways
1
There are four distinct types of documentation (Tutorial, How-To, Reference, Explanation)
each serves a different reader need, and mixing them creates documentation nobody can use effectively.
2
A good comment explains WHY a decision was made, not WHAT the next line does. If deleting the comment loses no information the code doesn't already provide, the comment shouldn't exist.
3
Your README is the front door of your project. If a newcomer can't get something running within five minutes of reading it, your README has failed
regardless of how much text it contains.
4
Documentation rot (docs that are out of date) is worse than no documentation, because it actively misleads people. Treat outdated docs as bugs and include doc updates as a non-negotiable part of your code review process.
5
Documentation is a team habit, not a solo task. Automate checks in CI, use PR templates, and schedule monthly doc sprints to keep knowledge alive.
Common mistakes to avoid
3 patterns
×
Writing comments that restate the code
Symptom
Developers skip all comments because they're trained to expect zero value — a phenomenon called 'comment blindness'.
Fix
Ask: 'Does this comment tell the reader something the code itself cannot?' If no, delete it and write clearer code instead.
×
Letting documentation go stale
Symptom
README says to run ./start.sh but that script was renamed to ./run-server.sh three months ago. New devs follow the docs, hit errors immediately, and lose trust in all documentation.
Fix
Make doc updates a required part of your code review checklist — if a PR changes a public method signature, the JavaDoc must change in the same PR, or the PR doesn't merge.
×
Over-documenting obvious things while under-documenting complex decisions
Symptom
A paragraph explaining what a for-loop does, but a complex caching strategy or subtle concurrency workaround is completely unexplained.
Fix
Invert your instinct: the more non-obvious a decision is, the more documentation it needs. Complex architectural decisions deserve Architecture Decision Records (ADRs).
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What's the difference between a comment that explains WHAT code does ver...
Q02SENIOR
How do you keep documentation up to date as a project evolves?
Q03SENIOR
If you joined a team with zero documentation on a large codebase, where ...
Q01 of 03JUNIOR
What's the difference between a comment that explains WHAT code does versus one that explains WHY? Which is more valuable?
ANSWER
A 'WHAT' comment restates the code in English, e.g., // increment counter above counter++. It adds no value because the code already says that. A 'WHY' comment explains the reasoning behind a decision, e.g., why a specific algorithm was chosen, why a null check is necessary despite the type system. WHY comments are more valuable because they provide context that can't be inferred from the code alone. Interviewers want to see that you understand comments exist to provide context a reader can't get from the code itself, not to translate syntax into English.
Q02 of 03SENIOR
How do you keep documentation up to date as a project evolves?
ANSWER
Strong answers mention: including doc updates in PR review requirements (a checkbox 'Documentation updated?'), treating outdated docs as bugs (file a ticket), automated tools that flag doc/code mismatches (e.g., CI job that fails if public method signature changes without JavaDoc update), and living READMEs with dated changelogs. Never treat documentation as a separate task — it's part of the code change.
Q03 of 03SENIOR
If you joined a team with zero documentation on a large codebase, where would you start?
ANSWER
Weak answers say 'document everything'. Strong answers say: start by documenting the parts you had to reverse-engineer yourself — that's the most painful gap. Write an ADR for each non-obvious architectural pattern you discover. Build a Quick Start guide as your first act so the next new hire has it easier than you did. Focus on the README and the most commonly used APIs first. Use a 'documentation debt' board to track what's missing.
01
What's the difference between a comment that explains WHAT code does versus one that explains WHY? Which is more valuable?
JUNIOR
02
How do you keep documentation up to date as a project evolves?
SENIOR
03
If you joined a team with zero documentation on a large codebase, where would you start?
SENIOR
FAQ · 4 QUESTIONS
Frequently Asked Questions
01
How much documentation is too much?
Documentation is too much when it starts restating what the code already clearly expresses, or when it describes implementation details that change frequently (causing the docs to go stale constantly). Aim to document decisions, edge cases, and context — not syntax. If your code requires a paragraph of comments to explain a five-line function, that's often a signal to refactor the code to be more readable rather than add more comments.
Was this helpful?
02
Should I write documentation before or after writing code?
Both approaches work, and the best engineers do a bit of each. Writing a README or API contract before coding (README-Driven Development) forces you to think about the user experience upfront and often reveals design flaws before you've written a single line. Writing JavaDoc before a method signature is written forces you to think clearly about inputs and outputs. At minimum, always update documentation in the same commit or PR as the code change — never as a 'I'll do it later' task.
Was this helpful?
03
What is an Architecture Decision Record (ADR) and does a beginner need one?
An ADR is a short document (usually under one page) that captures a significant technical decision: what was decided, why it was decided that way, and what alternatives were rejected. You don't need formal ADRs for small personal projects, but the moment you're working with a team or building something that will be maintained long-term, they're invaluable. The easiest way to start is a simple docs/decisions/ folder in your repo with numbered markdown files like 001-chose-postgresql-over-mysql.md.
Was this helpful?
04
How do I get my team to care about documentation?
Lead by example and make it frictionless. Write the first ADR yourself. Add a PR template with a documentation checkbox. Show the team how much time they save when onboarding a new member — or better yet, have them be the new member who suffers from missing docs. Once people feel the pain, they'll become advocates. Also, automate it: a CI check that fails on undocumented public methods is harder to ignore than a verbal reminder.