DEV Community

Cover image for How I made it impossible to write spaghetti code. Part 2
Denzyl Dick
Denzyl Dick

Posted on • Edited on

How I made it impossible to write spaghetti code. Part 2

This is the part(3) of a series. I suggest you read parts 1 and 2 before this one.

In part 2, I made sure that the test passed correctly. The focus now is how we traverse a block of code inside its scope. We should be able to increase the variables in the formula at the right moment.

If you have read part 1, you know that we need to implement the validate function that is mandatory in the trait Rule.


impl Rule for E009 {
    fn validate(
        &self,
        statement: &php_parser_rs::parser::ast::Statement,
    ) -> Vec<crate::project::Suggestion> {

    }
}

Enter fullscreen mode Exit fullscreen mode

In part 2 we have learned that :

Nodes are like the conditional statement: if, else, while, for, etc.
Edges are the paths that can be taken.

The validate function will be executed for every statement in a source code. In the source code below, there are 32 lines of code.

<?php

class Index{

  public function tooComplex(){
    $a = 1;
    $b = 2;


    if($a > $b){
      if($b < $a){

        if($a == $b){

          while($b > $a){

          }

        }
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Phanalist needs a way to detect when we are in the function's scope with the name tooComplex(). With pattern matching, it is super easy to detect if the statement is either: if,else,etc.. The first statement that I want to match for is class Index{ and from there, we will continue down the tree. If you think the same way as I do you know that I will be using recursion to calculate the cyclomatic complexity. After we have matched the scope of the tooComplex() function.

impl Rule for E009 {
    fn validate(
        &self,
        statement: &php_parser_rs::parser::ast::Statement,
    ) -> Vec<crate::project::Suggestion> {
        let mut suggestions = Vec::new();
        let mut graph = Graph { n: 0, e: 0, p: 0 };
        match statement {
            Statement::Class(class) => {
                for member in &class.body.members {
                    match member {
                        ClassMember::ConcreteMethod(concretemethod) => {
                            match concretemethod.body.clone() {
                                MethodBody {
                                    comments: _,
                                    left_brace: _,
                                    statements,
                                    right_brace: _,
                                } => {
                                   /// HERE !!
                                    }
                                }
                            }
                        }
                        _ => {}
                    }
                }
            }
            _ => {}
        }
        suggestions
    }
}

Enter fullscreen mode Exit fullscreen mode

If you aren't familiar with pattern matching, it's kind of a switch keyword on steroids. Look for the Look for /// HERE !! in the above code.

 MethodBody {
                                    comments: _,
                                    left_brace: _,
                                    statements,
                                    right_brace: _,
                                } => {
                                    /// HERE 1

                                    }
                                }

Enter fullscreen mode Exit fullscreen mode

As you can see we have four parameters that we can use. The ones we are looking for are the statements. The type of this parameter is Vec<Statement> meaning it can be anything.

Let's use the Graph we previously created in part 2

In the source code below, Phanalist calls a function with let graph = calculate _cyclomatic_complexity(); This function will return a struct of the type Graph. The first parameter passed is statements.clone(). This function uses recursion to traverse down the tree. In part 4 I will explain the recursion we were thinking about.


 let graph = calculate_cyclomatic_complexity(
                                        statements.clone(),
                                        &mut graph,
                                    );
 if graph.calculate() > 10 {
suggestions.push(Suggestion::from("This method body is too complex. Make it easier to understand.".to_string(),
                concretemethod.function,
"E009".to_string() ));
}

Enter fullscreen mode Exit fullscreen mode

The Graph struct has the calculate function we previously used to calculate the cyclomatic complexity. My threshold is always ten, so I will be using that.

If the value exceeds ten, I push the Suggestion onto the vector. The vector of Suggestion is the value returned to the validate function's caller.

Recursion

Image description

In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller instances of the same problem. Recursion solves such recursive problems using functions that call themselves from within their code. Wikipedia

Conclusion

If you are a PHP developer, you know that a developer can make many more mistakes in PHP. In the future, the list of Rules will continue to grow into something more useful.

Thanks for reading!

Contribution is always welcome if you have a mistake you would like to add to Phanalist.

Top comments (0)