---
name: effective-dart
source: https://app.decimal.ai/s/effective-dart@1/SKILL.md
source_sha256: 71f873d3fe0b
---

# Effective Dart — non-default conventions

## Contract

Enforces the arbitrary Effective Dart spellings, keywords, and shapes that a model does NOT
produce by default. Apply to every piece of Dart/Flutter code you write or refactor: identifiers,
error handling, strings, collections, async signatures, and doc comments.

## Rules (the complete spec)

### R1 — Acronyms in identifiers are word-cased

Treat an acronym/abbreviation **longer than two letters** as a single word: only its first letter
is capitalized inside `UpperCamelCase`, and it is all-lowercase inside `lowerCamelCase`.

- Write `JsonParser`, `UrlBuilder`, `HtmlSanitizer`, `CsvExporter`, `PdfGenerator`, `XmlValidator`,
  `ApiClient`, `HttpRequest`, `RestApiClient`. NEVER `JSONParser`, `URLBuilder`, `HTMLSanitizer`,
  `CSVExporter`, `PDFGenerator`, `XMLValidator`, `APIClient`, `HTTPRequest`.
- Inside `lowerCamelCase` the acronym is lowercase when leading (`jsonBody`, `apiClient`,
  `httpResponse`) and word-cased when interior (`parseJsonBody`, `buildUrl`, `sendHttpRequest`).

### R2 — Re-raise with `rethrow`, never `throw e;`

To propagate the caught exception unchanged, use the bare keyword `rethrow`. `throw e;` discards the
original stack trace. Logging-then-propagating is `} on X catch (e) { log(e); rethrow; }`.

### R3 — Catch one failure kind with a typed `on` clause

Handle a specific exception with `on FormatException catch (e)`. Do NOT use a bare `catch (e)` (it
swallows every error type) or `.catchError(...)` when you only mean to handle one kind.

### R4 — Split long strings with adjacent literals, never `+`

Break a long string literal across lines by placing two or more quoted strings side by side; the
compiler concatenates them at no runtime cost. NEVER join the pieces with the `+` operator.

### R5 — Filter a collection by type with `whereType<T>()`

To keep only the elements of a type, call `whereType<T>()`. Do NOT write
`.where((e) => e is T)` and do NOT chain `.where(...).cast<T>()`.

### R6 — Async members with no result return `Future<void>`

An `async` method that produces no value declares return type `Future<void>`. NEVER bare `void`,
bare `Future`, or `Future<Null>`.

### R7 — Boolean doc comments begin with "Whether"

A documentation comment on a boolean property/getter starts with the word `Whether`, e.g.
`/// Whether the token has expired.` NEVER "Returns true if…" or "Checks if…".

### R8 — Use `///` doc comments and `[bracket]` references

Document public APIs with `///` (never `/* */` block comments, never plain `//`). Refer to
parameters, return values, and exception types in prose with square brackets: `[a]`, `[id]`,
`[ArgumentError]`. The first line is a single-sentence summary ending in a period; doc comments go
**before** any metadata annotation (`@override`).

### R9 — Empty constructor bodies end with `;`, never `{}`

A constructor with no body is `Logger();`, not `Logger() {}`.

### R10 — Property-like reads are getters, not `getX()` methods

A conceptual property read is a getter: `String get fullName => '$first $last';`. NEVER expose it as
a `getFullName()` method.

### Adjacent surface (apply for completeness)

- Classes/enums/typedefs/extensions → `UpperCamelCase`; files/dirs → `lowercase_with_underscores`;
  variables/params/functions → `lowerCamelCase`.
- Annotate return types and parameter types on declarations, and variables without initializers.
- Prefer `final` over `var` when a local never changes; `const` for compile-time constants.
- Curly braces on all flow-control bodies, even one-liners.

## Worked examples (BEFORE = base default, AFTER = conforming)

**R1 acronym casing**
```dart
// BEFORE
class JSONParser { Model parseJSON(String body) => ...; }
// AFTER
class JsonParser { Model parseJson(String body) => ...; }
```

**R2 rethrow**
```dart
// BEFORE
try { read(); } catch (e) { log(e); throw e; }
// AFTER
try { read(); } catch (e) { log(e); rethrow; }
```

**R3 typed `on` clause**
```dart
// BEFORE
try { return int.parse(s); } catch (e) { return null; }
// AFTER
try { return int.parse(s); } on FormatException catch (_) { return null; }
```

**R4 adjacent string literals**
```dart
// BEFORE
const help = 'Usage: tool [options] ' + 'run the pipeline ' + 'and exit.';
// AFTER
const help = 'Usage: tool [options] '
    'run the pipeline '
    'and exit.';
```

**R5 whereType**
```dart
// BEFORE
final ints = values.where((e) => e is int).cast<int>();
// AFTER
final ints = values.whereType<int>();
```

**R6 Future<void>**
```dart
// BEFORE
Future saveSettings() async { await disk.write(data); }
// AFTER
Future<void> saveSettings() async { await disk.write(data); }
```

**R7 "Whether" docs**
```dart
// BEFORE
/// Returns true if the token has expired.
bool get isExpired => DateTime.now().isAfter(expiry);
// AFTER
/// Whether the token has expired.
bool get isExpired => DateTime.now().isAfter(expiry);
```

**R8 `///` + bracket refs**
```dart
// BEFORE
/* Divides a by b. Throws if b is zero. */
int divide(int a, int b) => ...;
// AFTER
/// Returns the quotient of [a] divided by [b].
///
/// Throws [ArgumentError] if [b] is zero.
int divide(int a, int b) => ...;
```

**R9 empty constructor**
```dart
// BEFORE
class Logger { Logger() {} }
// AFTER
class Logger { Logger(); }
```

**R10 getter not getX()**
```dart
// BEFORE
String getFullName() => '$firstName $lastName';
// AFTER
String get fullName => '$firstName $lastName';
```

## Edge cases & exceptions

- **Two-letter acronyms vs abbreviations.** A two-letter *acronym* like `IO` stays fully capitalized
  (`IOSink`). A two-letter *abbreviation* like `ID` is word-cased (`Id`, `userId`, not `userID`).
  The "word-case it" rule kicks in strictly for length > 2 (`Http`, `Json`).
- **`rethrow` only re-raises the in-flight exception.** If you must throw a *different* error, that is
  a genuine `throw NewError(...)` — R2 forbids `throw e;` (the same object), not deliberately raising a
  new one.
- **`whereType` vs `cast`.** Use `whereType<T>()` to *select* elements of a type; `cast<T>()` is for
  asserting an already-homogeneous list. Don't reach for `cast` to do filtering.
- **`Future<void>` vs `void`.** A *synchronous* callback that returns nothing is `void`. Only an
  `async` member (or one returning a future) uses `Future<void>`.
- **Bracket refs need an in-scope identifier.** `[a]` works because `a` is a parameter; don't bracket
  arbitrary prose words that aren't real identifiers.
- **Block comment for license headers is fine.** R8 governs *API doc comments*; a top-of-file `/* */`
  license banner is not a doc comment and is exempt.

## Do / Don't

- DON'T write `JSONParser`, `URLBuilder`, `APIClient`. DO write `JsonParser`, `UrlBuilder`, `ApiClient`.
- DON'T `throw e;`. DO `rethrow;`.
- DON'T `catch (e)` to handle one kind. DO `on FormatException catch (e)`.
- DON'T `'a' + 'b'` across lines. DO adjacent literals `'a' 'b'`.
- DON'T `.where((e) => e is T)`. DO `whereType<T>()`.
- DON'T return bare `void`/`Future` from a no-value async member. DO `Future<void>`.
- DON'T start a boolean doc with "Returns true if". DO start with "Whether".
- DON'T document with `/* */`. DO use `///` with `[bracket]` references.
- DON'T write `Logger() {}`. DO write `Logger();`.
- DON'T expose `getFullName()`. DO expose `String get fullName`.

## Common mistakes (the base's wrong defaults)

- Spelling acronyms in all-caps because that is the common English style — Dart overrides it.
- Reaching for `throw e;` out of habit from other languages, losing the stack trace.
- Catching broadly with `catch (e)` "to be safe," which hides unrelated bugs.
- Concatenating long strings with `+` as in Java/JS.
- Writing predicate-based `.where(...is T)` instead of the built-in `whereType`.
- Declaring `void`/`Future` on an async no-value method.
- Opening boolean docs with "Returns true if…" instead of "Whether…".
- Emitting `getX()` accessor methods instead of Dart getters.

## Quick checklist

- [ ] Acronyms word-cased (`Json`, `Url`, `Api`, `Http`, `Html`, `Csv`, `Pdf`, `Xml`).
- [ ] `rethrow` (not `throw e;`); typed `on X catch` (not bare `catch`).
- [ ] Long strings = adjacent literals (no `+`).
- [ ] `whereType<T>()` for type filtering.
- [ ] No-value async returns `Future<void>`.
- [ ] Boolean docs start "Whether"; `///` + `[bracket]` refs.
- [ ] Empty constructor `Name();`; property reads are getters.
