Photo by Pankaj Patel / Unsplash

Why clean code is important?

Nowadays, developers are relatively easy to start creating their own games. They can easily find tons of tutorials on youtube, vimeo, blogs (like this one ;)) or in a books. However, programming is a tricky profession — it is easy to learn but hard to master. Coding is a very flexible job — you can create things on different ways. Some of them are easy at the beginning but often the easiest way can cause some troubles when your project become more complex.

For example, on youtube, authors of courses show the simplest way to solve some problem. In tiny issues like the topic of certain tutorial it is no a problem. Later programmer will remember some bad habits from simple tutorials and he is willing to reproduce it later during development the real projects. Today we are going to talk about clean code — set of rules that allow programmers to be more efficient in their work. In this part, I will show you a “fat and ugly” Main.cs class which I find in one of projects.

Why meaningful names are good?

Names are really important in programming. They are everywhere in software. We name variables, methods, arguments and classes. We continuously set names. Because we use it so much, we should do it well.

Use clear intention-revealing names

Good name of a variables, methods or classes should reveal our intentions — it should answer all of important questions. Good names should tell you why it exists, what it does and how to use it. If name of variable require extra comment — it isn’t good name.

Simple example from *Main.cs* class “How do not name variables”

The name hvdestroyCount did not give us a lot of informations. We better should name it something like that:

int horizontalAndVerticalActiveRoutinesInFirstDestroyCounter;
int horizontalAndVerticalActiveRoutinesAfterFirstDestroyCounter;

You can notice that those names are pretty long. But please not use it like an argument for use short and not clear names (like hvdestroyCounter). You have available an amazing tool called IntelliSense in Visual Studio so you can no longer complain about long (and readable!) names.

Look at example function (from Main.cs) which use one of above variables. Is it clear for you what it does? How will it write more legible? For example:

void IncreaseHorizontalActiveRoutinesCounter(int horizontalArrayIndex)   
{
if (horizontalArrayIndex > 1 && horizontalArrayIndex < 9)
{
horizontalAndVerticalActiveRoutinesAfterFirstDestroyCounter++;
}
}

It looks more clearly now right? I omit meaning of this strange double if but the next question is what is hiding behind the values 2 and 9?

Do not use “bare” numbers

In case of using only numbers in your code — maybe now you understand what does 1 or 38 mean. But in the future you or other members of your team will have a problem with understanding of your present intentions. It is wise to use constant value to describe some number.

const int MIN_SPOT_TO_DESTROY_INDEX = 1;
const int MAX_SPOT_TO_DESTROY_INDEX = 9;

/...
if (horizontalArrayIndex > MIN_SPOT_TO_DESTROY_INDEX && horizontalArrayIndex < MIN_SPOT_TO_DESTROY_INDEX)
/...

Trust me it is a lot of more understandable to other coders (and for you too).

Classes and methods naming

Classes and objects should have noun or noun phrase names like Enemy, Player and DataConverter. A class name should not be a verb — verb is reserved for methods. They should have verb or verb phrase names like AddExperience, ReceiveDamage or SearchEnemy. What about first letter of name — it should be capital letter or lowercase? In University of Games we use the Unity Engine notation. So we start classes and methods from capital letter and variables from lowercase.

Clean functions

Functions are the first line of organization in our code. So it really good for you if you write them well. First of all:

Good function is small function

In “Clean Code” book, Robert C. Martin says that there are only two rules of functions:

  1. They should be small
  2. They should be smaller than that.

In Unity we have important method called Update(). It is good idea to split Update code in separate functions. For example Update() in Main.cs class has 166 lines. 166 lines of spaghetti code. In this case, same idea of superclass Mainwas bad idea. If we had many complex operations during Update() you should group them in small functions with good names. When we look at this code we know what author meant by that. Maybe we try refactor one of pretty methods from Main.cs.

void DestroyIfToLess(){ // unmarking spots if is marked less then tree spots
int howMuchIsMarked=SpotCount.numberOfMarkedSpots;
spotsScripts = originSpot.GetComponentsInChildren<Spot> ();

if ( howMuchIsMarked < 3) {
foreach (var item in spotsScripts) {
item.ID=0;
item.pressed = false;
item.over = false;
SpotCount.numberOfMarkedSpots--;
item.SpritePressed (false);item.ID=0;
}
}
SpotCount.numberOfMarkedSpots = 0;
images=originConnectingLines.GetComponentsInChildren<Image>();
foreach(var item in images){
item.gameObject.SetActive(false);
Destroy(item.gameObject);
}
}

How could you know, how to divide this block into smaller functions? It is simple:

One method do one thing

Code above doing lots more than one thing. It’s creating references, resetting states of “item”, turning off GameObject and finally destroying them.

Functions should do one thing. They should do it well. They should do it only.

So,let’s try to refator this little monster. What we can change before? We starts from divide them into smaller functions? First of all change names and remove useless references and repetitions. Secondly I decided to devide DevideIfToLess() to 4 smaller functions: CheckIfMinimumCountOfSpotsMarked, ResetSpotsState, ResetSpot (in Spot.cs now) and DestroyConnectionsImages.

Finally this method after small refactor should be like this:

void UnmarkSpots()
{
if (CheckIfMinimumCountOfSpotsMarked() == false)
{
ResetSpotsState();
}

SpotCount.ResetMarkedCount();
DestroyConnectionsImages();
}

bool CheckIfMinimumCountOfSpotsMarked()
{
if(SpotCount.numberOfMarkedSpots < MIN_SPOTS_COUNT_TO_VALID_MARK)
{
return false;
}
return true;
}

void ResetSpotsState()
{
Spot[] spots = originSpot.GetComponentsInChildren<Spot>();
foreach (Spot spot in spots)
{
spot.ResetState();
}
}

void DestroyConnectionsImages()
{
Image[] connectionsImages = originConnectingLines.GetComponentsInChildren<Image>();
foreach (Image image in images)
{
Destroy(image.gameObject);
}
}

//In Spot.cs
void ResetState()
{
ID = 0;
pressed = false;
over = false;
SpritePressed(false);
}

It is more clearly and easier to understand right?

To choose good names in programming structures are required good descriptive skills. With some training you should simply learn it. Often programmers afraid of renaming things for fear that it will make more problems than it’s worth. We believe that change names to more legible is always good idea. Follow some of above rules and check if they improve the readability of your code. The important lesson is start with idea of small functions. Smaller methods = smaller problem later ;)

Clean code — what next?

Welcome in the second part of article about clean code. Last time we talked about importance of naming in programming and idea of small functions. I hope that previous lesson was helpful for you. I am certain that programming with clean code rules is easier and more enjoyable for you ;) Remember:

Good code is like a good joke — is needs no explanation.

Today we are going to talk about two another aspects of clean code. First of them are comments. Use them or not to use them — that is the question. #Good comment, bad comment Comments are a real double-edged sword. Nothing can be quite so helpful as proper-placed comment. Nothing can be quite so damaging as deprecated comment that preaches untruth and disinformation. Programmers often disagree about comments role — they are pure good or necessary evil? I our case C# is a lot expressive language so we would not need comments very much to express our intent. It is fact that programmers often use comment to do fast makeup for bad code. This solution is like an adhesive tape — fast and easy though only for a moment. Unfortunately bad code even with beautiful makeup is still ugly bad code. Next time against comment confusing code just better clean it! For example: ``` //Check to see if the player done lethal hit to enemy below 30 hp if(enemy.healthPoints < 30 && isCritical) … ``` If you will use separate function, your comment becomes unnecessary.

if(enemy.isLethalDamage(isCritical))

In many cases better idea is to create a function that says the same thing as the comment you want to write. However some comments are necessary or favourable.

Good comments

In some situations comments will be very welcome:

  • Legal comments — often copyright and authorship statements are necessary things to put into a coment at the start of each source file. For example:
// Copyright © 2017 by University of Games sp. z o.o. All rights reserved.
  • Informative comments — it is sometimes useful to provide basic information with a comment. It is still better to provide this information by function name but in some case comment is quite good:
  • Intent explanation — sometimes comment provides also the intent behind a decision. In following case we see an interesting decision documented by a comment:
//Returns tracked Enemy object
protected abstract Enemy EnemyInstance();
//Less value is better, red color cards are better than black
bool CompareCards(Card card1, Card card2){

}

Clarification — of course always better is find way to argument or return value be more readable. But sometimes when we use some kind of external libraries that you cannot modify, comment can be very useful.

Warning of consequences — sometimes we can warn other programmers about certain consequences. For example:

//Do not use it every frame. Use only if is necessary!
AreaElements[] ScanArea()
  • //TODO comments — my favourites :) TODO comments mark task should be done but for some reasons we can’t do it at the moment. What is important is regularly remove unused TODO comments
//TODO implement this function after introduce frozen feature
protected Enemy GetFrozedEnemy()
{
return null;
}

Bad comments

Unfortunately most comments fall into this category. The worst genres of comments are:

  • Redundant comments — extra comment which not more informative than code. For example:
  • Misleading comments — not accurate comments which say another thing that they exactly do
  • Noise comments — they are nothing but noise. This comments describe obvious and provide no new information:
  • Closing brace comments — sometimes programmers will put comment on closing braces as below. This behaviour creates only chaos. IDE tells us which brace is on pair with selected one.
  • Commented-out code — please, don’t do this. Old commented code is like an old dust. Other programmers don’t know if those commented lines of code are important or not. Probably they leave it commented. Next time don’t hesitate and remove unused code. You can always go back to it using source code control systems like git.
//Method that return random powerup. Return null value if powerups list is empty
PowerUp GetRandomPowerUp()
{
if(powerups.count > 0)
return powerups[Random.Range(0,powerups.count)];
return null;
}
//Default constructor
public Weapon()
{
}
//THX for information bro!try {

while{

} //while
}//try
catch {
}//catch

Abstraction and encapsulation

In OOP abstraction and encapsulation are going hand in hand, they help accelerate the pace of innovation by allowing software to evolve without being stuck in the trap of version incompatibility. What are the encapsulation and abstraction? Generally speaking:

Abstraction — allows us to represent complex real world in simplest way. It is a process of identifying the essential features and behaviours an object should possess.

Encapsulation — is a process of hiding all the internal details of an object from the outside real world. “Encaplsulation” is like “enclosing” into a “capsule”. It restricts from watching its internal view where abstraction behaviour is implemented.

With this definitions we can talk about why this two things are so important in object-oriented programming. Let’s look at practical example below:

//abstraction – exposing only the relevant behaviour
public interface IShootable
{
void Shoot();
}
//encapsulation – hiding things that the rest of the world doesn’t need to see
public class Archer : IShootable
{
//exposed information
public string name {get;set;}
//exposed but unchangeable information
public int arrowsAmount {get; private set;}
//internal i.e hidden object detail. This can be changed freely, the outside world
//doesn’t know about it
private bool CanShoot(()
{
return arrowsAmount > 0;
}
//implementation of a relevant feature
public void Shoot()
{
if(!CanShoot())
{
Debug.LogError(“No arrows!”);
return;
}
LoadArrow();
Aim();
Fire();
}

private void LoadArrow() {};
private void Aim() {};
private void Fire() {};
}
public class Gunner : IShootable
{
//implementation
}

Any archer can shoot, because it implements IShootable ‘feature’. Having a group of shooters (list) this means that both Archer and Gunner are valid choices.This means that:

//this method isn’t implemented in Archer or a Gunner class
//it can work with ANY object implementing IShootable
public void ShooterAttackOrder(IShootable shooter)
{
shooter.Shot();
}

So you can have lots of implementors with plenty of properties and methods, but in this scenario what matters is their ability to Shoot. This is abstraction.

Since shooting requires some steps (LoadArrow etc.), these are hidden from the view as they are an internal concern of the class. The Archer has many other public behaviours which can be called by the outside world. But some details will be always hidden because are related to internal working. They’re private and exist only for the object, they are never exposed. This is encapsulation.

Conclusion

There is a dirty little secret of code comments: to write good comments you have to be a good writer. Comments aren’t written for the compiler but for other people. Writing good, meaningful comments is hard, like an art of writing the code itself. In most cases if you feel your code is too complex to understand without comments — your code is probably just bad. The earlier you start rewrite it, the less effort is needed to get good results.

Objects expose behaviour and hide data. This makes it easy to add new kinds of objects without changing existing behaviours. Using of abstraction makes life easier. We don’t need to worry about update copypasted code from another class. The best solution is to write code in way where we need change code in only one place to update existing behaviour. I hope that you have enjoyed clean code rules. To learn more about clean code we recommend you excellent book written by Robert C. Martin: “Clean Code: A Handbook of Agile Software Craftmanship”.

I hope it’s useful for you. If you have any comments or questions, please write them here!

To help us create more free stuff, consider leaving a donation through Patreon where you can vote for the next asset pack and get access to future packs & articles earlier. Sharing knowledge is our passion. Unfortunately, keeping a team of professionals costs a lot that’s why we are constantly taking on new opportunities.

​We’ll be publishing any news on the following outlets:

Thanks for reading!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
University of Games

University of Games

Free #tutorials, #courses, and guided pathways for mastering game development in #Unity with supportive community who share knowledge about creating own #games.