Skip to content

Instantly share code, notes, and snippets.

@timcunningham
Created November 21, 2025 19:44
Show Gist options
  • Select an option

  • Save timcunningham/4d22c0749df90350fa9beb49cb7cb6e8 to your computer and use it in GitHub Desktop.

Select an option

Save timcunningham/4d22c0749df90350fa9beb49cb7cb6e8 to your computer and use it in GitHub Desktop.

Response to Cameron's Refactoring Feedback

Introduction

This document addresses Cameron's feedback on the refactoring work documented in REFACTORING_SUMMARY.md. I'll be honest where I was wrong and defend where I was right.


1. Validator.cfc - XSS protection via sanitization

Verdict: Cameron is RIGHT. I was wrong.

You're absolutely correct - sanitization is NOT validation. This violates the Single Responsibility Principle.

What I should have done:

  • Keep Validator.cfc for validation only (returns true/false, throws validation exceptions)
  • Create a separate Sanitizer.cfc or InputCleaner.cfc for sanitization (transforms input)
  • These are different concerns: validation CHECKS, sanitization MODIFIES

The correct separation:

// Validator.cfc - ONLY validates, returns boolean or throws
function isValidEmail(required string email)
function isValidPhone(required string phone)

// Sanitizer.cfc - ONLY transforms
function sanitizeHTML(required string input)
function stripXSS(required string input)

My mistake

I conflated input validation with input sanitization because they often happen together in the workflow, but they're conceptually distinct operations.


2. "Helper" and "Utility Layer" - dumping ground concern

Verdict: Cameron is RIGHT to be suspicious.

"Helper" and "Utils" are code smells that often indicate:

  • Lack of proper domain modeling
  • Functions that don't have a clear home
  • Procedural thinking in an OO codebase

Partial defense:

  • QueryHelper.cfc has a clear, focused purpose: safe database query execution with parameterization
  • It's not a dumping ground - it's specifically about SQL query construction and execution
  • Better name might be QueryExecutor.cfc or DatabaseService.cfc

What I should reconsider:

  • Rename QueryHelper.cfcQueryExecutor.cfc or DatabaseService.cfc
  • If "Utility Layer" contains disparate functions, break them into properly named services
  • Each util should answer "What does this do?" not "Where else could I put this?"

3. ConfigManager.cfc - Secure credential handling

Verdict: Cameron is RIGHT.

"Secure credential handling" doesn't belong in a config manager. This violates SRP.

What I should have done:

// ConfigManager.cfc - ONLY manages configuration
- Read config files
- Environment detection (prod/staging/dev)
- Config value retrieval

// CredentialService.cfc or SecretsManager.cfc - Handles secrets
- Secure credential storage/retrieval
- Encryption/decryption
- Integration with secret management systems (Vault, AWS Secrets Manager)

// EnvironmentService.cfc - Environment detection
- Detect environment (prod/staging/dev)
- Environment-specific behavior

My mistake

I lumped "configuration" and "secrets management" together because they're both about "settings," but they're different concerns with different security requirements.


4. PolicyService.cfc - Payment eligibility rules

Verdict: Cameron is RIGHT.

"Payment eligibility rules" in PolicyService violates SRP. This is business logic that belongs elsewhere.

What I should have done:

// PolicyService.cfc
- Policy CRUD operations
- Policy lookup and validation
- Policy-specific business rules

// PaymentEligibilityService.cfc or EligibilityService.cfc
- Determines if policy is eligible for payment
- Payment-related business rules
- Could be used by PaymentService

// OR: If simple, could be in PaymentService
- But only if it's truly payment-processing logic, not policy logic

The test

If the rule is "Does this policy qualify for a payment?" that's probably in EligibilityService. If the rule is "Can we process this payment right now?" that's PaymentService.


5. PaymentService.cfc - Validation

Verdict: Cameron is right to question this.

Clarification needed:

  • If "validation" means "Is this payment data valid?" → That should use Validator.cfc
  • If "validation" means "Payment-specific business rules" (e.g., "Can't refund more than original amount") → That's fine in PaymentService

My defense:

  • Domain-specific validation (business rules) CAN live in the service
  • General-purpose validation (format checking) should use Validator.cfc

Example:

// PaymentService.cfc
function processRefund(required struct refundData) {
    // Use Validator for format checking
    validator.validateAmount(refundData.amount);

    // Domain-specific business rule (belongs here)
    if (refundData.amount > originalPayment.amount) {
        throw(type="BusinessRuleException", message="Refund cannot exceed original payment");
    }

    // Process refund...
}

Verdict

Depends on what "validation" means. If I conflated format validation with business rules, Cameron is right.


6. FeeService.cfc - Primoris API integration

Verdict: Cameron is ABSOLUTELY RIGHT.

API integration is a different layer than business logic. This violates SRP and makes testing harder.

What I should have done:

// FeeService.cfc - Business logic
- Fee calculation logic
- Fee breakdown generation
- Fee validation rules

// PrimorisApiClient.cfc or PrimorisGateway.cfc - Infrastructure
- HTTP calls to Primoris API
- Request/response transformation
- Retry logic for API failures
- Error handling for API-specific errors

// Usage in FeeService:
property name="primorisClient" inject="PrimorisApiClient";

function calculateFee(required struct data) {
    local.feeData = primorisClient.fetchFeeData(data);
    return computeFeeBreakdown(feeData);
}

Benefits of separating:

  • Can mock PrimorisApiClient in tests
  • Can swap API providers without changing FeeService
  • Retry logic is infrastructure concern, not business logic
  • Clear boundary between domain and external integration

My mistake

I left infrastructure concerns (HTTP, retry logic) mixed with domain logic (fee calculation).


7. Application.cfc vs application.cfc

Verdict: Cameron is RIGHT (mostly).

Defense:

  • In ColdFusion, component names are case-insensitive
  • Application.cfc is the convention and more readable
  • I should have used Application.cfc in documentation for consistency

BUT:

In the actual codebase, it must be Application.cfc because:

  • It's a special framework file that expects that exact name
  • Convention is Pascal case for components

Verdict

You're right, I should be consistent with proper casing in documentation.


Summary: Where I Was Wrong

  1. Validator.cfc - Conflated validation with sanitization
  2. ConfigManager.cfc - Mixed configuration with secrets management
  3. PolicyService.cfc - Payment eligibility rules don't belong there
  4. ⚠️ PaymentService.cfc - Depends on what "validation" means
  5. FeeService.cfc - API integration should be separate
  6. ⚠️ "Helper" naming - Suspicious but depends on implementation
  7. Application.cfc - Should use proper casing

What This Reveals

Cameron's feedback exposes that I was still not applying SRP rigorously enough:

Problems identified:

  • I grouped by "feels related" instead of by "has one reason to change"
  • I mixed layers: Domain logic with infrastructure (API calls)
  • I used vague names: "Helper," "Utils," "validation" without precision

The fix:

I need to do another pass with these questions for each class:

  1. What is the ONE reason this class would change?
  2. Is this domain logic, application logic, or infrastructure?
  3. Can I name this class with a precise noun (not Helper/Manager/Util)?

Conclusion

Cameron, you're right. I need to refactor the refactor. Thank you for the excellent feedback.

The key lesson: Single Responsibility Principle isn't about grouping related things together - it's about ensuring each class has exactly ONE reason to change.


Generated in response to feedback on REFACTORING_SUMMARY.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment