When programmers have abstract thinking

If you want to catch a big fish, you have to dive into the abyss. The fish in the abyss are more powerful and purer. Big and abstract, and very beautiful. --David Lynch

Preface


Abstract thinking is the most important thinking ability of our engineers. Because software technology is essentially an abstract art. Our work is a "game" of thinking, although we are using keyboards and monitors, we can see the motherboard, hard disk and other hardware when we turn on the computer. But we can neither see how the program is executed nor how 0101 is processed by the CPU.

We engineers use abstract thinking every day to analyze, summarize, synthesize, judge, and reason about the problem domain. In this way, various concepts are abstracted, and the relationship between concepts and concepts is explored, and the problem domain is modeled, and then business functions are realized through programming languages. Therefore, most of our time is not writing code, but sorting out requirements and clarifying concepts. Of course, it also includes trying to understand the "damn, someone else's" code.

Among the engineers I have contacted, there are not many who can deeply understand abstract concepts. Those who can organically combine abstraction with object-oriented and architectural design, and can use abstract thinking to analyze problems and reduce complexity to simplicity are even rarer.

For me, whenever I have a better understanding and cognition of abstraction, I can feel the qualitative changes it brings to me in coding and design. At the same time, I sighed why my previous understanding of abstraction was so superficial. If time can be turned back, I hope that in the early days of my career, I can fully realize the importance of abstraction, spend more time studying it seriously, and understand it deeply, so that I can avoid many detours.

What is abstract


Regarding the definition of abstraction, Baidu Baike says this:

Abstraction is the process of extracting common and essential characteristics from many things, and discarding their non-essential characteristics. To be more specific, abstraction is based on the practice of people, through the process of removing roughness and essence, removing falsehoods and keeping the truth, forming concepts, judgments, and reasoning forms of thinking, such as concepts, judgments, and inferences, to reflect the thinking of things. The method of essence and law.


In fact, abstraction is a concept corresponding to concrete. Concrete is the sum of various attributes of things. Therefore, abstraction can also be understood as a thinking activity in which certain attributes are discarded from the various attributes of concrete things and other attributes are fixed. [1] 

Wikipedia's explanation is:

Abstraction refers to filtering the information contained in a concept or a phenomenon for a certain purpose, removing irrelevant information, and retaining only information related to a certain ultimate purpose. For example, for a leather football, we can filter its material and other information to get a more general concept, that is, the ball. From another perspective, abstraction is the process of simplifying things and grasping the essence of things. [2]

To put it simply, “extracting” means pulling away, “image” means concreteness, literally understanding abstraction, the process of abstraction is the process of summarizing common features from “concrete” things, and “extracting” the concept of generalization. . The English abstract—abstract comes from the Latin abstractio, and its original meaning is to exclude and extract.

In order to better facilitate your understanding of abstraction, let us first look at a Picasso painting. As shown in the figure below, the left side of the picture is a buffalo, which is concrete; the right side is a Picasso painting, which is abstract. How do you feel that you understand the meaning of abstract paintings at once?

 

It can be seen that the abstract cow has only a few lines, but these lines are highly abstract lines, filtering most of the details of the buffalo, and retaining the most essential characteristics of the cow, such as horns, heads, bullwhips, and cows. Tails and so on. This kind of abandonment of details makes the "abstract cow" have better generalization capabilities.

It can be said that abstraction is closer to the essence of the problem, which means that all cows cannot escape these lines.


Abstraction and language are one


Regarding abstract thinking, we can see the following definition on Baidu Encyclopedia:

Abstract thinking, also known as word (concept) thinking or logical thinking, refers to the process of using words (concepts) to make judgments, inferences, and conclusions. Abstract thinking uses words (concepts) as an intermediary to reflect reality. This is the most essential feature of thinking, and it is also the fundamental difference between human thinking and animal psychology. [3]

The reason why abstract thinking is called word thinking or conceptual thinking is because language and abstraction are integrated. When we say "cow", we are talking about the abstraction of "cow", which represents the characteristics common to all cows. Similarly, when you create the Cow class in your program, the same is true. In life, we have only seen a concrete cow. As an abstract existence, "cow" is invisible and intangible.

This view of abstract concepts as the true nature of the world is also the most important philosophical thought of the ancient Greek philosopher Plato. Plato believes that all the things we perceive with our senses are derived from corresponding ideas. He believes that the "name" of specific things, that is, the "ideal world" as he said, is the real thing. A specific cow can be big or small, male and female, with different colors, temperaments, and appearances. Therefore, it is difficult for us to generalize with individual feelings, but since these cows are collectively referred to as "bulls", it means that they must all originate from the same "idea", that is, the so-called "bull idea" or "ideal cow", so They can be summed up with "cow". Regardless of whether the "ideal world" really exists, this is a philosophical question, but one thing is certain, our thinking and expression of concepts are inseparable from language. [4]

This is why, when I do design and code review (Code Review), I pay special attention to whether the naming is reasonable. Because the naming is good or bad, it reflects to a large extent whether our thinking about a concept is clear, whether our abstraction is reasonable, the response is in the code, the readability and understandability of the code is good, and our Is the design in place.

Someone has done a survey and asked programmers what the most headaches are. According to the survey results of Quora and Ubuntu Forum, the most troublesome thing for programmers is naming. If you've ever racked your brains for a naming, you won't be surprised by this result.

As Joel Spolsky, the founder of Stack Overflow, said: “It should be difficult to make a good name, because a good name needs to be condensed into one or two words. (Creating good names is hard, but it should be hard , because a great name captures essential meaning in just one or two words)."

Yes, this condensed process is an abstract process. I have discovered more than once that when I feel that the naming of a place is a bit awkward, it often means that either I didn't think clearly about the place, or my abstraction was wrong.

As for how to name it, I have already explained it in more detail in "The Road to Code Improvement", so I won't go into details here.

What I want to emphasize is that language is the basis for clarifying concepts and also the basis for abstract thinking. When building a system, it is worth spending a lot of time to consider and consider language. In a project I did, I discussed a key entity for two days. Because it was a new concept, I tried many names, but I still felt awkward and hard to understand. With the deepening of our discussion and the deepening of the understanding of the problem domain, we finally found a relatively suitable name, and then we were willing to give up.

Such consideration is meaningful, because clarifying key concepts is an important work in our design. Although unreasonable naming and unreasonable abstraction can also achieve business functions. But the price is the high cognitive load required to maintain the system. Over time, no one can understand the design of the system.


Hierarchical abstraction


Returning to Picasso's abstract painting, as shown in the following figure, if it is mapped to object-oriented programming, the abstract cow is the abstract class (Abstract Class), which represents the abstraction of all cows. Abstract cows can be generalized into more cows, such as buffalo, dairy cow, yak, etc. Each type of cattle represents a class (Class) of cattle. For each type of cattle, we can obtain a specific cattle instance (Instance) through instantiation.

 

From this simple case, we can find three characteristics of abstraction:

1. Abstraction ignores details . Abstract classes are the most abstract, and ignore the most details, just like abstract cows, just a few lines. In the code, this abstraction can be Abstract Class or Interface.

2. Abstraction represents the common nature . Class represents the common properties of a group of instances, and abstract class represents the common properties of a group of classes. For our case above, these common properties are the lines of abstract cows.

3. Abstraction is hierarchical . The higher the level of abstraction, the smaller the connotation and the larger the extension, which means that the smaller its meaning, the stronger the generalization ability. For example, the cow is more abstract than the buffalo, because it can express all cows, and the buffalo is just a class of cows.

This level of abstraction is in addition to abstract concepts, another concept that we must deeply understand, because it is as small as how to write a method, as large as how to structure a system, and what we will introduce later in Chapter 3. Structured thinking is inseparable from the concept of abstract levels.

Before further introducing the level of abstraction, let us first understand the meaning of extension and connotation:

Abstraction is the process of reflecting reality by concepts (words), and each concept has a certain extension and connotation. The extension of a concept is the range of all objects suitable for the concept, and the connotation of a concept is the sum of the essential attributes of the objects reflected by the concept . For example, the concept of "parallelogram", its extension includes all squares, rhombuses, rectangles, and general parallelograms, and its connotation includes "there are four sides, and the two opposite sides are parallel to each other" common to all parallelograms. Two essential attributes.

The broader the connotation of a concept, the narrower its extension; conversely, the narrower the connotation, the wider its extension. For example, the connotation of "parallelogram" is "there are four sides, and the two opposite sides are parallel to each other", and the connotation of "diamond" contains the essential attribute of "four sides are equal" in addition to these two essential attributes. The connotation of "diamond" is broader than that of "parallelogram", and the extension of "diamond" is narrower than that of "parallelogram".

The so-called level of abstraction is embodied in the extension and connotation of the concept. This level of level can basically be embodied in anything. For example, a newspaper has multiple levels of abstraction. "Publications" are the most abstract and have the smallest connotations. , But the extension is the largest. "Publications" can be newspapers or periodicals.

  1. A publication

  2. a newspaper

  3. San Francisco Chronicle

  4. The San Francisco Chronicle on May 18

When I want to count how many publications there are in the United States, I need to use the first level of abstraction of "Publications" at the top. If I want to check the news of San Francisco on May 18, I need to use the fourth level at the bottom. Abstraction.

Each level of abstraction has its purpose. For us engineers, how to grasp this level of abstraction is a test of our design ability. Too high or too low levels of abstraction will not work.

For example, if we want to write a fruit program now, we need to abstract the fruit, because there are red apples in the fruit, we can of course build a RedApple class, but this level of abstraction is a bit low and can only be used to express "red apples" . To get a green apple, you have to create a new GreenApple class.

In order to improve the level of abstraction, we can change the RedApple class to the Apple class, and let the color become an attribute of Apple, so that both red and green apples can be expressed. If we continue to abstract, we can also get fruits, plants, etc. Further abstraction is biology and matter.

You can see that the higher the level of abstraction, the smaller the connotation, the greater the extension, and the stronger the generalization ability. However, the price is weaker business semantic expression capabilities.

 

The level of abstraction depends on the specific situation. For example, if this program is dedicated to studying apples, it may be enough to go to Apple. If you are selling fruit, you may need to go to Fruit, if it is for plant research, you may need to go to Plant. , But Object is rarely needed.

I often joked that you call all classes Object and all parameters are called Map. The system is the most versatile, because Object and Map have the smallest connotation, the most extensible, and can adapt to all extensions. In principle, this abstraction is also correct, everything is an object. But what is the significance of this abstraction? It didn't express anything that it wanted to express, it was just a correct nonsense.

The more abstract, the more versatile, the stronger the scalability, but the weaker its semantic expression ability. The more specific, the more difficult it is to extend, but its semantic expression ability is very strong. Therefore, the balance of abstraction levels is the key to our system design and the key to distinguishing ordinary programmers from excellent programmers.

Layered abstractions in software are everywhere


The more complex the problem, the more layered abstraction is needed. Layering is divide and conquer. Abstraction is a reasonable division of the problem domain and the expression of conceptual semantics. Different levels provide different abstractions, and the lower layer hides the implementation details from the upper layer. Only through this hierarchical structure can we deal with super-complex problems such as network communication and cloud computing.

Network communication is the most important basic implementation of the Internet, but at the same time it is a very complicated process. You need to know to whom the data packets are transmitted-the IP protocol. You need to know what to do if something happens on this unreliable network ——TCP protocol. There are so many things to deal with, can we do them all at one level? Of course it is possible, but it is obviously unscientific. Therefore, ISO has formulated a seven-layer reference model for network communication. Each layer only deals with one thing, and the lower layer provides services for the upper layer until the application layer exposes protocols such as HTTP and FTP that are easy to understand and use to users.

 

The development history of programming languages ​​is also a typical evolutionary history of hierarchical abstraction.

The machine can understand only machine language, that is, various binary 01 instructions. If we use the 01 input method, its programming efficiency is extremely low (students who have studied digital circuits can experience addition and subtraction with switches). So we abstract the binary instructions with assembly language.

However, assembly is still very low-level, so we abstract assembly language with C language. The high-level language Java is a further abstraction of a low-level language similar to C. This layer-by-layer abstraction greatly improves our programming efficiency.


 



Duplicate code is the absence of abstraction


If the essence of abstraction is commonality, does the repetitive code in our code mean the lack of abstraction?

This is the case. Duplicate code is a typical bad smell of code, and its essential problem is the lack of abstraction. Because of our working habit of Ctrl+C plus Ctrl+V, we did not extract the common code; or although we extracted it, we simply used a Util name, did not give a proper name, and did not respond correctly to this code The abstract concepts embodied are all inadequate abstractions.

Once, when I was reviewing the code of the team, I found a piece of code for assembling search conditions, which was repeated in dozens of places. This search condition is more complicated, it is stored in the database in the form of metadata, so the assembly process is like this:

  • First, we need to fetch the list of search conditions from the cache;

  • Then, traverse these conditions and fill in the searched value;

//取默认搜索条件List<String> defaultConditions = searchConditionCacheTunnel.getJsonQueryByLabelKey(labelKey);for(String jsonQuery : defaultConditions){  jsonQuery = jsonQuery.replaceAll(SearchConstants.SEARCH_DEFAULT_PUBLICSEA_ENABLE_TIME, String.valueOf(System.currentTimeMillis() / 1000));  jsonQueryList.add(jsonQuery);}//取主搜索框的搜索条件if(StringUtils.isNotEmpty(cmd.getContent())){    List<String> jsonValues = searchConditionCacheTunnel.getJsonQueryByLabelKey(SearchConstants.ICBU_SALES_MAIN_SEARCH);    for (String value : jsonValues) {    String content = StringUtil.transferQuotation(cmd.getContent());    value = StringUtil.replaceAll(value, SearchConstants.SEARCH_DEFAULT_MAIN, content);      jsonQueryList.add(value);  }}

Simple refactoring is nothing more than extracting this code and putting it in a Util class for everyone to reuse. However, I think this kind of refactoring is only half of the work done. We just did a simple classification and did not abstract.

Simple analysis, it is not difficult to find that we are missing two concepts here : one is the class used to express search conditions-SearchCondition; the other is the class used to assemble search conditions-SearchConditionAssembler. Only by coordinating with naming and explicitly expressing these two concepts is a complete reconstruction.

After refactoring, the assembly of search conditions will become a very concise form, and the reuse of dozens of places only needs to quote SearchConditionAssembler.

public class SearchConditionAssembler {    public static SearchCondition assemble(String labelKey){        String jsonSearchCondition =  getJsonSearchConditionFromCache(labelKey);        SearchCondition sc = assembleSearchCondition(jsonSearchCondition);        return sc;    }}


It can be seen that extracting duplicate code is only the first step in our refactoring work. Conceptual abstraction of repetitive code and finding meaningful names is the focus of our work.

Therefore, every time you encounter duplicate code, you should be excited, thinking that this is an excellent opportunity to exercise abstraction skills, except of course, test code.

Coercive type conversion is a problem with the level of abstraction


There is a famous SOLID principle in object-oriented design proposed by Uncle Bob (Robert Martin), where L stands for LSP, which is the Liskov Substitution Principle. Simply put, the Richter substitution principle is that the subclass should be able to replace any place where the parent class can appear, and after the replacement, the code can still work normally.

Think about it, when we will use coercive type conversion in the process of writing code? Of course, when the LSP cannot be satisfied, that is to say, the method of the subclass is beyond the scope of the type definition of the parent class. In order to use the method of the subclass, the type can only be converted to the subclass type by type coercion.

For example, on the Apple class, there is an isSweet() method to judge whether the fruit is sweet or not; on the Watermelon class, there is an isJuicy() to judge whether the water is sufficient; at the same time, They all inherit a fruit (Fruit) class.

At this point, we need to select sweet fruits and watermelons with moisture. We will write a program as follows:

public class FruitPicker {
    public List<Fruit> pickGood(List<Fruit> fruits){        return fruits.stream().filter(e -> check(e)).                collect(Collectors.toList());    }
    private boolean check(Fruit e) {        if(e instanceof Apple){            if(((Apple) e).isSweet()){                return true;            }        }        if(e instanceof Watermelon){            if(((Watermelon) e).isJuicy()){                return true;            }        }        return false;    }}


Because the type of the input parameter of the pick method is Fruit, in order to obtain the unique methods on Apple and Watermelon, we have to use instanceof to make a type judgment, and then use a coercive type conversion to convert to a subclass type in order to obtain their proprietary methods Obviously, this violates the principle of Li substitution.

Where is the problem here? How do we optimize such code? After careful analysis, we can find that the root cause is that the abstraction level of isSweet and isJuicy is not enough. From the perspective of a higher level of abstraction, that is, Fruit, we choose delicious fruits, but when it comes to apples, we look at the sweetness. When it comes to watermelons, we only look at moisture.

Therefore, the solution is to abstract isSweet and isJuicy, and raise a level, create an abstract method of isTasty() on Fruit, and then let the apple and watermelon classes implement this abstract method separately. 

The following is the refactored code. Through the improvement of the abstraction level, we eliminated the instanceof judgment and forced type conversion, so that the code satisfies the principle of Li substitution again. The improvement of the abstraction level makes the code elegant again.

public class FruitPicker {
    public List<Fruit> pickGood(List<Fruit> fruits){        return fruits.stream().filter(e -> check(e)).                collect(Collectors.toList());    }
    //不再需要instanceof和强制类型转换    private boolean check(Fruit e) {        return e.isTasty();    }}


So, whenever we are going to use instanceof for type judgment in our program, or cast for coercive type conversion. Whenever our program does not satisfy the LSP. You should all be vigilant, good guy, this is another great opportunity to exercise abstract skills.


How to improve abstract thinking ability


Abstract thinking ability is a unique and innate ability of our human beings. In addition to the above-mentioned ability to exercise abstract ability in the coding process, we can also continuously improve our abstract ability through some other exercises.

Read more

Why is reading a book better than watching TV? Because images are more concrete than words, the process of reading can exercise our abstraction and imagination abilities, and when looking at the images, your brain will be covered, so abstraction and imagination are less needed.

This is why we don't advocate excessive exposure of children to TV or mobile phone screens, because this is not conducive to the exercise of their abstract thinking.

The difference in abstract thinking makes children's academic performance differentiated from junior high school. Many people who cannot adapt to this abstract level of training go to technical schools, because technical schools are more concrete than universities: turning, milling, and parts can be seen. See it tangible. Manual work is easier than mental work.

More summary precipitation

When I was young, I didn’t understand why the Chinese teacher always asked us to summarize the main ideas and main ideas of the paragraphs. Looking back now, this kind of thinking training is very necessary in basic education, and its essence is to help students improve their abstract thinking ability.

Recording is also a good summary habit. Take reading notes as an example. It is best not to extract the contents of the book in the original text, but to summarize the contents of the book in your own words. This will not only deepen your understanding, but also improve your abstract thinking ability.

I started to take notes systematically four years ago, do summarization and precipitation, and build my own knowledge system. The benefits of this kind of thinking training are obvious. It can be said that both the "From Code Farmer to Craftsman" I wrote before and the "Necessary Thinking Ability for Programmers" I am writing are inseparable from my habit of summarizing and precipitation.

Named training

Every variable naming, method naming, and class naming is a rare opportunity for abstract thinking training. As I said before, language and abstraction are integrated, and the quality of naming directly reflects whether our problem domain thinking is clear or not. Reflects whether our abstract is reasonable.

The reality is that many of our engineers often overlook the importance of naming. As long as business functions can be realized, names are never the point.

In fact, this is irresponsible for the system, it is also irresponsible for itself, and it is irresponsible for the person who maintains the system later. Writing programs and writing articles have a lot of similarities. In essence, they both use language to explain one thing. Just imagine, if the articles are all inconspicuous sentences, who can understand such articles, and who is willing to read them.

Similarly, I have always emphasized that the code should explicitly express business semantics, and naming plays an extremely important role in this process. For the readability of the code, the long-term maintainability of the system, and the training of our own abstract thinking, we should not let go of any ambiguous, ambiguous, or ambiguous name.

Domain Modeling Training

For technical students, we also have a very good means to improve abstraction ability-domain modeling. When we analyze, organize and abstract the problem domain, when we divide and model the domain, we are actually exercising our abstraction ability.

We can model the problem domain in our work. Of course , we can also learn how to abstract and how to model by reading the model design behind some excellent source code . For example, we know that the core function of Spring is the Bean container, so when looking at the Spring source code, we can focus on how it manages Beans? What is the core abstraction it uses? It is not difficult to find that Spring uses BeanDefinition, BeanFactory, BeanDefinitionRegistry, BeanDefinitionReader and other core abstractions to realize the definition, acquisition and creation of Bean. Grasping these core abstractions, we have grasped the main vein of Spring design.

In addition, we can further think about it, why is it so abstract? What are the benefits of this abstraction? And how it supports the two definitions of Bean, XML and Annotation.

This kind of abstract thinking exercise and thinking is very important to improve our abstract ability and modeling ability. Regarding this point, I deeply feel that when I first entered the workplace, when I tried to abstract and model the problem domain, I felt unable to start, and the model I built felt awkward.

However, after long-term and deliberate learning and exercise, it is obvious that my modeling ability and abstraction ability have been greatly improved. Not only is the speed of analyzing the problem faster, but the model built is also more elegant.

summary


  • Abstract thinking is the most important thinking ability of programmers. The process of abstraction is the process of finding commonalities, summarizing, comprehensively analyzing, and extracting related concepts.

  • Language and abstraction are integrated, abstract thinking is also called word thinking, because abstract concepts can only be expressed through language.

  • Abstraction is hierarchical. The higher the level of abstraction, the smaller the connotation, the greater the extension, and the better the scalability; conversely, the lower the abstraction level, the greater the connotation, the smaller the extension, and the worse the scalability, but the greater the ability to express semantics. Strong.

  • The grasp of the level of abstraction reflects our design skills. Depending on the specific situation, the level of abstraction can neither be too high nor too low.

  • Duplicate code means lack of abstraction, and coercion means that there is a problem with the level of abstraction. We can use these signals to reconstruct the code and make the code elegant again.

  • We can improve our abstract ability through deliberate exercises. These exercises include reading, summarizing, naming training, modeling training, etc.

references:

[1] https://baike.baidu.com/item/Abstract/9021828

[2] https://zh.wikipedia.org/wiki/Abstraction

[3] https://baike.baidu.com/item/Abstract thinking

[4] https://www.sohu.com/a/359915387_260616

Function calculation mirror acceleration: the leap from minutes to seconds

How does the e-commerce live broadcast platform use containers and middleware to increase R&D efficiency by 100%?

Actual Combat | How to realize the video frame-cutting architecture based on Serverless technology?

Click one to see, let more people see

Guess you like

Origin blog.csdn.net/weixin_39860915/article/details/114650388
Recommended