Preface
I don’t have a formal background in software, so I’ve never really learned programming design, either actively or passively. What I have is an empirical view built on observation and practice.
Board-level embedded engineering doesn’t pay attention to these things either. It mostly cares about RAM/ROM usage, execution speed, and whether the business logic meets the requirements. That leads to a serious problem: the output in such roles tends to be customized for one specific product.
There are two points worth discussing.
- Is it really impossible to use reusable code on truly resource-constrained MCUs? I don’t think so; I’ll discuss the reasons later.
- Can customized code like this, archived with almost no comments and little documentation, really be called
software assets? I personally don’t think so. In my view, any software module that a beginner cannot use within eight hours without guidance is unqualified.
Lately I’ve been thinking about how to produce true software assets, so I need to understand some basic programming design.So I spent ten minutes completely mastering it
The following is armchair talk about these designs. Just take it with a laugh—I haven’t actually practiced any of this much either.
Object-Oriented Programming
Object-oriented programming is a phrase that’s been talked to death. To be honest, I can’t keep those jargon terms straight at all.
In my view, the essence of object-oriented programming is defining a thing: specifying what functions, what variables, and what fixed quantities it contains. For example, we define a keyboard. It has a fixed count of 104 keys and the functions “Press A” and “Press B”.
But wait—I’ve seen all kinds of weird keyboards on the market. This involves the so-called “inheritance”: those weird keyboards simply override parts of our standard keyboard class. For example, they disable the numpad’s “Press 1” function and override the fixed count to say 88 keys.
In the object-oriented world, everything is defined as an object. We define objects, fix their shape, and extend them through inheritance; in this way, a module can be completed. Then we arrange and combine multiple objects, and a project can be completed.
I think that after weighing porting difficulty against functionality, object-oriented programming is without doubt the best way to achieve modular encapsulation (for now, at least).
But from a project’s perspective, object-oriented programming can be quite unfriendly. If the definition of some object changes mid-project, doesn’t the whole system change drastically, making it harder to track down the source of the smell? If you add an extra intermediate layer around every use of an object for isolation, isn’t that completely redundant and a new definition in itself? In any case, definition changes have too much impact, which is not very friendly to maintainers.
Of course, standardized things, such as communication protocols and interface protocols, can be objectified boldly. But what makes a project valuable is often the non-standard stuff.
What we actually want is to feel good. Hesitating, uneven skill levels, and chaos everywhere—that doesn’t feel good.
Functional Programming
Main reference: 1.7. Introduction to Category Theory (*) - Banana Space. This reference only covers mathematics and does not touch programming.
This isn’t really hard to understand: everything is a function. But the magical part is that business functionality can still be implemented when everything is a function.
In functional programming, there are two most important rules (I made these up):
- Every function must have an input and a return value.
- Under any circumstances, a function must return the same output for the same input.
In my view, the essence of functional programming is obtaining a function with no side effects and deterministic behavior, which converts an input into an output in a deterministic way.
In the diagram below, every arrow is a function, and every dot is both an input and an output. For example, the first arrow from the left takes the first dot as its input and outputs the second dot, and that second dot can then be turned into two other dots through two functions. A dot can be anything—and under this way of thinking, anything is a function. A real-number value is a function with a fixed output, and taking a variable is the identity morphism[^identity-morphism].

Image stolen from Functional Programming Tutorial – Ruan Yifeng’s Blog (https://ruanyifeng.com/blog/2017/02/fp-tutorial.html)
For example, implementations of CRC16 and all sorts of classical encryption algorithms are definitely functional programming: a set of values to be verified / plaintext passes through a function and outputs a set of check values / ciphertext. The verification process and parsing are also functional, so in these two scenarios the input and output are isomorphic[^isomorphism].
We can see that all purely mathematical problems are simple when approached with functional programming. Don’t argue that math can describe the world—oi oi.
In real engineering, however, all states cannot be defined so simply. With large inputs and outputs, writing functions is difficult, and I don’t think the number of functions would be small. If you decompose the problem, the number of functions is enormous. Most importantly, the accursed product managers keep changing requirements, and new idiot customers bring new idiot-proofing mechanisms, which makes the development environment for functional programming even worse.
Actually, functional programming is in line with industrial thinking: deterministic actions bring deterministic feedback. For certain business logic that targets a finite set of cases, functional programming can be used just fine, instead of a state machine, which is relatively difficult to manage.
This brings us back to “Discussion 1” from the preface: Is it really impossible to use reusable code on truly resource-constrained MCUs? Obviously, functional programming consumes similar resources in any situation, and we can shrink the category[^category] and directly delete unneeded functions to achieve reuse.
My view is that writing some math-related functions functionally is a good encapsulation, but bringing this mindset into engineering and business is unnecessary—the workload simply increases exponentially.
Perhaps standard instruction generation is also a good direction for functional programming. I might rework it if I get a chance.
Procedural Programming
The two approaches above both reduce the side effects of functions/methods, allowing them to deliver nearly identical functionality in different systems. Procedural programming is different. Its essence is using side effects to get the desired functionality, rather than passing input/output values through functions/methods to get there.
Procedural programming is the style that most closely matches how people think of solutions. I won’t elaborate—everyone knows it.
Summary
In short, in terms of layering software assets, the bottom layer consists of component modules built with functional programming, and feature modules use object-oriented programming while calling functional components. That’s the conclusion I’ve reached.
Let’s consider the classic example of “putting an elephant in the fridge.”
With object-oriented programming, you need to implement the definition of an Elephant object, the definition of a Fridge object (including storage space, a put-in method, and a take-out method), instantiate both objects, and then use the fridge’s put-in method.
With functional programming, define three objects in advance—empty object, fridge and elephant, and fridge containing an elephant. Function 1 takes “empty object” and outputs “fridge and elephant”; Function 2 takes “fridge and elephant” and outputs “fridge containing an elephant.”
With procedural programming, there is a catch-elephant function (side effect: produces an elephant), a buy-fridge function (side effect: produces a fridge), and a stuff-elephant-into-fridge function (side effect: changes the fridge into one with an elephant stuffed in it).
Further Thoughts
I remember seeing a quote somewhere, I don’t remember where, roughly saying: “Most leaders direct their subordinates procedurally, and in that situation you can’t develop normally using functional programming.” I’ve always thought that building software systems has similarities with building management/company/product systems. A good design and a good architecture can greatly reduce the subsequent cost of expansion, maintenance, and management—but it burns through a lot of the architect’s brain cells, and most architects only want to think procedurally, in the way closest to how people naturally think.
- identity-morphism: For any X: C, there is a morphism id~X~: X→X (also written 1~X~), called the identity morphism of X.Returnidentity-morphism
- isomorphism: Let C be a category, and given objects X, Y: C and a morphism f: X→Y. If there exists a morphism g: Y→X such that g∘f=1~X~ and f∘g=1~Y~, then f is called an isomorphism. A g satisfying this condition is generally written as f−1 and called the inverse morphism of f. If there is an isomorphism between objects X and Y, we also say X and Y are isomorphic, written X≅Y.Returnisomorphism
- category: Contains the definitions of all objects, morphisms, and compositions.Returncategory