In this article, we will explore the 3 programming paradigms and what we can learn from them. As we go through each paradigm, we will see that each tells us what not to do, more than they tell us what to do.
Paradigms are ways of programming that tells you which programming structures to use, and when to use them. There are 3 main paradigms: structured programming, object-oriented programming and functional programming. These paradigms are not programming languages, but rather, programming languages have evolved to cater to the paradigms they target. Java and C++ are examples of object-oriented programming languages, while Lisp and Haskell are examples of functional programming languages.
1. Structured Programming
This is probably the first paradigm that we were taught when we first learned programming. This is also the first paradigm ever to be adopted. It was formalized by Edsger Dijkstra in 1968, where he discovered that all programs can be constructed from just three structures: sequence, selection (if/else
) and iteration (do/while
).
Structured programming allows modules that used simple selection and iteration control structures to be recursively subdivided into "provable" units over and over, ad infinitum.
Dijkstra once said, “Testing shows the presence, not the absence, of bugs.” A program can be proven incorrect by a test, but it cannot be proven correct. The implications of this fact are stunning. Software development is not a mathematical endeavor, rather, it is science. In Mathematics, we try to prove statements/formulas to be true while in science, we show correctness by not being able to prove its incorrectness.
What can we learn from this paradigm?
Functional Decomposition - structured programming forces us to recursively decompose a program into into smaller functions using divide-and-conquer. We then use tests to try to prove these small provable functions are correct enough for our purposes.
2. Object-oriented Programming (OOP)
What is OO? Encapsulation, inheritance and polymorphism. It provides a way to model the real world by combining data and function using "objects", thus the name object-oriented programming.
In OOP, we use polymorphism extensively along with dependency inversion. They enable us to program to interfaces, rather than specific implementations. This flexibility gives rise to the "plugin architecture" - where specific implementations can be substituted as long as they adhere to the interface contract. As an example, when we write code to read from STDIN, we are programming to the interface STDIN. STDIN can can be swapped with multiple device drivers implementations and the best thing is, our program does not have to change to use the specific implementations (even if they are written in the future).
With this approach, software architects have control over the direction of source code dependencies in the system. They are not constrained to align those dependencies with the flow of control.
What can we learn from this paradigm?
Plugin Architecture - OO provides the ability (through polymorphism) to gain control over source code dependency in the system. It allows architect to create a plugin architecture, where modules containing high-level policies are independent of modules containing low-level details. These modules can then developed and deployed independently from each other.
3. Functional Programming
Functional programming puts emphasis on data immutability. Why does immutability matter? Because race conditions, deadlock and concurrent update problems exist due to mutable variables.
This becomes important as an application scales out, especially in distributed systems, since it will require multiple threads and processors, which can give rise to the concurrency issues above.
Is this practical? Yes, if certain compromises are made. By segregating mutable components from immutable components, we can use disciplines like transactional memory to protect the mutable components from concurrent updates and race conditions. Another way is by keeping track of running transactions instead of mutating values directly, we can calculate the supposedly mutated value from those transactions.
What can we learn from this paradigm?
Immutability - architects should design systems that is robust in the distributed world which require multiple threads and processors.
Conclusion
What do these programming paradigms teach us about software architecture?
- Functional Decomposition - structured programming is the algorithmic foundation of our modules
- Plugin Architecture - polymorphism is the mechanism to cross architectural boundaries while ensuring separation of components
- Immutability - impose discipline on the location and access of data
References: The content of this article is referencing concepts/ideas from the book Clean Architecture by uncle Bob (Robert C. Martin). It is a really great book that I recommend to further if you're interested about software architecture.
Buy it here: https://amzn.to/3SJguhf
Top comments (0)