Beyond the Linter

Crafting Enduring Code



Christopher R. Bilger

October 15th, 2025

Agenda



  • The Problem: Linters Can't Catch Everything
  • The Cost of "Just Ship It"
  • The Three Pillars of Enduring Code
  • Practical Patterns and Refactorings
  • Conclusion + Q&A

Why This Topic Matters



  • Code quality directly impacts team productivity and product reliability
  • Linters are a great start, but they don't ensure maintainable code
  • Understanding design principles helps create systems that endure change
  • Investing in code quality pays off in reduced technical debt and faster feature delivery

The Problem: Linters Can't Catch Everything



Note: This presentation focuses on TypeScript, but the concepts apply to any programming language.

What Linters Do Well



  • Enforce syntax rules and formatting standards
  • Catch common mistakes (unused variables, missing semicolons)
  • Ensure consistency across the codebase
  • Validate that code is technically correct


But they can't judge whether code is understandable or maintainable.

Example: Linter-Approved Code

This code passes all linting checks:

              
function processUserData(u: any) {
  if (u.s === 'active') {
    if (u.r === 'admin') {
      if (u.p && u.p.length > 0) {
        ...
      }
    }
  }
  return { access: 'limited', features: ['dashboard'] };
}
              
            

The Spell-Checker Analogy


"The brilliant blue banana ate the moon's quiet whispers."


✅ Spell-checker: No errors!

❌ Reader: This makes no sense.

Linters check grammar. They don't check for meaning.

The Cost of "Just Ship It"



  • The "writability" trap
  • Technical debt compounds over time
  • Real business costs of poor code quality

The "Writability" Trap



Writability ⚡

How fast you can write code now

Sustainability 🏛️

How easy it is to read, maintain, and test later


Optimizing for speed today creates maintenance debt tomorrow.

Technical Debt is Design Debt



Poorly designed code has higher interest:


  • Every change takes longer
  • Every bug fix introduces new bugs
  • Every new feature requires refactoring
  • Engineers are afraid to touch the code

Real Business Costs



  • 🐌 Slower feature development
  • 🧠 High cognitive load on engineers
  • 🐛 Increased bug count and severity
  • 💸 Higher onboarding costs
  • ⏱️ More time debugging than building

Example: Code Smell

Long Parameter List: This is hard to call, hard to test, and hard to change.

              
async function createAppointment(
  patientId: string,
  providerId: string,
  appointmentType: string,
  startTime: Date,
  endTime: Date,
  timezone: string,
  ...
) {
  ...
              
            

The "Jenga" Codebase


Every "quick fix" makes the tower more unstable.


  • Fix a bug in module A...
  • ...and something breaks in module C
  • Nobody knows why 🎲


This is caused by high coupling.

The Three Pillars of Enduring Code



  • Readability
  • Maintainability
  • Testability

Pillar #1: Readability



Code should communicate intent clearly.


  • Code is read 10x more than it's written
  • Clear naming eliminates the need for comments
  • Functions should do one thing and do it well
  • Self-documenting code reduces cognitive load

Pillar #2: Maintainability



Code should adapt to change easily.


  • High cohesion: Related functionality stays together
  • Low coupling: Components are independent
  • Changes in one place don't ripple unpredictably
  • New features don't require modifying existing code

Pillar #3: Testability



Code should be verifiable in isolation.


  • Tests provide confidence in deployment
  • Dependencies can be mocked or stubbed
  • Fast feedback loops during development
  • Testable code is a byproduct of good design

The Foundation: SOLID Principles


These pillars are supported by time-tested design principles:


  • Single Responsibility: A class should have one reason to change
  • Open/Closed: Open for extension, closed for modification
  • Liskov Substitution: Subtypes must be substitutable for their base types
  • Interface Segregation: Clients shouldn't depend on unused interfaces
  • Dependency Inversion: Depend on abstractions, not concretions

SOLID Principles Examples


  • Single Responsibility: A UserService handles user logic, not payment processing
  • Open/Closed: Add new payment methods via new classes, not modifying existing code
  • Liskov Substitution: A Square class can be used wherever a Rectangle is expected
  • Interface Segregation: Separate interfaces for reading and writing data
  • Dependency Inversion: Inject database clients via interfaces, not concrete implementations

Practical Patterns and Refactorings



  • Improving readability with the Extract Method
  • Improving maintainability with the Strategy Pattern
  • Improving testability with Dependency Injection

Improving Readability: Extract Method


Replace complex logic blocks with well-named functions.

Improving Readability: Extract Method (Before)


              
function handleRefund(userId: string, orderId: string) {
  const user = await getUser(userId);
  const order = await getOrder(orderId);

  // Check if user is eligible
  if (
    order.status === 'completed' &&
    order.purchaseDate > Date.now() - 24 * 60 * 60 * 1000 &&
    user.refundCount < 3 &&
    !order.isDigitalProduct
  ) {
    await processRefund(order);
  }
}
              
            

Improving Readability: Extract Method (After)


              
function handleRefund(userId: string, orderId: string) {
  const user = await getUser(userId);
  const order = await getOrder(orderId);

  if (isEligibleForRefund(user, order)) {
    await processRefund(order);
  }
}

function isEligibleForRefund(user: User, order: Order) {
  const withinRefundWindow =
    order.purchaseDate > Date.now() - 24 * 60 * 60 * 1000;

  return (
    order.status === 'completed' &&
    withinRefundWindow &&
    user.refundCount < 3 &&
    !order.isDigitalProduct
  );
}
              
            

Improving Maintainability: Strategy Pattern


Replace conditional complexity with polymorphism.

Improving Maintainability: Strategy Pattern (Before)


              
function processPayment(method: string, amount: number, details: any) {
  if (method === 'credit_card') {
    return stripePay(details.cardToken, amount);
  } else if (method === 'paypal') {
    return paypalPay(details.email, amount);
  } else if (method === 'apple_pay') {
    return applePayProcess(details.token, amount);
  }
  throw new Error('Unknown payment method');
}
              
            

Improving Maintainability: Strategy Pattern (After)


              
interface PaymentStrategy {
  process(amount: number, details: PaymentDetails): Promise<PaymentResult>;
}

class CreditCardStrategy implements PaymentStrategy {
  async process(amount: number, details: PaymentDetails) {
    return stripePay(details.cardToken, amount);
  }
}

class PaymentService {
  constructor(private strategies: Map<string, PaymentStrategy>) {}

  async processPayment(method: string, amount: number, details: PaymentDetails) {
    const strategy = this.strategies.get(method);
    if (!strategy) throw new Error('Unknown payment method');
    return strategy.process(amount, details);
  }
}
              
            

Improving Testability: Dependency Injection


Depend on abstractions, not concrete implementations.

Improving Testability: Dependency Injection (Before)


              
class AppointmentService {
  async scheduleAppointment(data: AppointmentData) {
    // Hard-coded dependencies - can't test without hitting real APIs
    const smsClient = new TwilioSmsClient();
    const calendar = new GoogleCalendarClient();

    const appointment = await this.createAppointment(data);
    await smsClient.sendSms(appointment.patientPhone, 'Confirmed!');
    await calendar.addEvent({ title: 'Appointment', start: appointment.startTime });

    return appointment;
  }
}
              
            

Improving Testability: Dependency Injection (After)


              
interface INotificationClient {
  sendSms(phone: string, message: string): Promise<void>;
}

interface ICalendarClient {
  addEvent(event: CalendarEvent): Promise<void>;
}

class AppointmentService {
  constructor(
    private smsClient: INotificationClient,
    private calendar: ICalendarClient
  ) {}

  async scheduleAppointment(data: AppointmentData) {
    const appointment = await this.createAppointment(data);
    await this.smsClient.sendSms(appointment.patientPhone, 'Confirmed!');
    await this.calendar.addEvent({ title: 'Appointment', start: appointment.startTime });
    return appointment;
  }
}
              
            

Best Practices We Should Follow



  • Code reviews focused on design, not just correctness
  • Refactor as you go, not as a separate phase
  • Write tests that validate behavior, not implementation
  • Favor composition over inheritance

Code Review Checklist



Beyond "does it work?", ask:


  • Can I understand what this code does without comments?
  • If requirements change, how many files need to be modified?
  • Can I test this code without external dependencies?
  • Does this code follow the Single Responsibility Principle?
  • Are there any obvious code smells (long parameter lists, deep nesting, etc.)?

Common Code Smells to Watch For



  • Long Parameter List: More than 3-4 parameters
  • Long Method: Functions longer than ~20 lines
  • Deeply Nested Conditionals: More than 2-3 levels
  • Duplicate Code: Same logic in multiple places
  • Large Class: Classes with too many responsibilities
  • Feature Envy: Methods that use more of another class than their own

Conclusion + Q&A



  • The Problem: Linters Can't Catch Everything
  • The Cost of "Just Ship It"
  • The Three Pillars of Enduring Code
  • Practical Patterns and Refactorings
  • Q&A

The Job is to Make It Last



Small, intentional improvements compound into enduring systems.

Enduring code is a fundamental investment in:

  • Your team's velocity
  • Your product's reliability
  • Your company's ability to adapt

Challenge for Your Next PR



Try applying one of these refactorings (if it makes sense to do so):


  1. Extract Method to improve readability
  2. Strategy Pattern to improve maintainability
  3. Dependency Injection to improve testability

Additional Resources on Refactoring & Design Patterns



  1. Refactoring Guru: Refactoring
  2. Refactoring Guru: Design Patterns
  3. TypeHero

Powered By



  1. A single, static webpage.
  2. reveal.js
  3. highlight.js