Future-proof & over-engineeringWillian GasparBlockedUnblockFollowFollowingFeb 3Let’s talk about Future-proof and over-engineering.
Many times these two concepts will clash.
Future-proof can make things easier to deal with in the future but also takes an extra afford.
If a future-proof part of a system never change that will never change that extra afford will never pay off.
In this article, I will be using a game simple grid game as a base project so we can wander about these subjects.
The full and final code is available at https://github.
com/williangaspar/phaser3TypescriptFirst, let’s talk about what is future-proof.
In software, future-proof something is to make it flexible and generic, so when it is time to change it, it is easier and faster.
So we are about to make a game called Gem-crush.
It will be a simple 2D game with the following rules:The game consists of a 2D grid with six columns and six rows (6×6)Each cell will have a diamonds or a bedrock.
There will be 4 different kinds of diamonds with the same behavior and different colors.
A sequence of 3 or more equal diamonds, at a column or a row, will result in points.
The diamonds should disappear, and new entities should replace the old ones as soon as a sequence is detected.
Bedrock cannot be moved or generate points.
The player can move a diamond to the immediately neighbor diamond that is located up, down, left or to the right of it.
When there are no more possibilities of making points, the game is over.
The player has one life to spare, so the first game over will destroy all the bedrocks instead of showing the game over text.
The description above is our project’s scope.
The complete understanding of a project’s scope is essential for many reasons.
Lets, for instance, analyze the size of a project’s scope.
Many say that it is complicated and not recommended for small projects.
But it pays off on the long run, especially if the project grows big.
People also say that a Node.
js is about a thousand times better than Java when it comes to simple websites, but as your business logic grows at the server side, Java might be a good option.
So scope size gives a good hint about what languages, tools, and frameworks are suitable to a project.
But of course, been software a process, it is always changing.
The best tools for the present scope may not be the best for the tomorrows.
So there is where future-proof comes in.
Before thinking in the most complex scenario possible lets first analyze the likely hood of change.
For example, if you ever worked with anything heavily dependent on laws you know things can change at any moment.
Politicians are always creating and altering laws and every time they do it the software will have to adapt.
An initially small scope can grow as the rules evolve.
But if you decide to build a tic-tac-toe game, there is no much to change at all.
The scope is small and will remind that way.
Going back to Gem-crush, what is most likely to change?.Maybe a new kind of diamond?.Perhaps this new diamond that does something different.
Like one that can explode or be more than one type at the same time.
Also, the grid can grow or shirk.
I’d say that is about it.
Lets us start by doing some code thinking only on the current scope.
First, language and tools.
I decided to go with: Typescript, Webpack, and Phaser.
That should do it.
We can go web desktop and mobile with this development stack.
The code above represents a first version or the first code that does what the scope requires.
On the very first line Phaser, our game framework is imported.
The second line is a class in a different file; we will take a closer look at it later.
By the name is quite clear what this class does.
It is responsible for loading images, sounds, etc.
Then there is a list with all the possible things this entity can be.
All looking pretty good.
The Gem’s class constructor shows the first room to improvement.
It takes too many parameters.
Some time objects do need a lot of properties.
When this happens what you should do is to turn all those parameters into one single object.
Otherwise, it can lead to confusion and mistakes, even with the help of typescript.
The first four arguments are numbers and if you swap x with y typescript won’t complain.
On the other hand, an object is explicit about its properties and doesn’t rely on order but on names.
Line 24 also could be largely improved.
As it is now, any change to GEM_LIST will break the code.
A better approach would be replacing the hard-code value ‘4’ with the size of GEM_LIST minus one.
The same can be applied to line 31, where we test if the type is a bedrock before enabling the click event, but there are far better solutions in this case.
Line 35 is emitting a custom event to the main scene object.
It works, but hard-coded strings are not very reliable when it comes to change.
An object would make refactoring easier and avoid mistakes.
This is looking better.
Now let’s take a look at the class Resources.
The code seems quite okay.
Phaser uses strings to name and find images.
So on the third line, there is a const IMAGE_PATH to make sure the path to those images will be something trivial do update.
There is also a load method to sugar coat the loading process.
That is a very nice thing to have when/if the framework changes.
If the Phaser team decides to alter the way images are loaded this project can get away using this same method just by updating the Resources class and nothing else.
At the bottom all the images have an object name, again, correlated strings spread across the code base can be quite a headache to update sometimes.
The Grid class is something worth to think about before coding.
What is a grid?.A grid is something that has columns, rows, and cells!.How can we do that in typescript?.The very first approach is a 2D array.
That will work, but it can be quite messy.
What dimension will be the columns and what will be the rows?.What if a want a list of columns and a list of rows?.With that in mind lets write a generic Grid class:This is a very generic grid with all the methods we need not only for this project but possibly to others too.
For example, it would be possible to reuse this class in a chess game.
And the grid object will be inside the GemGrid class.
We could extend it, but this way it is possible to pass the grid object around without having to give other object access to the methods inside GemGrid and also avoid circular dependencies.
This is how the final code looks like:A lot is going on with the imports.
We know what Phaser and Gem are, just like the Grid objects.
But what about the others?.MatchSequenceHelper is a very simple class responsible for helping us map sequences of diamonds.
GemFactory is a big improvement to the gem generation, and GemType is just a formalization of the entities present in the game.
This is the full implementation for GemFactory:And what GemFactory allow us is to have an object responsible for managing the gem generation instead of the class Gem itself.
And now Bedrock is a separated class extended from Gem.
That makes it easy to implement specific behavior.
So easy that a new entity was created.
A bomb!.That was definitely not in the initial scope.
But scope changes grows and evolves.
The random generation is also different; now there is a “rare” property to make sure bedrock will show up less the regular diamonds.
This is how a bomb looks like from the inside:I wish typescript had an “override” reserved word and also would be very nice if lambda properties could be overwritten.
A way of getting around it is to have a public method as lambda and having that method to call a different one that is normal and can be overwritten.
Like this:So far every future-proof feature has been a great deal.
So where does over-engineering comes in?.Well, over-engineering in software means something unnecessary, a written code that could be less complex.
Over-engineering can even lead to a massive and slow end product.
Sometimes people will take a hard path to a solution for no good reason.
Maybe there was some feature in the language or framework that would allow the problem to be solved in an easy way but they just didn’t know it.
But time is also a great reason to deem things as over-engineering.
Let’s say we alter the Grid class so it can support irregular column sizes, so some of then can have 5 rows and other 6 because maybe someday it would be useful.
That is a bet I’m not willing to make.
Even things like the game over check could be better for sure, but is it really getting in the way of something?.Is the game slow?. More details