Code Smells and Refactorings - ViralKaos/Frozen-Bubble-Android-port-x86 GitHub Wiki
The FrozenBubble class has a Bloater code smell. We notice a lot of the Bloaters’ symptoms such as Large Class, Data Clumps and Primitive Obsession. For instance, there’s a Data Clumps code smell from the fields adsOn to targetMode. We can use Extract Class and Move Method to replace these fields by a single field representing the Preferences class.
- We select the fields from adsOn to targetMode and extract a class that we call Preferences.
- In the Preference class, we change the attributes’ protection level to private.
- Since there are set and get methods for each attribute that has been extracted into the Preference class, we get create a get and a set method (i.e. getPreference(Preference preference), setPreference(Preference preference)) in the FrozenBubble class.
- We then move the getters and setters of each attribute into the Preference class.
In the FrozenBubble
class, a Message Chain code smell can be observed in the onPreparingOptionsMenu()
method. Using Extract Method and Move Method will allow us to isolate the chain and move it the class from which the chain starts.
- We are going to use Extract Method to extract the chain
findItem(x).setVisible(y)
into a method calledsetItemVisible(item x, bool y)
; - We are going to use Move Method to move the method into the
Menu
class. This way, theFrozenBubble
class only calls one Menu class method.
Still in the FrozenBubble
class, we notice that there is a Switch Statement code smell in the onOptionsItemSelected(MenuIntem item)
method. We can use Replace Type Code with State/Strategy and Replace Conditional with Polymorphism to remedy this.
- Using Replace Type Code with State/Strategy, we create a class
EditorType
and subclasses of theEditorType
class because it is an object of the classEditor
that acts differently in the switch statement. Each subclass will correspond to a case in the switch statement.
class EditorType{...}
class EditorNewGame extends EditorType{...}
- In the
EditorType
class, we create an abstractgetTypeCode()
method. - In the
Editor
class, we create an instance variable type of belonging to theEditorType
class and different constant values for each switch statement case. We also create asetType()
method which will call a factory method from theEditorType
class to create the appropriate subclass object.
class Editor
{
EditorType type;
public final static int MENU_COLORBLIND_ON = 1;
public final static int MENU_NEW_GAME = 8;
int setType(int value)
{
type = EmployeeType.newType(value);
}
}
class EditorType
{
newType(int value)
{
switch(value)
{
case MENU_NEW_GAME: return new EditorNewGame();
break;
…
}
}
}
- In each subclass, we override the
getTypeCode()
method to return one of the constant values respective to the appropriate type of subclass.
class EditorNewGame extends EditorType
{
int getTypeCode()
{
return Editor.MENU_NEW_GAME;
}
}
- In the
Editor
class, we create agetType()
method which returns one of the constant values respective to the appropriate type of subclass by callinggetTypeCode()
of the variable type.
int getType()
{
type.getTypeCode();
}
-
Using Replace Conditional with Polymorphism, we create an abstract method
onEventItemSelected()
in theEdirtorType
. -
In each subclass, we override the
onEventItemSelected()
method with the appropriate class behavior. -
We change the switch statement method to call the
onEventItemSelected()
of the type variable of theEditor
object, and it will call the appropriate subclass method.
editor.type.onEventItemSelected();
- Since we now have a Message Chain code smell, we will use Extract Method to create a class in the
Editor
class as follows:
void onEventItemSelected
{
this.type.onEventItemSelected();
}
The BubbleSprite
class also has a Bloater code smell. We can observe Data Clumps, Large Class, Long Parameter List, Long Method and Primitive Obsession. We notice the Data Clumps for the variables moveX
and moveY
, which are always used together, as well as realX
and realY
. We will use Extract Class to replace the fields with proper Coordinates
object.
- We select the
moveX
andmoveY
fields and extract a class that we callCoordinates
with the fields as attributes. The new field in theBubbeSprite
class is calledmoveCoordinates
. - We replace the fields
realX
andrealY
with the fieldrealCoordinates
and replace the variablesrealX
andrealY
withrealCoordinates.x
andrealCoordiantes.y
respectively.
Similarly, in FrozenBubble
, there’s a Data Clumps code smell from the fields adsOn
to targetMode
. We can use Extract Class to replace these fields by a single field representing the Preferences
class.
- We select the fields from
adsOn
totargetMode
and extract a class that we callPreferences
. - In the
Preference
class, we change the attributes’ protection level to private. - Since there are set and get methods for each attribute that has been extracted into the
Preference
class, we get create a get and a set method (i.e.getPreference(Preference preference)
,setPreference(Preference preference)
) in theFrozenBubble
class. - We then move the getters and setters of each attribute into the
Preference
class.
There exist only a few classes doing most of the work and this resulted in classes with large amounts of attributes, several of which are public constants, lots of unrelated methods, and several very long methods. This also leads to several other smells which will be described below and refactoring for those should help reduce the length of several classes.
The reason for the large amount of attributes comes from a large number of
primitives being used, mostly of type int
to store ID numbers, or type codes, to be used when setting
up the environment. For example, the FrozenBubble
class contains 30 such attributes while the
FrozenGame
class contains 16 plus several other attributes needed by each class. By using Replace
Type Code with Class, the amount of primitive type attributes being used can be greatly reduced as
well as reducing the overall length of the classes.
A side effect from building classes that try to do everything is that they can
sometimes end up retaining data used primarily by other classes. In this case, FrozenBubble
, the main
class responsible for setting up the environment in which the game runs, also holds the set of ID
numbers that will be used by SoundManager
, the class responsible for playing the sounds in the
game. Therefore, whenever a sound needs to be played a called has to be made to SoundManager
and the ID held by FrozenBubble
is passed as a parameter. This creates an unnecessary coupling
between the two classes.
From FrozenBubble.java:
public final static int SOUND_WON = 0;
public final static int SOUND_LOST = 1;
public final static int SOUND_LAUNCH = 2;
public final static int SOUND_DESTROY = 3;
public final static int SOUND_REBOUND = 4;
public final static int SOUND_STICK = 5;
public final static int SOUND_HURRY = 6;
public final static int SOUND_NEWROOT = 7;
public final static int SOUND_NOH = 8;
public final static int SOUND_WHIP = 9;
public final static int NUM_SOUNDS = 10;
Sample call - from FrozenGame.java:
soundManager.playSound(FrozenBubble.SOUND_NOH);
From SoundManager.java - Constructor:
public SoundManager(Context context)
{
this.context = context;
soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
sm = new int[FrozenBubble.NUM_SOUNDS];
sm[FrozenBubble.SOUND_WON] = ...;
sm[FrozenBubble.SOUND_LOST] = ...;
sm[FrozenBubble.SOUND_LAUNCH] = ...;
sm[FrozenBubble.SOUND_DESTROY] = ...;
sm[FrozenBubble.SOUND_REBOUND] = ...;
sm[FrozenBubble.SOUND_STICK] = ...;
sm[FrozenBubble.SOUND_HURRY] = ...;
sm[FrozenBubble.SOUND_NEWROOT] = ...;
sm[FrozenBubble.SOUND_NOH] = ...;
sm[FrozenBubble.SOUND_WHIP] = ...;
}
The main refactoring that needs to be applied here is Move Field to move the IDs into the
SoundManager
class so that it may have all the data it needs available from within itself.