[Clean Code Practices] Mess to Masterpiece with an Example
2023-12-11 20:15:2 Author: blogs.sap.com(查看原文) 阅读量:9 收藏

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.”

— Robert C. Martin, “Clean Code: A Handbook of Agile Software Craftsmanship”

What is Clean Code?

Clean code is more than just error-free syntax. It’s about:

  1. Readability: Can another developer easily understand what the code does?
  2. Maintainability: Can it be extended or modified without major refactorings?
  3. Efficiency: Does the code perform its tasks in an optimal manner?

The “Why” Behind Clean Code

Before diving into specifics, let’s ponder why clean code is paramount:

  1. Maintainability: Today’s tech landscape is dynamic. Features evolve, bugs emerge, and requirements change. Clean code allows modifications to be incorporated seamlessly.
  2. Collaboration: Clean code isn’t just for the author — it’s for the team. Your teammates will thank you when they can effortlessly understand and build upon your work.
  3. Reduced Debugging: Cleaner code leads to fewer bugs and simplifies the debugging process when issues do arise.
  4. Professional Pride: Just as an artist wouldn’t exhibit a rushed painting, professional developers shouldn’t be content with shoddy, hurried code.

Our “Messy” Java Code Sample:

Let’s consider a fragment from a hypothetical Java-based e-commerce system:

public List<String> process(List<String> a, Map<String, String> b, String c) {
    List<String> d = new ArrayList<>();
    for (String e : a) {
        if (b.get(e).equals(c) && e.length() > 10) {
            d.add(e);
        }
    }
    return d;
}

The purpose of this method is unclear due to poor naming and magic numbers.

Lets look into some clean code practices and try to evolve this piece of code.

1. Descriptive Naming

“There are only two hard things in Computer Science: cache invalidation and naming things.” — Phil Karlton

Descriptive naming refers to the practice of using names that provide clear insights into the purpose, usage, and implications of a function, variable, class, or any other code entity.

Why is it important?

  • Clarity: Descriptive names provide instant insight into a variable’s purpose, a method’s functionality, or a class’s responsibility.
  • Reduces Need for Inline Comments: A well-named function can often eliminate the need for comments explaining what the function does.

Originally, our method had the generic name process and variables like ab, and c. Lets apply this principle to our code and assign some meaningful names:

public List<String> filterProductIdsByCategory(List<String> productIds, Map<String, String> productCategoryMapping, String category){
    List<String> filteredProducts = new ArrayList<>();
    
    for (String productId : productIds) {
        if (productCategoryMapping.get(productId).equals(category) && productId.length() > 10) {
            filteredProducts.add(productId);
        }
    }

    return filteredProducts;
}

The method name and variable names are now much clearer, making the code’s purpose evident without diving deep into its logic.

2. Avoid Magic Numbers

Magic numbers are numericals or hard-coded values in your code without a clear explanation or context. The clean coding principle recommends replacing these numbers with named constants.

Why is it important?

  • Enhanced Readability: It’s often unclear what a number stands for. A named constant provides context.
  • Ease of Update: If the value needs to change, it can be updated in one place, preventing potential bugs.

We will replace the magic number 10 with a constant named MIN_PRODUCT_ID_LENGTH. This change makes it clear that we’re enforcing a product ID length condition and offers a single point of change if this criterion needs to be updated.

private static final int MIN_PRODUCT_ID_LENGTH = 10;

public List<String> filterProductIdsByCategory(List<String> productIds, Map<String, String> productCategoryMapping, String category){
    List<String> filteredProducts = new ArrayList<>();
    
    for (String productId : productIds) {
        if (productCategoryMapping.get(productId).equals(category) && productId.length() > MIN_PRODUCT_ID_LENGTH) {
            filteredProducts.add(productId);
        }
    }

    return filteredProducts;
}

3. Decompose your Methods

Decomposition involves breaking down large methods into smaller, more specific methods.

Why is it important?

  • Single Responsibility: Each method should have a clear, single purpose. This ensures that if there’s an issue or an update required, you’re only adjusting a small part of the system.
  • Reusability: Smaller methods can often be reused in multiple places.
  • Testability: Smaller methods are easier to test since they perform a singular function.

Our initial method was doing both filtering by category and validating product ID length. After decomposition:

private static final int MIN_PRODUCT_ID_LENGTH = 10;

public List<String> filterProductIdsByCategory(List<String> productIds, Map<String, String> productCategoryMapping, String category){
    List<String> filteredProducts = new ArrayList<>();

    for (String productId : productIds) {
        if (isProductOfCategory(productId, productCategoryMapping, category) && isValidProductId(productId)) {
            filteredProducts.add(productId);
        }
    }

    return filteredProducts;
}

private boolean isProductOfCategory(String productId, Map<String, String> productCategoryMapping, String category) {
    return productCategoryMapping.get(productId).equals(category);
}

private boolean isValidProductId(String productId) {
    return productId.length() > MIN_PRODUCT_ID_LENGTH;
}

These methods are now modular, with a clear and singular responsibility.

4. Clear Documentation (JavaDocs)

JavaDocs are Java’s built-in documentation tool. They allow developers to write comments for their methods, classes, and variables in a structured manner.

Why is it important?

  • Onboarding: New developers can understand the code’s intent and functionality without diving into the implementation details.
  • Consistency: JavaDocs provide a consistent documentation structure.
  • API Generation: JavaDocs can be used to automatically generate API documentation.

Lets add JavaDocs for our methods, making it clear what each method does, its parameters, and its return values. This added layer of documentation makes the codebase significantly more approachable for other developers.

private static final int MIN_PRODUCT_ID_LENGTH = 10;

/**
 * Filters product IDs by a specified category and validates ID length.
 *
 * @param productIds                list of product IDs.
 * @param productCategoryMapping    a map of product IDs to categories.
 * @param category                  the category to filter by.
 * @return                          a list of filtered product IDs.
 */
public List<String> filterProductIdsByCategory(List<String> productIds, Map<String, String> productCategoryMapping, String category){
    List<String> filteredProducts = new ArrayList<>();
    for (String productId : productIds) {
        if (isProductOfCategory(productId, productCategoryMapping, category) && isValidProductId(productId)) {
            filteredProducts.add(productId);
        }
    }
    return filteredProducts;
}


/**
 * Checks if a product belongs to the specified category.
 *
 * @param productId             the product's ID.
 * @param productCategoryMapping a map of product IDs to categories.
 * @param category              the category to check against.
 * @return                      true if the product belongs to the specified category, false otherwise.
 */
private boolean isProductOfCategory(String productId, Map<String, String> productCategoryMapping, String category) {
    return productCategoryMapping.get(productId).equals(category);
}

/**
 * Validates if the product ID meets length criteria.
 *
 * @param productId the product's ID.
 * @return          true if the product ID length is above the minimum threshold, false otherwise.
 */
private boolean isValidProductId(String productId) {
    return productId.length() > MIN_PRODUCT_ID_LENGTH;
}

The code snippet we began with has certainly evolved into a more readable and maintainable form, wouldn’t you agree?

While our example was a concise snippet, when diving into more intricate code structures, it’s important to also consider the following practices.

5. Code Structure and Conventions

Consistent code structure and adherence to language-specific conventions ensure the codebase is uniform and predictable.

Why is it important?

  • Predictability: Developers can navigate and understand the code faster.
  • Fewer Merge Conflicts: With standardized formatting, the chance of encountering a merge conflict due to formatting differences is reduced.

As per Java conventions, we should ensure that private helper methods are located after public methods and constants are defined at the top of the class. Additionally, consistent spacing, indentation, and brace placement make the code visually clear.

6. Do Not Repeat Yourself (DRY)

The DRY principle emphasizes the elimination of redundancy in code. Instead of writing the same code in multiple places, it’s recommended to encapsulate it in a singular location and reuse it.

Why is it important?

  • Ease of Maintenance: When a change is needed, you update in one place rather than chasing down multiple instances of the same logic.
  • Reduces Errors: Repetitive code can lead to inconsistencies if a particular instance is missed during updates.
  • Improves Code Understandability: A DRY codebase is often more concise and clearer, as the logic isn’t scattered and replicated throughout.

Though our provided Java snippet was compact, in larger codebases, it’s common to find repeated logic or operations. By applying the DRY principle, we’d encapsulate such repeated logic into separate methods or classes, ensuring that there’s a single source of truth.

For instance, if our system had multiple methods filtering lists based on different criteria but using similar logic, we’d centralize the filtering mechanism and make the criteria a variable or a strategy, ensuring consistency and reducing code repetition.

Conclusion

In the ever-evolving world of software, it’s not just about crafting code — it’s about crafting quality code. By incorporating the principles of clean coding into our daily routine, we pave the way for a more sustainable, maintainable, and collaborative coding environment.

Before applying clean code practices…, After embracing clean code principles!

Today’s masterpiece is tomorrow’s legacy. By adhering to these clean code principles, we ensure that our legacy stands the test of time, remaining robust and comprehensible for the developers of the future. Remember, clean code isn’t about taking extra time — it’s about saving countless hours in the future by investing a few more minutes today.


文章来源: https://blogs.sap.com/2023/12/11/clean-code-practices-mess-to-masterpiece-with-an-example/
如有侵权请联系:admin#unsafe.sh