This article is contributed by GoGoCode user Tao Ge

What is “junk” code?

What is junk code? If you want to know what junk code is, I’ll show you right now!

In the permission management scenario, we often call different interfaces or display different UI interfaces according to the different permission codes, so as to achieve the grayscale test of functions. After the stable operation of new functions, they will be fully opened to more users.

For example, in our business code, the global object Tryout is used to mount different permission attributes, such as a function permission code sid=123, then our global object after initialization, the fixed format tryout.tryout_sid_123 to identify whether the current user has the current permission.

if(Tryout.TRYOUT_SID_123) {
    doSomething()
}
else {
    doOtherthing()
}
Copy the code

Or:

let modelUrl = Tryout.TRYOUT_SID_123 ? 'url_A' : 'url_B';
Copy the code

Or:

let obj = {
    proA:  Tryout.TRYOUT_SID_123 ? 'aaa' : 'bbb'
}
Copy the code

In template code, we use mustache template as an example. Our template code might have something like this:

{{if Tryout.TRYOUT_SID_123}}
  <templateA>
{{else}}
  <templateB>
{{/if}}
Copy the code

The code for the above scenario will be stored in the JS and HTML files of our business code for a functional iteration.

So over time, as the project continues to iterate, and the business logic continues to grow, this code gets more and more and more stacked on top of each other, making our code look more and more complex.

The final code might look like this:

if(Tryout.TRYOUT_SID_123 && Tryout.TRYOUT_SID_234) {
    doSomething()
}
else if(Tryout.TRYOUT_SID_345 || Tryout.TRYOUT_SID_456){
    doOtherthing()
}
...
else{... }Copy the code

When we went back to the development of the new features of these pages, these logical stacked together, we need to know what did the code before, why there are these logic, comb out these old code, can we do to the development of new functions, the above example looks simple, but in fact business is much complex than the example above, This can eat up a lot of development time while sorting through the logic.

In fact, with the business permission to let go of, a lot of function in fact after a period of gray level test, has been open to all customers, also may be because the function of the utilization rate is low, and did offline processing, that is, a lot of conditional logic in the code is no longer needed, that the code still exist in the business logic, This code does nothing but increase our development costs.

How do I clean up “junk” code

Now that this code is in the past, wouldn’t it be better to reorganize it, using the original example as an example:

if(Tryout.TRYOUT_SID_123) {
    doSomething()
}
else {
    doOtherthing()
}
Copy the code

Tryout.tryout_sid_123 === = true, we don’t need to worry about else, we can simplify the above code: tryout.tryout_sid_123 === true

doSomething()
Copy the code

When tryout.tryout_sid_123 === false, this can be simplified as:

doOtherthing()
Copy the code

Well, it looks like you can still do it manually.

However, in our business code, the number of our permission codes has nearly reached four digits, and the corresponding code logic modules for each permission may reach dozens or hundreds of places. Then, I would like to think about whether there is any desire to manually sort them out. I think no one would want to waste time sorting out these “garbage”.

Finished, do not see any effect, made a mistake, even more deleted one character at a time, also can let business code crash, the risks and benefits is not completely, so with the words, the next function iteration, meet function particularly complex page, see with various access code, no desire for development.

So what to do? Could we have an automated way to do this for us by telling the tool which permission code is full or offline and automatically cleaning up the logic for us?

The scene to sort out

We start from the transformation of JS files. After combing the code snippets where these permission codes are located, we find that 99% of the scenarios are the following three scenarios

Variable object assignment scenario

var a = Tryout.TRYOUT_SID_sid
let b = Tryout.TRYOUT_SID_sid
const c = Tryout.TRYOUT_SID_sid

let d = {
    obj: Tryout.TRYOUT_SID_sid
}
Copy the code

Ternary operation scenario

Tryout.TRYOUT_SID_ ? 'aaa' : 'bbb'
Copy the code
! Tryout.TRYOUT_SID_ ?'aaa' : 'bbb'
Copy the code

Condition judgment scenario if… else

if(Tryout.TRYOUT_SID_123 && aaa || bbb) {
    doSomething()
}
else if(Tryout.TRYOUT_SID_234) {
    doOtherthing()
}
else {
    doElse()
}
Copy the code

Now we have basically sorted out the above three scenarios to deal with, because our permission code is a fixed format: Tryout_TRYOUT_SID_xxx, looks like a compound re match scenario, can we use the re match way to deal with the code, think about it.

There’s a lot of variation in the way conditional judgments are written, in terms of priorities, in terms of nesting, and in a word, there’s a lot of variation in the way code is written, and there’s no set of rules to fit those rules.

The obvious thing to think about here is, wouldn’t it be easier to use an AST to process this code?

After solving the problem of Vue2 migrating Vue3 with GoGoCode, I felt it was a magic tool to operate AST. I decided to use it to have a try!

The code to handle

Let’s summarize the three scenarios we need to work with, assuming that the permission code we want to work with is full (=== true). For example, for the assignment scenario, our conversion target is as follows:

var a = Tryout.TRYOUT_SID_sid
let b = Tryout.TRYOUT_SID_sid
const c = Tryout.TRYOUT_SID_sid

let d = {
    obj: Tryout.TRYOUT_SID_sid
}
Copy the code

The converted = >

var a = true
let b = true
const c = true

let d = {
    obj: true
}
Copy the code

GoGoCode conversion code:

// Variable assignment scenario
result = AST.replace([
    `var $_$ = Tryout.TRYOUT_SID_${sid}`.`let $_$ = Tryout.TRYOUT_SID_${sid}`.`const $_$ = Tryout.TRYOUT_SID_${sid}`].'let $_$ = true; ')
Copy the code

Ternary operation scenario:

let test = Tryout.TRYOUT_SID_sid ? 'aaa' : 'bbb'
Copy the code
lettest = ! Tryout.TRYOUT_SID_sid ?'aaa' : 'bbb'
Copy the code

The converted = >

let test = 'aaa'
Copy the code
let test = 'bbb'
Copy the code

GoGoCode conversion code:

// The ternary operator is true
result = AST.replace(`Tryout.TRYOUT_SID_${sid}? $_ $1: $_ $2 `.'$_ $1')
// The ternary operator is false
result = AST.replace(`! Tryout.TRYOUT_SID_${sid}? $_ $1: $_ $2 `.'$_ $2')
Copy the code

Conditional judgment if:

Huh? How to write the GoGoCode conversion here, because there are so many scenarios in the if statement, if statements can have expressions, logical operations, it seems that the simple replace scheme can not handle this scenario.

Then we can go back to the assignment statement and the ternary conversion above, and see if our rules are too simple. If the code logic is a little more complicated, like ternary operations:

let test = Tryout.TRYOUT_SID_sid ? 'aaa' : (isA ? 'bbb' : 'ddd');
Copy the code

This is the simplest code conversion, and we need to do more work to cover more scenarios.

Logical operation processing

For a more complex example, try the function variable tryout.tryout_sid_123 to participate in a complex logic operation

if(Tryout.TRYOUT_SID_123 || (aa && bb) && cc || (a == b)) {
    doSomething()
}
else {
    doOtherthing()
}
Copy the code

If statement contains content, in AST structure called the test, in fact we know Tryout. TRYOUT_SID_123 = = = true, so true and any value | | operators, the final result is true, we don’t care about the composition of the test.

. On the other hand, if the Tryout TRYOUT_SID_123 = = = false, false and other values of | | operation, it will ignore the false, continue to execute judgment, two kinds of different scenarios, the transformed code is completely different, we hope to get the transformation of the results are as follows:

Tryout. TRYOUT_SID_123 = = = true

doSomething()
Copy the code

Tryout. TRYOUT_SID_123 = = = false

if((aa && bb) && cc || (a == b)) {
    doSomething()
}
else {
    doOtherthing()
}
Copy the code

To sum up, in this conditional operation, we only need to evaluate the If statement to get the code result we want to convert:

If true, we take out the contents of the if statement;

If the value is false, we remove the contents of the else statement;

If the test value is uncertain, clear out the certain parts, as in the offline scenario above, we delete tryout.tryout_sid_123, which is confirmed to be false, and leave everything else unchanged.

If you go back to ternary operations and conditional assignments, they’re all logical operations.

Logical simplification

Here, our core problem becomes the simplification of the logic operation. Here is a simple diagram to share the processing process with you. Of course, we also need to use the powerful search function of GoGoCode:

We encapsulate the processing of logical operations into a utility function, excuteIF, that takes a conditional statement and an object as a parameter that identifies a known condition

excuteIF(
    '(Tryout.TRYOUT_SID_123 || (aa && bb) && cc || (a == b)',
    {
        Tryout.TRYOUT_SID_123: true})Copy the code

This function returns a simplified result, such as the tryout.tryout_sid_123 ===false example above, which will return

(aa && bb) && cc || (a == b)
Copy the code

Let’s try to implement this function:

const$=require('gogocode')

excuteIF = function (caseStr, options) {
    / / generated gogocodeAST
    let g_ast = $(caseStr)
    // Loop through the values passed in to the input parameter
    for (let o in options) {
      // Find the object code
      let node = g_ast.find(o)
      // Identifies whether the target value for the search is true or false to perform different operations
      let excuteType = options[o]
      let parent = node.parent()
      let nodePath = node[0].nodePath
      let parentNode = parent[0].nodePath.node

      // If the parent is a logical operation
      if (parentNode.type === 'LogicalExpression') {
        / / true scene
        if(excuteType) {
          // Operate with true or
          if (parentNode.operator === '| |') {
              parent.replaceBy($('true'))}// with true "and operation"
          else if (parentNode.operator === '&') {
              // Replace the object code
              if (nodePath.name === 'left') {
                parent.replaceBy(parentNode.right)
              } else if (nodePath.name === 'right') {
                parent.replaceBy(parentNode.left)
              }
          }
        }
        / / false scene
        else {
            // Operate with false
          if (parentNode.operator === '&') {
              parent.replaceBy($('false'))}// With false "or" operation"
          else if (parentNode.operator === '| |') {
              // Replace the object code
              if (nodePath.name === 'left') {
                parent.replaceBy(parentNode.right)
              } else if (nodePath.name === 'right') {
                parent.replaceBy(parentNode.left)
              }
          }
        }
      }
    }
    // Regenerate the code
    return g_ast.generate()
}

let result = excuteIF('Tryout.TRYOUT_SID_123 || aa && bb', {
  'Tryout.TRYOUT_SID_123': false
})

// result is treated as aa && bb
return result

Copy the code

For the implementation effect of the above code, you can go to playground and try it yourself. The code address is here

Results show

With that in mind, we can try processing real business code:

It can be seen that after the code conversion, the code is greatly simplified, the logic has become clear enough, on the basis of the concise code to do iteration, does not feel more confident.

conclusion

Here, let’s go back to the idea of cleaning js files:

  1. Get full or offline access codes
  2. Gets a file with a specific permission code tag
  3. Find the code block that contains the permission code
  4. Processes the results of logical operations
  5. According to the result of the logical operation, the AST tree is manipulated, the corresponding code is replaced, and new code is generated

We can then wrap the above steps into a command line tool so that we can now type NPM run sid — 123 to clean up the individual permission code 123.

Amway time

You might also ask why GoGoCode, why not Babel or jscodeshift, of course, but I can only tell you that GoGoCode is faster to get started than the other two. It has very little code and a lot of apis. Playground is also online, and if you don’t already know, get started now:

Github repository for GoGoCode (new project begged star ^_^) github.com/thx/gogocod…

GoGoCode’s official website gogocode. IO /zh

Play. Gogocode. IO /