What is it that we talk about when we use the term Software Architecture?
Properly called “Architecture” is, the single human discipline outside Mathematics, which generated more ideas which were historically influential on software development.
We (computer programmers, scientists, IT guys) even stole the term Architecture itself, which normally is relevant to the context of giving structure to human spaces, when talking about the STRUCTURE of programming constructs.
Architecture is a nice choice as a word, because, as the original discipline, it is about structure, but that structure is not, as it is in the case of biology, just a descriptive tool, rather it is intentional, it is all about design.
Architecture, is indeed structure with a purpose.
If we examine the historical evolution of computer programming, all programming paradigms originate from the need of removing control from the programmer: we started more or less with a free form attitude, complete freedom, with data and code not even separated, and conditional branching as a way to structure code, giving way first to structured programming, then object oriented programming and functional programming.
Structured programming removed freedom of branching, restricting to only three possible flow constructs: Linear Iteration, Conditional Branching, Loops.
A very smart Dutch mathematician and theoretical computer scientist, (Dijkstra) jtractually wrote an essay, “Goto considered harmful” which sent ripples, and was followed by a proof by two Italian computer scientists, Jacopini and Bohm, that those 3 constructs were sufficient, being Turing complete: any possible computer program can be expressed using just those 3 constructs.
Object oriented can be seen, as a way of removing freedom in structuring code and data structures: Functions closely tied to data, are declared as methods and bound together with the data structure they process, as objects.
You can subscribe to the view of object oriented programming having the traditional 3 magical qualities of:
Encapsulation, Inheritance and Polymorfism.
Encapsulation is actually enforcing loose coupling across modules, tight coupling, i.e. closely coupled data and functions have to reside in the same object, and some of the data might not even be accessed outside the object definition.
Inheritance is a way of removing duplication, by “inheriting” behaviour and data structure across classes: again a way to simplify code and structure, creating some order.
Polymorfism is a way of considering “behaviour” pluggable, rather than fixed: the same named function, can be invoked, depending on the object being passed as an argument, rather than having to write differently named functions for each different parameters signatures.
Incidentally in modern compilers, the human readable same name functions, were (are) still translated into completely different names, this process is called name mangling.
This functionality of invoking a similarly named function according to the type of an software construct (well, object) can be implemented in non Object Oriented languages such as C, with indirection, using function pointers. Object oriented programming is a more disciplined way of obtaining the same result, with less freedom for the programmer.
Functional programming, in its pure form, adds yet another limit to the programmer, removing even more freedom: The program is not allowed to have changes in state, that is, once assigned, in a functional language, a variable cannot mutate.
That requires substituting loops with recursion, and requires a harder and less intuitive approach, but has some advantages.
If you think that functional programming languages are difficult to grasp, think about Excel or any other Visual Spreadsheet: a cell depends on previous cells, and affects cells after its evaluation. If you change any one cell included in a computation, it will affect all results depending on it.
Not having state changes, means that code will produce the same result with same inputs, at all stages, and that makes reasoning about certain problems easier, despite having to deal with recursion.
Also, if you consider a function as equivalent to any other programming construct, i.e. having high order functions, i.e. a function being assignable to a variable or another function you end up with some interesting simplifications. You don’t need to deal with looping, because you can iterate on collections with high order functions, e.g.: mapping each element to a function, and returning the resulting array resulting from this function application, in a single line of code.
In terms of being expressive, high order functions offer a concise and powerful way, which allow to write less code, and achieve the same result you would have obtained by looping and branching.
One of the main tenet of software architecture is a concept we stole from the Bahaus architectural movement: form “follows function”, or, better: form should follow function.
Otherwise, we would all be using the simple black box and would structure ALL our problems in terms of “Input” -> “Process” -> “Output”.
Such a simplistic “architecture” was actually the basis of most simpler programs of the past, and practically drove the structure (enforced by the programming language) of COBOL.
When the IT industry was still in its infancy, we witnessed many failed attempts at creating software development methodologies, one of them, which failed spectacularly was the so called Structured Analysis by Tom De Marco and Ed Yourdon.
While Structured Analysis failed, actually some of the arguments pushed by Tom De Marco are pretty much relevant today, and Structured Design (Page Jones and others), the companion and little known sibling methodology, actually provided an important MEASURE of the quality of GOOD software architecture. That important measure is Coupling, the more coupled the system is, the worse it is its design.
You can define coupling as Dependent on. Meaning that any changes affecting a module, will also require changes in the coupled modules.
Decoupling is almost free in non statically typed languages, which however suffer from other problems.
In Structured Design you reasoned about program branch graphically, and had a way of displaying a parameter passing across modules.
That kind of diagram (module invocation, call-tree) is still to date produced by some reverse engineering tools, as it is a powerful way of understanding the structure and working of an unknown piece of code.
Why is too tight coupling bad? We might naively think that it doesn’t matter, that it is all about personal style, taste and choice. Some programmers would regret having to conform to increasingly more stringent constraints, which actually limit their freedom.
An assembly language programmer, especially a non disciplined one, enjoys practical unlimited freedom, while an object oriented programmer, or a functional language programmer has less freedom in the way he or she structures code.
When we talk about software architecture, and sometimes about languages, it is not about matters of taste, it is always, as it is the case in all engineering activities, a matter of economics, cost.
If I, as a computer programmer. need to pass a piece of information across all my computer architecture, then everything is coupled with everything else, and if I need to introduce a change, I need to change it in all places. That makes my software frail, as any change, no matter how tiny, affects my whole system.
As change is to be expected during the product development and during its maintenance, the faster I can make changes, the less I am going to spend, as those changes will be implemented more easily.
And the less changes I need to make in order to implement a new feature, the less likely is that those changes will introduce an error.
Another nice concept is Intellectual Integrity. A system is said to have this property if it can be understood completely by just one person. E.g.: Programming languages designed by a single researcher, tend to have more clarity and being simpler to grasp than programming languages designed by committees and revised multiple time by different people. Very large and complex systems often lack conceptual integrity, and that can cause many problems, greatly increasing the risks of operating such a system.
As times passed from the sixties to the seventies and early eighties software scientists slowly evolved and created initially many different data structures, and algorithms, in order to solve many common problems. I.e.: they noticed that some common problems could be tackled with “recipes”, and some of those recipes were actually mathematically predictably faster than other, at solving the same problem. Those recipes were named “Algorithms”. Actually, as some of the earliest computer scientists were mathematicians, that word choice was quite inevitable.
However, algorithms did not specify how better to structure code and data structures, only the computing steps required in order to obtain a certain result. Some algorithms were equivalent, meaning they obtained the same result with the same initial data, despite following a different procedure.
A good example is any real industry implementation of the industry standard DES. A real world implementation is a highly efficient, fast and equivalent implementation of the very slow standard algorithm. You could speed it up by implementing larger look up tables for the S functions, by using bit manipulation rather than masking, provided your processor instruction set supported it, or you could use large tables to implement the required bit permutations: a few look ups an an OR rather than actually slowly shuffling bits. This kind of reasoning made it possible to use this “slow” cryptographic algorithm fast enough to encode live video streaming data.
Then, after a while, reasoning about code, we stole yet another concept from architecture: Patterns.
That was a concept stolen from an individual architect, Alexander.
By definition In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. I would underline: “a reusable solution to a common problem, which is dependent on context” and notice that does not mandates any single implementation.
I.e: It means that applying a software design pattern outside its intended context is WRONG, by DEFINITION a pattern is not a universal solution to any given problem.
And that is the case, precisely why applying the black box “input”, “process”, “output” model is so wrong. It is too general, it can’t possibly be a good description of the problem at hand, and of all possible problems at hand.
Take for instance the oldest and most famous pattern of all times, the MVC, Model, View, Controller. It was designed at Xerox, while working on Smalltalk on the very first windows system in history.
The Model should deal with the Data, the View with showing that data to the user, and capturing user interaction, while The Controller should orchestrate and communicate with both.
Actually the controller used to be drawn in the center, Apple even merged the view and the controller together, in the ViewController, with the view itself hardly doing any processing at all.
MVC as a pattern just describes just a way to structure UI interaction, but does not mandate it being used as a way to structure application in its entirety.
I.e.: MVC does not prescribe where you should put your networking code, nor says anything about routing, database, or similar.
So, why is it that some particular implementation of certain UI design pattern, becomes a software architectural pattern, of all possible software applications you might want to design, in all conditions?
That is structuring your software architecture around premade templates, following recipes, “just add water”, without thinking.
That approach a sad mistake and we all know how it ended, in practice, and especially in iOS world, the ViewController usually had a tendency of becoming a fat class, violating the single responsibility principle, and tries to do all the processing in one place. That leads to code which is very hard to read, maintain, understand and change.
Note: The Single Responsibility Principle mandates that in a well designed system, a single component or “object” should be responsible for a single function, again using decoupling as a measure of good design.
Using always the same pattern is a one size fits it all approach. That is, MVC is quite convenient if you limit its scope to View and UI interaction, with some ample space for improvement. It becomes terrible if it becomes your single and only tool.
This does not imply that In the past I have not been personally guilty of using MVC out of its scope, blissfully copying the misguided traditional Apple approach, which might be ok in small applications, but becomes increasingly more difficult to use in more complex cases. Now I know a little better.
What is, supposedly, the “cure” to MVC? Other design patterns, which attempt to solve some of its problems.
That this approach is naive and superficial should be apparent as it is a fixed recipe which is mandated, and does not change according to context.
It can be argued is that, it is not even a proper design pattern, as it is a fixed recipe (a particular implementation) and is independent from context!
“Uncle Bob” Martin, the inventor of the term “clean programming” approach to which VIPER declares to be inspired made it very clear what his thinking process was when he came across the problem. The video in which he explains this process is available on youtube, the interesting clue is at about min. 11.
Notice that he explicitly says that Architecture is about intent, not just structure.
Uncle Bob was examining a web application written in Ruby on Rails, and noticed that the folder structure was that of a Ruby on Rails application, but he could not determine anything, from structure alone, on the purpose of the application itself.
I.e.: he noticed that in that case FORM DID NOT FOLLOW FUNCTION, and that is a hallmark of lack of intentional architecture design. It means you are just applying a pattern, without even thinking whether the pattern makes any sense to the context, the problem at hand! That is bad design.
So, why is it that VIPER, become mandated, in some organisation, as a required pattern?
A required pattern is a pattern which is applied outside any reflection on scope, and that is an ARCHITECTURE SMELL. Maybe you should be able to choose architecture, and reason critically about the consequences of your choices.
I am creating the term “architecture smell”, for symmetry with the term “code smell”.
Like a code smell is a possible indicator of a programming problem, an architecture smell is an indication of a possibly serious architectural error.
The old criteria for determining if an architecture is a good one remain valid:
Good architecture minimises coupling, complexity and cognitive load.
It needs to be simpler to reason with, otherwise, if it requires an additional effort to be understood, or if it increases complexity, it is a poor choice.
Good architecture is about minimising costs, and building BETTER, steadier, more resilient systems.
Good architecture does not mandate a particular software pattern to be used.
Good architecture requires DESIGN, that is actually doing architectural work, not just regurgitating, without even reflecting, a particular design pattern. That thoughtless application of a pattern outside its scope, incidentally, is exactly the problem which the new pattern aims to replace!
I would also observe that the implementation of VIPER which claimed to be the “accepted standard” about Clean Code was just an implementation of a CONCEPT, and that concept, described by its original author, was not at all that prescriptive!
It was supposed to be an approach, with a design decision, following a process, and some indicators on the quality of the process. Also, Robert Martin was talking about an approach towards a Clean Architecture and was not endorsing the substitution of another design pattern, he repeated that point in the Architecture Screams chapter: architecture should clearly show what the application is all about, and not, e.g.: the particular framework it is based upon.
You can decide to follow Uncle Bob’s writing and create YOUR OWN clean architecture, based on ‘plugin’ modules with minimal coupling. It is going to require some thought, but is probably. more likely at being successful than just copying a tutorial on VIPER over the Internet.
Robert Martin (Uncle Bob) describes very well what his standing point was: E.g. the Presenter should be a “plug in” to the core of the architecture, the Business Use Cases, which he calls Interactors, which are plug ins to Entities. The reason being that Entities are more general than the use cases which use them.
He prescribes a sort of Hierarchical Onion Architecture, in layers. Layers should be substitutable without the need to change all the rest of the system, they should be DECOUPLED.
MVVM, MVP, Clean (by another author, also known as VIP) are all design patterns, and, to be fair they are about UI, so it is even debatable if it is appropriate calling them “Architectural Frameworks” or, even less correctly, “Architectures”.
I now prefer to call them Presentation Patterns, and, to be fair, most of the discussion you find about them on the Internet is absolute garbage.
Maybe as Bob Martin says, it is someone else, who really did not get the pattern made a terrible work misusing it, History tends to repeat itself.