In this article I want to go over the following things:
Truth of coding
Refactoring
Code Smells
Extract & Insert Class (Refactoring)
Lastly, I won’t be showing code here on what exactly is changed.
For that please take a look at the video, it goes over a live session on refactoring based
on features we’d like to implement for the game.
The truth of coding
The one thing I notice beginners stuggling with coding is that programming a game
is a process.
It’s very common that programming a game or anything will require you to spend at least
70% of your working time thinking, structuring, and editing code.
The other 30% of your working time will be on the actual process of writing your code out.
As a matter of fact, writing good code is an act of persistence, not an act of genius.
Being a “good code” is having a way of thinking, structuring, and editing code
that makes it easier for you (and others) to see what the code is doing.
Refactoring
Refactoring is restructuring existing code, without changing its be behaviour.
Keep in mind that code is a living, breathing and ever-changing aspect of your application and/or game.
It’s actually quite common to apply multiple refactoring “sessions” over the course the project,
especially as more features and ideas come to light.
Another thing to note is that refactoring is an artform; as in there is no hard or simple rules to follow.
Basically there are no right answers on how to refactor, just that there are pros and cons to how you refacotr your code.
Lastly, understand that rules and guidelines you may find online can be broken.
To improve on refactoring you need to do the following things:
Understand that refactoring is 100% subjective
Practice, practice, practice
The second point is important, and that is what this episode aims to do.
Allow a base of code that you can practice on refactoring.
Why should you refactor code?
The most important reason to refactor code is to allow people to quickly learn, understand, and debug
the current code base.
New people who are working on an existing project will not know what the code base is doing.
Although it takes time to learn the application over time, the process can be sped up exponentially if the code
is traceable.
On top of that it is also common for people who have been working on a project to forget about features that have been
implemented.
Code smells
A code smell is an “abstract” indicator and/or charactersitic that shines a light on a
deeper problem of the current code base.
These ‘code smells’ can be a clear indicator that it is time to refactor your code.
Just like refactoring, finding and determining what is a code smell is 100% subjective and is an artform.
Common code smells include:
large classes
duplicated code
comments (too many or a lack of)
long methods
conditional complexity (large conditional blocks)
nested statements
if statements
switch/case statements
while loops
Large classes
The main problem of having a large class is that it is difficult to understand, debug, and most importantly add and/or remove
features.
The reason this is true is because the large class is doing to many things, therefore breaking the single responsibility rule of code design.
Large classes contains functions and variables that do not relate to the “purpose” of the class it resides in.
The solution to this is to implement the refactoring technique called ‘Class Extraction’.
Class Extraction
The idea of class extraction is to take a class that is too big to understand and split it into multiple classes (2 or more).
#Large Class
class_name Shapes
var shape
var color
var area
var animal # no relation to shape classvar name # no relation to shape class# no relation to shape classfunc speak():
print "hear me speak"func area():
return area
Here we have a class called shapes, and it has variables and functions that have nothing to do with the purpose
of the class named Shapes.
The simplest solution is to remove some code and add it inside a newly created class (Class Extraction).
# Better
class_name Shapes
var shape
var color
var area
func area():
return area
# Much Better
class_name Animal
var animal # no relation to shape classvar name # no relation to shape classfunc speak():
print "hear me speak"
Signs to know when to break a class apart:
when multiple parts of the code do not seem to fit or synergies together
when a class has more than “one responsibility”
A class has more than 400 lines of code
not a hard and fast rule, it really depends on what the class is doing, trying to accomplish, and how it needs to accomplish it
To do a class extraction you need to:
decide how to split the responsibilities
group methods and class variables together and see if they synergize
look at the original “big class” and see if more things need to be refactored out into an
existing class or a newly created class
Class Insertion/Inline
If you can remove pieces from one class into a new class, then the opposite is also true.
You are able to combine one or more classes into a single class.
Signs to know when combining classes:
if multiple classes have similar responsibilities
if multiple classes rely on each other and are not used by other classes
Let’s look at an example of two classes that would work better in a single class:
# ok
class_name SquareArea
func calculateArea(a: float, b: float) ->float:
return a * b
# ok
class_name TriangleArea
func calculateArea(a: float, b: float) ->float:
return (a * b) /2.0
Even though both classes follow the single responsibility principle, the two
classes would work better if they were combined.
# much better
class_name AreaCalculator
func quadrilateral(length: float, height: float) ->float:
return a * b
class_name TriangleArea
func triangle(base: float, height: float) ->float:
return (a * b) /2.0
Again keep in mind that refactoring is an artform and I’m sure there are reasons you’d need to separate area calculations into
their own classes.
Software Quality Standards
I’m not making up refactoring techniques, they have been around for the longest time.
As a matter of fact, there are standards built around what “good code” should entail.
One of those standards is the ISO 25010.
Transcript
Godot Tutorials is not sponsored by or affiliated with Godot Game Engine. In this episode, we will be taking a look at the following the truth of coding refactoring code it smells
or at least a basic
introduction into code smells, the extract and insert class refactoring technique and life coding of refactoring the pong game. Also, don't forget that the code will be uploaded to get help.
So if you don't actually want to
watch the video, please feel free to download the source code on GitHub under Pung
zero seven and that scene
will hold all the code for this episode
anyway. Moving on now, let's take a
look at the truth of coding. And this is true regardless of game programming or software
development, and that is
coding is a process. As a matter of fact, in my personal opinion, I would separate the process into 70 percent of the time. You will be thinking, structuring and editing
the code base and 30
percent of the time you will be coding the
features. And as a matter of fact, writing good
code is not an
act of genius. Being a good
coder is having a way of thinking, structuring and editing code that makes it easier for you and others to see what the code
is doing.
Refactoring is restructuring existing code without changing its behavior. Changing behavior would be adding new features. Now keep in mind that your code is a living, breathing, an ever changing
aspect of your project. You may
find yourself applying multiple refactoring sessions over the course of your project, especially as more features and ideas come to
light. Now, why
exactly should you refactor your code? And refactoring your code allows people to quickly learn, understand and debug your current code base.
In this regard,
you people that come onto projects don't know what the code base is
doing, and so
readable code allows
them to learn
and understand the
project. On top of that,
existing people and projects may forget about feature implementation. And so when they want to add a new feature that's based off of an already existing
feature in your project, refactored
code, which
is just readable code,
allows them to quickly learn and understand certain parts of
the project so they can
go ahead and quickly implement new
features.
Refactoring code is an
art form and it comes
with
practice and with
any type of refactoring. There are no simple rules. There are just general guidelines. As a matter of fact, there are no right answers and over time you will get a better sense of what needs to be done. And I would say that this is hard to show in a tutorial, but I will try my
best on top of the
rules. And general guidelines can
be broken since
general guidelines can be broken. Refactoring, in my personal opinion, is that refactoring code is one hundred percent subjective. Each individual programmer has their own standards, their own beliefs and how and when you should refactor.
And again,
refactoring is best learned through practice. So the more you code and the more you refactor, the better you get at it. One way to start up with identifying when to refactor is to look at coding smells. Now, what exactly is a code smell? Well, it could smell is an abstract indicator or characteristic that shines a light on a deeper problem in your current project. Basically, they are signs that
code or certain
parts of code in your project can be refactor to look cleaner. And again, when identifying code smells, these are one hundred percent subjective. Regardless, some common code smells you may run
into your journey
or the following large
classes, which is what
we have as the main issue currently in our ponged project. You may also find duplicated code. You may find issues with comments meaning that there are too many comments or there are a lack of comments.
You may run
into long methods. And lastly, you may run into conditional complexity, which are just large conditional blocks in your code, such as nested statements.
This could be
if statements, case switch statements and while loops. No, again, these are all subjective because, for example, what constitutes a large class and there are no hard rules for that. Another example of code smell is being subjective is how much is too much when it comes to duplicated code. On top of that, how many comments do you need? Another subjective question is what is considered a long method.
And you can start
seeing here why code smells are subjective. But regardless, let's look at the large class. So why are large classes a problem? And basically large classes are difficult. To understand and
debug on top of that, what
makes a class, a large class and in my personal opinion, a large class is a class that does too many things. As a
matter of fact, a class should
be doing just
one thing if it is
able to. And one smell of a large class is if a class contains functions and or variables that do not relate to the purpose of the class it is currently residing in. The solution to that is to do the class extraction refactoring technique. The extracting class technique
is basically
taking a class that is too big to understand and splitting it into multiple
classes. And this can be two
or more
classes. Now, some
signs that your class needs to be broken apart is when multiple
parts of your
code seem to fit together better. On top of that, when the class has more than one responsibility, it is doing. And on a personal note, a class that has more than four hundred lines of code. However, that is not a hard rule. That is just a personal rule, and that also depends on what the class is doing. So how exactly do we go about doing a class instruction? Well, first, we need to decide how to split the responsibilities
we need to group
methods and class variables and see if they
synergize. On top of that,
we need to look at the original big class and see if there are more things that need to be factored out into either an existing class or a newly created class. If you can refactor something out, you can also refactor something inside. And so in this case, we can do the class insertion or the class in learning
technique, and that is basically
taking two or more classes and combining them. And signs of one to consider class insertion is if multiple classes have similar responsibilities and or if multiple classes only rely on each other and are not used by other classes.
Now, with
everything that we learned so far, this is what I'm thinking for the current
Pong game.
The most important thing for me at this moment is to make the game state as clean and readable as possible. As a matter of fact, when I come back to it, whether that be a month from now or a year from now, I would like to have a sense of what is going on in each individual case
statement, which is our
game
state. And as a matter
of fact, maybe a year from now, I would like to add
other things to
the game, for example, adding power ups to the game or limiting the movement of the player pedal. So after thinking about it, my game plan is to create classes for collision
the ball on top of that,
creating a class for the paddle in which I would also create subclasses for the player paddle and the enemy paddle. And on top of that, I'm thinking of a
class to hotbox box
container that limits both the player paddle and the enemy paddle to a certain portion of the screen. But in this case, for now, we're just going to have the box container. Just hold the values
of our screen
now to make the game state readable. Whatever logic is left inside of our game state, I want to extract those out into their own functions. And that's basically it for the structuring phase. So after thinking and structuring now let's go ahead and actually edit our current pong game. So in this case, let's start with the low hanging fruit and the low hanging fruit would be renaming are seen to properly describe what we. And to do when creating our project or when you press the button and renaming our main gd file that runs our game.