- Published on
Writing Clean Code Part 2
- Authors
Examples of Clean Code
on this second part of the blog, we'll delve into some examples. you'll gain a deeper understanding of the practices and principles that make up clean code, and learn how to apply them in your own projects to improve readability, maintainability, and overall software development experience. Stay tuned as we explore various scenarios and discuss the thought processes behind crafting elegant, efficient, and well-documented solutions.
1. Choosing Descriptive Names:
a) Bad
def calc(x, y):
z = x + y * 2
return z / 4
b) good
def calculate_average_order_value(order_total, num_items):
average_value = order_total / num_items
return average_value
2. Functions
a) Bad
Issues:
- Combines multiple responsibilities: This function calculates order totals, applies discounts, processes payments, and sends emails.
# Bad example
def process_user_order(username, items, address, promo_code):
total_cost = 0
for item in items:
total_cost += item['price']
if promo_code:
total_cost *= 0.9 # Apply 10% discount
# Charge the user's credit card (omitted for simplicity)
# Send confirmation email (omitted for simplicity)
b) good
Improvements:
- Focused responsibilities: Each function has a single, clear purpose.
- Improved readability: Smaller functions are easier to understand at a glance.
- Enhanced reusability: Individual functions could now be used in other parts of the application.
- Better maintainability: Changes to one process (e.g., payment methods) won't affect unrelated logic.
# Good example
def calculate_order_total(items):
total_cost = 0
for item in items:
total_cost += item['price']
return total_cost
def apply_promo_code(total_cost, promo_code):
if promo_code:
return total_cost * 0.9
else:
return total_cost
def process_payment(total_cost):
# Charge credit card logic here
def send_confirmation_email(username, order_details):
# Send email logic here
3. Commenting
a) Bad
Issues:
- Comments mainly explain elementary operations that are apparent from the code.
#include <stdio.h>
int main() {
int x = 5; // Declare an integer variable named x and assign value 5
int y = 10; // Declare and initialize integer variable y with value 10
int sum = x + y; // Calculate the sum of x and y and store it in 'sum'
printf("The sum is: %d\n", sum); // Print the value of 'sum'
return 0; // Return 0 to indicate successful execution
}
b) good
Improvements:
- Explaining the constant: The comment clarifies the purpose of
PI. - Highlighting the formula: It reminds the reader of the area formula, which might not be immediately recognizable for everyone.
#include <stdio.h>
int main() {
const double PI = 3.14159;
double radius = 5.0;
// Calculate the area (note the use of a constant for PI)
double area = PI * radius * radius;
printf("The area of the circle is: %f\n", area);
return 0;
}
4. Formatting:
a) Bad
Issues:
- Inconsistent indentation: Makes it difficult to visually follow the code's structure.
- Missing spaces: Everything seems crammed together, hindering readability.
- Irregular bracing: Inconsistent placement of curly braces makes blocks less clear.
function calculateArea(width, height) {let result = width * height if (result > 50) {console.log('Area is large') }
return result
}
b) good
Improvements:
- Consistent indentation: Uses spaces to visually distinguish code blocks.
- Spacing around operators: Improves visual clarity around mathematical and comparison operators.
- Consistent brace placement: Braces follow a consistent style for easy identification of blocks.
function calculateArea(width, height) {
let result = width * height
if (result > 50) {
console.log('Area is large')
}
return result
}
5. Error Handling:
a) Bad
Issues:
- Error handling mixed with core logic: The
try-exceptblocks interrupt the flow of the main profile processing logic. - Multiple exit points: The function can return from different places within the
try-exceptblocks, making it harder to reason about.
def get_user_profile(user_id):
try:
file = open(f"user_{user_id}.json", "r")
data = file.read()
profile = json.loads(data)
file.close()
# Lots of complex processing logic with the profile data...
return profile
except FileNotFoundError:
print("User profile not found.")
return None
except json.JSONDecodeError:
print("Invalid JSON data.")
return None
b) good
Improvements:
- Separation of concerns: Error handling is moved outside the
process_profile_datafunction. - Cleaner core logic: The
process_profile_datafunction focuses solely on profile processing. - Context-aware error messages: The top-level function can log errors or provide more specific messages.
- Using "with" statement: Ensures the file is automatically closed.
def get_user_profile(user_id):
try:
with open(f"user_{user_id}.json", "r") as file:
data = json.load(file)
return process_profile_data(data) # Complex logic extracted
except FileNotFoundError:
print("User profile not found.")
except json.JSONDecodeError:
print("Invalid JSON data.")
def process_profile_data(data):
# Lots of complex processing logic with the profile data...
# (No error handling needed within this function)
6. Unit Tests:
a) The Code
code to calculate the factorial of a number.
// calculateFactorial.js
function calculateFactorial(num) {
if (num === 0) {
return 1
} else {
return num * calculateFactorial(num - 1)
}
}
b) Unit Test
The associated code test
// calculateFactorial.test.js
const calculateFactorial = require('./calculateFactorial')
// Use 'describe' to group related tests
describe('calculateFactorial', () => {
// Use 'it' or 'test' for individual test cases
it('calculates the correct factorial of 0', () => {
const result = calculateFactorial(0)
expect(result).toBe(1)
})
it('calculates the correct factorial of a positive number', () => {
const result = calculateFactorial(5)
expect(result).toBe(120)
})
it('handles negative numbers', () => {
expect(() => calculateFactorial(-1)).toThrow()
})
})
7. Refactoring:
a) Un refactored Code
def calculate_shipping(country, weight, has_insurance):
base_cost = 5 # Flat rate
if country == "US":
shipping_per_kg = 2
elif country == "Canada":
shipping_per_kg = 3
else:
shipping_per_kg = 5
total_shipping = base_cost + weight * shipping_per_kg
if has_insurance:
total_shipping += 10
return total_shipping
b) Refactored Code
- Extract Country-Specific Logic
- Simplify Calculation
def get_shipping_cost_per_kg(country):
if country == "US":
return 2
elif country == "Canada":
return 3
else:
return 5
def calculate_shipping(country, weight, has_insurance):
base_cost = 5
total_shipping = base_cost + weight * get_shipping_cost_per_kg(country)
insurance_cost = 10 if has_insurance else 0
total_shipping += insurance_cost
return total_shipping