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
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
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
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 { }
}- 5-15 lines is ideal
- One level of abstraction per function
- Should fit on one screen without scrolling
- Name tells you everything it does
// Probably TOO granular
function add( $a, $b ) {
return $a + $b;
}
function increment_count( $count ) {
return add( $count, 1 );
}// 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 );
}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!"
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 { }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();
}
}Functions should operate at ONE level of abstraction:
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;
}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' );
}
}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)
}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 { }
}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.
}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 );
}✅ 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
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.