Nothing Special   »   [go: up one dir, main page]

Clean Code Return From The Dark Side

Download as pdf or txt
Download as pdf or txt
You are on page 1of 56
At a glance
Powered by AI
The key takeaways are that developing in the 'dark side' can lead to messy, unmaintainable code and decreased productivity over time. Developers should avoid this by keeping their code clean from the beginning and not procrastinating on refactoring tasks.

Some of the dangers of developing in the 'dark side' include an accumulation of bugs, ever-growing requirements, piling up of tasks, decreased speed over time, unhappy managers and teammates who feel betrayed by the developer's broken promises.

To ensure clean test code, tests should be fast, independent of each other, repeatable in any environment, self-validating with a boolean output, and written just before the code they are testing. Functions should be used to clarify the code and make the tests' behavior easy to comprehend.

Clean

Code
Return from the dark side















Legal notice

Course material property of
Beyond Coding IT-Training g.h. e.U

Ceo & Founder
Guillermo Herrero

Book author
Guillermo Herrero

The dark side of developing

Experienced programmers have seen it. A place where the time seems to move
faster. A place with no rules, no boundaries, where code flows and your work is
done. The dark side of developing.

This abnormal void tries to constantly lure us in the form of happy managers,
impressed unwary team members and the coolness factor of having the words
dark and side enclosed in its name.

It sounds like a nice place to be, for a short time. After that, bugs begin to
emerge, requirements continue to grow, tasks pile up, speed turns into slowness,
managers complain and your impressed team members start looking at you with
cold revengeful eyes while thinking:

You were the chosen one! It was said that you would destroy the Sith, not join
them.
You were to bring balance to the Force, not leave it in darkness! [1]

Because the ugly truth is that the dark side of developing is nothing but
darkness. It’s the same as stealing tomorrow’s happiness. It sure feels good
today. But let’s see about that later on.
Snap out of it

We’ve all said we will go back. We will finish what we started and clean it up
later. But there is no such thing as later. LeBlanc’s Law says: Later equals never
[2]
. And if you never go back and clean up the mess you just did, it will follow
you. Forever.

Keeping a mess behind you while generating more mess cripples you, your team
and your entire company. The overall productivity decreases until nothing seems
to be working anymore.

We have to avoid this. Keeping our code clean is a matter of professional
survival.
We are what we code
We stop for a second. We turn around while standing right there, in the dark side
of developing. We see all the mess we just did all around us and suddenly it
starts making sense why this place is so dark and why the light cannot pass
through it. And it’s probably better this way so that no one else can see all the
terrible things we’ve done.

This is all wrong. And we know it. But there is something even more dramatic
that we have to realise.

This is all our fault.

We said we could do it in time but it turned out we couldn’t, so we made this
mess. Our managers told us to develop faster, so we made this mess. It doesn’t
matter which excuse we need to offer here, because we made this mess.

This mess is all our fault and we have to admit it. Once that we do, we will be
able to give real importance to the things that really matter. It is us who are
responsible for the code we write. It is us who know how many bad things can
happen when we write bad code. And it is us the ones who have to stand up with
determination and decide to write good code, clean code, professional code.

Because we are what we code. Write good code. Be a good professional.
Once upon a time
Developing software nowadays is a task that we don’t do alone. Our code will be
read by us and by our team members, constantly. It will be read and it will be
reviewed, used and eventually it will be extended. This becomes a problem if no
one understands our code, not even ourselves.

Reading clean code should be exactly as reading a good story. It should tell us
exactly what is happening. Clear and concise. In a way that we immediately
understand it. It should tell us what the programmer intended to do, it shouldn’t
obscure the decisions behind it and it should show us step by step what’s
necessary in order to complete the process. We shouldn’t be reading the same
code over and over again trying to decrypt its behaviour.

So in order to write clean code we will write a good story. A story that explains
the steps the computer will follow. And our story will be clear and concise. The
code within this story will be organised. It will avoid duplications. It will be easy
to extend and enhance. And it will be small so that it’s understood quickly. When
we write this story we will take care of it, from the beginning until the end.

We will write good code and we will read good code. But sometimes we will
also read bad code. And we will help each other to improve it in order to create
the best story. We will see code that can be improved, code that can be cleaned.
It doesn’t matter if it is ours or from someone else. We will find chances to
improve it. And we should in fact do it. It doesn’t matter if it takes some
seconds, some minutes or even hours. There will be better code afterwards. And
that’s exactly what we are looking for.

Cleaning the code we use is a benefit for us and the whole team. This is our
responsibility as professional developers. And this will be our pleasure when we
read good code, regardless of who wrote it.

Our story begins today.
The structure

Every good story has a beginning, a development and an end. They have a clear
structure: chapters, paragraphs, indentations, dialogues and much more. If we as
a team had to write a book we would have to agree at the beginning how the
headers of every chapter would look like, how big would the margins be and
how dialogues would be arranged.

Writing clean code in a team is no different.

Determining a set of rules and a clean formatting structure is important for a
developer and even more important for the whole team. When the code is
shared, reviewed and refactored, agreeing on the same format rules is mandatory.
It reduces considerably the time needed to understand everyone’s code and
increases the speed at which this code is used.

The best practice as a team is to determine as soon as possible which set of
formatting rules is the most comfortable and apply it to every line of code that is
developed. Development environments nowadays make this very easy since you
can create the rules and export them to a file that can be later on shared among
the team.

The team will then decide where to put the braces, where blank lines should stay
or should go, indentation rules as well as every other detail. Choosing not only
what they believe looks better, but also embracing clean practices, keeping them
consistent. This will impact considerably the performance of the version control
and improve the code review process.
A place to start
Following the common Java conventions facilitates the process of determining
the team’s formatting structure. Best practices encourage to have all the
attributes of a class grouped at the top of it. First the public static ones, then the
private static ones and finally the private attributes. Afterwards come the
constructors, followed by the relevant public methods. Methods that depend on
other methods should be placed next to each others in order of appearance. If a
public method uses a private one, this last one will be placed immediately after
the public one. This way we ensure that the story we are writing is followed
naturally as we read from the top to the bottom.
The Story class below is an example of how to present the information contained
within itself in a clear and clean way.


public class Story {

public static final int MAXIMUM_TITLE_LENGTH = 120;
private static final int MINIMUM_EXPECTED_CHARACTERS = 2;
private String title;
private Set<Character> characters =
new HashSet<>(MINIMUM_EXPECTED_CHARACTERS);

public Story(String title) {
assertBelowMaximumLength(title);
this.title = title;
}

private void assertBelowMaximumLength(String title) {
if (title.length() > MAXIMUM_TITLE_LENGTH) {
throw new IllegalArgumentException("Title is
too long");
}
}

public String getTitle() {
return title;
}

public void add(Character character) {
characters.add(character);
}

public Set<Character> getCharacters() {
return characters;
}
}


Having a shared indentation has become more and more relevant since the
release of Java 8 and the use of streams. Stream methods form a composition of
intermediate operations completed by a terminal operation. In order to obtain the
benefits of streams while keeping our code clean, we should place every new
method in a new line. This also implies that lambda expressions should be no
longer than just one line. If their code becomes more complex and needs
additional lines, we should just refactor it and place it inside a method.


public List<String> getCharacterNames() {
return characters.stream()
.map(Character::getName)
.collect(Collectors.toList());
}


Meaning behind every name

Names are everything in our story. They are the characters inside of it. Without
them there would be no story. We see them in projects, packages, classes,
variables, methods and arguments. They are everywhere. Use them wrong and
our story will make no sense at all.

Names should reveal the intention behind their use. A name should explain why
it exists, what it does and how it is used. It will take us some time to find the
right ones. But once we have them, our story will be completely full of meaning.
If we choose a name that needs to be explained with a comment, then it’s not the
right name. If we choose a name that we can’t pronounce, then it’s not the right
name.
Saying more than what we want to
Names are everywhere, not only inside our code. Some names already have a
meaning outside the context of our applications. Avoid using names that could
mean something completely different when taken out of context. They will
misinform rather than inform.


List<Cheff> everyone = findRestaurantCheffs();
KeyIngredientLettuceLeaf kill =
new KeyIngredientLettuceLeaf();
SupermarketDeliveryGuy tom =
new SupermarketDeliveryGuy();
tom.deliver(kill, everyone);


Saying less than what we want to
When names are not good enough we often see them followed by Manager,
Handler, Processor, Data, Info and so on. As our skills of clean coding improve,
we will read these name endings and suddenly start hearing giggle whispers.
Those whispers tell us that the developer who chose this name didn’t spend
enough time to find the right one. And since these endings do not provide any
additional information, the room they use with their letters is rather worthless.


FinancialHandler financialHandler =
new FinancialHandler();
InvoiceData invoiceData = getLatestInvoiceData();
financialHandler.process(invoiceData);


Spending more time in order to find the right names will free us from the need of
using such naming workarounds.


Accountant accountant = new Accountant();
Invoice invoice = getLatestInvoice();
accountant.bill(invoice);


The art of naming
The story goes on and we are presented with choices. These choices will affect
the way our code will look like. And once that we make a decision we should
stick to it and be consistent.

Interfaces names should have preference over their implementations. Instead of
prefixing an interface with the capital letter i, we should rather postfix its
implementation with Impl.


Fox fox = new FoxImpl();
fox.sayWhatTheFoxSays();


Classes should have noun names and methods should have verb names. We
should use the words get, set and is according to the javabean standards.


Cat cat = new Cat();
Mouse mouse = new Mouse();
cat.hunt(mouse);


Say what you mean and mean what you say. Choose one word per concept and
reuse it every time this concept needs to be handled. Avoid using the same name
for two different concepts.

Don’t be afraid of renaming. Don’t be afraid of refactoring code. Seize every
opportunity to improve the code you read. When writing code, change is the
only constant. Clean code is the only code ready for change.
Methods

Names give us the characters of our story, the set up, the stage. Methods tell us
what happens on the stage. Methods give us the action.

When we use methods we describe in simple commands what the computer has
to do in order to complete an operation. But those commands are not meant to be
understood by the computer, they are meant to be understood by the developers.
When we write bad code we are telling the computer what to do. When we write
good code we are explaining to other developers what the computer is going to
do.

The best way to communicate with other developers through our code is simple.
Go straight to the point and keep it easy. Methods should be small. They should
be concise and clear. And they should describe what is happening grouping the
steps in the same level of abstraction.


public void drinkThatGlassOfWater(){
approachTheGlass();
takeTheGlass();
drinkTheWater();
returnTheGlass();
}


Once that the first level of abstraction is reviewed and understood, we can
describe what happens during the first step inside the next method.


private void approachTheGlass(){
while(isFarAwayFromTheTable()){
stepForward();
}
}


Methods should be small. As small as doing only one small thing. One method
could command you to drink that glass of water. And there should be no doubt
that when the method is completed, we must have drunk that glass of water. That
method does one thing: it makes us drink that glass of water. The same
philosophy could be applied to a method that commands us to approach the
glass. In the level of abstraction above we don’t focus any more about drinking
the glass of water. We focus on approaching the glass as effectively as we can.
Any developer who reads the method name will expect that by the end of the
method, we have approached the glass and we are standing just in front of it.

The purpose of a method’s lines is to explain transparent clear what is
happening.
Method names
Method names should be descriptive. We shouldn’t be afraid of writing long
method names at all. We need them in order to explain clearly what the method
itself is going to do. A long descriptive name is better than a short enigmatic one.
Take all the time you need in order to choose the right name. If you write it and
it does not sound right, change it. If you read it again and it still doesn’t state
what you want it to say, change it again. Once you get used to this, be consistent
with the names you use. If you use the word extract or parse for a specific
operation, use it consistently every time this kind of operation appears in the
code.
The number of arguments
The ideal number of arguments that a method should receive is none at all. Code
becomes extremely expressive when we avoid the use of arguments. It certainly
feels like reading a story when we find many of these one after another.

prepareNextWeeksPresentation();

However, depending on the problem we need to solve, the use of arguments
could be necessary.

prepareNextWeeks(presentation);

As our problems begin to reach a certain level of complexity, the modularity of
our code will even demand the use of more arguments.

prepareFor(presentation, nextWeek);

But this is as far as it can go. When we have two arguments in our method it gets
complicated to form readable and comprehensive sentences with their names. It
is precisely because of this why we should try to evade writing methods with
two arguments and completely avoid writing methods with more than two. We
should have a very clear justification in order to write methods with more than
two arguments.

prepareFor(presentation, company, numberOfDevelopers, nextWeek, vienna);

To keep the number of arguments low we can create objects that wrap these
necessary variables into only one that gives a context for them to be together.

prepare(presentation, details);

This clarifies the code we write and creates a better story to tell.
Dangerous lies
When we write methods that do something different than what the name clearly
states, we are lying. Having methods that behave unexpectedly exposes our code
to unpredictable bugs.


public ShoppingList prepareShoppingList(Recipe recipe){
ShoppingList shoppingList = new ShoppingList();
List<Ingredient> ingredients =
recipe.getIngredients();
for(Ingredient ingredient : ingredients){
if(iAmAllergicTo(ingredient)){
recipe.remove(ingredient);
} else {
shoppingList.add(ingredient);
}
}
return shoppingList;
}


This method is silently ruining the recipes that it receives if it happens that they
contain an ingredient which we are allergic to. We clearly wanted to prepare the
shopping list necessary to cook this recipe. But in addition to that we do
something more. Not only we do something more. We do something nobody at
this point expected us to do.

Keeping the scope of our methods to do one thing and one thing only helps us
avoiding side effects.
The unexpected plot twists
We write our code, we call our methods and we expect a result. We receive it and
continue with the rest of the storyline. But what happens when the result we
received could potentially be not what we expected? What happens when there is
a plot twist in our story?

The script of our method is executed line by line and suddenly one requisite is
not met, we run into a place we shouldn’t be or we receive nothing to work with
for the next step. The story can’t go on. And we want to know why.

When we foresee unexpected plot twists in our code we use exceptions. Using
them allows us to know in detail what happened, when and how. It also allows
us to react to particular cases and turn our story back into the right direction. It
provides us a very powerful way to remain in control of our own story. But once
that we have written the try/catch statement and covered all the possible
outcomes, we see it. We open our eyes in horror and realise that we created
something evil. We created a monster that is difficult to read and goes practically
against every rule of clean code that we previously learned. We squeeze our
brains and spend endless nights trying to make things better until we reach the
final truth.

There is no way around this.

But there is something professional developers can do with this monster in order
to produce clean code again. We pet it. We stand in front of it with determination
receiving bites and scratches until we train it to do what we want. And when it
does, we hide it in the closet.


public void eatTheSoup(){
try {
eatTheSoupWithMySpoon();
} catch (NoSpoonException nse){
eatTheSoupWithAnotherSpoon();
} catch (SoupTooHotException sthe){
eatTheSoupAfterSomeMinutes();
}
}


We pet our try/catch statements by not writing more than one single line per
available code flow. We hide it inside a closet by making it be the only statement
within this method. And we use our own exceptions to provide a detailed
explanation of the error that interrupted the normal flow.

Keeping it clean and simple will help the reader understand exactly how our
story might change. There will be plot twists, but we will be the ones who
control them.
Don’t leave us empty handed
When our methods could potentially return null we are basically forcing
everyone who uses them to check for its value afterwards. It is a better practice
to either throw a meaningful exception or to return a special case object. A very
straightforward example of a special case object is the empty collection.

With Java 8 we can return Optional<T>. This does not take away from us the
need to check whether the value is present. But it certainly gives us already a
hint that we could potentially receive an empty value. On the other hand, the
cases where we don’t return an Optional<T> clearly state that we won’t have the
necessity of checking if null will be returned.
Classes

Java is an object oriented programming language. When we express ourselves in
Java we structure our thoughts in objects. And those objects are defined in
classes.
Developing with class
The place is full of people having loud conversations. One elegant man opens
the door. He approaches a table walking with style, confronts the lady as he sits
down in front of her and suddenly everything is quiet.

My name is Bond, James Bond. [3]

We could have a long conversation about why something like this can happen.
But the long story short is that he can do that because he’s got class. And when
we develop our classes we should also do it with class.

Java classes that follow the Java standard conventions have class. Their class
name should be followed by the public static attributes. Afterwards we find the
private static attributes. Then we will write the private attributes. Because of
encapsulation it should be completely uncommon to have the need of using
public attributes. When we are done declaring the attributes, next come the
constructors followed by the public methods. Public methods are directly
followed by the private methods that facilitate their clean code as it is expected
by the step-down rule.


public class JamesBond extends SpecialAgent
implements Seducer {
public static final String AGENT_NAME = "James Bond";
private static final String AGENT_NUMBER = "007";
private Integer seduceLevel;

public JamesBond(){
super(AGENT_NAME, AGENT_NUMBER);
}

@Override
public boolean hasPermissionToKill(){
return true;
}

@Override
public void seduce(Seducible seducible){
seducible.seduce(seduceLevel);
}

public void setSeduceLevel(Integer seduceLevel){
this.seduceLevel = seduceLevel;
}

public Integer getSeduceLevel(){
return seduceLevel;
}
}


Sometimes our classes need to have constructors with many arguments. This
entangles the clarity and understandability of our story. We can get rid of these
complexities by restricting their use and providing a fluent interface builder with
explanatory method names.


public class Pet {

private String name;
private String type;
private int age;
private String hairColor;

private Pet(String name, String type,
int age, String hairColor) {
this.name = name;
this.type = type;
this.age = age;
this.hairColor = hairColor;
}

public static PetBuilder builder() {
return new PetBuilder();
}

public static class PetBuilder {
private String name;
private String type;
private int age;
private String hairColor;

public PetBuilder name(String name) {
this.name = name;
return this;
}

public PetBuilder type(String type) {
this.type = type;
return this;
}

public PetBuilder age(int age) {
this.age = age;
return this;
}

public PetBuilder hairColor(String hairColor) {
this.hairColor = hairColor;
return this;
}

public Pet build() {
return new Pet(name, type, age, hairColor);
}
}
}


The Pet class is instantiated afterwards in a much cleaner way.


Pet mittens = Pet.builder()
.name("Mittens")
.type("cat")
.age(1)
.hairColor("grey")
.build();

nataly.playWith(mittens);


Nataly can now play with Mittens without having to worry about using a scary
constructor that has more than two arguments. In fact, even if she wanted, she
could not use the constructor because it has been restricted accordingly.

Classes should be concise and small. They should focus on one task and they
should have a name as concise as their purpose. If we find ourselves having a
bad time to name a class it is most certainly because this class does more than
one thing.
The Single Responsibility Principle
If a class does more than one thing it will expose itself in the future to be
changed because of several reasons. When we create classes it is important to
design them so they should have only one reason to be changed.

Trying to identify which could be the reasons for change is a fine way to figure
out whether your class is doing more than one thing and one thing only. By
reducing the complexity of a class and forcing it to take care of only one
behaviour we simplify the code making it easier to keep it clean and testable.
Conditional statements

Being able to make decisions with our code is precisely what makes it powerful.
But asking too many questions will pollute the structure and draw the attention
of the readers away. Especially when those questions are repeated over and over
again.
Keep on reading
We don’t need to keep in our minds all the obstacles we find throughout our
story. Conditions and loops increase the complexity of the code forcing us to
remember them constantly. Put them away.

We write an if statement and we need to open a pair of braces. Later on, indented
inside this condition we need to place a loop with another pair of braces. The
code inside the loop will be indented in a second level. While reading this code
trying to figure out what it does, we need to remember both the first condition as
well as the condition that breaks the loop. This process goes on until we reach
the end of the method.

Every time we open a pair of braces and indent the code we increase the
complexity level by one unit. We begin writing methods with no complexity
level. When we open a pair of braces, the code inside of them has a level one of
complexity. We should do everything we can to evade the level two of
complexity. However, it is true that in very rare occasions we will have no
choice but to reach it. Methods should never surpass a level two of complexity.


public int calculate(int number){
if(isEven(number)){
int result = number * 3;
if(isGreaterThan100(result)){
result -= 12;
if(isMultipleOf7(result){
return result * 13;
} else {
return result;
}
} else {
return result;
}
} else {
return number;
}
}


The method above reaches a level three of complexity. Some of its operations
depend directly on a previous condition and will not be applied if the previous
condition is not met. Reading this code is a complex process because we have to
keep in mind every condition in order to know exactly what and when we are
returning a specific value. Inverting the conditions will allow us to discard the
easier set of outputs first while keeping a level one of complexity.


public int calculate(int number){
if(!isEven(number)){
return number;
}
int result = number * 3;
if(!isGreaterThan100(result)){
return result;
}
result -= 12;
if(!isMultipleOf7(result){
return result;
}
return result * 13;
}


If conditions are met the process of the calculation continues. However, if any
single one of them would not be met the process concludes. The relevance of
these conditions happens when we stop the process rather than when we
continue. We transformed these conditions into preconditions. Once we get past
them they are not relevant for us any more. Therefore we focus on what happens
next without the need of keeping them constantly in our minds until we reach the
end of the method. We keep the level of complexity low.


public int calculate(int number){
int result = number;
for(Calculation calculation :
getOrderedCalculations()){
if(calculation.isNotApplicableTo(result)){
return result;
}
result = calculation.calculate(result);
}
}


This is one of the cases where we find ourselves in the need to reach a level two
of complexity. It is however acceptable because we do it only in order to break
the loop and the only line within this complexity level is very simple to
understand: it breaks the loop. In this last example we maintain the level of
abstraction because our purpose within the calculate method is to figure out
whether we can apply a specific list of calculations in order. If so, we continue
the process. Otherwise we stop. In this level of abstraction we don’t mind which
condition applies for every calculation or which calculation will be applied at all.
Going deeper into the next level of abstraction will show us what every
calculation does, what its preconditions are and exactly which operation it
applies to our final result.

Keep the level of complexity low. Stay in the same level of abstraction.
Transform conditions into preconditions. Write it as simple as possible so that
when we later on read it, it will seem so clear to us, that we will just keep on
reading.
Polymorph questions into answers
Long series of if statements and switch statements are a different kind of monster
to handle. We cannot pet them. But we can redefine their shape to express their
inner beauty inside out.

We can make most of them go away by using polymorphism. The two most
important cases where we should be using it are:

- The behaviour of our code changes based on state
- Parallel conditionals in multiple places

Using polymorphism can reduce considerably the number of conditional
statements in our code. But we need to keep in mind that we can’t fully avoid
them. Basic operations like +*-><% cannot be put away so easily.

Developing our code using polymorphism wisely will reduce considerably the
complexity of our tests. They will cover smaller and more particular cases
divided among smaller classes. They will focus on one single code flow rather
than having the need to set flags to test all the conditional possibilities. It speeds
up the testing process as it can be parallelised and run independently. It
centralizes the common logic and allows developers to find out easily the
differences by looking at the subclasses.

When we use polymorphism we are encapsulating the conditional behaviour
inside the polymorphed class itself. This means that other classes don’t have to
take care whether they are using the right implementation of this polymorphed
class or not, or whether they have to specify if statements at a given point in
time. They will just run the code and delegate these decisions to someone else.

If we follow this principle we will be able to differentiate two big groups of
classes within our project depending on their behaviour: the business logic group
and the construction group (factories, builders and providers).

By avoiding to use conditional statements in the business logic group we reduce
the number of times that we have to ask for these conditionals, improving their
performance, readability, maintenance and extensibility. We place those
conditional statements in the construction group instead in order to instantiate
the right classes. This helps us considerably to avoid code duplication.

A general rule for switch statements is that they can be tolerated if they appear
only once, are used to create polymorphic objects, and are hidden behind an
inheritance relationship so that the rest of the system can’t see them.

Let’s imagine that we have to develop a dynamic system that can perform
different operations depending on the input. Something like a calculator. For
simplicity’s sake we will cover only the following input: 1 + 2 * 3.


public class Node {

private char operator;
private double value;
private Node left;
private Node right;

public double evaluate() {
switch (operator) {
case '#':
return value;
case '+':
return left.evaluate() + right.evaluate();
case '*':
return left.evaluate() * right.evaluate();
default:
throw new IllegalArgumentException("Operation
not supported: " + operator);
}
}
}


So given the specific input 1 + 2 * 3, we can conclude that we can abstract the
behaviour of its elements into nodes. Some of them would be values and some of
them operations. We can identify what to do with them depending on the
operator field.

However, this brings up the situation where conditional statements like if and
switch are used, but they shouldn’t be.


public String display() {
switch (operator) {
case '#':
return String.valueOf(value);
case '+':
case '*':
return left.display() + operator + right.display();
default:
throw new IllegalArgumentException("Operation
not supported: " + operator);
}
}


Any future operation related to the Node will most certainly have the need to
bring back the switch behaviour tool, making our entire module a mess. There is
very little we can do to improve this, unless we embrace the polymorphic
refactoring and take a completely different approach.


public interface Node {
double evaluate();
}

public class ValueNode implements Node {

private double value;

public ValueNode(double value) {
this.value = value;
}

@Override
public double evaluate() {
return value;
}

}

public class OperatorNode implements Node {

private char operator;
private Node left;
private Node right;

public OperatorNode(char operator,
Node left, Node right) {
this.operator = operator;
this.left = left;
this.right = right;
}

@Override
public double evaluate() {
switch (operator) {
case '+':
return left.evaluate() + right.evaluate();
case '*':
return left.evaluate() * right.evaluate();
default:
throw new IllegalStateException("Operation
not supported: " + operator);
}
}
}


Separating value nodes from operation nodes is already a step forward. It
simplifies and solves the switch problem within the ValueNode, but it doesn’t do
the trick within the OperationNode.

What this intermediate step is trying to tell us is that the OperationNode itself is
still handling too many responsibilities. We already learned in our Classes
section the importance of the Single Responsibility Principle. So in order to take
full advantage of polymorphism, we will have to break it down again into classes
that will handle one, and only one responsibility.


public abstract class OperatorNode implements Node {

private Node left;

private Node right;

OperatorNode(Node left, Node right) {
this.left = left;
this.right = right;
}

protected Node getLeft() {
return left;
}

protected Node getRight() {
return right;
}

}

public class AdditionOperator extends OperatorNode {

AdditionOperator(Node left, Node right) {
super(left, right);
}

@Override
public double evaluate() {
return getLeft().evaluate() +
getRight().evaluate();
}

}

public class MultiplicationOperator extends OperatorNode {

MultiplicationOperator(Node left, Node right) {
super(left, right);
}

@Override
public double evaluate() {
return getLeft().evaluate() *
getRight().evaluate();
}

}


By distinguishing every single operation from another and also from the value
nodes, we achieve to get rid of the switch statement. Now the implementation of
each one of the nodes remains a responsibility of the developer. But this way we
lose entirely the dynamicity of the code.

Regardless of what we do, we will soon realise that we still need one switch
statement. It is the only way that we can determine which particular
implementation we will need depending on the given input. So we will
implement a construction group class, commonly known as a factory, to keep
this switch statement away from our business group classes.


public class NodeFactory {

private NodeFactory() {}

public static Node createValue(double value) {
return new ValueNode(value);
}

public static Node createOperator(char operator,
Node left, Node right) {
switch (operator) {
case '+':
return new AdditionOperator(left, right);
case '*':
return new MultiplicationOperator(left, right);
default:
throw new IllegalStateException("Operation
not supported: " + operator);
}
}

}


We hide the constructors of these classes so developers are not allowed to use
them. Instead, they can benefit from using the NodeFactory that lives in the
same package and will do the hard work for them.

The NodeFactory encapsulates the logic of choosing which one is the right
implementation for every given OperationNode. And from the outside, within
our business logic, we don’t have to take care of these decisions at all.

Testing these smaller classes that have only one particular responsibility is much
easier than the first approach. The code remains dynamic and easy to extend. If
in the future we would be interested in implementing a SubtractionNode we
would only implement this particular node and add it to the NodeFactory. The
rest of our code would remain as clear as it is and untouched.

Don’t pollute business logic with particular implementation decisions. Replace
those questions into answers using polymorphism and create factories that take
over the responsibility of instantiating them.
No comments

We all have experienced this moment when we find a complex piece of code that
doesn’t clearly reveal its’ steps. On top of that, it could also be that the algorithm
behind it is very complex on its own. Somehow this acts as a magnet to attract
comments all around the place that try to explain miserably what the code is
doing.

Don’t comment bad code. Rewrite it.

We have also seen commented out code. Delete it instead. We can find it later
with version control if we need it.


public void play(Song song){
Instrument instrument = getMyInstrument();
// clean(instrument);
tune(instrument);
// warmUpMyFingers();
MusicSheet musicSheet = song.getMusicSheet();
instrument.play(musicSheet);
}


Imagine you are developing a music related project and need to extend the play
method. Why are those two lines commented out? Does this mean that when we
play a song we shouldn’t clean the instrument first? Or perhaps we don’t have
time to warm up our fingers? Could it possibly be that this behaviour was
expected but the previous developer forgot to implement it? Was it planned to be
implemented later? Should we delete the commented out lines? But what if they
mean something to someone and we would be making a mistake by deleting
them? Why are we asking so many questions?

Because commented out code does nothing but rise constant questions. Let’s
confront the last question. Would you have asked yourself any of these questions
if you had read the following code instead?


public void play(Song song){
Instrument instrument = getMyInstrument();
tune(instrument);
MusicSheet musicSheet = song.getMusicSheet();
instrument.play(musicSheet);
}


We find sometimes funny comments that could have been a running gag among
the development team for a particular topic, but if you are a new employee you
will most certainly not understand it. And besides, the comment adds no value to
what the code is doing anyway.


// this practice goes against best practices,
// but let's try to live with it
// this combines values together, otherwise
// we would need a new row for each value
public static final String MAGIC_SEPARATOR = "!-:-!";


The developer who wrote this code is warning us. This code goes against
everything that is beautiful in this world. However, let’s try to live with it. In
addition to that, the magic separator will be used in many parts of our project
rising at least as many questions everywhere as the ones we read before. Why
didn’t he spend enough time to find a proper way to do this? Why do we have to
try to live with it?

We have also seen TODO comments that hopefully will be implemented
someday and not be forgotten.


// TODO consider this, do we really need it ,
// does it bring more good or bad?
@Enumerated(EnumType.STRING)
private InstanceType instanceType;


Well, how can we know? But thank you. We will really consider this.

Finally, we keep on seeing comments that try to explain what a particular value
means.


// if the person is qualified for the job
if(person.hasRelatedDegree(job) &&
person.getYearsOfWorkingExperience(job)){
person.applyFor(job);
}


We can avoid these last ones by placing that value into a variable with a proper
name.


boolean isQualifiedForTheJob =
person.hasRelatedDegree(job) &&
person.getYearsOfWorkingExperience(job);
if(isQualifiedForTheJob){
person.applyFor(job);
}


We can even extract this logic inside a private method. This encapsulates the
complexity of the calculation and keeps the main code flow much more
understandable.


if(isQualifiedFor(job, person)){
person.applyFor(job);
}

(...)

private boolean isQualifiedFor(
Job job, Person person){
return person.hasRelatedDegree(job) &&
person.getYearsOfWorkingExperience(job);
}


The real reason to use comments is to compensate for our failure to express
ourselves in code. Having the need to use a comment means that we didn’t put
enough effort in writing good code. Not putting enough effort in writing good
code means that we failed at writing good code. As a conclusion we have to
accept that comments themselves are a failure. So the next time you see yourself
writing a comment, stop, think and find a way to express yourself with good
code instead.
I was told that I should use comments
Were you also told that they lie? And that they lead to misinformation? They are
thieves and rogues of this story. They await for you to feel too comfortable and
then jump from the shadows to backstab you when you least expect them. They
will trick you in order to gain your trust with their nice words and then bring you
to the dark place that we always wanted to stay away from.

Let’s take a moment to read the following examples.


// if we have our wallet in the pocket with money
// then buy a coffee
if (m > 0.0 && place == Place.POCKET) {
place = Place.HAND;
m -= 2.0;
thing = Thing.COFFEE;
}


It seems that the comment above matches what the code does. We had to spend
some time reading the code to try to decrypt what it says anyway because it is
definitely not good code. But let’s move on.


// Get the cellphone quick and check the time
place = Place.OTHER_HAND;
thing = Thing.CELLPHONE;
System.out.println("Time is 9:03");
place = Place.POCKET;


Apparently there is not too much complexity in here. But the comment states
clearly what it seems to be happening.


// if we still have some money
// then give the barista a tip
if (m > 0.0) {
sr += m;
m = 0.0;
}


This looks like a kind thing to do. After all, the barista put a lot of effort in
preparing our coffee. And it would have given us a big smile except for the part
where the barista was a sneaky rogue and the tip that we wanted to give is in
reality all the money that was left. We just got robbed by a tricky comment. But
in such a subtle way that we might have not realised. If only we had seen a code
like this without any comments, we might have had decided not to execute these
lines.


if(wallet.hasMoney()){
wallet.giveAllMoneyTo(sneakyRogue);
}


As the logic of our code evolves and changes, comments remain the same and
become obsolete quickly. Soon they become lines of information that do not
inform about the code that surrounds them. But by staying there anyway, they
rather misinform. Sometimes we try to write a comment but we are not precise
enough and it ends up again being misleading. Eventually we will find ourselves
in a situation where we see that the code is doing one thing but the comment is
saying completely another. What should we do?

Trust the code. When we write good code, the code itself is the one that tells us
what it is doing. There is no place for comments when we write good code.
Write good code instead of bad code with comments.
Javadocs
The use of comments is justified when we are writing a public API. In this case
the comments are not only expected to be there, but they should also be concise
and accurate with their explanations.

There is good code and bad code. But there is also good comments and bad
comments. The purpose of the javadocs is to explain the inputs, outputs and
overall process that will happen inside a method or a class. Unnecessary lines
make everything more complicated to understand. Useless lines have no
meaning at all.


/**
* Gets the value
* @return the value
**/
public int getValue(){
return value;
}


Seriously?
Unit tests

The purpose of writing clean code is to provide readability and understandability
in order to increase development speed. Clean code must be easy to change and
extend. Because of this, clean code has to be also robust and reliable. To ensure
it we write unit tests.

When we hear the words unit tests together we might think this sounds
annoying. That we do this for the company. That we do this because there is
some kind of quality control that forces us into writing them. Perhaps we might
even think that we have to waste time right after finishing our code just to prove
to somebody that it works.

To think this, is wrong. We don’t write unit tests for somebody else. We do it for
ourselves.

We are professional programmers who develop professional code. Professional
code does what it needs to do. It doesn’t do what you don’t expect it to do. It
does what it was designed for, no bugs, no secondary effects. And the best way
to ensure as professional programmers that our professional code works is to
deliver it together with its unit tests.

There is no better feeling after extending or refactoring a module than seeing the
green lights of its’ unit tests. There is a special place in our hearts for an emotion
of comfort like this. We feel safe. We feel that we did a good job. We got rid of
this strange dark feeling that follows us everywhere and whispers from time to
time: will my code do exactly what i wanted?
The evil character that always returns
We finally face the evil character of our story. We clash and battle for hours. And
when we reach the end and see the dark robes lying still on the ground, we still
hear this voice inside of our minds. Our fear talks to us.

Is it done? Is he dead?

In that very moment Mr. Unit Test checks the body for pulse, turns around and
looks at us with his intense green eyes and says:

It’s done. We won.

And no more questions are needed. No bugs are coming back to haunt us. No
need to have bad feelings about the consistency of our code. We did it. We won.
We succeeded.
Make it do what you want it to do
Unit tests are the best way to ensure that our code does what we want it to do,
and nothing else. It helps us find out possible problems that might happen in the
future. It assists us into having a much better understanding of how to use this
code. It tells us with a given state that the code is working as we expected. It
tells us now and it will tell us at any time in the future. And if something in the
code changes impacting the expected behaviour, it will shout and tell us right
away.

Unit tests put away a lot of pressure that we, as professional programmers, put
on our shoulders when our code reaches production. We don’t want our code to
have bugs. We don’t want it to fail. We don’t want to have the fear of refactoring
our code or someone else’s because later on it could bug it or break it. Because
then it will be our fault. We don’t want to fail.

That’s exactly what unit tests are for. They tell us that it works. That’s it. No
more pressure. No more worries. It works. We succeeded.
Test Driven Development
Up to this point we agree that unit tests are very important. It is then necessary to
determine how to create meaningful tests and when to create them. Test driven
development (TDD) is a well known practice adopted by many professional
developers who rely constantly on their tests to ensure the consistency and
quality of their code. But they don’t test their code once it’s done. They write
their production code to fulfill the expectations of their tests following the three
TDD laws:

- You may not write production code until you have written a failing
unit test.
- You may not write more of a unit test than what is sufficient to fail,
and not compiling is failing.
- You may not write more production code than what is sufficient to
pass the currently failing test.

So clearly, as its name suggests, we write our tests first and afterwards our
production code to comply with the expectations of the tests themselves. You
don’t write the answer and later on the question. You write your expectations and
later on you answer them with your production code.

These three laws lock you into a cycle that is perhaps thirty seconds long. The
tests and the production code are written together, with the tests just a few
seconds ahead of the production code.

If we work this way, we will write dozens of tests every day, hundreds of tests
every month and thousands of tests every year. Our tests will cover virtually all
of our production code. So we will basically end up having just as much test
code as production code, perhaps even more. This opens a big question that
needs to be answered straight away. When the size of our tests is as big as our
production code, it implies that our test code is as important as our production
code. Our tests need to stay clean.

Every test code has three distinguishable parts: preparation, execution and
assertion. At the beginning of our tests we will find the part where we build up
the test data necessary for this particular test. Right afterwards we operate with
this test data using precisely the tools we want to test. At the end we will receive
the result and compare it with the expected values.

In order to ensure that our test code is clean we follow the same principles as
with production code. It needs to be readable, simple and meaningful. It needs to
be flexible, maintainable and reusable. We will use functions to clarify the code
so that it’s easy to comprehend the behaviour of the tests.
The F.I.R.S.T. rule of tests
By following these five rules we will obtain clean test code:

Fast - Tests should be fast and run quickly. Otherwise we might decide not to
run them regularly. Eventually both the test and production code will rot.

Independent - No test should depend on another. The order in which they are run
should not matter.

Repeatable - Tests should be repeatable in any environment, regardless of
whether it is production, test or development in your laptop. If they fail in a
specific environment then they are not reliable enough.

Self-Validating - Tests should have a boolean output. Either they pass or they
don’t.

Timely - Tests need to be written just before the production code that makes them
pass. Doing it otherwise could lead you to write production code that is very
hard to test.
Embrace clean code

This is the era of technology. We want our code to be readable and
understandable. We want it to be effective and reliable. But code is also
something we as professional developers produce. It is, after all, our product.
And when we code, it is after all, our reputation.

Remember. Your story begins today. You are what you code.

Don’t be just a developer. Go beyond coding. Be a beyond developer.
About Beyond Coding

Let’s face the ugly truth: technology is intricate. But this doesn’t mean that
learning should be that way.

Our challenge is to truly understand those subjects first so that we can present
them to you as simple as abc.

Beyond Coding gives trainings to motivated experts like you, to improve their
abilities and simplify their tasks.

www.beyond-coding.com
Footnotes

[1]
Obi-Wan-Kenobi in Star Wars Episode III: Revenge Of The Sith.
[2]
Mentioned in the blog article “On Agile: Why You Won’t Fix It Later” (http://on-
agile.blogspot.co.at/2007/04/why-you-wont-fix-it-later.html)
[3]
James Bond in James Bond films.

You might also like