Preface#
The author is not a trained software engineer, so has never actively or passively learned programming design, but rather relies on empirical experience based on observation and practice.
Embedded systems, in fact, do not focus on these aspects; instead, they assess RAM/ROM usage, execution speed, and whether the business logic meets requirements. This leads to a serious problem: the output in such positions is often highly customized for a specific product.
There are two discussions here:
- Is it really impossible to use reusable code on truly resource-constrained MCUs? I think the answer is no, and I will discuss the reasons later.
- Can such customized software code archives, which have very few comments and almost no explanations, really be called
software assets
? Personally, I think not; in my understanding, any software module that cannot be used by beginners without guidance within 8 hours is unqualified.
Recently, I wanted to think about how to produce real software assets
, which requires understanding basic programming design. So I spent ten minutes to fully understand
Below is a theoretical discussion of these designs; just smile, as I haven't really practiced much with these things.
Object-Oriented Programming#
Object-oriented is a term that has been overused; I actually can't fully grasp those technical terms.
In my view, the essence of object-oriented programming is the definition of an object, which includes what functions, variables, and quantities a transaction contains. For example, we define a keyboard, which has 104 keys and functions like "press A" and "press B".
But that's not right; I clearly see various oddly shaped keyboards on the market, which involves the so-called "inheritance." This means that the oddly shaped keyboards have simply overridden some aspects of our standard keyboard class. For example, they may disable the function "press 1" on the numeric keypad or redefine the quantity to 88 keys.
In the world of object-oriented programming, everything is defined as an object, and then we define the object, solidify its representation, and then extend it through inheritance, allowing for the realization of a module. By combining multiple objects, we can implement a project.
I believe that after balancing portability and functionality, object-oriented programming is undoubtedly the best implementation of modular encapsulation (at least that's what I think for now).
However, from a project perspective, object-oriented programming can be very unfriendly. If the definition of an object changes during the project's progress, does that mean the entire system undergoes a significant change, making it difficult to trace the source of issues? If we have to write additional middleware for each object's usage to isolate them, wouldn't that be redundant and create new definitions? In short, the impact of changes in definitions is too significant, which is not very friendly to maintainers.
We actually hope for a smooth experience, without looking back and forth, with varying levels of clarity, and chaos everywhere; that is not pleasant.
Functional Programming#
Mainly referenced from 1.7. Introduction to Category Theory (*) - Banana Space. This reference only introduces mathematical knowledge and does not involve programming.
This is not difficult to understand; everything is a function. But the fact that everything is a function and can achieve business functionality is quite magical.
In functional programming, there are two most important rules (as I see it):
- Any function must have input and output.
- Under any circumstances, the same input will yield the same output from any function.
In my view, the essence of functional programming is to obtain a function with a deterministic effect that has no side effects.
In the diagram below, each arrow represents a function, and each point is both an input and an output. For example, the first arrow from the left takes an input at the first point and outputs to the second point, which can then be transformed into two other points through two functions. Here, the points can be anything; indeed, under this way of thinking, anything is a function, with real values being fixed-output functions, and taking variables being unit morphisms1.
For example, the implementation of the CRC16 algorithm and various classical encryption algorithms must be functional programming; a set of values to be verified/plaintext will output a set of checksums/ciphertext through a function. The verification process and parsing are also functional, so in these two scenarios, the input and output are isomorphic2.
We can find that all purely mathematical problems are simple when using functional programming. Don't argue that mathematics can describe the world, okay?
However, in actual engineering, all states cannot be simply defined, involving large inputs and outputs, making function writing quite challenging, and I suspect the number of functions won't be small. If we break down the problem, the number of functions can be massive.
In fact, functional programming aligns with industrial thinking; deterministic actions yield deterministic feedback. For certain finite business logic scenarios, functional programming can be effectively used instead of managing relatively complex state machines.
Returning to the "Discussion 1: Is it really impossible to use reusable code on truly resource-constrained MCUs?" from the preface, it is clear that functional programming consumes similar resources in any situation. We can narrow down the category 3 and directly remove unnecessary functions to achieve reuse.
My view is that functionally writing some mathematical-related functions is a good encapsulation, but bringing this mindset into engineering and business is unnecessary, as the workload increases exponentially.
Perhaps instruction generation is also a good functional direction, and I might consider transforming it.
Procedural Programming#
The above two programming methods focus on reducing the side effects of functions/methods, allowing them to achieve similar functionality across different systems. Procedural programming is different; its essence is to use side effects to achieve desired functionality rather than navigating through various functions/methods using input/output values.
Procedural programming is the most aligned with how people think about solutions; I won't elaborate further, as everyone understands.
Conclusion#
In summary, regarding the layering of software assets
, the lowest layer is functional programming forming component modules, while functional modules use object-oriented programming and call functional components. This is my conclusion.
Let's consider the classic case, "Putting an elephant in a refrigerator."
In object-oriented programming, we need to define the elephant object and the refrigerator definition (including storage space, methods for putting in and taking out), instantiate the two objects, and then use the refrigerator's method to put in the elephant.
In functional programming, we predefine three objects—an empty object, a refrigerator, and an elephant, and the refrigerator that holds the elephant. Function 1 takes the "empty object" as input and outputs "the refrigerator and the elephant," while Function 2 takes "the refrigerator and the elephant" as input and outputs "the refrigerator that holds the elephant."
In procedural programming, we have a function to catch the elephant (with the side effect of producing an elephant), a function to buy a refrigerator (with the side effect of producing a refrigerator), and a function to stuff the elephant into the refrigerator (with the side effect of changing the refrigerator to one that holds the elephant).
This article is updated by Mix Space to xLog. The original link is https://www.yono233.cn/posts/novel/25_4_25_coder
Footnotes#
-
For any X, there is a morphism id~X~:X→X (or denoted as 1~X~), called the identity morphism of X. ↩
-
Let C be a category, and given objects X, Y and a morphism f:X→Y. If there exists a morphism g:Y→X such that g∘f=1~X~, f∘g=1~Y~, then f is called an isomorphism. The morphism g satisfying this condition is generally denoted as f−1, called the inverse morphism of f. If there is an isomorphism between objects X and Y, we also say that X and Y are isomorphic, denoted as X≅Y. ↩
-
Contains definitions of all objects, morphisms, and aggregates. ↩