A programmer’s relationship with functions is much like that special thing you needed in your life, but for so long didn’t realize you needed it. Later on, you find it and it changes your life completely.
The discovery of that special thing betters your life. Simplifies your life. Brings clarity to your life.
Functions are much like that for newbie programmers when they finally understand the importance of functions.
As programmers, we must meet two minimum expectations
- Solve problems (programmatically).
- Write and refactor code to provide a pleasant experience for our future selves and other developers.
What is a function?
A function with an analogy is a container with content. The container has an inlet and outlet; the inlet receives content into the container, and the outlet dispels content from the container.
Technically, a function encapsulates a block of code that performs a particular task without side effects.
A side effect in functions is the potential of a function to alter states outside its local scope. Avoiding side effects in your functions eliminates unexpected runtime behaviours, which could be costly.
Functions relieve developers of the responsibility of constantly figuring out how the code works, and instead focus on what the code is doing.
The usage of functions is about intention more than it is about implementation.
The purpose is to provide a high-level understanding of what the code is doing, without knowledge of the algorithmic details on how it’s doing it.
The components of a function
A function signature is a collective name for the individual components of a function.
A function signature comprises
Function name
This is a descriptive name that relays the intent of the function. It answers the question “What does this function do when called?”. The name of the function answers this question. A descriptive function is important in avoiding miscommunication of intent. A function must perform the operation the function name suggests it does.
Function name must be long enough to describe the intent of its operation, but not too long it’s hard to read.
Function parameters
Remember our earlier analogy with a container? We mentioned the inlet and outlet of the container respectively bring in content and dispels out content from the container.
The inlet of the container technically is the function parameter.
Parameters define what a function accepts from the outside scope into its local scope and the constraints guarding what it accepts.
A function takes multiple parameters or no parameter at all.
Function code block
This defines the algorithmic details of the function. It details out in code the operation of the function using expressions, statements, iterations, conditionals and variables.
A good function block must be concise and perform only a single operation. It must fulfil a singular intent. This intent must be communicated clearly through a descriptive function name.
Return result
Revisiting our container analogy, we returned the results of a function through the container outlet. The outlet shares the results of the operation of the function with the global scope. A function may return or not return results.
function <function_name>( [<param1>, <param2>, <param3>,...] ):
<function block>
<return result>
Function declaration
A function declaration defines the signature of a function, which is the function name, function parameter, function logic (or code block), and finally, the function return results.
Our example function declaration below, written in Python, has the following parts for its signature:
-
find_median
as the function name -
numbers
as the function parameter - Line
#13
to#27
as the function code block(logic) - Statement at Line
#30
is the function return result
import math
def find_median(numbers=[]):
"""
Implementation details
Algorithm:
1. Reorder numbers in ascending order
2. Determine if the count of the numbers is even or odd
3. If odd, divide by 2 and round up the result. Look up the median using your result as the lookup index. Skip step 4
4. If even, divide by 2, your results becomes your first index1, subctract one from index1 to have index2
Now look up both indices and sum them up then divide by 2 to derive your median
5. Return derivied median
"""
# Step 1: Reorder numbers in ascending order
numbers.sort() # Python's list already have a method sort() which abstracts away the details on how to sort numbers
# Step 2: Determine if the count of the numbers is even or odd
is_even = len(numbers) % 2 == 0
if is_even:
# Step 4
index1 = math.ceil(len(numbers) / 2)
index2 = index1 - 1
median = (numbers[index1] + numbers[index2]) / float(2)
else:
# Step 3
index1 = math.ceil(len(numbers) / 2)
median = numbers[index1]
# Step 5
return median
Function calls
Also called function invocation. A function call literally is calling the name of the function and supplying data to match it’s parameters.
The function find_median
is now callable wherever in our codebase we intend to derive the median of a set of numbers. This function can be called multiple times, with a different set of numbers as arguments to derive different results.
Arguments are data in the global scope we pass to the function for its operations using the function parameters.
Arguments passed to a function must meet the parameter constraints defined in the function's signature.
Examples of calling multiple times our find_median
function with a different list of numbers as argument.
# 1st call
median1 = find_median([40, 57, 12])
print(f"median1 = {median1}")
# 2nd call
median2 = find_median([3, 13, 7, 5, 21, 23, 39, 23, 40, 23, 14, 12, 56, 23, 29])
print(f"median1 = {median2}")
# 3rd call
median3 = find_median([3, 13, 7, 5, 21, 23, 23, 40, 23, 14, 12, 56, 23, 29])
print(f"median1 = {median3}")
Multiple calls of the find_median
function with different arguments(parameters)
Why are functions important?
If you’re wondering in what ways functions simplify and clarify your code, then this section presents you with answers.
Code readability
If you have to spend effort into looking at a fragment of code to figure out what it’s doing, then you should extract it into a function and name the function after that “what”
— Martin Fowler
Generally, code is imperative. With imperative programming, the programmer details the steps necessary to achieve intended results. This programming paradigm focuses on the details of intention using complex repetitive logic and control flows.
Declarative programming is the opposite of imperative programming. Here we look towards high-level code comprehension. The developer focuses on intention and not implementation.
Functions help achieve declarative programming by abstracting away implementation details.
Declarative code tells the story of what the application does, without upfront knowledge of how it does it.
Functions are one way to implement the declarative programming paradigm.
Declarative programming prioritizes developer experience.
Code re-usability
Functions reduce code repetition. They replace code fragments that once were performing the same operations in different places in your codebase.
Functions extract and abstract away repeated operations under a function with a descriptive name.
An additional benefit of using functions is it reduces lines of codes in your application’s entry modules.
Code maintainability
The cost of maintaining a codebase becomes expensive when
- The codebase is brittle. Every single change breaks something.
- The codebase is difficult to read and comprehend due to complex logic with little or no abstractions.
- The codebase isn't DRY. There are several duplications of functionalities and operations. A change in the logic of an operation requires several other changes across the codebase.
Functions help address the above concerns and minimize the cost of maintaining our codebase. Functions introduce modularity to our application design and implementation.
Code refactoring becomes appealing to developers when the codebase is designed with high-level abstractions over complex application and domain logic. Developers can now focus on the “what” and not the “how”. Each unit of operation in our codebase becomes easy to refactor.
Functions minimize the potential of breaking operations beyond the function that is refactored.
Code testability
Code evolves with time. We identify new insights and business constraints changes. We identify bugs, be it logical, syntax, system bugs after we’ve deployed to production.
All these situations require us to revisit and refactor our codebase. We expect to perform these revisions without unintended changes in the behaviour of our software.
How do we guarantee this? The answer is “testing”.
A problem remains, even with knowledge about testing. The problem: not every code is testable. Without implementing the right programming paradigms and principles, it becomes difficult to guarantee your application will behave the same after revisions to the codebase.
Functions introduce modularity into our codebase. And this modularity makes our codebase testable.
Application operations are now abstracted into descriptive functions that are testable.
We can run assertions on the outputs of functions to validate their operations.
Unit testing focuses on each unit of operation in your application. Functions define operations as units and each unit becomes testable.
With functions, we re-introduce confidence into our codebase. Developers are comfortable and confident in approaching the codebase and improving its quality through refactoring.
Closing Thoughts
As programmers, we spend a sizeable chunk of our time maintaining existing projects than starting new ones. Therefore, good developer experience navigating and understanding the project codebase becomes crucial.
Functions tell the story of what the code does. This encourages old and new developers on a project to approach the codebase with confidence without fear of breaking things.
On your next project, consider employing functions to conceal implementation details. Remember, your code is telling a story. You and other developers don’t have to think hard to understand the story being told.
Programming languages like Scala, Closure, and more already have the concept of functional programming baked into them. But if you’re using a language that’s not strictly a functional language, still remember to abstract your re-usable operations into functions.
Languages like Python have functions like map
, filter
to simplify transformation and filtering operations on lists and other iterable data structures.
I hope you found this article helpful. Wish you the best and see you again some time.
Top comments (0)