Skip to content

Latest commit

 

History

History
428 lines (332 loc) · 9.12 KB

File metadata and controls

428 lines (332 loc) · 9.12 KB

Early Returns vs Deep Nesting

The Problem

Nested if statements create "arrow code" that's hard to read:

  • Hard to follow - Logic is deeply nested
  • Hard to find the happy path - What happens when everything is OK?
  • Cognitive overload - Must track multiple indentation levels
  • Difficult to modify - Adding conditions makes it worse
  • Arrow anti-pattern - Code marches to the right

Bad Practice (bad.php)

function update_post_meta_value( $post_id, $meta_key, $meta_value ) {
    $post = get_post( $post_id );
    
    if ( $post ) {
        if ( $post->post_status === 'publish' ) {
            if ( current_user_can( 'edit_post', $post_id ) ) {
                $old_value = get_post_meta( $post_id, $meta_key, true );
                
                if ( $old_value !== $meta_value ) {
                    $result = update_post_meta( $post_id, $meta_key, $meta_value );
                    
                    if ( $result ) {
                        do_action( 'post_meta_updated', $post_id, $meta_key );
                        return true;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

Problems:

  • 6 levels of indentation!
  • The actual work is buried deep inside
  • Hard to see what conditions must be met
  • Lots of else { return false; } noise

Good Practice (good.php)

Guard Clauses (Early Returns)

function update_post_meta_value( int $post_id, string $meta_key, mixed $meta_value ): bool {
    $post = get_post( $post_id );
    
    if ( ! $post ) {
        return false;
    }
    
    if ( $post->post_status !== 'publish' ) {
        return false;
    }
    
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return false;
    }
    
    $old_value = get_post_meta( $post_id, $meta_key, true );
    
    if ( $old_value === $meta_value ) {
        return false;
    }
    
    $result = update_post_meta( $post_id, $meta_key, $meta_value );
    
    if ( ! $result ) {
        return false;
    }
    
    do_action( 'post_meta_updated', $post_id, $meta_key );
    
    return true;
}

Benefits:

  • All code at same indentation level
  • Error conditions handled first
  • Happy path is clear and linear
  • Easy to add new validation
  • Reads top-to-bottom like a checklist

The Pattern

Before: Nested Ifs

if ( condition1 ) {
    if ( condition2 ) {
        if ( condition3 ) {
            // Do the work
        }
    }
}

After: Guard Clauses

if ( ! condition1 ) {
    return;
}

if ( ! condition2 ) {
    return;
}

if ( ! condition3 ) {
    return;
}

// Do the work

Early Continue in Loops

Bad: Nested in Loops

foreach ( $posts as $post ) {
    if ( $post->post_status === 'publish' ) {
        $meta = get_post_meta( $post->ID );
        
        if ( ! empty( $meta ) ) {
            foreach ( $meta as $key => $value ) {
                if ( ! empty( $value[0] ) ) {
                    // Deep inside!
                    process( $key, $value[0] );
                }
            }
        }
    }
}

Good: Early Continue

foreach ( $posts as $post ) {
    if ( $post->post_status !== 'publish' ) {
        continue;
    }
    
    $meta = get_post_meta( $post->ID );
    
    if ( empty( $meta ) ) {
        continue;
    }
    
    foreach ( $meta as $key => $value ) {
        if ( empty( $value[0] ) ) {
            continue;
        }
        
        // Easy to find!
        process( $key, $value[0] );
    }
}

Validation Functions

Bad: Nested Validation

function validate( $data ) {
    if ( isset( $data['title'] ) ) {
        if ( strlen( $data['title'] ) > 0 ) {
            if ( strlen( $data['title'] ) <= 200 ) {
                if ( isset( $data['content'] ) ) {
                    // More nesting...
                    return true;
                }
            }
        }
    }
    return false;
}

Good: Early Return Validation

function validate( array $data ): bool {
    if ( ! isset( $data['title'] ) ) {
        return false;
    }
    
    if ( strlen( $data['title'] ) === 0 ) {
        return false;
    }
    
    if ( strlen( $data['title'] ) > 200 ) {
        return false;
    }
    
    if ( ! isset( $data['content'] ) ) {
        return false;
    }
    
    return true;
}

Even Better: With Error Messages

function validate( array $data ): bool|WP_Error {
    if ( ! isset( $data['title'] ) ) {
        return new WP_Error( 'missing_title', 'Title is required' );
    }
    
    if ( strlen( $data['title'] ) === 0 ) {
        return new WP_Error( 'empty_title', 'Title cannot be empty' );
    }
    
    if ( strlen( $data['title'] ) > 200 ) {
        return new WP_Error( 'title_too_long', 'Title must be 200 characters or less' );
    }
    
    if ( ! isset( $data['content'] ) ) {
        return new WP_Error( 'missing_content', 'Content is required' );
    }
    
    return true;
}

Combining Conditions

Sometimes you can combine related checks:

// Multiple early returns
if ( ! isset( $data['title'] ) ) {
    return false;
}

if ( strlen( $data['title'] ) === 0 ) {
    return false;
}

// Can be combined to:
if ( ! isset( $data['title'] ) || strlen( $data['title'] ) === 0 ) {
    return false;
}

// Or even:
if ( empty( $data['title'] ) ) {
    return false;
}

But don't go too far:

// BAD: Too complex to read
if ( ! isset( $data['title'] ) || strlen( $data['title'] ) === 0 || strlen( $data['title'] ) > 200 || ! isset( $data['content'] ) ) {
    return false;
}

// GOOD: Separate checks are clearer
if ( empty( $data['title'] ) ) {
    return false;
}

if ( strlen( $data['title'] ) > 200 ) {
    return false;
}

if ( ! isset( $data['content'] ) ) {
    return false;
}

Extract Complex Conditions

For complex conditions, extract to variables:

// BAD: Hard to understand
if ( $post->post_status === 'publish' && ( $post->post_author == $user_id || in_array( 'administrator', $user->roles ) || ( in_array( 'editor', $user->roles ) && time() - strtotime( $post->post_date ) < 86400 ) ) ) {
    // ...
}

// GOOD: Extract to readable variables
$is_published = $post->post_status === 'publish';
$is_author    = $post->post_author == $user_id;
$is_admin     = in_array( 'administrator', $user->roles );
$is_editor    = in_array( 'editor', $user->roles );
$is_recent    = time() - strtotime( $post->post_date ) < 86400;

$editor_can_edit = $is_editor && $is_recent;

if ( ! $is_published ) {
    return false;
}

if ( ! ( $is_author || $is_admin || $editor_can_edit ) ) {
    return false;
}

// Continue...

When NOT to Use Early Returns

Early returns work best for validation and error handling.

Don't use them when you need cleanup:

// BAD: Resource leak!
function process_file( $file_path ) {
    $handle = fopen( $file_path, 'r' );
    
    if ( ! $handle ) {
        return false; // OK here
    }
    
    $data = fread( $handle, 1024 );
    
    if ( empty( $data ) ) {
        return false; // ✗ Didn't close handle!
    }
    
    fclose( $handle );
    return $data;
}

// GOOD: Cleanup guaranteed
function process_file( $file_path ) {
    $handle = fopen( $file_path, 'r' );
    
    if ( ! $handle ) {
        return false;
    }
    
    $data   = fread( $handle, 1024 );
    $result = ! empty( $data ) ? $data : false;
    
    fclose( $handle );
    
    return $result;
}

// BETTER: Use try-finally (PHP 5.5+)
function process_file( $file_path ) {
    $handle = fopen( $file_path, 'r' );
    
    if ( ! $handle ) {
        return false;
    }
    
    try {
        $data = fread( $handle, 1024 );
        
        if ( empty( $data ) ) {
            return false;
        }
        
        return $data;
    } finally {
        fclose( $handle );
    }
}

Key Takeaways

Return early from error conditions
Use guard clauses for validation
Keep happy path at main indentation level
Use continue to skip loop iterations
Extract complex conditions to variables

❌ Don't nest if statements deeply
❌ Don't bury the happy path
❌ Don't forget cleanup when using early returns
❌ Don't make combined conditions too complex

The Bottom Line

Code should read like a list of requirements:

// Clear requirements list
function can_publish_post( int $post_id ): bool {
    // Must have a post
    $post = get_post( $post_id );
    if ( ! $post ) {
        return false;
    }
    
    // Must be in draft status
    if ( $post->post_status !== 'draft' ) {
        return false;
    }
    
    // Must have a title
    if ( empty( $post->post_title ) ) {
        return false;
    }
    
    // Must have content
    if ( empty( $post->post_content ) ) {
        return false;
    }
    
    // User must have permission
    if ( ! current_user_can( 'publish_posts' ) ) {
        return false;
    }
    
    // All checks passed!
    return true;
}

Each requirement is clear, and the happy ending is obvious: we return true!