VDD or The solution of Logic Errors in Software Development?
This article reviews what logical errors are and proposes a solution to them using validator types (a well-known way of validating data using types).
A few days ago I realized that my common development flow includes a lot of calls to print (console.log if programming in javascript) to debug logical errors in my code. And I concluded that we currently have 2 solutions:
Solution 1: Use a debugger/devtools, This solution works for small functions with very few variables, but it soon becomes a tedious and complicated task when the list of variables grows and the flow becomes more complex, it is also a manual way of doing things, this cannot be automated.
Solution 2: Do tests, this is not really a solution, tests are useful to verify the output values of a function, but they are useless when you are developing a function and you need to debug logical errors.
Let’s rethink why we use print to debug logical errors in the first place:
Logical errors occur when a variable gets an incorrect/unexpected value, they are silent unlike syntax errors or exceptions, and we give them visibility with print.
Types of logical errors:
Valid Syntax Error: (easy to identify) Occurs when we make a mistake with the name of a function, property or object method, variable, etc. or we write a correct expression, but it does not do what we expect. A syntax error does not occur because the name actually exists. It is enough to give the code a re-read to solve it and be careful when writing. Example:
if(i=1)
instead of
if(i==1)
Logic Mathematic Error: (easy to identify) It happens simply by making mistakes in mathematical formulas or logical operations (&& || !)
Incorrect Value Error: (difficult to identify, the difficulty grows with complexity) It occurs when a data (variable, property, function return) receives an unexpected value for which our program is not prepared, and strange things happen silently (in untyped languages they become incredibly difficult problems to solve), the value can be a huge number, an object with fewer or extra properties, an array with unexpected values inside, (in javascript null), empty arrays, empty objects , etc, and for which we constantly do validations in the code:
if(num < VAL) if(array.length > 0) if(obj.has(“prop”)) if(array[i].test(/w+/g)
As the program grows, trying to identify “at what point the system started to fail” becomes more and more difficult and inevitably we end up with a lot of prints scattered throughout the code to see the value of the data at a given moment. (Again, devtools help but it’s a manual, not automated task that soon gets tedious.)
Side Effect Error: A function modifies the value of an external variable and we do not react to this side effect in our code, for example:
var num = 5; function addOne() { num++; } for(let i=0; i < num; i++) { if(i == 5) addOne(); print(i); }
We wanted it to print 01234, but it prints 012345 because we incremented num and didn’t take it into account in our code.
No error occurs, it’s just hard to find the error because we’re not assigning num++ anywhere, this is inside the function.
Here is a version including validator types:var num: int5 = 5; //Here we limit the value of num to 5 function addOne() { num++; } for(let i=0; i < num; i++) { if(i == 5) addOne(); //An exception occurs because num is the print(i); //wrong type }
Expressions:
But we don’t only need to check variables, but also expressions, because these also influence the flow of the program, and therefore also produce undetectable logical errors.
But how to validate function calls in the if clause? We check if the returned value is of the specified type as follows:
if(func() as ValidArray) //dynamic casting
Although really this use case is limited to very few scenarios.
Whether it’s a side effect error, incorrect value in variables or expressions (within if/for or in side effect calls), in all these cases data has an unexpected value.
Conventional validation:
Conventional validation consists of writing a validate() function that returns true if the variable has valid data, we store this value and use it later in the program flow to act accordingly:
var foo = func();
var validated_foo = validateFoo(foo);
if(validated_foo) {
…
}
This soon becomes tedious to do for each variable, a better solution are validator types, they check the type at runtime and if an unexpected value is entered, an exception is thrown at runtime.
Types of validations with validator types:
Regular expressions in strings
Number ranges
Types of objects
Types of interfaces, check if an object implements any method
Advantages:
This article was called Validation Driven Development (VDD) because this approach should be used more as a way of programming than as a tool, thinking of all the values that a variable can acquire before its use not only solves the so-called logical errors, but also It helps us prevent bugs that we might not otherwise have discovered. As a personal example, I decided to modify an old code base to introduce validations. Along the way I discovered bugs in my software that I never thought of before and had never occurred to me at runtime (These are the bugs that are discovered by your users months after release).
In addition, you understand better the problem you are solving and you get a robust code.
It automates the verification of values at runtime, we do not need to manually verify the value that a variable has taken at runtime with the debugger.
This certainly doesn’t solve all your development problems, you’ll still have to create mental models of the solutions to the problem, but you won’t have to deal with weird logic errors at runtime that take up a lot of development time and a lot. stress.
At the moment it is not known exactly if this way of programming replaces TDD or if it can be used together. But it can also be used to validate the return values of functions, which is what TDD does.
Precautions:
Checking the values of all the variables of a program can be expensive, the language that implements this should have mechanisms to activate the validation only in development environments and not in production.
Any correction on the article will be welcome, this is a first draft of some ideas I had a while ago.
Let’s build a project following VDD with an imaginary language that implements VDD:
To be continue…