πŸ“ Programming

Functions: Best Practices 🎯

0
Author
04e5cc8b-58ac-4bdc-bdee-661bbb
πŸ“…
Published
03.04.2026
⏱️
Reading time
9 min
πŸ‘οΈ
Views
101
🌱
Level
Beginner

Goal: Learn how to write proper functions that are easy to read, test, and reuse.


πŸ€” What Makes a Function Good?

Bad function:

def f(x, y):
    print("Result:")
    z = x + y
    print(z)
    return z

Good function:

def calculate_sum(first_number, second_number):
    """Calculate the sum of two numbers."""
    return first_number + second_number

The difference:
- βœ… Descriptive name
- βœ… Descriptive parameter names
- βœ… Documentation (docstring)
- βœ… Only computation, no output
- βœ… Simple and predictable


πŸ“ Function Writing Rules

πŸ“ Rule #1: The Name Should Say WHAT the Function Does

❌ Bad Names

def f():          # What does "f" do?
def data():       # What about data?
def process():    # What are we processing?
def do_stuff():   # What "stuff"?
def x():          # What is this?

Problem: You have to read the code inside just to understand what it’s for.


βœ… Good Names

def calculate_total_price():
def send_email():
def validate_user_input():
def find_max_value():
def save_to_database():

Principle: The name is a verb + what we’re doing.


🎯 Naming Patterns

Action Examples
Get get_user(), get_balance(), get_settings()
Set set_name(), set_balance(), set_status()
Calculate calculate_total(), calculate_tax(), calculate_discount()
Create create_account(), create_user(), create_report()
Validate validate_email(), check_balance(), is_valid()
Find find_user(), search_products(), locate_file()
Save save_data(), save_to_file(), save_user()
Load load_data(), load_from_file(), load_settings()
Delete delete_user(), remove_item(), clear_cache()
Update update_balance(), refresh_data(), sync_data()

πŸ” Special Prefixes

is_ / has_ / can_ β€” for boolean functions:

def is_adult(age):
    """Check if the user is an adult."""
    return age >= 18

def has_permission(user):
    """Check if the user has the required permissions."""
    return user.role == "admin"

def can_withdraw(balance, amount):
    """Check if the withdrawal amount is available."""
    return balance >= amount

Return: True or False


πŸ“ Rule #2: One Function β€” One Job (SRP)

SRP (Single Responsibility Principle) β€” a function should do exactly one thing.

❌ Bad: Function That Does Everything

def process_user(name, age):
    print(f"Processing user {name}...")

    if age < 18:
        print("Minor!")
        return False

    with open("users.txt", "a") as f:
        f.write(f"{name},{age}\n")

    print("Sending email...")
    # send email

    print("Writing to DB...")
    # write to DB

    print("Done!")
    return True

Problems:
- πŸ”΄ Does 4 different things
- πŸ”΄ Hard to test
- πŸ”΄ Parts can’t be reused
- πŸ”΄ Hard to understand what’s happening


βœ… Good: Break It into Small Functions

def is_adult(age):
    """Check if the user is an adult."""
    return age >= 18

def save_user_to_file(name, age):
    """Save the user to a file."""
    with open("users.txt", "a") as f:
        f.write(f"{name},{age}\n")

def send_welcome_email(name):
    """Send a welcome email."""
    # send email
    pass

def save_user_to_database(name, age):
    """Save the user to the database."""
    # write to DB
    pass

def register_user(name, age):
    """Register a new user."""
    if not is_adult(age):
        return False

    save_user_to_file(name, age)
    send_welcome_email(name)
    save_user_to_database(name, age)
    return True

Benefits:
- βœ… Each function does one thing
- βœ… Easy to test individually
- βœ… Can be reused
- βœ… Easy to read and understand


πŸ“ Rule #3: Functions Should NOT Print (Pure Functions)

❌ Bad: Function with print Inside

def calculate_discount(price, discount_percent):
    discount = price * discount_percent / 100
    final_price = price - discount
    print(f"Price: {price}")
    print(f"Discount: {discount}")
    print(f"Total: {final_price}")
    return final_price

Problems:
- πŸ”΄ Can’t use it without side-output
- πŸ”΄ Hard to test
- πŸ”΄ Violates the single responsibility principle
- πŸ”΄ Mixes logic and display


βœ… Good: Separate Logic and Display

def calculate_discount(price, discount_percent):
    """Calculate the discounted price."""
    discount = price * discount_percent / 100
    final_price = price - discount
    return final_price

# Display separately
def show_discount_info(price, discount_percent):
    """Show discount details."""
    final_price = calculate_discount(price, discount_percent)
    discount = price - final_price

    print(f"Price: {price}")
    print(f"Discount: {discount}")
    print(f"Total: {final_price}")

# Usage
price = calculate_discount(1000, 20)  # Pure calculation
show_discount_info(1000, 20)          # With output

Benefits:
- βœ… calculate_discount() is a pure function
- βœ… Can be used in tests
- βœ… Can be used without output
- βœ… Logic is separated from display


🎯 Pure Functions

A pure function:
1. Returns a result (doesn’t print)
2. Doesn’t modify external state (no side effects)
3. Always returns the same result for the same arguments

βœ… Pure Functions

def add(a, b):
    """Add two numbers."""
    return a + b

def calculate_area(width, height):
    """Calculate the area."""
    return width * height

def is_even(number):
    """Check if a number is even."""
    return number % 2 == 0

Characteristics:
- Only return, no print
- Don’t modify variables outside the function
- Predictable


❌ Impure Functions

total = 0

def add_to_total(value):
    """Add to the global variable."""
    global total
    total += value  # Modifies external state!

def print_greeting(name):
    """Print a greeting."""
    print(f"Hello, {name}!")  # Prints instead of returning!

def get_random_number():
    """Get a random number."""
    import random
    return random.randint(1, 100)  # Different result every time!

Why impure:
- Modifies external data (global)
- Prints instead of returning
- Depends on randomness/time


πŸ“ Rule #4: Parameters Should Be Descriptive

❌ Bad

def f(x, y, z):
    return x + y * z

result = f(10, 5, 2)  # Which is which?

βœ… Good

def calculate_total_price(base_price, quantity, tax_rate):
    """Calculate the total price including tax."""
    return base_price + quantity * tax_rate

result = calculate_total_price(
    base_price=10,
    quantity=5,
    tax_rate=2
)

Benefits:
- βœ… Clear what we’re passing in
- βœ… Order can be changed
- βœ… Self-documenting code


πŸ“ Rule #5: Return Results, Don’t Mutate Arguments

❌ Bad: Mutating an Argument

def add_item(items, new_item):
    """Add an item to the list."""
    items.append(new_item)  # Modifies the original list!

my_list = [1, 2, 3]
add_item(my_list, 4)  # my_list is now [1, 2, 3, 4]

Problem: Unexpected modification of external data!


βœ… Good: Return a New Value

def add_item(items, new_item):
    """Create a new list with the added item."""
    return items + [new_item]  # Return a new list

my_list = [1, 2, 3]
new_list = add_item(my_list, 4)  # my_list stays [1, 2, 3]
print(new_list)  # [1, 2, 3, 4]

Benefit: The original data is not modified.


πŸ“ Rule #6: Use Default Parameter Values

def create_account(name, balance=0, currency="USD"):
    """Create a bank account."""
    return {
        "name": name,
        "balance": balance,
        "currency": currency
    }

# Different ways to call it
account1 = create_account("Alice")
account2 = create_account("Bob", 1000)
account3 = create_account("Charlie", 500, "EUR")

Benefits:
- βœ… Fewer required arguments at call sites
- βœ… Sensible defaults
- βœ… Flexible usage


πŸ“ Rule #7: Document Your Functions (Docstrings)

❌ Without Documentation

def calc(p, d):
    return p - (p * d / 100)

Problem: Unclear what it does, what p and d are.


βœ… With Documentation

def calculate_discounted_price(price, discount_percent):
    """
    Calculate the price after applying a discount.

    Args:
        price: The original item price
        discount_percent: The discount percentage (0-100)

    Returns:
        The price after the discount is applied

    Example:
        >>> calculate_discounted_price(1000, 20)
        800.0
    """
    discount = price * discount_percent / 100
    return price - discount

Docstring format:
1. Brief description (one line)
2. Extended description (if needed)
3. Args: parameters
4. Returns: what it returns
5. Example: usage example


πŸ“ Rule #8: Don’t Make Functions Too Long

❌ Bad: A Function That’s Way Too Long

def process_order(order_id):
    # 100+ lines of code
    # order validation
    # price calculation
    # inventory check
    # send email
    # write to DB
    # update stats
    # logging
    # ...
    pass

Problem: Hard to read, test, and understand.


βœ… Good: Break Into Small Functions

def validate_order(order_id):
    """Validate the order."""
    # ...

def calculate_order_total(order):
    """Calculate the total amount."""
    # ...

def check_inventory(order):
    """Check product availability."""
    # ...

def send_confirmation_email(order):
    """Send a confirmation email."""
    # ...

def save_order_to_database(order):
    """Save the order to the database."""
    # ...

def process_order(order_id):
    """Process the full order."""
    order = validate_order(order_id)
    total = calculate_order_total(order)

    if not check_inventory(order):
        return False

    save_order_to_database(order)
    send_confirmation_email(order)
    return True

Rule: A function should fit on one screen (~20-30 lines max).


πŸ“ Rule #9: Return Meaningful Results

❌ Bad: Uninformative Return

def save_user(name):
    # saving...
    return True  # What does True mean?

βœ… Good: Informative Return

def save_user(name):
    """Save a user."""
    try:
        # saving...
        return {"success": True, "message": "User saved"}
    except Exception as e:
        return {"success": False, "message": f"Error: {e}"}

result = save_user("Alice")
if result["success"]:
    print(result["message"])

Or better yet β€” use exceptions:

def save_user(name):
    """Save a user. Raises an exception on failure."""
    # saving...
    # if error: raise Exception("...")

try:
    save_user("Alice")
    print("Saved!")
except Exception as e:
    print(f"Error: {e}")

πŸ“ Rule #10: Avoid Too Many Parameters

❌ Bad: 7+ Parameters

def create_user(name, age, email, phone, address, city, country, postal_code):
    # ...

Problem: Hard to remember the order, easy to make mistakes.


βœ… Good: Use a Dictionary or Class

def create_user(user_data):
    """Create a user from a dictionary."""
    name = user_data["name"]
    age = user_data["age"]
    email = user_data["email"]
    # ...

user_data = {
    "name": "Alice",
    "age": 25,
    "email": "alice@example.com",
    "phone": "+1...",
    "address": "...",
    "city": "New York",
    "country": "USA",
    "postal_code": "10001"
}

create_user(user_data)

Rule: More than 3-4 parameters β€” use a dictionary or data structure.


🎯 Example: Before and After

❌ Bad Code

def f(x, y):
    print("Calculating...")
    r = x + y
    print("Result:", r)
    if r > 100:
        print("Big number!")
        with open("log.txt", "a") as f:
            f.write(f"{r}\n")
    return r

result = f(50, 60)

Problems:
- Unclear name f
- Unclear parameters x, y
- Mixing computation and output
- Multiple responsibilities in one function


βœ… Good Code

def calculate_sum(first_number, second_number):
    """
    Calculate the sum of two numbers.

    Args:
        first_number: The first number
        second_number: The second number

    Returns:
        The sum of the two numbers
    """
    return first_number + second_number

def is_big_number(number):
    """Check if a number is large (> 100)."""
    return number > 100

def log_number(number, filename="log.txt"):
    """Write a number to a file."""
    with open(filename, "a") as file:
        file.write(f"{number}\n")

def show_result(number):
    """Display the calculation result."""
    print(f"Result: {number}")
    if is_big_number(number):
        print("Big number!")

# Usage
result = calculate_sum(50, 60)
show_result(result)

if is_big_number(result):
    log_number(result)

Benefits:
- βœ… Descriptive names
- βœ… Each function does one thing
- βœ… Pure functions (no print in calculations)
- βœ… Easy to test
- βœ… Easy to reuse


⚑ Good Function Checklist

βœ… Name says WHAT it does (verb + object)
βœ… One function β€” one job (SRP)
βœ… Returns a result, doesn’t print (pure function)
βœ… Descriptive parameter names
βœ… Doesn’t mutate arguments
βœ… Has documentation (docstring)
βœ… Length up to 20-30 lines (fits on screen)
βœ… Maximum 3-4 parameters
βœ… Uses default values (where appropriate)
βœ… Easy to test


🎯 Example: Banking System

❌ Bad

def process(acc, amt, t):
    print("Processing...")
    if t == "d":
        acc["b"] += amt
        print("Done")
    elif t == "w":
        if acc["b"] >= amt:
            acc["b"] -= amt
            print("Done")
        else:
            print("Error")
    print(f"Balance: {acc['b']}")

βœ… Good

def deposit(account, amount):
    """
    Deposit funds into an account.

    Args:
        account: Account dictionary
        amount: Amount to deposit

    Returns:
        Updated balance
    """
    account["balance"] += amount
    return account["balance"]

def withdraw(account, amount):
    """
    Withdraw funds from an account.

    Args:
        account: Account dictionary
        amount: Amount to withdraw

    Returns:
        Updated balance, or None if insufficient funds
    """
    if amount > account["balance"]:
        return None

    account["balance"] -= amount
    return account["balance"]

def show_balance(account):
    """Display the account balance."""
    print(f"Balance: {account['balance']}")

# Usage
account = {"name": "Alice", "balance": 1000}

new_balance = deposit(account, 500)
if new_balance:
    print(f"Deposited! New balance: {new_balance}")

new_balance = withdraw(account, 200)
if new_balance is not None:
    print(f"Withdrawn! New balance: {new_balance}")
else:
    print("Insufficient funds!")

show_balance(account)

πŸŽ“ Summary

Golden rules for functions:

  1. Name = action (verb + what it does)
  2. One job (SRP β€” Single Responsibility)
  3. Pure function (return, not print)
  4. Descriptive parameters (names speak for themselves)
  5. Don’t mutate arguments (return a new value)
  6. Document it (docstring is required)
  7. Keep it short (up to 30 lines)
  8. Few parameters (up to 4)
  9. Testable (easy to verify)
  10. Reusable (can be used in different contexts)

Principle: A function should speak for itself. If you need to read the internals to understand it β€” the name is bad!


Now your functions speak for themselves! 🎯✨

Your reaction to the article

πŸ’¬ Comments (0)

πŸ” Sign in to leave a comment
πŸšͺ Login
πŸ’­

No comments yet

Be the first to share your opinion about this article!

πŸ”— Similar

Similar articles

Continue learning with these materials

πŸ“

map() β€” Transform Everything at Once! πŸ”„

map() applies a function to every element of an iterable and returns the results.

πŸ“… 03.04.2026 πŸ‘οΈ 119
πŸ“

filter() β€” Pick the Best! πŸ”

filter() selects elements that pass a test (return True).

πŸ“… 03.04.2026 πŸ‘οΈ 111
πŸ“

Lambda Functions: Tiny Smart Functions ⚑

A lambda is a way to create a small, single-line, anonymous function.

πŸ“… 03.04.2026 πŸ‘οΈ 104

Did you like the article?

Subscribe to our updates and receive new articles first. Grow with PyLand!