Preface#
The author is not a computer science major, so has never really 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; they are more concerned with evaluating 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 particularly tailored 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 are rarely commented on and have little explanation, really be called
software assets
? Personally, I think the answer is no; in my understanding, software modules that cannot be used by beginners without guidance within 8 hours are unqualified.
Recently, I wanted to think about how to produce true software assets
, which requires understanding some basic programming design. So I spent ten minutes to fully understand
Below is a theoretical discussion on 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, and I actually don't fully understand 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 a quantity of 104 keys and functions like "press A" and "press B".
But wait, I clearly see all sorts of bizarre keyboards on the market, which involves the so-called "inheritance"; these bizarre keyboards simply override 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 say there are 88 keys.
In the world of object-oriented programming, everything is defined as an object, and then we define the object, solidifying the image of this object, and through inheritance and extension, we can realize a module's implementation. By combining multiple objects, we can achieve project implementation.
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, 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 great, which is actually not very friendly to maintainers.
What we really want is to be comfortable, without looking back and forth, with varying levels of expertise and chaos everywhere; that is not comfortable.
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 actually not hard 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.
- Any function, under any circumstances, will produce the same output for the same input.
In my view, the essence of functional programming is to obtain a function with a definite output and no side effects.
In the diagram below, each arrow represents a function, and each point is both input and 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. Real numbers are functions with fixed outputs, and taking variables is a unit morphism[^unit morphism].
[^unit morphism]: For any X, there exists a morphism id~X~:X→X (or denoted as 1~X~), called the identity morphism of X.
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 definitely 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 isomorphic1.
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, and writing functions is fraught with difficulties. Additionally, I don't think the number of functions will be small. If we break down the problems, the number of functions could be massive.
In fact, functional programming aligns with industrial thinking; definite actions yield definite feedback. For certain business logic targeting finite situations, functional programming can be used, rather than managing relatively complex state machines.
Returning to the "Discussion 1: Is it really impossible to use reusable code on truly resource-constrained MCUs?" in the preface, it is clear that functional programming consumes similar resources in any situation. We can narrow down the category 2 and directly remove unnecessary functions to achieve reuse.
My view is that functionally writing some mathematically related functions is a good encapsulation, but bringing this thinking into engineering and business is unnecessary, as the workload increases exponentially.
Perhaps instruction generation is also a good functional direction, and there may be opportunities for transformation.
Procedural Programming#
The above two programming methods aim to reduce the side effects of functions/methods, allowing them to achieve similar functionalities 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 programming style that most closely aligns with how humans consider solutions; I won't elaborate further, as everyone understands.
Conclusion#
In summary, regarding the layering of software assets
, the lowest layer consists of functional programming forming component modules, while functional modules adopt 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, the refrigerator's 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 elephant"; Function 2 takes the "refrigerator and elephant" as input and outputs the "refrigerator holding the elephant."
In procedural programming, there is a function to catch the elephant (with the side effect of producing the elephant), a function to buy the refrigerator (with the side effect of producing the 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 synchronized and updated to xLog by Mix Space. The original link is https://www.yono233.cn/posts/novel/25_4_25_coder
Footnotes#
-
Let C be a category, and given objects X, Y and 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 X and Y are isomorphic, denoted as X≅Y. ↩
-
The definition includes all objects, morphisms, and aggregates. ↩