Skip to content

Latest commit

 

History

History
389 lines (323 loc) · 9.5 KB

File metadata and controls

389 lines (323 loc) · 9.5 KB

Function Length - Small Functions vs God Functions

The Problem

Long functions that do too much are problematic:

  • Hard to understand - Too much to hold in your head
  • Hard to test - Testing one thing requires setting up everything
  • Hard to reuse - Can't reuse parts of the logic
  • Hard to modify - Changes affect too many things
  • Violates Single Responsibility Principle - Function does many things

Bad Practice (bad.php)

function handle_post_submission() {
    // Validate form (15 lines)
    // Sanitize data (7 lines)
    // Create post (10 lines)
    // Handle category (5 lines)
    // Handle tags (5 lines)
    // Handle featured image (10 lines)
    // Handle custom fields (10 lines)
    // Send notifications (5 lines)
    // Log activity (8 lines)
    // Update user stats (5 lines)
    // Redirect (3 lines)
}
// Total: 83 lines doing 11 different things!

Problems:

  • Does 11 different things
  • Impossible to test individual parts
  • Can't reuse validation logic elsewhere
  • Hard to understand what it does overall
  • Changing one part risks breaking others

Good Practice (good.php)

Break Into Small, Focused Functions

class PostSubmissionHandler {
    public function handle(): void {
        $this->verify_security();
        $data = $this->get_validated_data();
        $post_id = $this->create_post( $data );
        $this->process_taxonomy( $post_id, $data );
        $this->process_featured_image( $post_id );
        $this->process_custom_fields( $post_id, $data );
        $this->send_notifications( $post_id, $data );
        $this->log_activity( $post_id, $data );
        $this->update_user_stats();
        $this->redirect_to_post( $post_id );
    }
    
    // Each method does ONE thing (5-10 lines each)
}

Benefits:

  • Main function reads like a table of contents
  • Each method has single responsibility
  • Easy to test each part independently
  • Easy to reuse validation, logging, etc.
  • Changes are isolated

The Single Responsibility Principle

Bad: One function, multiple responsibilities

function process_user_registration($data) {
    // Validate email (5 lines)
    // Check if email exists (3 lines)
    // Validate username (5 lines)
    // Check if username exists (3 lines)
    // Validate password (3 lines)
    // Create user (7 lines)
    // Set user role (3 lines)
    // Add user meta (5 lines)
    // Send welcome email (5 lines)
    // Log registration (2 lines)
    // Update site stats (3 lines)
}

Good: Each class has one job

class UserRegistrationHandler {
    public function register( array $data ): int|WP_Error {
        $validator = new UserRegistrationValidator( $data );
        if ( ! $validator->is_valid() ) {
            return new WP_Error(
                'validation_failed',
                $validator->get_error_message()
            );
        }
        
        $user_id = $this->create_user( $data );
        $this->setup_user_profile( $user_id, $data );
        $this->send_welcome_email( $user_id, $data );
        $this->log_registration( $data['username'] );
        $this->update_site_stats();
        
        return $user_id;
    }
    
    // Small private methods for each step
}

// Separate validator with ONE job: validate
class UserRegistrationValidator {
    public function is_valid(): bool { }
    private function validate_email(): bool { }
    private function validate_username(): bool { }
    private function validate_password(): bool { }
}

How Small Should Functions Be?

Rule of Thumb

  • 5-15 lines is ideal
  • One level of abstraction per function
  • Should fit on one screen without scrolling
  • Name tells you everything it does

Too Small?

// Probably TOO granular
function add( $a, $b ) {
    return $a + $b;
}

function increment_count( $count ) {
    return add( $count, 1 );
}

Just Right

// Good: Clear purpose, reasonable size
function increment_post_count( int $user_id ): void {
    $count = (int) get_user_meta( $user_id, 'post_count', true );
    update_user_meta( $user_id, 'post_count', $count + 1 );
}

Refactoring Strategy

Step 1: Identify Sections

Look for comments that describe sections:

function process() {
    // Validate input
    // ...
    
    // Process data
    // ...
    
    // Save results
    // ...
    
    // Send notifications
    // ...
}

These comments are screaming "I should be a function!"

Step 2: Extract Methods

function process() {
    $this->validate_input();
    $data = $this->process_data();
    $this->save_results( $data );
    $this->send_notifications();
}

private function validate_input(): void { }
private function process_data(): array { }
private function save_results(array $data): void { }
private function send_notifications(): void { }

Step 3: Extract Classes

If methods don't all relate to the same thing:

// Before: One class doing everything
class PostHandler {
    public function handle() { }
    private function validate() { }
    private function save() { }
    private function send_email() { }
    private function log() { }
}

// After: Separate concerns
class PostHandler {
    public function __construct(
        private PostValidator $validator,
        private PostRepository $repository,
        private EmailSender $mailer,
        private Logger $logger
    ) {}
    
    public function handle() {
        $this->validator->validate();
        $post = $this->repository->save();
        $this->mailer->send();
        $this->logger->log();
    }
}

Levels of Abstraction

Functions should operate at ONE level of abstraction:

Bad: Mixed levels

function create_post($data) {
    // High level
    $validator = new Validator();
    
    // Low level  
    if ( ! isset( $data['title'] ) || strlen( $data['title'] ) === 0 ) {
        return false;
    }
    
    // High level
    $post_id = wp_insert_post( $data );
    
    // Low level
    $wpdb->query( "UPDATE stats SET count = count + 1" );
    
    return $post_id;
}

Good: Consistent level

function create_post( array $data ): int {
    $this->validate_data( $data );
    $post_id = $this->insert_post( $data );
    $this->update_stats();
    
    return $post_id;
}

// Each helper operates at its own level
private function validate_data( array $data ): void {
    if ( empty( $data['title'] ) ) {
        throw new ValidationException( 'Title required' );
    }
}

Real-World Example

Before: 100-line monster

function generate_monthly_report( $month, $year ) {
    global $wpdb;
    
    // Get post stats (10 lines)
    // Get comment stats (10 lines)
    // Get user stats (10 lines)
    // Calculate engagement (5 lines)
    // Get top posts (15 lines)
    // Format report (20 lines)
    // Save report (10 lines)
    // Email report (5 lines)
    // Update last report date (3 lines)
}

After: Composed functions

class MonthlyReportGenerator {
    public function generate(): string {
        $stats = $this->collect_stats();
        $report = $this->format_report( $stats );
        $file_path = $this->save_report( $report );
        $this->email_report( $report );
        $this->update_last_generated();
        
        return $file_path;
    }
}

class MonthlyStatsCollector {
    public function get_post_count(): int { }
    public function get_comment_count(): int { }
    public function get_new_user_count(): int { }
    public function get_engagement_rate(): float { }
    public function get_top_posts(): array { }
}

class ReportFormatter {
    public function format( array $stats ): string { }
}

class ReportSaver {
    public function save( string $report, int $month, int $year ): string { }
}

Testing Benefits

Bad: Can't test parts

function process() {
    // 100 lines of code
}

// Test requires full setup for everything
function test_process() {
    // Mock database
    // Mock email
    // Mock file system
    // Mock user session
    // etc.
}

Good: Test each part

class PostHandler {
    public function __construct(
        private Validator $validator,
        private Repository $repository
    ) {}
}

// Test validator alone
function test_validator() {
    $validator = new Validator();
    assertTrue( $validator->validate( ['title' => 'Test'] ) );
}

// Test repository alone
function test_repository() {
    $repo = new Repository( $mock_wpdb );
    $id = $repo->save( [ 'title' => 'Test' ] );
    assertEquals( 123, $id );
}

Key Takeaways

Functions should do ONE thing
5-15 lines is ideal
One level of abstraction per function
Extract sections into methods
Extract related methods into classes
Function name should describe everything it does

❌ Don't write 100+ line functions
❌ Don't mix levels of abstraction
❌ Don't make functions that need 10 parameters
❌ Don't use section comments instead of extracting functions
❌ Don't duplicate code - extract shared logic

The Bottom Line

If you can't give a function a simple name that describes everything it does, it's doing too much.

// BAD: Name doesn't describe half of what it does
function save_post( $data ) {
    // Validates
    // Sanitizes
    // Inserts
    // Updates taxonomy
    // Sends email
    // Logs activity
    // Updates cache
    // Triggers webhooks
}

// GOOD: Name matches what it does
function save_post( array $data ): int {
    return $this->repository->save( $data );
}

Aim for: The reader should understand the function without reading its implementation.