September 20, 2021

Code formatting done right


What is discussed here

Code formatting

What is code formatting?

In this post I describe how to think about code formatting and how to do it correctly.
Spoiler - There is more than 1 correct answer!

Code as text

Code is a text that tells the computer what to do - what instructions to perform.
In high level code it’s abstracted in special keywords with expressions, clauses and blocks.

Before we discuss elements of code I want to discuss the concept of Code as Text.
To simplify writing code - a special language is used.
The most popular languages are based on the English/Latin language.
Keywords for example: if, for , while, with, etc…

Formatting Text

To understand how to format code we should consider how to format regular text.
Formatting creates a better reading experience.
If we kept everything untidy it would look very chaotic.

Let’s take this silly email:

There are error identations, different symbols, grammer mistakes and non aligned text.

Elements of alignment and formatting

The idea

When we write something we want to be clear as possible.

Elements

Elements of reading

The idea

Alignment elements clears noise around the idea and allow us to split an idea into smaller points.
Reading elements help us introduce variaty and guide the reader to the right information.
However, there are certain elements that - when misused - create a worse reading experience.

Elements

Formatting code

Treat code as text

The same ideas that we use to write texts, books, papers, etc…
Can be - and should be - used to write code.

Elements of alignment in a masked code

In this exmaple I maksed the actual code.

Important code - implementation details.
Details such as variable initialization, functions calls, etc...
Common code such as for loops, loggins, etc...

So what elements are there in this code?

Identation

Identation is spaces or tabs in a line.
Identation keeps the hierarchy very clear.
It’s one of the most improtatnt tools for formatting the code.

No identations:

1
2
3
4
5
6
7
8
9
var hasAbovePiece = !isUpSide;
if (hasAbovePiece && y - 1 >= 0)
{
var abovePiece = puzzle[x, y - 1];
if (abovePiece.Down != JigsawType.NotSet)
{
piece.Up = negateType(abovePiece.Down);
}
}

Identations:

1
2
3
4
5
6
7
8
9
var hasAbovePiece = !isUpSide;
if (hasAbovePiece && y - 1 >= 0)
{
var abovePiece = puzzle[x, y - 1];
if (abovePiece.Down != JigsawType.NotSet)
{
piece.Up = negateType(abovePiece.Down);
}
}

Spaces

Spaces seperate between implementation details.

No spaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Menu:
def Execute(self):
wantsToPlay = True
hasPlayed = False
while wantsToPlay:
userInput = input('Please Enter to Begin, C to exit: ')
if userInput == 'c' or userInput == 'C':
break
categories = self.__questionsGenerator.Load()
game = Game(self.__activePlayers, categories, self.__pointsToWin)
game.Run()
hasPlayed = True
if hasPlayed:
print('Thanks for playing!')
else:
print('Hope you\'ll play next time!')

Spaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Menu:

def Execute(self):
wantsToPlay = True
hasPlayed = False
while wantsToPlay:

userInput = input('Please Enter to Begin, C to exit: ')
if userInput == 'c' or userInput == 'C':
break

categories = self.__questionsGenerator.Load()

game = Game(self.__activePlayers, categories, self.__pointsToWin)
game.Run()

hasPlayed = True

if hasPlayed:
print('Thanks for playing!')
else:
print('Hope you\'ll play next time!')

Spaces help to create less strain on the eyes and the brain by seperating details.
This section for example:

1
2
3
4
5
6
7
8
9
10
userInput = input('Please Enter to Begin, C to exit: ')
if userInput == 'c' or userInput == 'C':
break

categories = self.__questionsGenerator.Load()

game = Game(self.__activePlayers, categories, self.__pointsToWin)
game.Run()

hasPlayed = True

The 3 ideas are:

Common blocks

Each marked block consists of a ceratin information - we do not blend between them.
This is highly recommended by the principle Single responsibility.
Each “Block” contains a single general idea.

One of the most common blocks is - a Function!
Functions seperate implementation details by creating actions.

1
2
3
4
5
6
7
8
9
public void Main(string[] args)
{
var (ip, requestInfo) = ParseArguments();

var request = GenerateRequest(requestInfo);

var client = CreateHTTPClient(ip);
client.Send(request);
}

By using loops and if statements we create another block that is identified easily.

Pro tip: Make statements more visible.

That’s why I prefer to put spaces before and after statements and seperate brackets { } into new lines:

1
2
3
4
5
6
7
8
9
10
var race = character.Race; 

if(race == Races.Elven)
{
character.MaxManaPoints += 20;
}
else
{
character.MaxHitPoints += 20;
}

Brackets - { }

Brackets are symbols that seperate code into elements called Blocks.
A block of code not only seperate the code into ideas but in some languages defines the scope of variables.
So something inside a block isn’t “leaked” outside of the block.

C++ code for example:

1
2
3
4
5
6
{
auto myVariable = 3;
}
{
std::cout << "My variable is: " << myVariable << "\n";
}

This would produce a compilation error because we defined a variable out of scope.

New lines or not?

An endless argument is To put new lines or not?

Same lines:

1
2
3
4
5
6
7
var race = character.Race; 

if(race == Races.Elven) {
character.MaxManaPoints += 20;
} else {
character.MaxHitPoints += 20;
}

New lines:

1
2
3
4
5
6
7
8
9
10
var race = character.Race; 

if(race == Races.Elven)
{
character.MaxManaPoints += 20;
}
else
{
character.MaxHitPoints += 20;
}

The answer:
It doesn’t matter - what matters is to stay consistent.
If you use new lines - keep using new lines.
If you put them on the same line - Keep doing that.

RAII - Resource Acquisition Is Initialization

In C++ it’s even used as a technique called RAII.
By using the ctor and dtor as scope handlers we can manage resources more easily.
A common practice is using a mutex guard alongside the brackets.

1
2
3
4
5
6
7
8
9
10
11
12
13
void MyFunction(std::string playerName)
{
auto hasFound = false;
{
std::lock_guard<std::mutex> myGuard { mMutex };
hasFound = mPlayers.Find(playerName);
}

if(hasFound)
{
// Do code here
}
}

Locking should be minimzed as possible so if we only lock the mPlayers variable we should minimize the scope by introducing a block using the brackets.

Headers in code

Headers present ideas.
In code we have few definitions that act like headers:

Classes/Structs

1
2
3
4
5
6
7
/*
A monster is a Creature that has hostility.
*/
class Monster final : public Creature
{
// Code goes here.
}

The class defines a Monster which inherits the Creature class.
The final seals the class for overriding virtual methods.

Functions

1
2
3
4
public void PerformHit(float damage, IEnumerable<IDamageBuffs> buffs)
{
// Code goes here.
}

A function states:

By emphasizing the headers with spaces, clear identation and proper documentation we guide the programmer more easily in our code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// <summary>
/// A monster is a living entity in the game that is hostile to nearby players.
/// </summary>
public abstract class Monster : LivingEntity
{
protected bool mIsHostile = false;
protected LivingEntity mEntityToAttack = null;

public override void TakeHit(float damage, IEnumerable<IHitBuffs> accumelatedHits)
{
// Calculate hit...
}

/// <summary>
/// A monster is a living entity in the game that is hostile to nearby players.
/// </summary>
public virtual void DetectHostileEntity(IEnumerable<LivingEntity> nearbyEntities)
{
// Small tip: .Net 6.0 introduces a "***By" methods :)
var minDistanceEntity = nearbyEntities.MinBy(entity=> Distance(entity,this));
if(minDistanceEntity != null)
{
mEntityToAttack = minDistanceEntity;
mIsHostile = true;
}
}

/// .... More methods
}

Ordering elements in classes

When the code is ordered and a consistent order exists the reader can expect the same element in the same location for every class.
What do we order in a class?
In C# there are many elements that may exist in a class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;

namespace Game
{
public class Hero : LivingEntity
{
public const int AdditionalHitpoints = 200;

private bool mHasUltiAction = false;
private Skill mMainSkill = null;

public Hero() { }

public event Action<float> OnHit;

public override void PerformMainSkill()
{
var isOnCooldown = HasCooldown(mMainSkill);
// .... Code
}

private bool HasCooldown(Skill skill)
{
// ... Code
}
}
}

Different languages has different styles and each language has its own culture of arrangment.
C# Has many elements therefore it’s a good example on how we order elements inside a class.

Prefer to put on top more common methods and as we go “deeper” in a file we get more implementation details.
Prefer to group everything so it would be easier to navigate throught the class.
Prefer to hide details and make the public interface as clear as possible.

Regions and splitters

C# allows the usage of region text processor:

These kind of regions are helpful to hide elements and focus on what’s important right now.

A more common and trivial approach is to use seperators such as comment lines:

I’m not advocating for any approach as the usage of splitters is very subjective.
As I stated already the preference is not important as long as - you’re consistent!.

Consistency is key.

Breaking consistency

When should we break consistency?
When we want to emphasise on an idea or make it more readable.

Lambdas

I still struggle today with the idea of lambdas especially when different languages and IDEs prefer different styles.
What I prefer is - READABILITY.
It means that I may alter the style to make it more readable but less consistent overall.

First example

This is a regular lambda, the common practice is to put { in the same line:

1
2
3
4
5
6
7
8
public void Main()
{
Func<int,bool> performCheck = (int num) => {
var result = false;
// Some checking code.
return result;
};
}

Second example

This example uses many nested elements.
Here I prefered to spread the lambda over new lines to focus on the banning user code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void Main()
{
for(int i = 0; i< 10 ;i++)
{
if(someCondition)
{
var bakaTitles = myList.Select(s=> s.Title).Where(title=>title.Contains("Baka"));
PerformOn(bakaTitles,
(title) =>
{
var user = FindUser();
if(user.Titles.Contains(title))
{
BanUser(user);
}
}
);

// Some code...
}
}
}

Formatting tools - automation

Integrated development environments such as ‘Visual Studio’ and tools like ‘Resharper’ automates the process of formatting.
So if you don’t use an IDE and want to auto format your code it’s recommend to use one.

Of course there are CLI tools as well, some examples:


Consistency and Readablity are important characteristics for code as it decreases the time a programmer spends reading it.
The more you automate this process the better your code will look.

Thanks for reading!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Software#Code#CodeFormatting