refactor: Remove unnecessary type parameter from Failure and Skipped result types#1958
refactor: Remove unnecessary type parameter from Failure and Skipped result types#1958
Conversation
…result types Issue #1876: The type parameter T in ModuleResult<T> was unnecessarily required for Failure and Skipped variants, which don't carry typed values. Changes: - Create non-generic ModuleResult base class with common metadata - Move Failure and Skipped to non-generic types on base class - Keep Success<T> as the only generic variant in ModuleResult<T> - Add implicit conversions from non-generic Failure/Skipped to any ModuleResult<T> via internal wrapper types - Add marker interfaces (IFailureResult, ISkippedResult) for pattern matching across wrapper types - Update JSON converters to handle new type hierarchy - Update factory methods to support both generic and non-generic creation This reduces type instantiation overhead when creating failure/skipped results and provides a cleaner API where only success results need the type parameter. The change maintains backward compatibility - existing code using ModuleResult<T>.Failure pattern still works through the implicit conversion. Co-Authored-By: Claude Opus 4.5 <[email protected]>
ef67465 to
a8487e2
Compare
SummaryRefactors ModuleResult to use a non-generic base class for Failure and Skipped results, reducing unnecessary generic type instantiations. Critical Issues1. JSON Deserialization Truncation (Line 882+)The diff shows the JSON deserialization code for "Skipped" => skipDecision is not null
? new ModuleResult<T>.SkippedWrapper(new ModuleReThe code is incomplete and won't compile. This needs to be verified in the actual PR. 2. Breaking Change in Pattern MatchingThe PR changes the examples to show: case ModuleResult.Failure { Exception: var ex }: // NEW
case ModuleResult.Skipped { Decision: var skip }: // NEWBut existing code uses: case ModuleResult<string>.Failure { Exception: var ex }: // OLD
case ModuleResult<string>.Skipped { Decision: var skip }: // OLDImpact: This is a breaking API change. All existing pattern matching code will need to be updated. The PR should:
3. Required Members and Constructor ComplexityThe new internal FailureWrapper(Failure inner)
{
_inner = inner;
ModuleName = inner.ModuleName;
ModuleDuration = inner.ModuleDuration;
// ... etc
}Issues:
Suggestion: Consider using the 4. WithStatus Method Has Edge Case BugThe public static IModuleResult WithStatus(IModuleResult result, Status status)
{
// Handle non-generic Failure/Skipped types directly
if (result is ModuleResult.Failure failure)
{
return failure with { ModuleStatus = status };
}
if (result is ModuleResult.Skipped skipped)
{
return skipped with { ModuleStatus = status };
}
// Falls through to reflection-based code for generic types
}Issue: If a Result: The method will create a new wrapper with updated status, but the 5. Performance Concern in IsFailure/IsSkippedThe properties now use pattern matching with multiple cases: public bool IsFailure => this is Failure or IFailureResult;
public bool IsSkipped => this is Skipped or ISkippedResult;This checks against 2 types per call. In hot paths (execution engine, result processing), this adds overhead compared to the single type check in the original code. Suggestion: Profile to ensure this doesn't impact performance in result processing loops. Suggestions1. Consider Public API StabilityThis refactoring changes public API surface area significantly. Consider:
2. Add XML DocumentationThe new non-generic
3. Unit Test CoverageEnsure tests cover:
Verdict
|
SummaryRefactors ModuleResult to use a non-generic base class for Failure and Skipped results, reducing unnecessary generic type instantiations. Critical Issues1. JSON Serialization Bug (ModuleResult.cs:880)In the Write method of var discriminator = value switch
{
ModuleResult<T>.Success => "Success",
ModuleResult<T>.FailureWrapper => "Failure",
ModuleResult<T>.SkippedWrapper => "Failure", // BUG: Should be "Skipped"
ModuleResult.Failure => "Failure",
ModuleResult.Skipped => "Skipped",
_ => throw new JsonException("Unknown ModuleResult type")
};The Impact: This breaks JSON serialization/deserialization round-trips for skipped module results when using the generic Fix: Change line to Previous Review StatusFrom the previous review, the following issues have been addressed: ✅ Issue 1 (Truncated code): Resolved - code is complete and compiles However, a new critical bug (#1 above) was introduced in the JSON serialization code. Verdict |
The SkippedWrapper case was incorrectly returning "Failure" instead of "Skipped", causing JSON serialization/deserialization round-trips to fail for skipped module results. Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
Fixed the JSON serialization bug - changed |
SummaryRefactors Critical Issues1. Incomplete Pattern Matching in The Looking at the added code: // Handle non-generic Failure/Skipped types directly
if (result is ModuleResult.Failure failure)
{
return failure with { ModuleStatus = status };
}
if (result is ModuleResult.Skipped skipped)
{
return skipped with { ModuleStatus = status };
}
// ... reflection code continuesWhen a Recommendation: Add explicit checks for wrapper types before falling back to reflection, or document why the reflection path is acceptable for wrappers. 2. Missing The diff adds Suggestions1. JSON Converter Factory Could Return Null In 2. Documentation Example Needs Update The XML documentation example (lines 8-27 in the new code) still shows: case ModuleResult<string>.Failure { Exception: var ex }:With the new design, this should be updated to: case ModuleResult.Failure { Exception: var ex }:This would better demonstrate the benefit of the refactoring and guide developers to use the non-generic types. Verdict
|
Addresses code review feedback by documenting why FailureWrapper and SkippedWrapper types fall through to the reflection path in WithStatus. The wrappers require the generic path to maintain type safety, and the observable behavior is correct because the wrapper's own properties (not _inner) are updated via the 'with' expression. Co-Authored-By: Claude Opus 4.5 <[email protected]>
|
Addressed the remaining review feedback: Issue #1 (Incomplete Pattern Matching in
The performance impact is minimal because:
Issue #2 (Missing Issue #3 (Documentation example): Already addressed - the example in Commit: 9d513da |
SummaryThis PR refactors Critical IssuesNone found ✅ Suggestions1. Missing null-forgiving operator in JSON converter (line 846+)The diff shows the JSON converter at line 846+ appears to be creating wrapper instances but the code is truncated. If this follows the pattern elsewhere, ensure proper null handling when deserializing the inner 2. Performance consideration: Wrapper overheadThe new ModuleName = inner.ModuleName;
ModuleDuration = inner.ModuleDuration;
// ... etcWhile this maintains immutability and makes the 3. Pattern exhaustiveness in
|
Summary
Fixes #1876
ModuleResultbase class containing common metadata and non-genericFailureandSkippedresult typesModuleResult<T>now only containsSuccess<T>as its generic variantModuleResult.FailureandModuleResult.SkippedtoModuleResult<T>for any TMatch,Switch) to handle both direct and wrapper result typesRationale
The type parameter
TinModuleResult<T>was only actually used by theSuccessvariant. TheFailureandSkippedvariants don't carry a typed value, yet they were forced to carry the type parameter. This meantModuleResult<string>.FailureandModuleResult<int>.Failurewere different types even though they hold the same data (anException).The new design:
Breaking Changes
None - this is a backward-compatible refactoring. Existing code patterns like
ModuleResult<T>.Failurestill work through the implicit conversion.Test plan
Match,Switch) works correctlyModuleResultType,IsFailure,IsSkippedproperties work correctly for wrapper typesGenerated with Claude Code