Functions & Modularity
What Are Functions and Modularisation?
Functions allow reusable blocks of code to be executed with different inputs.
Modularisation structures a program into separate, maintainable components.
Benefits of Modularisation
Readability
Modular programs split large tasks into small, clearly named functions and classes, so intent is obvious at a glance. Short, single-purpose modules reduce cognitive load and make code reviews and maintenance faster.
Testing
Functions with well-defined inputs and outputs are easy to unit test in isolation, without running the whole program. This speeds up debugging and helps you catch issues early when a module changes.
Code Reuse
Common helpers (validation, formatting, calculations) can live in one module and be imported wherever needed. Reusing the same, trusted code avoids duplication and ensures that one fix benefits every place it’s used.
Updates
When requirements change, you update a single module rather than hunting through many files. Clear module boundaries reduce the chance of breaking unrelated code, and all callers automatically gain the improvement.
Function (and Procedure) Syntax in Python and Java
| Feature | Python | Java |
|---|---|---|
| Defining a Procedure | def function_name(): |
public static void functionName() { } |
| Procedure with Parameters | def greet(name): |
public static void greet(String name) { } |
| Defining a Function | def function_name():return value |
public static int functionName() {return value;} |
| Calling a Procedure | function_name() | functionName(); |
| Calling a Function | var = function_name() | var = functionName(); |
Example: Defining and Using Functions
Function with Parameters and Return
# Function to calculate the square of a number
def square(num):
return num * num
result = square(4)
print("Square:", result)
public class FunctionExample {
public static int square(int num) {
return num * num;
}
public static void main(String[] args) {
int result = square(4);
System.out.println("Square: " + result);
}
}
Understanding Function / Procedure Signatures
The Anatomy of a Subroutine
In defining a function, procedure or method, we need to be able to identify the parts of its signature. These parts represent the name of the subroutine, its return type (if any), and its parameters
Anatomy
Function/Procedure name and parameters, optionally with types and defaults; the arrow -> specifies the return type.
def area(width: float, height: float = 1.0) -> float:
return width * height
In the signature above: area is the function name; width and height are parameters typed as float, with a default for height; the function returns a float.
Anatomy
ReturnType, Function/Procedure name, and parameters (typed).
public static double area(double width, double height) { ... }
public static void log(String message) { ... }
Here, double / void are the return types; area / log are method names; and each parameter has an explicit type.
Understanding Scope: Local vs. Global
- Local Variables: Defined inside a function; exist only during its execution, preventing unintended side-effects.
- Global Variables: Declared outside any function; accessible throughout the program but risk conflicts.
# GLOBAL variable (module-level)
counter = 0
def add_to_counter(step):
global counter # declare intent to modify the GLOBAL
message = f"Adding {step}" # LOCAL variable (exists only in this function)
print(message)
counter += step # modifies the GLOBAL
return counter
print("Before:", counter)
add_to_counter(3)
print("After:", counter)
public class ScopeDemo {
// "Global" in Java: class-level (static) field
private static int counter = 0;
public static int addToCounter(int step) {
String message = "Adding " + step; // LOCAL variable (method scope)
System.out.println(message);
counter += step; // modifies class-level "global"
return counter;
}
public static void main(String[] args) {
System.out.println("Before: " + counter);
addToCounter(3);
System.out.println("After: " + counter);
}
}
Example: Modularised Code
Benefits of Modularisation (as shown below)
- Readability: Small, named units (add, subtract) make intent obvious.
- Reuse: The same function/method can be called from many places without duplicating logic.
- Testability: Each unit takes inputs and returns outputs, making unit tests trivial.
- Maintainability: Fix/extend one module without touching others (e.g. replace subtract’s internals).
- Extensibility: Easy to add new operations (multiply, divide) without rewriting callers.
Why this is modular: Each operation is a single-responsibility function that accepts inputs and returns a result. Callers don’t care how it’s implemented, which makes the code easy to reuse, test, and extend.
# Addition function
def add(a, b):
return a + b
# Subtraction function
def subtract(a, b):
return a - b
print("Addition:", add(5, 3))
print("Subtraction:", subtract(5, 3))
Why this is modular: Each method encapsulates one job and exposes a stable interface (name, parameters, return). Callers rely on the signature, so internal changes don’t ripple through the codebase.
public class ModularExample {
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
public static void main(String[] args) {
System.out.println("Addition: " + add(5, 3));
System.out.println("Subtraction: " + subtract(5, 3));
}
}
Avoiding Common Errors
- Uninitialised Variables: Always initialise function parameters and return variables to prevent runtime errors.
- Improper Scope: Avoid overusing global variables; pass data via parameters to maintain modularity.
- Incorrect Return Types: Match function return types and values to avoid inconsistency across calls.
Key Takeaways
- Functions encapsulate reusable code blocks that accept inputs and return outputs.
- Modularisation divides programs into coherent, maintainable components.
- Scope determines variable accessibility: local (inside functions) vs global (throughout program).
- Effective modular design boosts readability, testability, and maintainability.