Skip to content

R2: the Null Object is named and no argument is nil - #29

Merged
buzzdan merged 2 commits into
mainfrom
claude/handoff-eval-rebaseline-uxs66q
Sep 11, 2026
Merged

R2: the Null Object is named and no argument is nil#29
buzzdan merged 2 commits into
mainfrom
claude/handoff-eval-rebaseline-uxs66q

Conversation

@buzzdan

@buzzdan buzzdan commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #28, from the first re-run of the case-e evals on 2.10.1.

The 2.10.1 R2 example defaulted a nil sink inside the constructor:

if sink == nil { sink = DiscardSink() }      // or make callers pass it

That keeps NewReporter(nil) legal and only relocates the nil-check. The re-run of case-e-nils-refactor (two passes) showed both halves of the gap:

  • Pass 1 reproduced that exact shape: io.Discard composed inline in the constructor, no named DiscardSink(), a test still calling NewReporter(nil, ...).
  • Pass 2 reached the option-based constructor with no nil anywhere, then introduced a one-implementation Sink interface with a noopSink{} for the no-op, which R11 and R2 already forbid but the refactoring skill's move table only names the move and points at the rule.

The review side did move: fix-null-object went 0/2 → 2/2 and one run wrote func DiscardSink() *Sink { return NewSink(io.Discard) } verbatim.

Changes (regenerated into the plugin):

  • core/rules/R2-self-validating-types.md: the design point says no parameter accepts nil to mean "default", names the constructor guard as the smell in a new place, and the example supplies DiscardSink() and time.Now through an option: NewReporter(opts ...Option), NewReporter(WithSink(sink)) in production, NewReporter(WithClock(fixed)) in tests. The Introduce Null Object fix move says the default is named and passed or supplied by an option, never inferred from nil.
  • core/skills/refactoring/SKILL.md: the move table lists Introduce Null Object under R2 for optional collaborators and carries the Go shape beneath it: a named value of the existing concrete type (DiscardSink() composing io.Discard) supplied through an option or passed by name; never a new interface for the no-op (R6), never a nil parameter meaning default (R2).
  • Changelog entry under Unreleased. task check, lint-core (Residue table refreshed) and the docs gate pass.

Companion grader change: buzzdan/ldd-evals#3.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WrQTEgY87C1UdfgejismGr

The 2.10.1 example defaulted a nil sink inside the constructor
(if sink == nil { sink = DiscardSink() }), which keeps NewReporter(nil) legal
and only relocates the nil-check. The first re-run of case-e-nils-refactor on
2.10.1 produced exactly that shape: io.Discard composed inline, no named
DiscardSink, and a test still calling NewReporter(nil, ...). The example now
supplies DiscardSink() through an option, the fix move says the default is
named and passed (never inferred from nil), and the changelog records it under
Unreleased.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrQTEgY87C1UdfgejismGr
…gent applying it reads

The second case-e refactor re-run on 2.10.1 reached the option-based constructor
with no nil anywhere, then introduced a one-implementation Sink interface with a
noopSink{} for the no-op. The R11 and R2 rule files already forbid that, but the
refactoring skill's move table only names the move and points at the rule. The
table now lists Introduce Null Object under R2 for optional collaborators and
carries the Go shape beneath it: a named value of the existing concrete type
(DiscardSink() composing io.Discard) supplied through an option or passed by
name; never a new interface for the no-op (R6), never a nil parameter meaning
default (R2).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WrQTEgY87C1UdfgejismGr
@buzzdan
buzzdan marked this pull request as ready for review September 11, 2026 09:37
@buzzdan
buzzdan merged commit 5c21497 into main Sep 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants