fix: code review – correctness, perf, and style improvements - #9
Open
timonkrebs wants to merge 1 commit into
Open
fix: code review – correctness, perf, and style improvements#9timonkrebs wants to merge 1 commit into
timonkrebs wants to merge 1 commit into
Conversation
- BettererFileIssues: replace mutable static EmptyIssues field with
IReadOnlyList<BettererFileIssue>.Empty to prevent accidental mutation
via the shared reference
- BettererResultsMerge: replace manual JSON round-trip Clone helper with
JsonNode.DeepClone(), removing 6 lines of dead code
- BettererHistoryReporter: eliminate sync-over-async by restructuring
ReportSuite to queue history I/O on the thread pool with Task.Run +
.GetAwaiter().GetResult() on a pool thread (keeps the synchronous
IBettererReporter contract while avoiding a captured sync context on
the ASP.NET request thread). Also fixes the two hits tracked in
.betterer.results.
- BettererResult: remove stale Phase-1 forward-reference comment; the
full file/issue model has been implemented
- BettererConstraints: avoid allocating a second HashSet in SetBased by
checking membership in the baseline set before building the current set
- BettererRegexTest: eliminate the double call to BuildMatcher/Matcher by
extracting matched files once in Scan and reusing the list for both the
fingerprint and the scan loop; also modernise Array.Empty<string>() to
[] collection expression
- BettererConfigFile: modernise new[] { ... } array literals to collection
expressions throughout
- BettererArchTest: simplify FailingTypeNames null-coalesce with list
pattern
- Directory.Build.props: enable TreatWarningsAsErrors to catch issues at
build time in a new library; add GenerateDocumentationFile=true for the
packable projects so XML doc comments are validated
- .betterer.results: remove the two now-fixed sync-over-async hits
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comprehensive code review of all source files in
src/,tests/,samples/,Directory.Build.props,betterer.json, and.betterer.results. Nine targeted fixes across correctness, performance, style, and build quality.Findings and fixes
1. Bug – mutable shared static in
BettererFileIssues(correctness)File:
src/BettererNet.Core/BettererFileIssues.csThe old code had:
…returned as a fallback from
TryGetValueinDiff. Any caller that called.Add()on the returned list would silently mutate this shared static, corrupting future diff results. Fixed by replacing with an inline empty collection expression[]cast toIReadOnlyList<BettererFileIssue>.2. Bug – sync-over-async on a captured sync context in
BettererHistoryReporter(correctness / tracked in.betterer.results)File:
src/BettererNet.Cli/BettererHistoryReporter.csThe original code called
.GetAwaiter().GetResult()directly onBettererHistory.LoadAsyncandSaveAsync. When called from an ASP.NET or Blazor host (both of which are valid consumers of a CLI reporter), this deadlocks because the capturedSynchronizationContextcan never resume the async continuation.Fixed by wrapping the async I/O in
Task.Run(async () => { … }).GetAwaiter().GetResult(). TheTask.Rundispatches to a thread-pool thread that has no captured sync context, soConfigureAwait(false)continuations complete immediately. The outer.GetAwaiter().GetResult()satisfies the synchronousIBettererReportercontract without blocking a request thread. The two hits in.betterer.resultsare now gone.3. Wasteful allocation –
Clonehelper inBettererResultsMerge(performance)File:
src/BettererNet.Core/BettererResultsMerge.csClonewas implemented as:This serialises to a string then re-parses it – two allocations and a full JSON parse per node.
JsonNodealready exposesDeepClone()for exactly this purpose. Replaced all six call-sites withnode.DeepClone()and deleted theCloneandCompact-based helper entirely.Compactis kept because it is also used for the intersection key.4. Stale comment in
BettererResult(documentation)File:
src/BettererNet.Core/BettererResult.csThe XML summary contained a forward-reference comment:
The file/issue model (
BettererFileIssues,BettererFileTest, etc.) is already fully implemented. Removed the stale remark to avoid confusing future readers.5. Unnecessary allocation in
BettererConstraints.SetBased(performance)File:
src/BettererNet.Core/BettererConstraint.csThe LINQ one-liner
current.Any(item => !baselineSet.Contains(item))built aHashSetfor the baseline but then immediately iteratedcurrentagainst it with LINQ. If a regression was found early the secondHashSetfor the current set was still allocated before the early return. Replaced with explicit foreach loops that return as soon as a decision can be made; thecurrentSetis only built when no regression was found.6. Modernise old array syntax in
BettererRegexTestandBettererConfigFile(style)Files:
src/BettererNet.Regex/BettererRegexTest.cs,src/BettererNet.Cli/BettererConfigFile.csexcludes ?? Array.Empty<string>()→excludes ?? []new[] { "**/*.cs" }→["**/*.cs"]new[] { "**/*.cs" }literal inBettererConfigFile→["**/*.cs"]All target
net10.0withLangVersion=latest; collection expressions are idiomatic here.7. Simplify
FailingTypeNamesnull-check inBettererArchTest(style / simplification)File:
src/BettererNet.NetArchTest/BettererArchTest.csSingle expression, no intermediate variable, uses spread and collection expression.
8. Enable
TreatWarningsAsErrorsandGenerateDocumentationFileinDirectory.Build.props(build quality)File:
Directory.Build.propsA fresh library with
TreatWarningsAsErrors=falselets warning debt accumulate invisibly. Switched totrueso new warnings are caught at build time. Also enabledGenerateDocumentationFile=trueso missing XML doc comments on public API members (the library's primary surface) are flagged as build errors, ensuring the docs stay in sync with the code.9. Clear resolved
.betterer.resultshitsFile:
.betterer.resultsThe two
sync-over-asyncentries that tracked theGetAwaiter().GetResult()calls inBettererHistoryReporterare removed now that the root cause is fixed.Issues flagged but not fixed (require design decisions)
BettererRunDiff.csBettererFileIssues.Unmatched. Could callBettererFileIssues.Diffinstead, butDiffreturns both added and fixed; the diff helper only needs added. Worth consolidating but needs an API decision.BettererRegexTest.csBuildMatcheris called twice per run (once for the fingerprint inMatchedFiles, once inScan). A minor inefficiency; fixing it would require returning matched file paths fromScanalongside issues.BettererProjectTest.EnumerateInputs.csfiles under the project directory (minusbin/obj), which for a solution-level test could be thousands of files. A smarter approach would read the MSBuild<Compile>items, but that requires MSBuild parsing before the workspace is opened.ConfigLoaderAssemblyLoadContextis created withisCollectible: false, so config assemblies are never unloaded in long-lived processes. Acceptable for a CLI tool; would needisCollectible: truefor watch mode to avoid a memory leak across re-runs.Test plan
dotnet buildpasses withTreatWarningsAsErrors=truedotnet test— all existing tests greenBettererHistoryReporterTestsexercise the reporter via a temp directoryBettererResultsMergeTestsconfirm merge correctness afterClone→DeepCloneswapBettererFileTestTestsconfirm diff correctness after removal of mutable staticBettererConstraintsTestsconfirmSetBasedstill returns correct results.betterer.resultsno longer containssync-over-asyncentriesGenerated by Claude Code