Control Flow Keywords on Their Own Line
PublicPut a guard clause's return or throw on its own line inside braces, so exits stay visible and can take a breakpoint.
Control Flow Keywords on Their Own Line
Put a guard clause's return or throw on its own line inside braces, so exits stay visible and can take a breakpoint.
Place guard-clause return and throw statements on their own lines inside
blocks. This keeps exits visible in procedural code.
Why?
-
Scannability: When scanning code vertically,
returnandthrowsignal exits from the current scope. Placing them at the start of a line makes them immediately visible: you don't have to read the full line to notice a control flow exit. -
Debuggability: When
returnorthrowis on its own line inside a block, you can place a breakpoint on it. Inline one-liners likeif (x) return;don't allow breakpoints on the return itself. -
Minimize Diffs: Inserting a comment or logging before the branch creates a minimal diff. Adding the extra curly braces is extra clutter when reviewing the new diff
Examples
Example 1: Early return guard
❌ Avoid: Inline return
if (!mounted) return;
✅ Prefer: Block with return on its own line
if (!mounted) {
return;
}
Example 2: Throw on failure
❌ Avoid: Inline throw
if (!opened) throw Exception('failed to open');
✅ Prefer: Block with throw on its own line
if (!opened) {
throw Exception('failed to open');
}
Example 3: Return with value
❌ Avoid: Inline return with value
if (items.isEmpty) return const [];
✅ Prefer: Block with return on its own line
if (items.isEmpty) {
return const [];
}
Example 4: Multiple guards
❌ Avoid: Inline guards
Future<void> save() async {
if (!isValid) return;
if (!mounted) return;
await repository.save(data);
}
✅ Prefer: Block guards
Future<void> save() async {
if (!isValid) {
return;
}
if (!mounted) {
return;
}
await repository.save(data);
}
Exception: compact classification ladders
An ordered, side-effect-free classification ladder may keep each condition and returned value on one line:
factory FixtureSignalStrength.fromRssi(int? rssi) {
if (rssi == null || rssi >= 0) return FixtureSignalStrength.lost;
if (rssi > -60) return FixtureSignalStrength.best;
if (rssi > -70) return FixtureSignalStrength.good;
if (rssi > -80) return FixtureSignalStrength.fair;
return FixtureSignalStrength.poor;
}
This reads as an ordered lookup table: each line is one condition-result pair. The strongest reason for the inline form is that a mapper often contains several small checks and usually trivial returned values. Keeping each pair together makes the complete mapping easier to compare, while braces add vertical noise between related conditions and results.
Use this exception only when:
- the function or block is recognizably a classifier or mapper;
- it contains multiple
if (...) return value;mappings; - the checks and returned values are usually short and trivial.
A final fallback return is not required. Logging or other supporting statements do not invalidate an otherwise clear mapper.
The exception does not apply to bare early exits, a lone inline guard in
procedural code, or inline throw statements.
Using return inside an IIFE or short lambda as a single-line expression is
also fine:
// OK: return inside IIFE/lambda is the body, not a guard
final status = () {
if (isLoading) return LoadingStatus.inProgress;
return LoadingStatus.success;
}();