Stop Coding Bad Practices
I created a tic tac toe video game that runs in the console using ruby, the game works fine but at the time to add some testing, a big problem appeared.
The game requires input from the user in the console, and to test the validation to that input it was necessary to use a weird
StringIO.new() function. It seems like an easy task, but as I didn’t know some fundamentals of Object Oriented Programing, I committed errors in the design that made it difficult to test the application up to the point that the testing was completely horrid.
When you are learning testing It is natural that you want to test every single line of code you write, and that is good for learning purposes but its a very bad practice.
when I got this bunch of lines in the RSpec testing I realized that what I was doing was wrong but I didn’t know why.
The fundamental problem is that I was calling a function that writes to the console and that’s why the output appears in the console. 😑
I left this problem for a while, It took me a long time to feel comfortable about writing this article because I didn’t know how to explain what was wrong and the fundamental errors that I committed when coded this project. it’s the kind of situation that you don’t know how to tackle and you have to give space to your mind to rethink it again and again.
The Actual Problem
As this project was created to learn OOP in ruby I was asked to organize the code into classes, so I created a class called
Boardto put all the code related to the board object, that’s object-oriented programming! well, that’s not all.
let’s dive into the code, the
choose method takes a number and a symbol to emulate a user marking a cell in the Tic Tac Toe, then loops through
@board_numbers a two-dimensional array until finds the given number to the function, then changes that number in the actual
@board , prints the board in the console, returns true or displays to the user “Already Chosen”.
To summarize the
- validates if the provided number is valid
- looks for the chosen number in the board and changes it with the provided symbol
- Prints out the board
- returns true if the number is valid
- if the input is invalid displays ‘Already Chosen’ into the console
Charging all this functionality to one method is in essence too complex and breaks a fundamental OOP principle called The Single Responsibility which states that a module, class, object or method, should only have one responsibility. The
choose method seems to do a task to the game but is achieving many small tasks; writes to the console loops through an array and changes the
Ok, this code is bad but what I should think about to make a better structure?
As the objects should have a single responsibility it is very easy to get lost at that idea, as a clue you can think of certain roles that an object can assume consistently in your program.
- Information holder — an object designed to know certain information and provide that information to other objects.
- Structurer — an object that maintains relationships between objects and information about those relationships.
- Service provider — an object that performs specific work and offers services to others on demand.
- Controller — an object designed to make decisions and control a complex task.
- Coordinator — an object that doesn’t make many decisions but, in a rote or mechanical way, delegates work to other objects.
- Interfacer — an object that transforms information or requests between distinct parts of a system.
Further information on Design Rules check this book two books https://www.amazon.com/Object-Design-Roles-Responsibilities-Collaborations/dp/0201379430 and https://www.amazon.com/Practical-Object-Oriented-Design-Agile-Primer/dp/0134456475
before continuing, be aware that these are guidelines, not rules.
To answer that I strongly recommend this article, explains the SOLID principles, an acronym for the first five object-oriented design principles by Robert C Martin, popularly known as uncle bob
Structuring The Code
look how simple and elegant is this refactored code, the
choose method takes a number as an argument to mark it as chosen in the board, calls the
valid_number? method that checks if the number is valid and whether has been already chosen, then adds that number to the
If you read every one of these seven lines of code so that’s why this implementation is best, I bet that you didn’t read the first example.
To make this implementation possible I have to change other parts of the Board class and off course I broke 60% of the tests that I did write, Even though this change is positive.
one of the fundamental changes was to change the
board_numbers from a two-dimensional array to a single array, the main Idea of the two-dimensional array was to emulate the 3x3 matrix of the tic tac toe but that is not necessary. one can use a simple array and at the moment of printing the array to the console convert it to a 3x3 matrix. the gain of that play is that at the moment to search for a number or put a number I have to do it on a simpler data structure.
Do you remember that I mention above that I broke 60% of the tests, well I have to say that I end up deleting at least half of them.
As I tested almost every line of code, my tests were tied to a single implementation, so It was not possible to refactor the code without breaking the tests, so I just delete them.
After that, I had to write some tests, not many but smarter. following certain basics.
Objects are black boxes, we do not have to tests everything that happens inside an object, we better have to focus on testing the inputs and outputs, that way we are not going to be tied to a single implementation.
In the Rails Conf 2013 called The Magic Tricks of Testing By Sandi Metz she describes that principle as testing the messages from a space capsule. you should check that wonderful speech here.
I have to say that I decided to write this article because I did not like the output of the testing, and I wanted to document a super-specific ruby maneuver to solve that.
In the end, I just realized that I have to delete those silly tests that were not testing anything, and highlight the practices and principles that for sure are going to make me and you a better programmer, testing and writing smarter code.
The last idea I want to share is the concept of pure functions, Pure functions are functions that have the following properties
1) Don’t alter a variable or object — create new variables and objects and return them if need be from a function.
2) Declare function arguments — any computation inside a function depends only on the arguments, and not on any global object or variable.
It is a good practice to write just pure functions and avoid the temptation of declaring global variables as much as possible, however, all the time is not possible, even so, try, there is no easier thing to tests than a pure function because they just depend on the inputs.
- Test The interfacing of your objects, not the inner implementation
- Try to write just pure functions
- You do not have to test the
putsmethod or the console output
- learn and apply the SOLID design principles
- The code is not just for the computer is for people too!!