Where you might want to start reading ...

Is there something wrong with software architecture - or with us?

I am a software architect (one of a few) for a 20-million LOC business software, with currently a few thousand installations, developed and ...

Monday, May 15, 2017

Models, notations, and languages

In previous postings, I used the terms "notation" and "language" too sloppily. Here is a short explanation how I intend to use them in subsequent postings—I hope that this is in line with common usage:
  • Whenever we want to work with some real world things, we need a model of it. The model is a more or less rigid (mathematical) abstraction of the object(s) under consideration (which are called the "universe"). For example, a real CPU might be modelled via an abstract processor, which only considers its assembly-level commands, but not e.g. its heat emission. A model of an SQL database might only consider tables, columns and views, but "abstract away" triggers, stored procedures and everything else the vendor might have added as a feature.
  • A notation is some sort of symbols that adhere to some syntax. Many notations are linear text notations (all programming languages I know of), but there are graphical notations like UML's notation.
  • A language is a combination of a model and a notation, where the notation is mapped to the model or modifications of it.
The last definition implies that one can have many languages for the same model. Here is a simple example of this: Let our model (and also universe) be expressions of integral numbers, with e.g. subtraction, multiplication, and evaluation. Three possible notations are
  • parenthesized infix expressions, e.g. "(5 – 3) * (5 – 2)"
  • postfix expressions, e.g. "5 3 – 5 2 – *"
  • and a tree notation that shows the expression tree.
All three (and many other) notations can be mapped to the model in a way that they "compute the result" of an expression correctly.

Having a good notation is important, but getting notation right is better done by a series of experiments with real people than a conceptual process. I will, therefore, not be very stubborn about notations for architectural problems. On the other hand, I will try to find a single small representation and manipulation model for architectural problems; and then try to argue that the chosen model is sufficient and practical and, well, good.

In spite of my laissez-faire approach to notation, I do hold a few beliefs about notation that I will try to argue more or less emphatically.

The most important is that any notation must scale to large descriptions. Thus, it must be possible to describe, in a manageable and legible way, a system that consists of a "flat 1000 different parts". By "flat 1000 different parts", I mean that the notation must not force the writer and the reader to introduce any sort of abstractions solely because the notation becomes unwieldy. I call this the "telephone directory property": A useful notation must be capable of practically notating a large, boring list of slightly different things "just so".

As a special case, I will not consider any diagrammatic notations for the moment (later, I'll come back to diagrams). For almost the complete history of software engineering, people—intelligent people—have tried to come up with a graphical replacement for formal textual languages like programming languages. There is a complete theory and much practical experience with two-dimensional diagram languages—but on the whole, they have never replaced textual languages in anything but small, and often not-too critical, software systems. The reason is exactly that diagram notations do not have the "telephone directory property"—diagrams describing a 1000-part system are, for all purposes, unusable: They cannot be viewed easily (especially if they contain longer, winding line paths), cannot be printed easily, and they cannot be manipulated easily. The morale: Designing and maintaining diagrams that are not useless from the outset is very hard.

(If you think "UML", and especially "UML according to all the software architecture textbooks out there", I remind you that my focus is not the use of diagrams for informal or "semi-formal"—whatever that means—purposes. For this, many diagram notations are perfectly fine. But I consider only what I called "use case no.3", i.e., languages for describing and maintaining architectures that have a strict semantics that can be used to prove or maintain something interesting in a software system).

There are a few more aspects—important aspects— that will influence all the many parts I want to assemble for useful "rule-based architecturing", but in order to keep the suspense low, I will now immediately give away what my proposed model is: (Finite) directed graphs with labelled edges and nodes. I will not restrict this quite general model much more, except that I have to define the allowed labels. They are:
  • A node is identified by a label that is a tuple of strings. In addition, a node can have informational tags, each of which consist of a name and a real number.
  • An edge is only identified by the nodes at its ends. It has three counts that are called the overall count, the questionable count, and the bad count. In addition, it can also have name+number tags like nodes.
  • Both nodes and edges can have an arbitrary source information that is intended to find the object from which the node or edge were derived at some time.
For "historical reasons" (we invented the basics of this model some 10 years ago), I use the following terms:
  • Nodes are called items.
  • Edges are called dependencies, and the two items at the end of a dependency are called the using item and the used item.
  • The identifying strings of items are called values, and the non-identifying tags of items and dependencies are called markers.
I hope that these terms do confer a rough meaning of the purposes for which they are used.

But—for which purposes are they used?

Sunday, May 14, 2017

What made xunit testing successful?

The xunit revolution introduced
  • a very simple notation (actually, two notations);
  • a reasonable benefit for every developer;
  • and, later, a culture that extended "mere xunit testing" to various "development philosophies" like TDD, TDD with baby steps, or BDD.
The notations have a set of important properties:
  1. They define a small language of a few important concepts:
    • At the core, only testcases that run in a predefined test harness framework; and—almost unrelated to that framework—assertions;
    • for scalability, testfixtures and setup and teardown of test cases and fixtures.
  2. The building blocks are very small: A single assertion is atomic; a single testcase can also be made atomic (i.e. just test a very tiny segment of the intended behavior).
  3. There is a simple tool that efficiently does the mundane job of collecting and executing all notated items (test fixtures and test cases).
  4. The tool can be easily run by any developer at any time.
  5. The tool can also be easily integrated into existing automated build processes.
  6. And, finally, the automatic execution can have a drastic feedback on the processes: Tests that do not pass halt the delivery process (by resulting in a "red" build).
The direct benefit for the developer is not that more quality assurance can be done during code development—even though later "xunit philosophies" are, one could argue, roughly founded on this belief (and delivered arguably better processes for direct support of development). On the contrary, more quality assurance (in the sense of "trying to find destructive input to check a program against the limits of a specification") during development would actually be an annoyance, because it disrupts the developer's constructive thought processes necessary for constructing code.

Rather, xunit testing helps to solve the problem of "later regression checks" occurring after code changes, when it is necessary to remember and run the simple as well as the tricky test cases that actually allow a developer (or a team) to hold the belief that the modified piece of code still behaves sanely.

The important experience is that that "later" is not only "much later", when a feature upgrade or bug fix requires changing the code, but that it can be right after the next (well or not so well thought out) modification during the initial development of some piece of code. That really helps developers.

Finally, xunit testing is open in multiple ways—how many tests one writes, how much behavior each one ascertains, when they are run in the development cycle, and when in the build cycle, and, last but not least, how writing and executing of xunit tests feeds back into design and code development. Because all this is not enforced by the tooling in any way, a host of "philosophies" could emerge on top of xunit testing, leading to a lively and sometimes heated debate with a huge effect on wide understanding and on "marketing" of xunit testing.

Great.

Could the same be accomplished for some parts of "architecting"?

We should try, at least, shouldn't we?

So, you and I and everyone should start to invent notations and tools for "architecting" along the lines of what made unit testing successful. I'll leave your ideas to you; in the next posting, I'll start to present mine.

Thursday, May 4, 2017

A role model: The (x)unit testing revolution

In order to overcome waterfall and moralistic approaches to software architecture documentation and process, one should take a look at other areas that succeeded in establishing new ways of doing things. One paradigmatic example is xunit testing.
Martin Fowler argues that what I describe here should not be called "unit testing", but "xunit testing". I follow his advice.
Two decades ago, testing had some problems that were, on a high level, similar to those of software architecture processes and documentation:
  1. Scope—what do you test, where do you stop?
  2. Documentation—how to make test cases permanently available, e.g. for regression testing?
  3. Planning—when and how long do you test?
  4. Responsibility—who does the testing?
  5. Maintainence—how to upgrade existing artefacts, i.e. test cases with their input and output? 
  6. Automation of testing—how can the repetetive parts of testing be done by software?
  7. And finally, the conceptual question—how much of quality assurance should a testing philosophy encompass?
Before the "xunit testing watershed", the answers were:
  1. The boundaries of what is tested, and where you stop, are ill-defined and arbitrary.
  2. Documentation is done by natural language or slightly formalized natural language. Formal approaches, using their specific "testing languages", are research topics and typically way overboard for almost all projects.
  3. Planning is mostly done "at the end", i.e. based on a waterfall view of software development (which is wrong), or rather, "between devlopment and delivery". This carries an extreme risk that testing is squashed between development overruns and promised delivery dates.
  4. The reponsibility for testing lies with "others"—not developers, but "testers". Therefore, there is no continuous process, with conseqeuent small granularity, between development and testing, but a "break" with corresponding hurdles in communications and planning.
  5. Maintenance is a heroic or bureaucratic effort that either fails or is expensive to keep up.
  6. Automation is done by external "test harnesses", for example "automated GUI testing tools". This is quite expensive and produces brittle test code.
  7. The testing philosophy—which has to be implemented by a testing process—should encompass as many quality assurance aspects as possible (consider the alternative:
    Test lead: "Our testing process only deals with functional expectations, but not non-functional ones."
    Manager: "Do you mean we need yet another organization to do the non-functional testing? Do you know how much your non-productive department already costs us?!?")
The net effect was that testing was essentially either a "bureaucratic" or a "moral enterprise": Either one had set up a separate organization to do the testing; or it was left to the morale of single people who—depending on which book they had read; or which catastrophe had occurred the day before at a customer—would either find testing the most important and underrated activity in the world; or an obstacle to development and shipping at that last possible minute.

The xunit testing movement (which was initiated by Kent Beck with his SUnit tool for Smalltalk) gives, fundamentally, answers that are exactly the opposite of the above:
  1. What you test are small units that are easy to handle.
  2. The test cases are code.
  3. and 4. Tests are run during development by the developers, with an arbitrarily small granularity.
  4. Maintenance is just like code maintenance.
  5. Automation is inherent.
  6. The goal is to cover only some software quality features, mostly correctness aspects. Other test topics like usability or acceptance tests are not targeted by the method.
Because of the last item, xunit testing cannot replace manual and other testing techniques—which was, of course, in the "old philosophy", an argument dealt against it. But over time, this argument eventually vanished.

In sum: Xunit testing introduced a striking new alternative on a subset of the testing problem.

Software architecture in real projects is, it seems to me, in the same situation as testing was twenty years ago:
  1. The boundaries of what is to be described by a software architecture are ill-defined and arbitrary.
  2. The description is, by the rule, done by informal text and "diagrams"—which are interpreted informally even if they use e.g. UML notation. There are formal approaches, but they are way overboard for almost all projects.
  3. The documentation is done "at the beginning", "before design sets in", i.e. with the implicit assumption of a waterfall process.
  4. The responsibility lies with "others"—not developers, but "architects"; with a resulting break in the process.
  5. Maintenance of documentation and architectural rules is a heroic or bureaucratic effort that either fails or is expensive to keep up.
  6. Automation?—there is no automation for architecture.
  7. Architecture, by definition, covers everything—sometimes limited to "everything that is important", but that does not really exclude anything.
It is obvious, from that list and from the xunit testing experience, that something can be done.

Two common, and defective, approaches in software architecture

Let me deviate—or actually, approach my target from a different angle—for two more postings before presenting one such "mundane notation" for software architecture documentation (which I have promised in my last posting).

What are the main problems with current (explicit) approaches to software architecture? Very briefly, they might be dubbed
  • the "waterfall approach"; and
  • the "moralistic approach".
The first one, "waterfall thinking", is the old idea that one "starts" with deciding on basic and important architectural aspects, and "then" goes on to design and write software accordingly. Some parts of software engineering might follow this pattern, but there are at least two major scenarios—or maybe forces—that lead to a different process:
  • One is the fact that in almost all cases, a huge software is already in place; and the architectural problem is to modify this software "from inside out". This can and is often be done by small exploratory "experiments" in the software that prove or disprove whether some concept might be worthwhile. And in many cases, this is done implicitly and "under the hood", when some developer starts, on his or her own initiative, to introduce the first RESTful service, a "small NoSQL database on the side", or reuses some executable for production purposes that originally started out as a tool for developers only.
  • The second scenario is brought on us by the typically vast capabilities of commercial and open-source frameworks or tools. When you buy SQL server instead of using Postgres (maybe for external reasons, like having a partner status with Microsoft); or when you take Angular instead of some lesser-known JS framework because some graphics library ties in better with it, you also "buy into" a huge feature set that comes with that tool. Your architectural possibilities are suddenly, and at the same time, extended by the tool's many for-free features, and also limited by the grand architectural and technological lines of it. And like your software, such a toolset is often "just there", without any possibility or even wish to ponder any underlying architectural requirements and decisions.
It is by no means clear that bottom-up approaches, as done by the hypothetical developer above, aren't on par or even better than processes that proceed from "grand architectural analyses" "down" to design and implementation. And, in real life, such bottom-up situations are unavoidable anyway. Thus, "waterfall thinking", while certainly an option, should not be the only and preconceived approach to architectural decisions.

The second problem is the "moralistic approach" to architecture (and design). Architecture and design decisions produce rules: "In our system, code on the GUI layer must access the database via an intermediate DAO layer"—or the other way round; "plugin registration happens explicitly by adding an entry to the configuration, and not implicitly by merely placing the plugin at some location"—or not. And somehow, such rules must be enforced. Most of the time, there are only two enforcement regimes in place:
  • One is the "build-and-install-regime": The build and, later, installation processes of a software require that certain rules are followed. These rules are often implicit, but at least it is hard to violate them. It is also often very hard to change them.
  • The other regime is the "moralistic one"—"you should", or "you must": Without support from tools, it is assumed that developers have the capabilities to follow the current rules. When, later, some disaster happens, one can more or less easily find a person that is the "culprit": "You shouldn't have added that trigger that implicitly calls itself and then fills up the audit table!", "You should not have hardcoded that connection string, but taken it from that (faraway) configuration file to keep database accesses consistent!" But of course, people will only adhere to some sub-100% percentage of rules—and this assumes that the rules are explicitly documented and consistent to begin with. And also of course, culprit-finding does not solve problems well (it might, in some cases, prevent others from violating the same rules in the near future). And finally, we are all versed in putting the fault on the shoulders of the ultimate culprit: "This has grown historically."
Both the "waterfall approach" and the "moralistic approach" are wrong in their fundamentals. But just by saying so, there is no positive alternative in place that replaces them. And, to tread somewhat more carefully, one should certainly not throw out top-down approaches (of which "waterfall" is a special case) and rules-of-thumb (an essentially "human-compatible" method for solving problems, just like "moral") from the portfolio of process building blocks for "doing software architecture": These are worthwhile at the, well, right places.

But some alternative view on "doing it" should be possible.

Wednesday, May 3, 2017

Purposes of architectural documentation disentangled

I have been a little unfair in my last posting: The eight pages on UML 2.0 in Gorton's "Essential Software Architecture" are more than a mere advertisement for that (then) new UML version 2.0—they do actually contain some core advice about how to document architectural aspects of a program. I'll try to extract a compact view of what architecture documentation is, in Gorton's and, I think, the mainstream architecture's textbooks' view, from these pages and the case study in chapter 7.

First of all, architecture documentation is a collection of artifacts for human beings only. This is in contrast to code, which is targeted both at the "machine" and at human readers. In the background, there looms the idea of model-driven architecture, where an architecture model is used to create code—essentially, a compiler for a new language on some "higher" level than standard programming languages. However, like the book, I will disregard this aspect right now and return to it somewhat later.

The clear target of providing information to humans has lead most of us to the use of informal diagrams and standard prose to describe the architectural aspects of a software—"simple box-and-arrow diagrams", as Gorton calls them. He claims that there is "an appropriate diagram key to give a clear meaning to the notation used" in his examples, but most diagrams in his chapters 1 to 5 don't have such a key, and in any case, most people drawing such diagrams don't include one. The problem with this is that any plan to derive hard facts from such diagrams is then doomed.

Now, one purpose of architecture documentation is to give someone a "feeling of the interplay of things", and for this purpose, informal diagrams with textual or oral explanations are perfectly fine and, I am quite sure, even preferable: They appeal to our intuitive approach to most problems, which includes working with somewhat unclear terms and their relations in order to limit thinking about tricky consequences, so that our mind is free to "suck in the universe" of the problem area at hand.

Maybe it should be noted that formal clarity, precise meaning and even "simple" (mathematical) consistency entail, in almost all cases, "hard thought work", as the history of mathematics has shown:
  • Geometry in the plane seems like an easy subject, until you start trying to understand its base and algorithms from Euclid's axioms and definitions, well over 2300 years old: There is nothing easy with concepts like parallels or ratios of line segment lengths! And later formalizations, mainly from about the 1800s onwards, are even more intricate.
  • The other, apparently so "simple" basis of mathematics, namely the natural numbers, lost its simplicity also in ancient times with some prime number theory by the Greeks. It was and is by no means obvious what can emerge from simple addition and multiplication, let alone from the algebraic structures and formalizations extracted in the 19th century, leading to Gödel's mind-bending encodings and Turing's work.
Let me state this in my "Axiom 1": Mathematics, by and large, is not what we want in software documentation (and that from me, who majored in theoretical computer science ...).

Still, it seems we all want something more than the informal box-and-arrow-diagrams.

Gorton, like many others, proposes the use of UML. I cannot help the feeling that he is not really happy about it. The summary of chapter 6 has the following two sentences:
I’m a bit of a supporter of using UML-based notations and tools for producing architecture documentation. The UML, especially with version 2.0, makes it pretty straightforward to document various structural and behavioral views of a design.
"A bit of a supporter", "pretty straightforward": This does not really sound like wholehearted endorsement.

So, what is the problem?

The problem is, in my humble opinion, that there is no clear picture of what a notation for architectural documentation should do. The described use-cases typically oscillate between a "better notation" for those informal, easily comprehensible overviews over some aspects of a software system, and a more formal notation that can help derive hard knowledge about a system, with that implied goal of "generating code" in model-driven approaches.

I am, after many years in the field, now certain that we have to structure the use cases for architectural documentation in a threefold classification, with different notations for each area:
  1. Informal documentation, from which humans can learn easily and intuitively gather a common understanding and a useful overview about some aspects of the system. In the best case, such a documentation is part of a common culture about "how we name and see things." However, this documentation is not intended to derive any hard facts: Everything shown can be disputed and discussed and viewed differently, and the notation can be extended at will if it helps with that intuitive understanding. All must agree that formal arguments based on such documentation are futile and hence must be avoided.
  2. Formally sound and precise documentation that can be used to derive invariants and definitive properties of the documented system. If such documentation is used as the basis for a tool-supported model-driven approach, then there is no difference between a descriptive and a prescriptive architectural documentation for the aspects covered by the process. However, such an approach is very expensive in more than one respect:
    • First, especially without full tool support, keeping such a documentation in line with the system is much work, as even tiny changes on one or both sides require precise updates.
    • Second, as software can exhibit very complex behavior, the notation must be capable of describing many and, usually, deep concepts, which makes it hard and "mathematical" to understand and even harder to write. Such documentation therefore blatantly contradicts "Axiom 1".
    • Last, on a conceptual level, it is not really clear that such a documentation is actually "documentation" in the sense of "humanly accessible information relevant for many decisions in the software life-cycle". Rather, it might be more of a formal specification or even—when used in a model-driven process with code generation—part of the implementation, albeit (maybe) on some higher or "more compact" level than standard programming languages.
Thus, rich informal and deep formal notations are not sufficient for documenting and arguing about architectural aspects of a software.
  1. Therefore, we need notations that are somewhere in-between: Not informal, so that they can be used to derive and ensure hard facts. But equally, they must be easily usable so that they can be read and written by the average software engineer under average project circumstances. It should be obvious that this type of notation cannot be very rich and also not very abstract. Only then, it can on the one hand avoid requiring an extensive semantics for formal derivations, and on the other hand being too esoteric to be used for understandable documents. In other words, it must be a quite mundane notation. I'll show my preferred notation for this, and its uses, in later postings—just in case you think that this looks a little like the search for the holy grail.
UML, incidentally and unfortunately, does not work really well for any of these purposes if its complex semantics is taken seriously:
  1. For an informal notation, it carries a too heavy backpack of that formal semantics which no-one wants to remember when drawing informative diagrams in a running text (as, e.g., in the case study in Gorton's book).
  2. For a formal notation, it is too indirect: One needs to map UML propositions back to the underlying semantic model (like Petri nets or state machines), and only then one can formally draw conclusions; as far as I can oversee it, the number of publications that use UML as a formal base has declined quite a bit over the last years.
  3. Finally, as a simple but yet strict notation, UML is much too baroque, because it was lobbied to include every useful diagram and icon. This large notational size would recommend it for many different informal diagrams—if it weren't for that formal semantics ballast ...
But even if  you think that UML does work well (or well enough) for one area, there is the danger of misinterpreting UML diagrams: Is a diagram which your team uses as a basis for a decision a "type 1." diagram?—then it conveys informal concepts, but does not limit the decision strictly or formally. A "type 2." or "type 3." diagram, on the other hand, would narrowly limit some choices you can make—and definitely require a formally (for "type 2.") or at least collectively (for "type 3.") approved update of the diagram for any change in the software or the architecture. But most diagrams do not spell out explicitly their "conformance level".

Nonetheless, our analysts and some of our developers and architects (including me) are happy enough to use UML as a pool of symbols for sketching explanatory diagrams that help us to keep our complex machinery at least somewhat documented. So yes, I am, and we are also "a bit of a supporter of using UML-based notations and tools", as Ian Gorton puts it.

But now, I feel, I am starting to owe you an explanation how to do architectural documentation better. The next posting ... well, after I wrote it, it turned out to still consider some general observations about software architecture and how we deal with it.