One of the most important considerations in software design is how it is broken into pieces. Most software conceptually consists of many tiny parts, all of which are essentially glued together by the user interface. Whether the conceptual separation is reflected in the design, however, is left up to the designer.
There are many other interesting facets of software design, but they are left for the curious.
One of the most common techniques for designing reliable software is abstraction: finding a common solution to many problems, and solving them once. Subroutines and functions are perhaps the oldest examples of abstraction, but there are others (more than we have time to discuss).
One of the reasons for abstraction is to separate concerns: any given part of a given program should be written to solve a single problem; if the problem can be broken down conceptually into isolated sub-problems, then these sub-problems should be solved elsewhere.
x = int(input("Tell me the first number: "))
y = int(input("Tell me the second number: "))
op = input("tell me the operation (add, multiply): ")
if op == "add":
print("result: ", x+y)
elif op == "multiply":
print("result: ", x*y)
Exercise: In the above program, identify and describe at least two of the sub-problems which are solved multiple times. (I see at least three or four.)
Exercise: Choose one of the sub-problems, write a common solution to that subproblem in the form of a function, and modify the program to use your common solution instead of solving the problem multiple times.
Software in which the conceptually separate pieces are designed separately are called "loosely coupled", or sometimes "modular". Common design patterns for loosely-coupled software involves
The opposite, tightly-coupled or monolithic software can have
For kernels, maximally decoupled systems are called "microkernels." In a microkernel system, the core kernel itself is just the bare minimum necessary for inter-process communication; everything else is written as a collection of cooperating userspace applications.
Faults in kernels generally require a hard reset of the computer; faults in userspace applications generally allow the computer to continue running, perhaps taking action to restart the faulting component.
These days all kernels are modular in some part; they are able to load third-party drivers. The Linux kernel seems to be slowly transitioning to more modular: there are several frameworks for writing filesystem and device drivers in userspace.
The official Linux kernel is monolithic as a codebase: there are no
official drivers outside of ~torvalds/linux.git
, and any breaking
design changes to the kernel design must include updates to all
included drivers. It is also monolithic in terms of process separation
and security: the kernel (currently) has one view of memory; this may
change in the future as efforts to compartmentalise progress.
Internal design of the Linux kernel, however, has many modular aspects: most of the kernel consists of a large collection of filesystem, network protocol, and device management drivers which can be configured to be built as "modules", loadable and unloadable portions of kernel code.
For those interested in microkernel gluttony, read about GNU HURD (the GNU stab at a microkernel-based libre operating system) and Plan9 (the Bell Labs attempt at a successor architecture to UNIX) on Wikipedia.
Common examples of tightly-coupled software include popular web-browsers (Firefox, Chrome), compilers (gcc, llvm), office suites (libreoffice, etc), graphical mail programs (Thunderbird, Evolution, Kmail), version control systems (git), graphical file managers.
Loosely-coupled systems are generally made of many separate software
projects, each with its own set of developers. E.g. in order to use
the mutt
e-mail client, you also need an editor (typically vim
),
a program to download the e-mail (e.g. isync
), a program to send
e-mail (sendmail
?), ...
There are other measures of coupling besides "codebase": systemd
is
a suite of tools for system management; though it is one codebase,
many of the tools are unrelated, and are largely developed separately.
Taking in entire communications systems at a time, we can see the decoupling between client, intermediate, and server software. For example, the web is highly decoupled from a birds-eye view:
This structure is essentially the backbone of cloud computing.