Leer en español · Lire en français · Auf Deutsch lesen
Section 0.3 of 0.8 · Beginner · 16 min read
Functions, modules, objects, interfaces, libraries and frameworks
Video lesson
5 min · narrated, with captionsListen only (audio)
Read the transcript
Think of a large restaurant. Instead of one cook doing everything, there are stations: grill, pastry and salads, each with its own staff, tools and recipes. Orders pass between stations on tickets, so the grill doesn't need to know how the pastry chef works. Code is organised in just the same way, and this lesson shows how.
The smallest building block is the function: a named piece of code that does one job. Think of a coffee machine. You put in beans, water and a choice of drink, and out comes a coffee. Functions let you write a rule once and reuse it. A shop might write its VAT calculation in one function, and call it from the checkout, the invoice and the refund screen. Then, when the VAT rate changes, one function changes, not twenty scattered copies that would soon drift apart.
Next, related functions are grouped into modules, and related modules into a package. Here, a package called shop holds two modules: pricing and orders. Each module has a small public part, meant for other modules to use, and private details. In the pricing module, the VAT function is public. The helper below it is private, marked in Python by a leading underscore. The orders module calls only the public pricing function, never the private helper. Think of a department with a public front desk and internal processes nobody outside needs to see. The value of this split is that the private part can be rewritten freely without breaking anyone else, as long as the public part keeps its promises.
Many languages let you bundle data together with the functions that work on it. A class is the template, and an object is one thing made from it. A bank account class says every account has an owner and a balance; each customer's account is an object. The key idea is encapsulation. If any code could set the balance directly, nothing would stop it going negative. Instead, the withdraw method is the only door, and it checks the rules every time, like a bank teller in front of the vault.
The most important idea in this lesson is the interface. It's a contract: the operations one part offers to others, what they take, what they return and what can go wrong. A power socket is an interface: any appliance with the right plug works. Here's why it matters. Suppose a shop's checkout calls one payment provider's own functions directly, everywhere. If the shop switches provider next year, the checkout has to be rewritten. Instead, the shop defines its own small payment gateway interface, with one operation: charge this amount to this card. It writes one adapter per provider. The checkout depends only on the interface, so switching provider means writing one new adapter. It makes testing easy too. In tests, a fake gateway that never contacts a real bank can stand in for the real one.
Almost no one writes everything from scratch. A library is code someone else wrote that your code calls when it needs it, such as a library for dates or for making PDF files. Your code stays in charge. A framework is the other way round. It provides the skeleton of the application and calls your code at the right moments. This is called inversion of control: don't call us, we'll call you. Frameworks save a lot of design work, but they are usually hard to replace later.
Every library or framework you use is a dependency, fetched by a package manager such as pip for Python or npm for JavaScript. Here, a shop app chooses three packages directly. But those packages bring in five more of their own, called transitive dependencies. You chose three packages, but you depend on eight. Each one is code you didn't write, running with all the permissions of your own. So a flaw in any one of them, such as this crypto package, is a flaw in your app.
Why does all this structure matter? Because most of the cost of software is spent changing it. Two ideas describe good structure. Coupling is how much one part depends on the details of another. Cohesion is how well the contents of one part belong together. On the left, four modules all reach into each other's data. A change to one can spread to all the others. On the right, the same modules have just three links, each through a small interface. That's the goal: loose coupling and high cohesion. It also lets separate teams work and release independently.
Architecture is this same activity at a larger scale: deciding the main parts, their interfaces and which way dependencies point. The full section has the code and a checklist for adding dependencies. Next, in Section zero point four, the code leaves the single computer, as we look at how computers talk.
What you need to know
- A function is a named, reusable piece of code with inputs and an output; it lets a rule be written once and changed in one place.
- Modules and packages group related code and separate a small public part from private details that can change freely.
- Classes bundle data with the methods that act on it; encapsulation keeps the data private so the rules are always enforced.
- An interface is a contract between parts; code that depends on an interface rather than an implementation can swap implementations easily.
- You call a library; a framework calls your code. This inversion of control makes frameworks powerful but harder to replace.
- Dependencies, fetched by package managers such as pip and npm, save effort but bring transitive code, security risk and maintenance work.
- Good structure means loose coupling and high cohesion, which keeps change cheap and lets teams work independently.
Why organise code at all
A small script of twenty lines can be read top to bottom in a minute. A real system, such as a food-delivery platform, may have hundreds of thousands or millions of lines written by dozens of people over many years. Nobody can hold that in their head. The only way to work on such a system is to divide it into parts that can be understood, tested and changed one at a time, with clear rules about how the parts may use each other.
Programming languages give us a ladder of building blocks for this, from small to large: functions, modules and packages, classes and objects, and the interfaces between them. Above the code we write ourselves sit libraries and frameworks written by others. This section climbs that ladder. The same ideas, applied at larger scale, are what software architecture is about.
Functions: name a job, then reuse it
A function is a named piece of code that does one job. It takes inputs (called parameters or arguments), does some work and usually gives back an output (its return value). Think of a coffee machine: you put in beans, water and a choice of drink; it does its work; out comes a coffee. You do not need to know how the pump works to use it.
Functions bring three benefits. Reuse: the VAT calculation is written once and called from the checkout, the invoice and the refund screen. Readability: total_with_vat(basket) says what is happening without showing how. Change in one place: when the VAT rate changes, one function changes, not twenty copies of the same arithmetic scattered through the code (copies that would soon drift apart).
A good function does one thing, has a name that says what it does, takes the inputs it needs as parameters rather than reaching out for hidden data, and is short enough to read at a glance. A function that only computes its output from its inputs, without changing anything else, is called a pure function; pure functions are especially easy to test, because the same inputs always give the same output.
VAT_RATE = 0.20
def total_with_vat(prices_in_pence):
"""Return the total of a basket in pence, including VAT."""
subtotal = sum(prices_in_pence)
return round(subtotal * (1 + VAT_RATE))
checkout_total = total_with_vat([1250, 399]) # 1979
invoice_total = total_with_vat([5000]) # 6000Modules and packages: grouping related code
A module groups related functions and data into a unit with its own name. In Python a module is simply one .py file; pricing.py might hold everything to do with prices, discounts and tax. Another file uses it by writing import pricing. A package is a folder of related modules, so a shop might have a shop package containing pricing, orders and customers modules. Other languages use different words (Java has packages, C# has namespaces and assemblies, JavaScript has modules), but the idea is the same.
A module is like a department in an organisation. The finance department has a public front desk where others can make requests ("please raise this invoice"), and internal processes nobody outside needs to see. Modules similarly separate a small public part, meant for other modules to use, from private details. Python marks private names by convention with a leading underscore, such as _round_pence; Java and C# enforce privacy with keywords such as private. The value of the split is that the private part can be rewritten freely without breaking anyone else, as long as the public part keeps its promises.
Functions, modules and a package
Classes and objects: data with its behaviour
Many languages let you bundle data together with the functions that work on it. A class is the template; an object is one thing made from it. A BankAccount class might say that every account has an owner and a balance, and can accept deposits and withdrawals. Each customer's account is then a separate object made from that class. Functions that belong to a class are called methods. (See the glossary entry for class and object.)
The most useful idea here for architecture is encapsulation: an object keeps its data private and lets other code change it only through its methods. If anyone could set account.balance directly, nothing would stop code elsewhere from making a balance negative or skipping the audit log. With encapsulation, the withdraw method is the only door, and it can enforce the rules every time. A bank teller is the analogy: customers cannot walk into the vault, but they can ask the teller, who checks the rules before handing over money.
There is much more to object-oriented programming (inheritance, polymorphism, design patterns), and practitioners disagree about how much of it is helpful: some languages, such as Go, deliberately leave out inheritance, and many teams favour a more functional style. Encapsulation, however, is valuable in every style.
class BankAccount:
def __init__(self, owner):
self.owner = owner
self._balance = 0 # private by convention
def deposit(self, pence):
if pence <= 0:
raise ValueError("Deposit must be positive")
self._balance += pence
def withdraw(self, pence):
if pence > self._balance:
raise ValueError("Insufficient funds")
self._balance -= pence
def balance(self):
return self._balance
acct = BankAccount("Ana")
acct.deposit(10000)
acct.withdraw(2500)
print(acct.balance()) # 7500Interfaces: contracts between parts
An interface is the agreed set of operations one part of a system offers to others: their names, what inputs they take, what they return and what can go wrong. It is a contract. The caller relies on the contract, not on how the work is done. A power socket is an interface: any appliance with the right plug works, and the appliance does not care whether the electricity comes from wind, gas or nuclear power.
In code, an interface lets you swap one implementation for another. Suppose a shop takes card payments through one payment provider today but may switch providers next year. If the checkout code calls the provider's own functions directly, everywhere, switching means rewriting the checkout. If instead the shop defines its own small PaymentGateway interface with one operation, charge(amount, card_token), then writes one adapter per provider, the checkout depends only on the interface and switching providers means writing one new adapter. The same trick makes testing easy: in tests, a fake gateway that never contacts a real bank can stand in for the real one.
Interfaces are the most important idea in this section for architects. Every boundary in a system, from a function signature to a web API used by thousands of other companies, is an interface. Good ones are small, stable and hide decisions that are likely to change; that principle, called information hiding, was set out by David Parnas in 1972 and is covered in Chapter 3.
from typing import Protocol
class PaymentGateway(Protocol):
def charge(self, amount_pence: int, card_token: str) -> bool: ...
class ProviderAGateway:
def charge(self, amount_pence, card_token):
... # call Provider A's web API
return True
class FakeGateway: # used in tests
def charge(self, amount_pence, card_token):
return amount_pence < 100_000 # pretend large payments fail
def checkout(basket_total, card_token, gateway: PaymentGateway):
return "paid" if gateway.charge(basket_total, card_token) else "declined"Libraries and frameworks: code written by others
Almost no one writes everything from scratch. A library is reusable code that someone else has written and that your code calls when it needs it: a library for handling dates and time zones, for generating PDFs, for talking to a database. You stay in charge; the library is a tool you pick up and put down.
A framework is different. It provides the overall skeleton of an application and decides when things happen; you fill in the gaps with your own code, and the framework calls that code at the right moments. A web framework such as Django (Python), Spring (Java), Ruby on Rails or Express (JavaScript) receives each incoming web request, works out which of your functions should handle it, calls that function and turns its result into a web page or data. The short way to remember the difference: you call a library; a framework calls your code.
That reversal is called inversion of control. Martin Fowler credits Ralph Johnson and Brian Foote with using the term in 1988, and the idea is often summed up as the Hollywood principle: Don't call us, we'll call you
. A restaurant franchise is a fair analogy: the franchise supplies the building, the ordering system, the opening hours and the rules; the franchisee supplies the cooking in the slots the franchise provides.
Who calls whom
Library or framework
| Library | Framework | |
|---|---|---|
| Who controls the flow | Your code | The framework (inversion of control) |
| Examples | A date library, a PDF generator, a JSON parser | Django, Spring, Ruby on Rails, Angular |
| Ease of replacing later | Usually moderate: change the places that call it | Usually hard: the framework's structure shapes the whole application |
| Main benefit | Saves writing one capability yourself | Saves designing the whole structure; consistent conventions across a team |
| Main risk | One more dependency to maintain | Lock-in to its way of working and its upgrade cycle |
Dependencies and package managers
Every library or framework your code needs is a dependency. Modern projects have many, so they use a package manager to fetch them: pip for Python (from the Python Package Index, PyPI) and npm for JavaScript (from the npm registry) are the best known, with equivalents such as Maven for Java and NuGet for .NET. A project lists what it needs, often with version ranges, and the package manager downloads those packages and, crucially, the packages they depend on. These indirect, transitive dependencies often outnumber the direct ones many times over.
Versions are commonly numbered MAJOR.MINOR.PATCH under a convention called semantic versioning: a change to the major number signals a breaking change, a minor number new features that should not break existing users, and a patch number bug fixes. Projects also keep a lock file (such as package-lock.json for npm) recording the exact version of every package installed, so that every developer's machine and the production build use the same code.
Direct and transitive dependencies
Dependencies save enormous effort, but each one is code you did not write, running with all the permissions of your own. Two well-known incidents show the risks. In March 2016 a developer removed his packages from npm in a dispute, among them left-pad, a tiny function of about eleven lines for padding text; thousands of projects that depended on it, directly or indirectly, suddenly failed to build until npm restored it. In December 2021 a severe security flaw, nicknamed Log4Shell, was disclosed in Log4j 2, a widely used Java logging library; organisations around the world spent weeks finding every system that contained it, often buried several levels deep in their dependencies.
Checklist: before adding a dependency
Questions a careful team asks before relying on someone else's package.
- Do we need it?A few lines of our own code may be cheaper over time than a package that must be tracked and updated.
- Is it maintained?Look at recent releases, open security issues and how many people maintain it. An abandoned package becomes your problem.
- What does it bring with it?Check how many transitive dependencies it adds; each is more code to trust.
- Is the licence compatible?Open-source licences differ; some impose obligations when you distribute your software.
- Are versions pinned and scanned?Use a lock file and an automated tool that warns when a known vulnerability affects a version you use.
- Could we replace it?Wrap important dependencies behind our own interface, so a change of supplier touches one place.
Why structure matters: change and teams
Code is read and changed far more often than it is first written. Most of the lifetime cost of software is spent changing it: fixing bugs, adding features, adapting to new laws and new partners. Structure decides how expensive that is. In a well-organised system, a change to how VAT is calculated touches one module. In a badly organised one, it touches forty files owned by five teams, and something unrelated breaks.
Two ideas, first described by Larry Constantine and colleagues in the structured design movement of the late 1960s and 1970s, capture what "well organised" means. Coupling is how much one part depends on the details of another: in a tightly coupled system, changing one part forces changes in others. Cohesion is how well the contents of one part belong together: a highly cohesive module does one clear job. The goal is loose coupling and high cohesion: parts that each do one job well and talk to each other through small, stable interfaces.
Structure also matters for people. If two teams must both edit the same module for most of their work, they will constantly wait for and break each other. If each team owns cohesive modules with clear interfaces, they can work and release independently. Melvin Conway observed in 1968 that systems tend to mirror the communication structure of the organisations that build them, an observation known as Conway's law and explored in Chapter 3.
Coupling at a glance
Why this matters for architecture
Architecture is largely the same activity as this section, done at a larger scale and with higher stakes: deciding what the main parts of a system are, what each is responsible for, which interfaces connect them and which way dependencies point. A module inside one program and a separately deployed service across a network are both parts with interfaces; the questions of coupling, cohesion and information hiding apply to both.
Chapter 3 makes these principles precise, starting with modularity and coupling and cohesion, and Chapter 8 returns to dependencies as a security matter in supply-chain security. Next, the code leaves the single computer: the following section explains how computers talk to each other.
Check your understanding
Why is it better to call one total_with_vat function from three places than to write the calculation three times?
The rule then lives in one place: when the VAT rate changes, one change fixes every caller, and the copies cannot drift apart.
What does encapsulation stop other code from doing to a bank account object?
It stops other code changing the balance directly, so every change has to go through methods such as withdraw that enforce the rules.
A web framework calls your function whenever a request for /orders arrives. What is this arrangement called?
Inversion of control: the framework, not your code, controls the flow and calls your code when needed.
What is a lock file, such as package-lock.json, for?
It records the exact version of every package installed, including indirect ones, so every developer machine and every production build use exactly the same code.
Sources and further reading
Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.
Ask: “How do I decide whether to build a feature on a framework or keep my business logic independent of it?”