I am new to the company and inherited this code which implements the traditional cat game, over a 3x3 matrix.
Now they ask me to transform it to an NxN game, and they told me that I had to refactor it to make it simpler.
What is code refactoring?
This is the code:
#include <stdio.h>
int main(){
char matriz[3][ 3], opc;
int i, j;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
matriz[i][j]=' ';
printf(" %c", matriz[i][j]);
}
printf("\n");
}
int fila, col, ganador=0, turno=1;
//para jugador 1
do{
if(turno%2==1){
do{
scanf("%d", &fila);
scanf("%d", &col);
//ciclo para cuando el usuario ingrese coordenadas invalidas
if(matriz[fila][col] == 'x' || matriz[fila][col] == 'o' || fila > 2 || col > 2){
}
}while(matriz[fila][col] == 'x' || matriz[fila][col] == 'o' || fila > 2 || col > 2);
matriz[fila][col]='x';
for(i=0; i<3; i++){
for(j=0; j<3; j++){
printf(" %c", matriz[i][j]);
}
printf("\n");
}
turno++;
//para jugador dos
} else if(turno%2==0){
do{
scanf("%d", &fila);
scanf("%d", &col);
//ciclo para cuando el usuario ingrese coordenadas invalidas
if(matriz[fila][col] == 'x' || matriz[fila][col] == 'o' || fila > 2 || col > 2){
}
} while(matriz[fila][col] == 'x' || matriz[fila][col] == 'o' || fila > 2 || col > 2);
matriz[fila][col]='o';
for(i=0; i<3; i++){
for(j=0; j<3; j++){
printf(" %c", matriz[i][j]);
}
printf("\n");
}
turno++;
}
if(matriz[0][0] == 'x' && matriz[0][0] == matriz[0][1] && matriz[0][0] == matriz[0][2]
|| matriz[1][0] == 'x' && matriz[1][0] == matriz[1][1] && matriz[1][0] == matriz[1][2]
|| matriz[2][0] == 'x' && matriz[2][0] == matriz[2][1] && matriz[2][0] == matriz[2][2]
|| matriz[0][0] == 'x' && matriz[0][0] == matriz[1][0] && matriz[0][0] == matriz[2][0]
|| matriz[0][1] == 'x' && matriz[0][1] == matriz[1][1] && matriz[0][1] == matriz[2][1]
|| matriz[0][2] == 'x' && matriz[0][2] == matriz[1][2] && matriz[0][2] == matriz[2][2]
|| matriz[0][0] == 'x' && matriz[0][0] == matriz[1][1] && matriz[0][0] == matriz[2][2]
|| matriz[0][2] == 'x' && matriz[0][2] == matriz[1][1] && matriz[0][2] == matriz[2][0]){
ganador=1;
printf("1\n");
}
if(matriz[0][0] == 'o' && matriz[0][0] == matriz[0][1] && matriz[0][0] == matriz[0][2]
|| matriz[1][0] == 'o' && matriz[1][0] == matriz[1][1] && matriz[1][0] == matriz[1][2]
|| matriz[2][0] == 'o' && matriz[2][0] == matriz[2][1] && matriz[2][0] == matriz[2][2]
|| matriz[0][0] == 'o' && matriz[0][0] == matriz[1][0] && matriz[0][0] == matriz[2][0]
|| matriz[0][1] == 'o' && matriz[0][1] == matriz[1][1] && matriz[0][1] == matriz[2][1]
|| matriz[0][2] == 'o' && matriz[0][2] == matriz[1][2] && matriz[0][2] == matriz[2][2]
|| matriz[0][0] == 'o' && matriz[0][0] == matriz[1][1] && matriz[0][0] == matriz[2][2]
|| matriz[0][2] == 'o' && matriz[0][2] == matriz[1][1] && matriz[0][2] == matriz[2][0]){
ganador=1;
printf(" 2\n");
}
} while(ganador != 1);
return 0;
}
[Note: C examples are used, but technique agnostic]
Refactoring a code means transforming its source code without altering its results. This is done to simplify it, reduce duplication, remove dead code, and make it more understandable, thus easier to maintain and update.
It is applied to working code (correct logic), and proceeds iteratively, applying one transformation after another.
In this example, since we are trying to make the game multidimensional, we start by replacing all mentions of the size of the game with a constant specified by a
#define
. We might also want to give names to the characters used to represent a cell that is either empty or occupied by a player. We do that by declaring aenum
which gives us the range of possible values for the variable.We then check the source by changing all the '3's to
DIM
and the constants' ', 'x' y 'o'
to the respective names EMPTY, PLAYER_1 and PLAYER_2The following will normally break the code into small functions. For that, the code is traversed identifying pieces that perform specific functions and extracting it to functions.
In the case shown, we have this piece
and I'm going to move it to a separate function. I'm also going to remove the
printf
, because you shouldn't mix operation (zero the array) with I/O (print the array).Notice that I pass the array as a parameter instead of using a global variable. This gives me versatility, since the program could use more than one matrix without the need for changes. Avoid global variables.
And to print the matrix, which has to be done in several parts, I define a print function:
Now the program is a bit simpler. The beginning of the original looks like this:
If we look at what follows, we see that it is about alternating between two players and clearly all the code is duplicated:
There are two separate actions here:
so naturally I will divide the chunk into two parts. Since I already have the print ready, I only have to enter the coordinates and populate the cell:
This function is general. It receives in mark the character that identifies the player ('x' or '0'), so it works for both cases. After populating the cell, it prints the matrix so the player can see the result of their move.
Here we made a small change in the logic: the function calls
revisar_ganador
, so when it returns it already knows if the current player has won the game.With that,
main
it boils down to:This is much more understandable. In a few lines, the general logic of the application is presented, which is structured as a collection of short, simple functions with a single purpose.
It remains for us to account for these lines:
Clearly the intent is to determine if a row, column, or diagonal is filled, but the code is not to transform it to NxN. You have to implement a general solution, which is not derived from the original code.
In this example we have not detected dead code. Nothing is left over in this version.
That is refactoring. When you have problems debugging code (yours or someone else's), apply refactoring to make it easier to understand.