Skip to content

Commit 7660a5b

Browse files
fix(skill): tighten optional workflow guidance
Tighten the Java Optional skill workflow while preserving eval lift and improving Tessl quality review to 100.
1 parent c24c89a commit 7660a5b

1 file changed

Lines changed: 35 additions & 96 deletions

File tree

skills/java-optionals/SKILL.md

Lines changed: 35 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,18 @@ description: Write, review, and refactor Java Optional code using best practices
66
# Java Optional Skill
77

88
Use this skill before writing Java code that may introduce `Optional`, and when reviewing or
9-
refactoring existing Optional code. Keep `Optional` as a clear present/absent boundary while
10-
preserving behavior, exception contracts, public output, laziness, and readability.
11-
12-
This skill is based on observed production failures where agents avoided one Optional antipattern by
13-
introducing another. Treat it as a practical guardrail, not a broad Java style guide.
9+
refactoring existing Optional code. Preserve behavior, exception contracts, public output, laziness,
10+
and readability.
1411

1512
Open [references/optional-examples.md](references/optional-examples.md) for non-trivial edits,
16-
review-only tasks, checked-exception cases, or benchmark-style examples.
13+
review-only tasks, priority selectors, checked-exception cases, or benchmark-style examples.
1714

18-
## Decision Procedure
15+
## Core Workflow
1916

20-
1. Classify each Optional shape before editing:
21-
- single `Optional<T>`;
22-
- real collection stream ending in an Optional terminal operation;
23-
- boolean-only presence check;
24-
- absence-as-error boundary;
25-
- side-effecting or expensive fallback;
26-
- checked-exception or prompting fallback;
27-
- nullable interop with an API that genuinely requires `null`.
28-
2. When writing new code, choose the Optional boundary before writing branches. Decide whether
29-
absence means fallback, error, side effect, prompt/IO, or nullable interop.
30-
Don't start with an `isPresent()` skeleton and clean it up later; write the boundary directly
31-
when the intent is ordinary fallback, transformation, or side-effect dispatch.
32-
3. Reject ordinary-control-flow workarounds:
33-
- `optional.isPresent()` or `optional.isEmpty()` followed by `get()` or `orElseThrow()`;
34-
- `optional.orElse(null)` followed by local `value != null` branching;
35-
- `optional.stream().toList()` or similar just to loop over one Optional;
36-
- replacing a readable real collection stream with nested loops, labels, or sentinel flags only
37-
to avoid an Optional terminal result.
38-
4. Use the Optional API that matches the intent when it stays readable:
17+
1. Classify the Optional boundary before writing branches: fallback, error, side effect,
18+
boolean-only check, collection lookup, checked IO/prompt, or nullable API interop. Don't start
19+
ordinary value flow with an `isPresent()` skeleton.
20+
2. Use the Optional API that matches the intent when it stays readable:
3921
- `map` for transforming a present value;
4022
- `flatMap` when the transform already returns Optional;
4123
- `filter` to keep a value only when a predicate matches;
@@ -44,91 +26,48 @@ review-only tasks, checked-exception cases, or benchmark-style examples.
4426
- `orElseGet` for lazy, expensive, or side-effecting fallbacks;
4527
- `orElseThrow` when absence is truly an error at that boundary;
4628
- `ifPresent` or `ifPresentOrElse` for side-effect boundaries.
47-
5. Extract a named helper when a fluent chain becomes dense. Prefer a clear helper over a clever
48-
Optional expression.
49-
6. Preserve laziness. If fallback work creates state, performs IO, mutates data, calls external
50-
services, or is expensive, use `orElseGet(...)` or an explicit lazy branch, not `orElse(...)`.
51-
7. For review-only tasks, always return an explicit review decision. If no code change is needed,
52-
say that and give the Optional-shape rationale; don't return an empty answer.
53-
8. For collection streams, choose `findFirst()` only when encounter order is part of the behavior.
54-
Use `findAny()` when any matching value is equivalent. When changing or intentionally keeping
55-
either method, include a short rationale unless the target output format is code-only.
56-
If a real collection lookup feeds more complex stateful code, keep the lookup as a small helper
57-
returning `Optional<T>` and consume that result directly. For option matchers, centralize exact
58-
matches and `option=value` matches in that helper instead of splitting matching logic across
59-
separate branches. If the surrounding loop needs a boolean such as "does this argument exactly
60-
equal the matched option?", derive it from the Optional value with `match.map(arg::equals).orElse(false)`
61-
or a named helper; don't use `match.filter(arg::equals).isPresent()` as a new presence gate.
62-
9. For multiple independent Optional selectors, boolean-only validation may stay as presence checks
63-
when no value is read. Once a branch needs the value, map that Optional to the domain action or
64-
bind the value once; don't turn a selector into a list, stream, or null branch.
65-
For priority selectors, prefer a shape like:
6629

6730
```java
68-
return primary
69-
.map(value -> selectedFromPrimary(value))
70-
.orElseGet(() -> secondary
71-
.map(value -> selectedFromSecondary(value))
72-
.orElseGet(this::defaultSelection));
31+
return findCart(cartId).map(this::toSummary).orElseGet(() -> createSummary(cartId));
7332
```
7433

75-
If the selected domain object stores the chosen value as an `Optional`, wrap the mapped value
76-
with `Optional.of(value)` inside the mapping lambda rather than reopening the original Optional.
77-
10. For checked-exception or prompting fallbacks, plain branching is acceptable:
34+
3. Reject ordinary-control-flow workarounds:
35+
- `isPresent()` / `isEmpty()` followed by `get()` or `orElseThrow()`;
36+
- `orElse(null)` plus local null branching;
37+
- `optional.stream().toList()` or another fake collection around one Optional;
38+
- loops, labels, or sentinel flags that only avoid an Optional terminal result.
7839

7940
```java
80-
Optional<String> configured = options.workspaceId();
81-
if (configured.isEmpty()) {
82-
return promptForWorkspace(terminal);
83-
}
84-
return configured.orElseThrow();
85-
```
86-
87-
Keep this exception narrow. The absent branch must genuinely perform checked IO, prompting, or
88-
another checked operation, and the enclosing method should honestly expose that boundary. When
89-
using this exception, say why plain branching is clearer than hiding checked exceptions in an
90-
unchecked wrapper or local helper unless the target output format is code-only.
91-
11. For nullable interop, keep `orElse(null)` only at an actual API boundary that uses `null` for
92-
absence. Don't add local null branching around it. If changing that boundary would require
93-
altering records, DTOs, serialization, or external APIs, call that out as a separate API/design
94-
decision rather than bundling it into an Optional cleanup.
41+
// avoid
42+
if (cart.isPresent()) return summarize(cart.get());
43+
return createSummary(cartId);
9544

96-
## What Not To Do
97-
98-
- Don't replace `isPresent()` plus `get()` with `orElse(null)` plus null checks.
99-
- Don't force a single `Optional<T>` through stream/list syntax to avoid a branch.
100-
- Don't ban `orElseThrow()`, `ifPresent()`, or `isPresent()` globally. Classify the shape first.
101-
- Don't replace readable collection streams with loops merely because the stream returns Optional.
102-
- Don't hide checked exceptions inside unchecked wrappers just to keep fluent Optional syntax.
103-
- Don't add Vavr or another functional library for a few Optional call sites. Treat that as a
104-
repository-wide style decision requiring the target repository's design process.
105-
106-
## Review Checklist
107-
108-
Before finishing an Optional-related Java change, verify:
45+
// prefer
46+
return cart.map(this::summarize).orElseGet(() -> createSummary(cartId));
47+
```
10948

110-
- you scanned touched code for sibling instances of the same pattern;
111-
- ordinary `isPresent()` or `isEmpty()` plus immediate value reads are removed or justified as a
112-
narrow checked-exception boundary;
113-
- no `orElse(null)` plus local null-control-flow workaround was introduced;
114-
- no single Optional was converted to a collection or stream just to branch;
115-
- real collection streams remain streams when clearer than manual loop state;
116-
- `findFirst()` is used only where order matters, otherwise `findAny()` is used;
117-
- non-obvious ordering, checked-exception, side-effect-boundary, and nullable-interop decisions are
118-
briefly explained when the output format allows prose;
119-
- review-only no-op findings still include a short rationale;
120-
- boolean-only Optional validation stays separate from value-reading branches;
121-
- side-effecting or expensive fallbacks remain lazy;
122-
- exception types/messages, public output, prompts, generated output, and branch order are
123-
preserved;
124-
- any attractive rejected approach is documented in the form expected by the target repository.
49+
4. Preserve laziness. If fallback work creates state, performs IO, mutates data, calls external
50+
services, or is expensive, use `orElseGet(...)` or an explicit lazy branch.
51+
5. For collection streams, use `findFirst()` only when order matters; otherwise use `findAny()`.
52+
Keep real lookups as streams. When stateful code consumes the match, extract an `Optional<T>`
53+
helper that centralizes exact and `option=value` matching.
54+
6. For selectors, keep presence checks only for boolean-only validation. Once a value is needed,
55+
map or bind it once. For priority selectors, map the first Optional and use lazy fallback for
56+
later sources. If the domain object stores an Optional, wrap the chosen value inside the mapping
57+
lambda.
58+
7. Handle special boundaries directly: use a plain branch for checked IO or prompts; keep
59+
`orElse(null)` only at real null-based API boundaries; return an explicit decision for
60+
review-only tasks.
61+
8. Verify the result: same return values, exceptions, prompts, side effects, laziness, generated
62+
output, and branch order; scan sibling code for the same Optional smell.
12563

12664
## When To Open References
12765

12866
Open [references/optional-examples.md](references/optional-examples.md) when:
12967

13068
- you're unsure whether a stream source is a real collection or a single Optional workaround;
13169
- a fallback has side effects or checked exceptions;
70+
- priority selectors need a worked example;
13271
- `findFirst()` versus `findAny()` is under review;
13372
- you need examples for testing an agent or skill implementation.
13473

0 commit comments

Comments
 (0)