Have we idolized modern front-end development frameworks too much?

There are two "sects" in the front-end world, one is called Vue and the other is React. Vue members look down on React, and React members despise Vue. They believe that the "doctrine" in their hands is the truth and can eliminate all suffering in the world.

But just as there is no absolute truth, there is no absolutely perfect system framework. We need a pair of eyes that can distinguish right from wrong to analyze the problems we face and lead us to find the correct method to solve the difficulties we face. We need to look at modern front-end development frameworks with skepticism. Can they really solve our problems? The answer is yes and no. The framework cannot function independently. Developers are a big variable, and developers, the biggest variable, are the important factor that ultimately affects whether the problem can be solved.

This article starts from the phenomenon of "admiration" for modern front-end frameworks, and points out that front-end development faces overemphasis on the tools themselves, and ignores how developers write good code, which is the essential problem that affects code quality. Finally, it gives a solution that I think can be solved The code architecture solution for business front-end projects (which can also be said to be a development idea), I hope it can bring you some ideas and help.

1. Dilemma of front-end development

From my experience, the 'worship' of modern front-end frameworks has caused front-end development to become more complex, which indirectly leads to poorer code quality and shorter software life cycles.

The dilemma we are currently facing is that sometimes technical solutions are very easy to use and efficient, but sometimes they are very poor and hit walls everywhere? Some people use it to be relaxed and cheerful, while some people use it to be irritable? Some projects have introduced new tools and their development efficiency has suddenly increased, while other projects have become more and more complex and difficult to iterate?

The reason is that we have been pursuing a perfect technical solution that is simple enough, efficient enough, fast enough, and able to cope with complex and changing business scenarios. This has become a mainstream consciousness in the front-end industry. Every new framework and tool that appears has the slogan of being more outstanding and better than the former, giving us a feeling that if we use it, all problems will be solved. . But does such an “artifact” really exist? I am skeptical. Software development is a complex system process. This is an open problem. It goes against the truth to try to solve this problem with a "closed" solution.

Modern front-end development frameworks are faced with this problem. We are a bit  "making a mountain out of a molehill"  and want to use the framework to solve all problems, ignoring that its essence is to solve the problem of view layer rendering. With the popularity of data-driven UI frameworks such as React/Vue, it seems to have become an "artifact" in our hands, as if it can solve all problems in development. In fact, this is also the case. If you find that some problems have not been solved, then just expand new tools around these frameworks. So we have the React family bucket and the Vue family bucket. If this is not enough, we also have numerous tool libraries that can be freely combined and used, including indistinguishable data status management libraries, multiple routing jump solutions, rich component collections, various development and debugging tools, and secondary encapsulated full-stack frameworks. wait. It seems that the situation is getting better and better, everything is prosperous, and the other side of happiness is right in front of us. The fact is that our developers have been carrying a heavy load, and our system has become very complex (so complex that no individual or even a team can control it). We dare not think whether it will still be running in five years.

Tools can improve efficiency and make work more precise, but the most important factor affecting the quality of the finished product is the way it is used rather than the tool itself. For the front-end framework, we must realize that it is used to solve the problem of the view layer. When we try to use the framework to solve all problems, we have already entered a misunderstanding and ignored the essential problem.

2. Is this the fault of “modern front-end development framework”?

I expressed earlier that " From my experience, the 'worship' of modern front-end frameworks has caused front-end development to become more complicated, which indirectly leads to poorer code quality and shorter software life cycles." This does not mean that modern front-end frameworks caused this series of problems. In fact, I very much recognize the changes brought by the front-end framework to front-end development. It solves the tedious DOM operation problems in the process of developing interfaces; it brings a convenient component-based code reuse and sharing mechanism. ; This is a positive technological progress. The essence of the problem is the "worship" culture brought by the community. This is a problem caused by the operating mechanism. The front-end framework is just an important node. We cannot blame the tool itself because the tool is not used well.

1. Why do good things become “bad”?

There is a relatively universal principle of doing things: "do small things big, big things small." We made a mistake in the front-end framework. We placed too much emphasis on "making small things big", which can also be said to be "making a mountain out of a molehill". Big and small are relative. We now want to use the React/Vue solution to solve the entire front-end. Development issues, this is how good things can become "bad".

I came into contact with frameworks such as React/Vue in the early days. To be honest, I was very surprised at that time. At that time, they had very clear positioning and problem-solving boundaries. They were very pure view layer tool libraries. (The concept of framework was not mentioned at that time), simple and elegant. But as they continue to iterate and the community adds fuel to the fire, they gradually move towards an uncertain path, with more and more derivative tools and more and more concepts. I admit that these things have their merits, but good things are not omnipotent and need to be used in the right place using suitable methods, otherwise they will become "evil".

2. The contradiction between the rapid development of frameworks and the slow absorption of developers

Software development is a complex system building process, where developers are always the biggest variable. The problem faced by the front-end interface is the rapid development of technical solutions in a short period of time. 1% of the creators propose the theory, and 10% of the participants transfer the solutions To the market, most developers are in a passive acceptance and use perspective. This process will lead to information loss in program promotion, errors, and variant programs. At the same time, it may also bring negative feedback to the framework maintainers and cause defects.

Technical "religion" or excessive "worship" of a certain technology will accelerate the process of bringing new technologies to the market, and misunderstandings and ambiguities will occur during the promotion process. Some of this is unintentional, and some is intentional. These appear in modern front-end frameworks, because this unhealthy growth phenomenon has caused the use of frameworks to be pushed to extremes, and developers may not have established keen technical sensitivity and judgment capabilities, leading to the emergence of the entire front-end view. It seems to be booming, but everyone is suffering.

3. How to solve the dilemma?

1. Good things may become "bad", and bad things may become "good"

Good intentions may lead to bad things, and bad things may also bring unexpected benefits. One of the factors that plays a role in this is the big “variable” of the person doing the work. In order to get things done, we need to cultivate a team with high technical capabilities. Developers are consciously paying attention to code quality, caring about technical architecture, and thinking about writing better code. This is the most primitive and most effective method, and of course it is also the most difficult and costliest solution.

2. Establish an operating mechanism with positive feedback capabilities?

The mechanism is a very powerful thing and can even make up for the negative problems caused by influencing factors. For a complex system, the role of the mechanism is greater than that of a single factor. The mechanism can adjust the error caused by a single factor, but a single factor can hardly affect the entire operating mechanism.
I haven't thought of a good way to establish a good operating mechanism. Students who have ideas can share it together. Although I haven’t found a way to build it yet, I think this is the right path and worth exploring.

3. For effective training, is the code "written"?

As a developer, I find that there are two things that I do basically every day, one is writing code non-stop, and the other is continuous learning. Logically speaking, if we write code every day, and according to the 10,000-hour rule, our coding level should continue to improve, but the reality is that there seems to be a boundary, and when I reach that boundary, I stagnate.

A breakthrough from quantitative change to qualitative change may occur only with changes in sustained behavior. Long-term ineffective training will lead to short-term effective training.

I read many books and articles, listened to many sharings and lectures, reviewed many plans, and used many new technologies. But there will always be a feeling of dabbling, doesn’t diligent study bring about growth?

  • No matter how much information we have, it does not equal knowledge. What we convert through our own thinking and absorb in our own way of understanding is our own knowledge. What we combine with the application of knowledge in our daily work is our personal ability.

  • Human thinking ability is limited, and the real phenomenon is a complex and multi-dimensional system that far exceeds the limit that an individual can control. The correct approach should be to start from small to large, deeply explore the phenomena of single dimensions, and finally combine them in an orderly manner to form a complex multi-dimensional system.

4. Do we need a suitable code architecture solution?

I have designed many low-level framework tools. If I want to say what is the most difficult part, it is that the framework designer must consider the antagonistic relationship between the framework itself and the developers who use it (the framework and developers are in a resource-competing relationship. What they compete for is the amount of computation in the software, or simply the amount of code). Handling this relationship well and allowing it to reach a dynamic equilibrium process is the essential problem that designers need to solve.

Here I will introduce an architectural solution that extends on the basis of the front-end framework and effectively solves the complexity problem of business-type (heavy business logic) front-end class requirements.

This solution is still in the theoretical design and verification stage and has not yet been implemented in real business. You can look at it from a dialectical perspective.
If you agree with the plan, you can communicate with each other; if you don't like it, please don't comment.

Three is a good number. I think an architectural solution can be considered an excellent solution if it can solve three systemic problems. The solution introduced here is based on the following three principles.

  • layered

  • combination

  • one-way dependency

At a large level, the idea of ​​layering is used to classify and isolate things. Each layer is directly connected through interfaces to reduce the complexity of the entire system. For example, the client does not need to care about the specific implementation of the backend interface, as long as the interface works as expected.

In the implementation of each layer, complete functions can be finally obtained through module combination. Because the front-end has the characteristics of high-frequency changes, it is more recommended to use composition rather than object-oriented inheritance to organize code logic. The inheritance pattern should be more suitable for a stable structure.

After layering and sub-modules, they need to be connected through lines to get a complete application. One-way dependence can avoid getting a disordered mesh structure that makes it difficult to straighten out the logic.

In the process of thinking about the three principles mentioned above, I found that the existing MVP architecture model is very good. With a little optimization based on the MVP architecture, I got an architectural design that I think is good. The key issue is how we combine it with our own development thinking and business characteristics.

In addition to architectural principles, let's analyze the essence of code writing. What causes code quality to deteriorate? And how to solve it?

1. What causes front-end code to deteriorate?

One of the core issues in front-end development is the development of views. This problem is effectively solved by the framework. When there is no need to directly operate the DOM, the development experience has been greatly improved. But the problem or misunderstanding is that the development of views is everything, so we have turned to component programming, and the consequence is that all codes are too closely connected.

I think that the view-centered code organization model will cause the code to become more and more confusing as iterations proceed, and the coupling of the logical code to the view layer will cause all the code to be as messy as a bowl of noodles.

  • Views change frequently. View-centered code organization is like building a house with the view as the base. The result is that every time the view changes, you need to put a lot of effort into adjusting other parts of the house. The components adapt to changes in the base, which is exactly what it means to move the entire body.

  • Another problem with using the view as the main body of the code is that the view cannot fully reflect the business needs, and some business logic has nothing to do with the view. As the proportion of business logic increases, there will be a top-heavy phenomenon.

  • Can data state management tools solve the problem of heavy business logic? I think it is negative. The data state management tool does not break away from the essence of the view framework. It just converts the local state management mode within the component into the global state management mode. Similar to having a central warehouse to place idle items, it will not reduce the difficulty of managing items. The key point should be a set of scientific and reasonable idle item classification management plans to turn a messy giant warehouse into a large supermarket like Wumei. .

Based on the above analysis, I think one solution is to separate the view layer, isolate the view from the logic, and achieve the separation of dynamic and static.

2. What is the main focus of the front-end?

As mentioned earlier, the view-centered front-end code organization method has disadvantages, so what should be a good way? Returning to the essence of business, with business as the center, views are just interface expressions of business processes. UI is a simple thing. I don't think it can be regarded as business logic. The business logic in the front end should be the connection between the business process and the interface. There is a distinction between primary and secondary.

As a front-end, have you ever had questions? Sometimes the boundary between the front-end and the back-end is not very clear. Some functions can be done on the end or on the server. The opposition between client and server also affects the development of client technology, thus retaining the shadow of the server.

Think about a question, if the front-end eliminates the view part, what will be the difference between the front-end and front-end code? It is not impossible to think about bringing the long-term stable structure of the back-end to the front-end. The MVC/MVP architecture is a relatively mature solution in back-end development. I think it is also feasible to apply it to the front-end. The key is to combine it with the existing technical solutions on the front-end.

3. What changes in development thinking have been brought about by React Hook/Vue Composition API?

React Hook is not just a replacement for Class components, and Vue Composition API is not just a syntax upgrade of Options API. I think it is a more fundamental change in programming thinking.
Take the following swr sample code as an example:

function useUser(id) {
  const { data, error } = useSWR(`/api/user/${id}`, fetcher)


  return {
    user: data,
    isLoading: !error && !data,
    isError: error,
  }
}

From the above we can see that the introduction of useSWR is just like introducing an ordinary util function. What is considered here is not what to do when the component is initialized? What do you do when a component is refreshed? Instead, it is purely about thinking about the change process of data, which becomes a calculation problem. This is actually an idea of ​​separating the view and the logic layer, focusing the business logic into useSWR hook (including request, loading intermediate state, and error processing). But unfortunately, the current hook is not perfect, and there are still problems such as not being able to be used in conditional statements and repeated rendering. The Vue Composition API has not yet become a mainstream method, and everything is still being explored. There are already many data status update solutions based on this in the community. I believe that more front-end code architecture solutions based on this will appear in the future.

4. Architecture solution with business logic as the core - (A)

This plan is temporarily named A. The applicable scenario of Plan A is mainly medium-sized pages. The number of split sub-models and sub-logic modules does not exceed 20. It may not seem like much, but in fact it has met the development needs of most pages. .

If it does not match the above scenario, it needs to be adjusted based on plan A, mainly due to the orderly combination plan problem within each layer and the optimization of the communication method between each layer.

If you find that the logical layer of the data layer is too large, you can consider using DDD directly. Facing complex scenarios, it will be better to adopt a non-ideal architectural solution than without architectural guidance.

4.1. Introduction to evolutionary architecture based on MVP model

9c848d86d08ffa212d4c2e25bc421277.png

  1. Communication between various parts is two-way.

  2. There is no contact between View and Model, both are passed through Presenter.

  3. View is very thin and does not deploy any business logic, which is called "Passive View", that is, it does not have any initiative, while Presenter is very thick and all logic is deployed there.

We can expand to the following layered architecture (retaining the advantages of MVP):

25d81882302b11133edd608320c3ac72.jpeg

  • Layered architecture based on MVP (understanding the boundaries of layering is an important factor in whether the layering solution is effective) 

    • Data layer (Model) - the data entities that business logic depends on (please refer to the definition of DDD entities)

    • Logic layer (Presenter) - handles business logic

    • View layer (View) - handles front-end interface interaction

  • Each layer forms a complete function based on combination 

    • The data layer is composed of multiple sub-models

    • The logical layer is composed of multiple sub-logical modules (the combined main logical module interacts with other layers)

    • The view layer is composed of multiple components

  • A single dependency reduces coupling and makes the process clearer

4.2. Analysis of some disadvantages of Plan A

The first is the need for judgment brought by the layered architecture. When you actually practice it, you will find that the boundaries become blurred and you don't know which layer the code should be written on. This is very likely to happen. For this situation, I think we can follow the approach of seeking "completion" first and then "perfection", and use practice to accumulate correct and reasonable methods suitable for your own team.

The second is excessive abstraction, which is also present in most plans. Sometimes things that seem obvious to be done in one step have to follow the rules.

Loading the elephant into the refrigerator requires a few steps:

  1. open the refrigerator

  2. Elephant walks into the refrigerator

  3. Close the refrigerator

You can also do it in one step, and the elephant walks into the refrigerator by itself.

Judging from the above examples, there is nothing wrong with it, but the real system will be more complicated. There may be more than 10 complete steps, involving multiple participating objects, and there may be interactions between participating objects. So it is very effective to follow the strict instruction manual step by step. necessary. In the end, it becomes a matter of specific analysis of the business scenario and whether it matches the architectural plan. If it is difficult to unify the evaluation criteria and the evaluation cost is too high, that is, it cannot be easily concluded whether it is suitable for use? It seems okay without it? Is it ok to use it? If there is no clear reason why it is unnecessary to use it, then I suggest using it directly, otherwise there is no need to use it.

Another negative impact is the issue of framework costs (R&D costs, maintenance costs, promotion costs, business access costs), which is unavoidable.

5. Summary

  • The emergence of modern front-end development frameworks has led to changes in the front-end world, but we cannot "worship" it too much. It mainly solves the problem of complex interactive development and is not perfect for developing complex needs of business logic. It can be used As a pure view layer solution.

  • There is no absolutely perfect technical solution. There are too many influencing factors involved. Developers are a big "variable" among them. The code written by developers with good architectural thinking may be good code, which is a technology in itself. Feedback on architectural proposals. The design output of technical solutions is a universal and easy-to-use solution.

  • Design universal technical solutions from small to large, and then from large to small; pursue solving core problems rather than all problems, and balance the antagonistic relationship with developers; negative costs within an acceptable range do not affect the entire program decisions.

6. Example

The following is a more realistic example of code architecture splitting:

cbd41e3ddf7cee361a11c25a32a2bc4f.png

As shown in the above example, we need to obtain product data and display it on the page in the form of a list. According to Plan A, we can disassemble this function as follows.

1. Conventional writing pseudo code:

export default class PortalPage extends Component {
    registerModels () {
        return [
            {
                namespace: 'PortalGoodModel',
                state: {
                  pageNum: 1,
                  pageSize: 10,
                  hasNextPage: true,
                  goodsData: []
                },
                reducers: {
                  updateGoodsData (state, payload) {
                    // 更新数据操作
                  }
                }
              }
        ]
    }
  
    state (state) {
        return {
          ...state,
          // 是否有数据,纯 UI 内部状态
          noResultPageVisible: state.PortalGoodModel.goodsData.length
        }
    }
  
    ready () {
        this.getPageData();
    }


    async getInfo () {
        const locationInfo = await this.dispatch({type: 'PortalLocationModel/updateInfo'})
        return locationInfo;
    }


    async getPageData (type) {
        const { adCode, userAdCode, longitude, gpsValid } = await this.getInfo();
        
        if (!(adCode && userAdCode && longitude) || !gpsValid) {
            // 定位失败
            if (type === 'onErrorRetry') {
                utils.toast('请开启定位');
                this.dispatch({
                    type: "PortalLocationModel/updateLocationStatus",
                    value: 'error'
                })
            } 
        } else {
            this.dispatch({
                type: "PortalLocationModel/updateLocationStatus",
                value: 'success'
            });
            const requestParams = utils.getRequestParams({
                pageData: this.$page.props,
                location,
            });
    
            const res = await fetch(requestParams);
            if (res.code !== 1) {
                // 请求错误处理
            } else {
                const result = this.processResult(res);
                this.dispatch({
                  type: 'PortalGoodModel/updateGoodsData',
                  ...result
                });
            }
        }
    }
}

Observing the above code, we will find that the code for data model operations, business logic, and views is softened in getPageData. This is already simplified. Real business will have more logic intertwined in a non-linear structure, and the complexity will increase unconsciously.

2. A architecture split pseudo code

2.1 Model layer example

Construct the following product model. The essential difference between the model layer and the logic layer is that the model layer is a more atomic data operation, which can be compared to the back-end database or the abstract definition of entities in the domain model.

The logic layer will rely on the model layer to form complete business logic, while the model layer is independent and will not rely on anything in the logic layer .

// 这里使用 dva 做为建模工具,你可以用其它的状态工具或者纯 js 对象都可以
export default {
  namespace: 'PortalGoodModel',
  state: {
    pageNum: 1,
    pageSize: 10,
    hasNextPage: true,
    goodsData: []
  },
  reducers: {
    updateGoodsData (state, payload) {
      // 更新数据操作
    }
  }
}

It is worth mentioning here that front-end development students may be more adaptable to the thinking of interactive models (high proximity to the interface), so it is not mandatory to establish an independent sub-model here. Compared with the physical isolation of the code, I think the logical Isolation is more necessary.

2.2 Logical layer example

The criterion for judging the boundary between the logic layer and the view layer is whether the state is needed in the business logic. If so, it should be modeled in the model layer, the dependency logic should be written in the logic layer, and the view layer should be converted into the UI State required for rendering.

  • Provide the getPageData interface for use by the view layer

  • Split out the sub-logic modules for obtaining geographical location and obtaining product data

  • Communication between sub-logical modules and between the logic layer and the view layer is in the form of interfaces

The following is the core logic module, which is responsible for communication and interaction with views:

// 该 presenter 会与视图关联,提供给视图使用的接口
// PortalPresenter.js
export default class PortalPresenter {
    getPageData (obj) {
        // 获取地理位置信息是 PortalLocationPresenter 做的,而拿到地理信息后
        // 该怎么去用则是另一个模块的事情
        this.$PortalLocationPresenter.onLocation({
            success: () => {
                this.$PortalGoodsPresenter.fetchData(obj)    
            }
        })
    }
}

Geographic information processing module:

// PortalLocationPresenter.js
export default class PortalLocationPresenter {
    async getInfo () {
        const locationInfo = await this.dispatch({type: 'PortalLocationModel/updateInfo'})
        return locationInfo;
    }


    async onLocation (obj) {
        const { adCode, userAdCode, longitude, gpsValid } = await this.getInfo();
        // 定位失败
        if (!(adCode && userAdCode && longitude) || !gpsValid) {
            this.dispatch({
                type: "PortalLocationModel/updateLocationStatus",
                value: 'error'
            })
            obj?.fail();
        } else {
            this.dispatch({
                type: "PortalLocationModel/updateLocationStatus",
                value: 'success'
            })
            obj?.success();
        }
    }
}

The following are sub-modules for processing product data logic:

export default class PortalGoodsPresenter {
    name = 'PortalGoodsPresenter'
    async fetchData (obj) {
        // 请求参数处理
        const requestParams = utils.getRequestParams({
            pageData: this.$page.props,
            location,
        });


        const res = await fetch(requestParams);
        if (res.code !== 1) {
            obj?.fail()
        } else {
            const result = this.processResult(res);
            this.dispatch({
              type: 'PortalGoodModel/updateGoodsData',
              ...result
            });
          obj?.success();
        }
    }
}

2.3 View layer example

  • Do pure UI rendering operations and return to the essence of ui = fn(state)

  • The view layer uses the data acquisition interface provided by the logic layer to get the data.

  • The UI operations involved in obtaining asynchronous data are still processed in the view layer 

    • Toast prompt if positioning fails

    • For example, whether to display a UI with empty data (this UI logic layer will never be used)

{
  state (state) {
    return {
      ...state,
      // 是否有数据,纯 UI 内部状态
      noResultPageVisible: state.PortalGoodModel.goodsData.length
    }
  }
  ready () {
    this.getPageData();
  },
  getPageData (info = {}) {
    this.$presenter.getPageData({
      type: info.type,  // 获取数据类型
      fail (errorInfo) {
        if (errorInfo.type === 'locationError') {
          // from 代表调用类型
          if (info.from === 'onErrorRetry') {
            utils.toast('请开启定位');
          }
        }
      }
    });
  }
}

The split code will be more focused, what each layer does is isolated, and the dependencies will be clearer.

Recommended related reading: Learning and understanding architectural design from business development

Follow "Amap Technology" to learn more

Guess you like

Origin blog.csdn.net/amap_tech/article/details/130878098