Welcome to your next step in mastering Clean Code! 🚀 Previously, we emphasized the significance of naming conventions in clean coding. Now, we delve into the realm of functions and methods, which serve as the backbone of application logic and are crucial for code organization and execution. Structuring these functions effectively is vital for enhancing the clarity and maintainability of a codebase. In this lesson, we'll explore best practices and techniques to ensure our code remains clean, efficient, and readable.
Clean Functions at a Glance
Let's outline the key principles for writing clean functions:
Keep functions small. Small functions are easier to read, comprehend, and maintain.
Focus on a single task. A function dedicated to one task is more reliable and simpler to debug.
Limit arguments to three or fewer. Excessive arguments complicate the function signature and make it difficult to understand and use.
Avoid boolean flags. Boolean flags can obscure the code's purpose; consider separate methods for different behaviors.
Eliminate side effects. Functions should avoid altering external state or depending on external changes to ensure predictability.
Implement the DRY principle. Employ helper functions to reuse code, minimizing redundancy and enhancing maintainability.
Now, let's take a closer look at each of these rules.
Keep Functions Small
Single Responsibility
Limit Number of Arguments
Avoid Boolean Flags
Avoid Side Effects
Don't Repeat Yourself (DRY)
Avoid code repetition by introducing helper functions to reduce redundancy and improve maintainability.
The printUserInfo and printManagerInfo functions below repeat similar logic, violating the DRY principle:
In this lesson, we learned that clean functions are key to maintaining readable and maintainable code. By keeping functions small, adhering to the Single Responsibility Principle, limiting arguments, avoiding side effects, and embracing the DRY principle, you set a strong foundation for clean coding. Next, we'll practice these principles to further sharpen your coding skills! 🎓
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal
Functions should remain small, and if they become too large, consider splitting them into multiple, focused functions. While there's no fixed rule on what counts as large, a common guideline is around 15 to 25 lines of code, often defined by team conventions.
Below, you can see the processOrder function, which is manageable but has the potential to become unwieldy over time:
PHP
function processOrder($order, $inventory, $logger) { // Step 1: Validate the order if (!$order->isValid()) { $logger->log("Invalid Order"); return; } // Step 2: Process payment if (!$order->processPayment()) { $logger->log("Payment failed"); return; } // Step 3: Update inventory $inventory->update($order->getItems()); // Step 4: Notify customer $order->notifyCustomer(); // Step 5: Log order processing $logger->log("Order processed successfully");}
Given that this process involves multiple steps, it can be improved by extracting each step into a dedicated function, as shown below:
PHP
function processOrder($order, $inventory, $logger) { // Step 1: Validate the order if (!validateOrder($order, $logger)) return; // Step 2: Process payment if (!processPayment($order, $logger)) return; // Step 3: Update inventory updateInventory($order, $inventory); // Step 4: Notify customer notifyCustomer($order); // Step 5: Log order processing logOrderProcessing($logger);}function validateOrder($order, $logger) { if (!$order->isValid()) { $logger->log("Invalid Order"); return false; } return true;}function processPayment($order, $logger) { if (!$order->processPayment()) { $logger->log("Payment failed"); return false; } return true;}function updateInventory($order, $inventory) { $inventory->update($order->getItems());}function notifyCustomer($order) { $order->notifyCustomer();}function logOrderProcessing($logger) { $logger->log("Order processed successfully");}
A function should embody the principle of doing one thing only. If a function handles multiple responsibilities, it may include several logical sections. Below you can see the saveAndNotifyUser function, which is both too lengthy and does multiple different things at once:
PHP
function saveAndNotifyUser($user, $dataSource, $webClient) { // Save user to the database $sql = "INSERT INTO users (name, email) VALUES (?, ?)"; try { $connection = $dataSource->getConnection(); $statement = $connection->prepare($sql); // Set user details in the prepared statement $statement->execute([$user->getName(), $user->getEmail()]); } catch (Exception $e) { echo $e->getMessage(); // Handle exception } // Send a welcome email to the user $webClient->post('/sendWelcomeEmail', [ 'json' => ['name' => $user->getName(), 'email' => $user->getEmail()] ]);}
To enhance this code, you can create two dedicated functions for saving the user and sending the welcome email. This results in dedicated responsibilities for each function and clearer code coordination:
PHP
function saveAndNotifyUser($user, $dataSource, $webClient) { // Save user to the database saveUser($user, $dataSource); // Send a welcome email to the user notifyUser($user, $webClient);}function saveUser($user, $dataSource) { $sql = "INSERT INTO users (name, email) VALUES (?, ?)"; try { $connection = $dataSource->getConnection(); $statement = $connection->prepare($sql); // Set user details in the prepared statement $statement->execute([$user->getName(), $user->getEmail()]); } catch (Exception $e) { echo $e->getMessage(); // Handle exception }}function notifyUser($user, $webClient) { $webClient->post('/sendWelcomeEmail', [ 'json' => ['name' => $user->getName(), 'email' => $user->getEmail()] ]);}
Try to keep the number of function arguments to a maximum of three, as having too many can make functions less understandable and harder to use effectively. 🤔
Consider the saveAddress function below with five arguments, which makes the function less clean:
PHP
function saveAddress($street, $city, $state, $zipCode, $country) { // Logic to save address}
A cleaner version encapsulates the details into an Address object, reducing the number of arguments and making the function signature clearer:
PHP
function saveAddress($address) { // Logic to save address}
Boolean flags in functions can create confusion, as they often suggest multiple pathways or behaviors within a single function. Instead, use separate methods for distinct behaviors. 🚫
The setFlag function below uses a boolean flag to indicate user status, leading to potential complexity:
PHP
function setFlag($user, $isAdmin) { // Logic based on flag}
A cleaner approach is to have distinct methods representing the different behaviors:
PHP
function grantAdminPrivileges($user) { // Logic for admin rights}function revokeAdminPrivileges($user) { // Logic to remove admin rights}
A side effect occurs when a function modifies some state outside its scope or relies on something external. This can lead to unpredictable behavior and reduce code reliability.
Below, the addToTotal function demonstrates a side effect by modifying an external state:
PHP
// Not Clean - Side Effectfunction addToTotal($value) { global $total; $total += $value; // modifies external state return $total;}
A cleaner version, calculateTotal, performs the operation without altering any external state:
PHP
// Clean - No Side Effect 🌟function calculateTotal($initial, $value) { return $initial + $value;}