Writing Better Functions
A guide to improving code quality and maintainability
[Link] 1
1. Follow the Single Responsibility Principle
A function should do only one thing well. Combining tasks makes
maintenance and debugging harder.
[Link] 2
Problem
A function loads, cleans, and creates features, making it hard to
reuse and debug.
def process_data(file_path):
data = pd.read_csv(file_path) # Loading data
data = [Link]() # Cleaning data
data['new_feature'] = data['feature1'] * data['feature2'] # Feature engineering
return data
[Link] 3
Solution
Split tasks into individual functions:
def load_data(file_path):
return pd.read_csv(file_path)
def clean_data(data):
return [Link]()
def create_features(data):
data['new_feature'] = data['feature1'] * data['feature2']
return data
def process_data(file_path):
data = load_data(file_path)
data = clean_data(data)
return create_features(data)
[Link] 4
2. Limit the Method Size
Smaller functions improve readability and testability.
[Link] 5
Problem
A large function mixes prediction, metrics, and visualization.
def analyze_model_performance(model, X_train, X_test, y_train, y_test):
y_train_pred = [Link](X_train)
y_test_pred = [Link](X_test)
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)
print("Train MSE:", train_mse)
print("Test MSE:", test_mse)
[Link](y_test, y_test_pred)
[Link]()
[Link] 6
Solution
Break it into focused functions:
def calculate_mse(model, X, y):
predictions = [Link](X)
return mean_squared_error(y, predictions)
def plot_predictions(y_true, y_pred):
[Link](y_true, y_pred)
[Link]("True Values")
[Link]("Predicted Values")
[Link]()
def analyze_model_performance(model, X_train, X_test, y_train, y_test):
train_mse = calculate_mse(model, X_train, y_train)
test_mse = calculate_mse(model, X_test, y_test)
print("Train MSE:", train_mse)
print("Test MSE:", test_mse)
plot_predictions(y_test, [Link](X_test))
[Link] 7
3. Use Descriptive Names
Names should clearly describe a function’s purpose.
[Link] 8
Problem
Ambiguous names make code hard to understand.
def do_work(df):
df = [Link]()
df['norm_col'] = df['col'] / df['col'].max()
return df
[Link] 9
Solution
Use meaningful names:
def clean_and_normalize_data(df):
df = [Link]()
df['normalized_column'] = df['column'] / df['column'].max()
return df
[Link] 10
4. Minimize Parameters
Too many parameters make functions harder to use and
maintain.
[Link] 11
Problem
A function with many parameters is difficult to call and modify.
def train_model(X_train, y_train, learning_rate, epochs, batch_size):
model = SomeModel()
[Link](X_train, y_train, lr=learning_rate, epochs=epochs, batch_size=batch_size)
return model
[Link] 12
Solution
Encapsulate parameters in a dictionary:
def train_model(X_train, y_train, config):
model = SomeModel()
[Link](X_train, y_train, lr=config['learning_rate'], epochs=config['epochs'], batch_size=config['batch_size'])
return model
config = {
'learning_rate': 0.01,
'epochs': 100,
'batch_size': 32
}
model = train_model(X_train, y_train, config)
[Link] 13
5. Avoid Side Effects
Functions that modify external states can cause unpredictable
behavior.
[Link] 14
Problem
A function modifies a global variable:
global_data = [Link]()
def clean_data():
global global_data
global_data = global_data.dropna()
[Link] 15
Solution
Return a modified copy instead:
def clean_data(data):
return [Link]()
global_data = clean_data(global_data)
[Link] 16
6. Handle a Single Level of Abstraction
Mixing high- and low-level tasks makes code harder to read and
refactor.
[Link] 17
Problem
A function mixes file reading and data analysis:
def process_and_analyze(file_path):
data = pd.read_csv(file_path)
data = [Link]()
print("Data processed")
print([Link]())
[Link] 18
Solution
Separate responsibilities:
def load_data(file_path):
return pd.read_csv(file_path)
def process_data(data):
return [Link]()
def analyze_data(data):
print("Data processed")
print([Link]())
def process_and_analyze(file_path):
data = load_data(file_path)
data = process_data(data)
analyze_data(data)
[Link] 19
7. Use Early Returns
Early returns simplify logic by reducing nesting.
[Link] 20
Problem
Deeply nested conditions:
def validate_data(data):
if data is not None:
if len(data) > 0:
if 'important_column' in [Link]:
return True
return False
[Link] 21
Solution
Simplify with early returns:
def validate_data(data):
if data is None or len(data) == 0 or 'important_column' not in [Link]:
return False
return True
[Link] 22
8. Reuse Existing Code
Avoid duplicating logic by extracting shared functionality.
[Link] 23
Problem
Duplicate logic wastes effort and increases maintenance.
def calculate_mean(values):
return sum(values) / len(values)
def calculate_mean_squared(values):
mean = sum(values) / len(values)
return mean ** 2
[Link] 24
Solution
Extract shared logic:
def calculate_mean(values):
return sum(values) / len(values)
def calculate_mean_squared(values):
mean = calculate_mean(values)
return mean ** 2
[Link] 25
9. Keep Methods Stateless
Stateless functions are predictable and avoid external state.
[Link] 26
Problem
A method modifies an internal variable:
class Model:
def __init__(self):
[Link] = []
def predict(self, data):
[Link] = [x * 2 for x in data]
[Link] 27
Solution
Return results instead:
class Model:
def predict(self, data):
return [x * 2 for x in data]
[Link] 28
10. Add Proper Comments (If Needed)
Use comments to explain non-obvious logic.
[Link] 29
Problem
Complex logic without explanation:
def calculate_growth_rate(data):
return (data[-1] - data[0]) / data[0] # Ambiguous growth calculation
[Link] 30
Solution
Add clear comments or docstrings:
def calculate_growth_rate(data):
"""
Calculate the growth rate of a dataset.
Args:
data (list or array): Time-series data with numerical values.
Returns:
float: Growth rate as a percentage.
"""
return (data[-1] - data[0]) / data[0]
[Link] 31
Thank You!
Adopt these principles to improve your code !
[Link] 32