Follow the wechat public account “dependency Injection” for a better reading experience.

The following is the arrangement and supplement I shared in the team a year ago, the level is limited, if there is any mistake, please feel free to comment.

Hello, everyone, my name is Wang Liguo, and I am currently the head of the RPA front-end team. In the past year, we have built the RPA front-end platform from scratch. Currently, there are about 130,000 lines of front-end code maintained, of which more than 92% are TypeScript codes.

  1. Admin backend built using TS3.5 + Angular8 + Rxjs (currently upgraded to Angular9)
  2. A low code development platform built using TS3.7 + Electron5 + React16.8 + Redux + Mobx + Nodejs
  3. A desktop application built using TS3.7 + Electron8 + React16.8 + Mobx + Nodejs

In this period of time, after everyone’s efforts, it should be said that the codes of the above three warehouses are relatively elegant and the maintenance cost is relatively low (we should not have worked overtime for half a year, haha). During this period of time, MY main work is architecture design and code review, during which I have accumulated some experience. I am very happy to have the opportunity to communicate with you today. Today’s sharing consists of 10 parts, which are as follows:

Before the official start of the first statement I’ll talk to you about, more than 10 part is only my personal think the higher priority problem, organize to discuss with you alone, you if you want to get to the specific line of code that how to write a more elegant, you can read more books about coding methodology on the market, in addition, Today these are only applicable to upper-level business development projects, which means not open source/base library projects.

In addition, we use both Angular and React, so today’s examples will probably include code from both frameworks. However, they will not involve too many framework concepts and will not affect your understanding.

1/10. Basic Agreement:

1. Directory structure

Most of the time when your directory structure is well organized, your maintainability passes muster, and a good directory structure should be self-explanatory.

In particular, it’s important to explain why you should also have modules in Angular projects, because over time you’ll find it difficult to simply have a shared directory. Sometimes you need to import the entire shared directory when you only rely on one component. This is because sometimes a routing module corresponds to more than one domain. In this case, I highly recommend splitting the shared directory into a more detailed domain model, or even removing the shared directory and replacing it with modules, so that one routing module can import several domain modules as needed.

2. Naming style

A few points need to be made:

  1. Type definitions are best prefixed to distinguish types from values (this can be constrained by TSlint)

    // tslint.json
    {
      "rules": {
        "interface-name": [true."always-prefix"].}}Copy the code
  2. CSS classes can also be named using the stylelint constraint

    // .stylelintrc.json
    {
      "rules": {
        // example: aa-bb-cc, aa-bb-width120
        "selector-class-pattern": "^[a-z][a-z0-9]*((-[a-z0-9]+)*|[a-z0-9]*)$"}}Copy the code

2/10. Type safety

In early team also only I a person to choose to use es6 rapid development, then the product was promoted to the company strategic level product, team is also in the explosive expansion of engineer level has a gradient, the increasing need of type constraints, so we are very decisive decided to transform the entire project to 100% TS, at that time, in order to reduce the cost of migration, The code is still riddled with a lot of ‘any’, and it’s frustrating that using TS increases the mental burden and reduces the coding efficiency if you don’t get the type system right. Then we started a craze to learn TS. We read the documentation, the advanced tutorials, the elegant TS code on the market, and tried to build a standard TS system.

Soon the any in the project will be less visible, and we’ve now turned on two important constraints in full-link Lint checks:

  1. Error level -> any is not allowed
  2. Error level -> does not allow implicit any

I strongly recommend that you implement Lint checks in your cloud CI streams and enforce that branches that fail Lint checks cannot be merged.

After turning on the full disable on any early on, our coding was very inefficient and we often had to write a lot of type definitions.

Fortunately, in most cases, the tools or libraries we use already export some of the tool types. There are very few tool types we need to write ourselves. Here are a few resources that can help you write faster and safer TS code:

  1. TypeScript built-in tool types
  2. Community tool type library
  3. React + TypeScript best practices

3/10. Comments are guilty

Doesn’t this comment look funny? But I’m sure you have them in your project, and they’re still generating them. There is a real art to how to write comments, and people will scold you for writing too much and too little. My advice is simple: never comment unless you have a good reason for it.

Think about when you complain about code that doesn’t have comments?

  1. Difficult to understand or completely incomprehensible
  2. (Deleting a piece of code that you thought was useless caused serious problems)

So it’s easy to understand when we need comments:

  1. Complex code: complex business/uses hard to understand technology, gimmicky implementation
  2. Compromise code: poorly designed, but with no immediate alternative to the business
  3. Compatibility code: Downward/platform compatibility code is best commented to avoid accidental deletion

4/10 Configuration separation

For component-level differences, a lot of times Props will suffice. If you want to differentiate a module from multiple components, you might want to use multiple parameter pass-through or static variables, but I want to tell you that most of the time Provider is probably more appropriate. Especially if you want to use several configuration schemes in the same application life cycle.

You’re familiar with providers. Providers can be used easily by Angular or React, as shown below

In particular, get in the habit of exposing InjectToken when writing reusable modules in Angular projects, even if it doesn’t seem to need to be configured yet.

5/10 Status management

Rxjs, Mobx, Redux… Today we are not going to talk much about global state management, but more about local state management.

Let me start with a question: can local state management be reused? Or should you reuse local state management code? 🤔

One more question: if it can be reused, should we use composition or inheritance? 🤔

My advice: reuse, but don’t use inheritance, think composition.

In Angular, you can reuse local state management code simply by injecting services from the component level. Isn’t that easy? This is essentially an implementation of the MVP architecture, where component-level Providers act as the Presenter layer.

As of React 16.8, you can use Hooks to combine local state management. You’ve heard of them.

We also have our own Hooks library (@bixi/ Hooks) from the past, which you can use if you are interested.

6/10 Performance optimization

My consistent view of performance optimization is that the best time to do it is when you start a project, and the second best time is now.

It’s best to get into the habit of writing high performance code, but here are some of the performance optimizations that we often use in development. Without going into details, you can explore them for yourself.

7/10 Version management

There are two parts to versioning:

  1. Versioning of dependent libraries:
    1. Be sure to lock third-party dependencies (yarn.lock)
  2. Versioning of business code
    1. At least one major version is backward compatible
    2. Compatibility code is marked and commented
    3. Periodically clean up compatible code

8/10 moderate package

Speaking of encapsulation, let’s look at the following code snippet change process

  1. You need to write a piece of code that listens for changes in the values of the four components, synchronizes them to the local State, and you write something like this on the left, and you think it’s neat, and you submit it
  2. There are side effects when the requirements change and the values of components A and B change, so you adjust the code to the right and it seems to be slowly evolving out of control
  3. You start complaining that the code is bad because the requirements are messy….

In the past code review process, I found that many students would like to write this kind of code, they can give me a good reason: the function is broken down to be easy to reuse.

But is your code actually being reused?

In fact, a lot of the time in business code development, over-encapsulation actually increases complexity and reduces maintainability, so one of the phrases I often tell people is “you’re making it complicated.”

We can try the dumbest and simplest way to modify the above code as follows

As you can see, the number of lines of code seems to have increased, but it is very easy to maintain, and I don’t care if IT affects B/C/D when I deal with a logic…

9/10 component design

Let’s look at the following component design requirements. This is a real component in our application that has the following four main features:

Some students’ principle of component disassembly is: whatever it is, disassemble until it can no longer disassemble.

What’s the problem with disassembling a component like the one on the left, or even finer grained, when it comes to design requirements?

  1. Styles 1/2/3/4 are coupled, and uncoupling can make style writing difficult

  2. 5/6/7 components do not have much reuse value, dismantling will only increase complexity

Obviously, the finer the component is, the better, so we recommend that you break it into two components as shown on the right (although if your code is complex, you can break it down a little more).

Let’s look at the following simple Input component and see how many SINS it has:

  1. Suspected dependencies on styles of other components

    input{
    	border-right: none;
    }
    Copy the code
  2. The component is not reusable and generates a searchTasks event

  3. The component exposes implementation details and relies on external validate methods

  4. The component made a copy of the value at initialization and then did not continue to listen for external value changes to refresh the copy, resulting in non-data-driven, non-idempotent

10/10 Defensive programming

Writing more reliable code should of course be a long-term goal, but some bad programming habits can make your code problems harder to track, and your bug-monitoring tools (we use Sentry to monitor code errors) can become useless, like the code shown above:

// bad
if (this.editor) {
  this.editor.destory();
}

// bad, equivalent to the above code
this.editor? .destory();Copy the code

This is bad because we know that this.editor should always exist after the component is mounted, so if it doesn’t exist when we unmount it, we should throw errors in time instead of silently eating them. A better way to handle this would be:

// Good, we conclude that it must exist
(this.ediotor as Editor).destory();
Copy the code

In general, you should be careful to use Lodash or optional chaining to bring it up in time.

Of course, in order not to make your app crash too ugly, you still need to do a good job of bug collecting and UI degradation.

Thank you.