At present, I believe that dependencies are the first-order problem.
Hidden dependencies exacerbate the problem.
Languages must be designed to allow construction of independent units. (For example, calling functions by-name should be disallowed).
Package managers and build managers (like make) facilitate our use of dependencies, instead of making such dependencies more obvious (and painful to use).
Libraries that depend on other libraries (ad infinitum) contain hidden dependencies and build up dependency debt.
I believe that diagrams show dependencies more readily than textual code.
Whiteboards are found in every software shop.
This observation is a tell - it indicates that something about current PLs is inadequate.
(My suggestions follow).
The Little Things matter.
If you think of parsers as needing tokenizers (scanners), it changes the way you approach a solution.
How does your approach change if you though that parsers were as easy-to-use as REGEXPs?
Software components are asynchronous by default.
Synchronous components are the exception, not the rule.
Software components “live forever” (like web servers).
Components that wake up and die are the exception, not the rule.
Software components can be supplied inputs at different points in their lifetimes.
Components that need all inputs at once are the exception, not the rule.
Componenets that need all inputs delivered in single blocks are the exception, not the rule.
Software components can produce outputs at various points in their lifetimes.
Components that provide all outputs at the same time are the exception, not the rule.
Components that provide all outputs in single blocks are the exception, not the rule.
Exceptions are not exceptional.
Exceptions produced by components are the same as all other outcomes produced by components.
The problems and the solutions dictate which outcomes are considered to be erroneous. Software Architects design solutions that produce the desired outcomes, picking from a multitude of choices. Few problems have exactly one solution - it is the Architects’ responsibility to make vaarious trade-offs and to make the design clear to readers.
Software components have input and output ports.
Most current PLs have APIs that imply synchronous operation.
One Universal Type
Components are plugged together port-to-port where ports have a single, universal, simple type, e.g. a message.
The above does not imply that type-checking is discarded.
Types checking is done in a pipeline, from simple to more complex.
Type checkers are, currently, interpreters that filter incoming information and raise errors.
Type checkers become regular software components, with one input and two outcomes (data to be checked, data that satisfies the check, error condition, respectively).
Multiple checks can be inserted into the pipeline, suiting the problem-at-hand.
Not all software components need to fit this simple - one-in-two-out - model.
Components are built in layers.
No layer contains more units than can be comprehended, e.g. 7±2.
Components can, themselves, contain layers, recursively.
Long running loops and deep recursion are not allowed.
Long running loops and deep recursion can be broken into smaller steps by sending continue messages to the looping component(s) (or self-sending such messages).
Compilers can insert yields into loops (say, the bottom of a loop) and new syntax can be used to flag deep recursion for automated breaking-up.
This is, essentially, mutual multitasking.
Most programmers quote Windows 3.11 as a failed attempt at mutual multitasking, but do not balk at the idea that applications may contain bugs (esp. early in the development of the application).
Preemptive multitasking is a special technique - an exception, not the rule - that needs to be employed when building operating systems. There are very few programs that need to use this technique, e.g. the software called Linux, Windows, MacOS, etc.
Applications do not need preemptive multitasking internally. There is no need to pay the cost of preemption[^2] when it is not needed.
[^2:] Preemption does have costs, e.g. (1) hardware facilities, (2) accidental complexities, (3) overkill use of libraries (often called “operating systems”), etc.
Diagrams are a way to visualize multiple outcomes.
Diagrams are a way to show nesting and locality of reference.
Diagrams can visualize information leakage.
Diagrams make it difficult to draw leaky components, especially when everything (e.g. function calls) is made explicit.
Example Diagram Scenario
A software component is represented as a box.
Software components are asynchronous.
Lines represent message flow paths.
Software components contain input and output ports.
Input ports are small green circles.
Output ports are small yellow circles.
A Dispatcher routine invokes ready components in a random order.
All names are relative to components.
Components have 5 namespaces:
connections between components
A component refers to a component that is contained in it by using a name (e.g. “inner”) or and index (1, 2, 3, …), for example:
Likewise, it can refer to a named input “in” as, for example: