Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/C#-Coding-Style-Essential.md
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,30 @@ public DeliveryConfirmation ProcessDeliveryRequest(DeliveryRequest request)
return DeliveryConfirmation.Successful();
}


// Reduce nesting with helper extraction
public string ProcessFile(string path, bool applyFilters = true)
{
var fileExists = File.Exists(path);
if (fileExists) return ProcessExistingFile(path, applyFilters);

var fallbackPath = TryFindAlternative(path);
var fileNotFound = fallbackPath == null;
if (fileNotFound) return "File not found";

Logger.Info($"Using alternative: {fallbackPath}");
return ProcessExistingFile(fallbackPath, applyFilters);
}

private string ProcessExistingFile(string path, bool applyFilters)
{
// All file processing logic extracted here
// Called by both direct and fallback paths
var content = File.ReadAllText(path);
return applyFilters ? ApplyFormatting(content) : content;
}


// Use ternary for returns - single line for very simple cases
public string GetDeliveryStatus(Package package)
{
Expand All @@ -829,12 +853,14 @@ public string GetDeliveryInstructions(DeliveryAddress address)

### Principles:
- Use early returns to reduce nesting and improve readability
- Extract complex processing to private helpers when validation leads to nested logic
- Use ternary operators for simple conditional return values
- Return empty collections instead of null for collection results
- Use expression-bodied methods for very simple returns

### Notes:
- Early returns make code more readable by reducing indentation levels
- Helper extraction keeps validation/routing flat while enabling code reuse
- Consistent return types make your APIs more predictable

## 17. Parameter Handling
Expand Down
159 changes: 159 additions & 0 deletions docs/C#-Coding-Style-Expanded.md
Original file line number Diff line number Diff line change
Expand Up @@ -3538,6 +3538,35 @@ public OptimizedRoute PlanDeliveryRoute(List<Package> packages, DeliveryVehicle

return OptimizedRoute.Successful(routeStops, estimatedDuration, fuelConsumption);
}


// Delivery hub organization - reducing routing complexity with specialized processing centers
public string ProcessPackage(string trackingNumber)
{
// Main delivery hub check - package already in system
var packageExists = _warehouse.HasPackage(trackingNumber);
if (packageExists) return ProcessExistingPackage(trackingNumber);

// Backup hub search - check alternative storage facilities (TOP LEVEL - no extra nesting)
var alternativeLocation = _backupWarehouse.FindPackage(trackingNumber);
var packageNotFound = alternativeLocation == null;
if (packageNotFound) return "Package not found in any facility";

// Found in backup facility - use same processing workflow
Logger.Info($"Package located in backup facility: {alternativeLocation}");
return ProcessExistingPackage(alternativeLocation);
}

private string ProcessExistingPackage(string location)
{
// Centralized package processing - used by both main and backup routes
// Scan package, verify contents, update tracking, prepare for delivery
var package = _packageService.Retrieve(location);
var scanResult = _scannerService.VerifyContents(package);
_trackingService.UpdateStatus(package.TrackingNumber, "Processing");
return $"Package {package.TrackingNumber} ready for delivery from {location}";
}

```

### Core Principles
Expand All @@ -3562,6 +3591,15 @@ Think of method returns as delivery confirmations and package manifests from a p
- Make return values self-documenting through clear property names and status indicators
- Provide actionable information that helps callers respond appropriately to different outcomes


**Specialized Processing Centers (Helper Extraction):**
- Extract complex package processing to dedicated facilities (private helpers) when validation leads to nested operations
- Keep routing logic flat at the hub level - main delivery path and backup path both visible at same level
- Centralize processing in specialized centers that multiple routes can use
- Route selection happens at top level with zero extra nesting - hub checks package location then routes to processing
- Processing centers handle all actual work - validation layer just decides which center to use


### Why It Matters

Imagine a delivery service that provides unclear confirmations, inconsistent status reports, and unpredictable responses to delivery problems. Customers would never know if packages were delivered, rejected, or lost, making the service unreliable and frustrating to use.
Expand Down Expand Up @@ -3871,6 +3909,127 @@ public class DeliveryProcessor
}
```



#### Evolution: Reducing Nesting with Helper Extraction

Let's see how a method with nested validation logic can evolve into a flat, maintainable structure:

**Initial Version - Deeply nested delivery routing:**

```csharp
// BAD: Complex routing logic buried inside validation
public string RoutePackage(string trackingNumber)
{
var packageInMainHub = _mainWarehouse.HasPackage(trackingNumber);
if (!packageInMainHub)
{
// NESTED: Fallback logic indented inside validation
var alternateLocation = _backupWarehouse.FindPackage(trackingNumber);
if (alternateLocation != null)
{
// DOUBLE NESTED: Processing logic further indented
var package = _packageService.Retrieve(alternateLocation);
var scanResult = _scannerService.VerifyContents(package);
_trackingService.UpdateStatus(package.TrackingNumber, "Processing");
LogInfo($"Retrieved from backup: {alternateLocation}");
return $"Package ready from {alternateLocation}";
}
else
{
return "Package not found";
}
}

// Main processing at end, separated from fallback by many lines
var mainPackage = _packageService.Retrieve(trackingNumber);
var mainScan = _scannerService.VerifyContents(mainPackage);
_trackingService.UpdateStatus(mainPackage.TrackingNumber, "Processing");
return $"Package ready from main hub";
}
```

**Problems:**
- Processing logic duplicated in two places
- Fallback path heavily nested (two levels deep)
- Hard to see both paths at same level
- Main path and fallback path not clearly separated

**Intermediate Version - Extract duplication but still nested:**

```csharp
// BETTER: Extracted processing but routing still nested
public string RoutePackage(string trackingNumber)
{
var packageInMainHub = _mainWarehouse.HasPackage(trackingNumber);
if (!packageInMainHub)
{
// STILL NESTED: Fallback logic indented
var alternateLocation = _backupWarehouse.FindPackage(trackingNumber);
if (alternateLocation != null)
{
LogInfo($"Retrieved from backup: {alternateLocation}");
return ProcessPackage(alternateLocation); // Helper reduces duplication
}
return "Package not found";
}

return ProcessPackage(trackingNumber); // Main path uses same helper
}

private string ProcessPackage(string location)
{
var package = _packageService.Retrieve(location);
var scanResult = _scannerService.VerifyContents(package);
_trackingService.UpdateStatus(package.TrackingNumber, "Processing");
return $"Package ready from {location}";
}
```

**Better, but:**
- Fallback logic still nested inside validation
- Can't see both routes at the same indentation level
- Processing helper is good, but routing could be flatter

**Final Version - Flat routing with helper extraction:**

```csharp
// EXCELLENT: Flat routing, clear paths, reusable processing
public string RoutePackage(string trackingNumber)
{
// Direct route: package in main hub
var packageInMainHub = _mainWarehouse.HasPackage(trackingNumber);
if (packageInMainHub) return ProcessPackage(trackingNumber);

// Backup route: search alternative facilities (TOP LEVEL - no nesting)
var alternateLocation = _backupWarehouse.FindPackage(trackingNumber);
var packageNotFound = alternateLocation == null;
if (packageNotFound) return "Package not found in any facility";

// Found in backup - use same processing workflow
LogInfo($"Package located in backup facility: {alternateLocation}");
return ProcessPackage(alternateLocation);
}

private string ProcessPackage(string location)
{
// Centralized processing - called by both routes
var package = _packageService.Retrieve(location);
var scanResult = _scannerService.VerifyContents(package);
_trackingService.UpdateStatus(package.TrackingNumber, "Processing");
return $"Package ready from {location}";
}
```

**Wins:**
- Both routing paths visible at same indentation level (zero extra nesting)
- Validation and routing stays flat and clear
- Processing logic centralized and reusable
- Each function has single responsibility: RoutePackage = routing, ProcessPackage = processing
- Easy to add more routing paths without increasing complexity

**Key Insight:** When validation leads to complex processing, don't nest the processing inside the validation. Instead, use early returns to route to a specialized processing helper that both paths can share.

### Deeper Understanding

#### Delivery Confirmation Patterns
Expand Down
139 changes: 139 additions & 0 deletions src/common/Helpers/FileHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -684,5 +684,144 @@ private static char[] GetInvalidFileNameCharsForWeb()
return null;
}

/// <summary>
/// Normalizes bash-style paths (e.g., /c/folder/file.txt) to Windows paths (e.g., C:\folder\file.txt).
/// On Windows, Git Bash uses /c/ to represent C:\, /d/ for D:\, etc.
/// </summary>
/// <param name="path">The path to normalize</param>
/// <returns>The normalized path, or the original path if no normalization needed</returns>
private static string NormalizeBashStylePath(string path)
{
if (string.IsNullOrEmpty(path)) return path;

// Check for bash-style paths: /c/, /d/, etc. (Unix-style root with single letter)
// Pattern: starts with / followed by single letter followed by /
if (path.Length >= 3 &&
path[0] == '/' &&
char.IsLetter(path[1]) &&
path[2] == '/')
{
var driveLetter = char.ToUpper(path[1]);
var remainingPath = path.Substring(3); // Skip "/c/"

// Convert forward slashes to backslashes
remainingPath = remainingPath.Replace('/', Path.DirectorySeparatorChar);

return $"{driveLetter}:{Path.DirectorySeparatorChar}{remainingPath}";
}

return path;
}


/// <summary>
/// Attempts to find a file using progressive fuzzy matching when the exact path doesn't exist.
/// Progressively relaxes path matching from right-to-left by adding wildcards.
/// </summary>
/// <param name="requestedPath">The originally requested file path that doesn't exist</param>
/// <returns>The matched file path if exactly one match is found, null otherwise</returns>
public static string? TryFuzzyFindFile(string requestedPath)
{
// Already checked that exact path doesn't exist before calling this
requestedPath = PathHelpers.ExpandPath(requestedPath);

// Normalize bash-style paths (e.g., /c/folder -> C:\folder)
var normalizedPath = NormalizeBashStylePath(requestedPath);
if (normalizedPath != requestedPath)
{
Logger.Verbose($"TryFuzzyFindFile: Normalized bash-style path '{requestedPath}' -> '{normalizedPath}'");

// Check if the normalized path exists exactly
if (File.Exists(normalizedPath))
{
Logger.Info($"TryFuzzyFindFile: Found file after bash path normalization '{requestedPath}' -> '{normalizedPath}'");
return normalizedPath;
}

// Use the normalized path for fuzzy matching
requestedPath = normalizedPath;
}

// Split path into segments
var segments = requestedPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

if (segments.Length == 0) return null;

// Strategy: Progressively add * suffix to segments from right-to-left
// Example: a/b/c/file.cs -> a/b/c*/file.cs -> a/b/*/file.cs -> a/*/*/file.cs -> **/file.cs

for (int wildcardIndex = segments.Length - 2; wildcardIndex >= 0; wildcardIndex--)
{
// Build pattern with * suffix on segments from wildcardIndex onwards (except the filename)
var patternSegments = new string[segments.Length];
for (int i = 0; i < segments.Length; i++)
{
if (i >= wildcardIndex && i < segments.Length - 1)
{
// Replace this segment with *
patternSegments[i] = "*";
}
else
{
patternSegments[i] = segments[i];
}
}

var pattern = string.Join(Path.DirectorySeparatorChar.ToString(), patternSegments);
Logger.Verbose($"TryFuzzyFindFile: Trying pattern: {pattern}");

try
{
var matches = FindFiles(pattern).ToList();

if (matches.Count == 1)
{
Logger.Info($"TryFuzzyFindFile: Found unique match for '{requestedPath}' -> '{matches[0]}'");
return matches[0];
}
else if (matches.Count > 1)
{
Logger.Verbose($"TryFuzzyFindFile: Pattern '{pattern}' matched {matches.Count} files (ambiguous)");
// Keep trying more specific patterns
}
}
catch (Exception ex)
{
Logger.Verbose($"TryFuzzyFindFile: Exception trying pattern '{pattern}': {ex.Message}");
}
}

// Last resort: try filename with ** (search anywhere)
var filename = segments[segments.Length - 1];
var lastResortPattern = $"**{Path.DirectorySeparatorChar}{filename}";
Logger.Verbose($"TryFuzzyFindFile: Trying last resort pattern: {lastResortPattern}");

try
{
var matches = FindFiles(lastResortPattern).ToList();

if (matches.Count == 1)
{
Logger.Info($"TryFuzzyFindFile: Found unique match with last resort for '{requestedPath}' -> '{matches[0]}'");
return matches[0];
}
else if (matches.Count > 1)
{
Logger.Info($"TryFuzzyFindFile: Last resort pattern matched {matches.Count} files, suggestions:");
foreach (var match in matches.Take(5))
{
Logger.Info($" - {match}");
}
}
}
catch (Exception ex)
{
Logger.Verbose($"TryFuzzyFindFile: Exception trying last resort pattern: {ex.Message}");
}

Logger.Verbose($"TryFuzzyFindFile: No unique match found for '{requestedPath}'");
return null;
}

private static char[] _invalidFileNameCharsForWeb = GetInvalidFileNameCharsForWeb();
}
Loading