Clean Code Tips Tricks World Coding
Clean Code Tips Tricks World Coding
As you can see, the name find does not really give a precise description of the
code. Hence, the author had to add a comment to explain what the code does.
Problem solved, right? Well, what happens if you see the same find function
is being called from another module in the code. Now there is no way to tell
what the function does. So unless this developer intends to add a comment
wherever the function appears (which would be another messy move), then
he has failed at expressing himself and writing clean code.
For a much cleaner code, the author could have written the function name
differently, like in the example below.
The new developer has split the variable name into firstName & lastName,
but sadly, the comment above says something else entirely. To keep
comments true and valid, you will have to change them whenever you make
any changes to your code. But how can you tell a new change you want to
make isnt going to comment somewhere in your code invalid. And do you
even want to start manually editing all the comments in your code? Thats
adding more stress and time to an already complicated process. To avoid this
problem entirely, it is simply advisable to keep comments to the barest
minimum.
So when is comment good and when is it bad? Lets see how that works.
Bad Examples of Comments
Ugly Code
Usually, people add a comment to code when they are trying to fix messy
code instead of cleaning up the code itself. When you have an ugly code,
comments wont fix it. You cant suddenly make bad code beautiful by
explaining why it is so messy. Youll probably do a good job at explaining,
but your code is still the same terrible mess that it is. If you have an ugly
code, just refactor it and rewrite it in a way that makes it look good to read.
Code Explanation
This cannot be emphasized enough. You should always use code to explain
what codes are doing. A comment is not the right way to explain code.
Before you add a comment to explain your code, try to see if you can rename
the fields, or switch around any other element that will make it easier to
understand what is going on. For instance, you can extract a method and give
it a meaningful name. This way, anyone reading the code can easily
understand what is going on based on just the field names alone.
Mumbling
In many cases, the comments on code are nothing more than the programmer
talking to himself. It is a terrible practice to add a comment to your code
merely because it seems like the necessary thing to do. With no real
background or reason for adding a comment, you are only littering your code.
Readers will find your code clumsy and difficult to read or understand.
Redundant Comments
A good name is a perfect substitute for a comment. Once you can find a good
name for a method or field, then a comment that describes what the method
does is no longer needed and should be removed. For instance, if you have a
method with the name SendEmail, it is clear from the name what the method
does. No additional comment is needed, and you shouldnt add one just
because you feel you want to.
Any comment that is not more informative than an already informative name
is not needed. Comments should justify the intent of code or give information
about the codes intent. It should not be harder to read than the code itself.
Misleading, Mandated and Noise
Like every piece of text, comments can be misleading, especially if it is not
accurately written. Some developers inadvertently write comments that do
not express their true intention. This will only lead to confusion when a new
user is interpreting it.
For instance, you may end up with a code whose comment says that the
function sends a mail to the customer. But while debugging to figure out why
the code isnt sending the email as intended, you may discover that the
comment misled you all along. The code was never meant to send an email,
as the comment stated. Instead, the method only constructs an email that will
be sent to a user. Misleading comments like this may occur because the
author used information that is known only to him. Or the sentence is simply
not precise enough to be accurate.
A mandated comment is another problem developers have to deal with.
Typically, some big companies may have rules that mandate developers to
add a comment to every method or class created. If it is up to you to decline
that rule, then you should. Adding comments where it doesnt add any true
value to the code will only clutter things up and pollute your code. For
instance, you do not need to add comments to constructors for any reason at
all since the scope of any constructor is always clear.
When Are Comments Okay?
Comments arent always bad, though. In fact, there are still a lot of developers
that are diehard fans of adding a comment to code. To find common ground,
it is safe to say that comment is, most times, a necessary evil. You should
always aim to make your code explanatory enough without comments. But if
you cant, here are some instances where commenting your code is still okay.
When Writing Complex Expression
Even for the best programmers, complicated regex or SQL codes can be
difficult to express cleanly. In such a situation, adding a comment above the
expression will help any developer checking out the code later to understand
it without stress.
Legal Comments
There are certain that you are mandated to add to your program by corporate
coding standards. This can be due to legal reasons such as copyright or
authorship of the code. Such comments are necessary and should be added.
Warnings
Sometimes, you need to warn other developers that will be looking at your
code about the possible consequences of an action to prevent them from
breaking something. In such cases, it is okay to leave a comment close to this
code. Such comments add value to your code and make it easy to avoid
mysterious behavior in the code in the future. Apart from warnings, you may
also use a comment to amplify the significance of a piece of code that may
otherwise be overlooked by a user.
When All Else Fails
Although I recommend making your code as expressive as possible without
comment when all else fails, and you are unable to write a code that is
expressive on its own, feel free to write a comment. Do not leave poorly
written code the way it is simply because you dont want to write a comment.
You can add a comment to explain or clarify the intent of your code instead
of leaving it as it is.
However, if you must write comments at all, make sure you make it local. A
local comment is placed close to what it is referring to. A non-local comment,
on the other hand, is far away from the code it is explaining and is bound to
become a lie at some point.
Place comments that refer to a variable or function directly above it. A
warning comment should be right beside or immediately above the code it is
referencing. You may also make a warning comment stand out from the rest
of your code if your IDE supports highlighting.
Additional Commenting Rules
Refactor Long Code
Typically, codes need clarification when their abstraction level is wrong.
Bear in mind that functions should be small and should do only one thing.
Any function that breaks this rule will most likely need comments to explain
what it does. Rather than rush to add comments, you should rewrite the code
and see if each piece of logic can be written as a separate function instead. If
you do this right and use meaningful names, your code may not need a
description, after all.
Writing each piece of logic as its own function enhances the readability of
your code. Any developer looking at the code will most likely be able to tell
what it does by simply reading the function name and get additional
information by looking at the implementation. It also improves the testability
of your code since each function can be tested on its own more easily
compared to when it was part of a larger chunk of code. Future changes to
any part of the code will be easier as well
Commented Out Code
One of the most terrible practices when writing comments is commenting out
codes. You should avoid this entirely. Not only does this mess up your code,
but it will continue to pile up because new developers may not be willing to
delete it since they are not unsure of why its there in the first place.
If you ever find commented out code in your work, dont wait and ask where
it came from, just delete it. The longer it stays in your code, the messier
things will become. Dont even try to uncomment the block of code. Just get
rid of it.
TODO Comments
Some programmers argue that it is okay to leave TODO comment since it
explains why a comment has a degenerate implementation. A TODO
comment also tells what the future of that function might be. But I do not
totally agree with the idea of writing TODO comments.
They are basically comments that explain things the developer should do but
cannot do right now? But most of the time, most TODOs never get done.
They will only become irrelevant after a while. Someone else looking at the
code in the future will have a hard time figuring out if the task has been done
or not. Hence, instead of writing TODO comments, it is best you just do them
immediately if you can.
Chapter 6: Formatting
Youve probably noticed that you have a hard time reading some books
compared to others. And it's not always because one has better content. Just
like attractive handwriting will make it easier to read a letter, good formatting
can make all the difference between whether you will read a book or drop it.
Writing code is very much like a book. No matter how great what you have
written is, if the formatting isnt right and your code only hurts the eyes,
anyone looking at it will have a hard time making sense of things. This is
why your code should be well formatted. The indentation and spacing should
be right and used consistently throughout your code and so on.
When people look at your code, how it is formatted will determine whether
they will be impressed or not. The neatness of your code, the consistency,
and how much attention you have paid to the details will affect the beauty of
what they perceive. Using meaningful names and following all the other rules
we have stated is something. But all of that wont matter if your code is a
scrambled mess that looks tacky and unorganized.
When it comes to formatting code correctly, there are no specific standards to
adhere to. The most important thing is to adhere to the same standard
consistently throughout your code. Some programmers even come up with
their preferred formatting standards. This is fine too, as long as you stick to
that same standard everywhere in your code.
The right formatting will guide anyone reading through your code and makes
it easier for them to understand. Even when you are working as a team, the
team is expected to agree to the same formatting rules that every member
must comply to.
What you will find in this chapter are mostly recommendations about how
clean the code should look. We may not be able to tell you how many
numbers of lines your code should have or the number of characters that
should be in a line. These are things you will figure out as you write your
code.
Why is Formatting Important?
As a developer, your first and perhaps most important job is to communicate.
Most developers only focus on functionality and getting the code to work. If
you are concerned about writing clean code (which you should), then
communication will be your priority over everything else. Your code will
work, eventually, but you would have done a terrible work if the readability
of your code is compromised in the process.
This is why correct formatting is important, especially if you intend to
maintain the future extensibility of your code. If you get it right, your
formatting is the one part of your code that will most likely stay the same
long after most parts of your code has been changed in the future. And what
will determine the ease of that change is how neat of a job you have done
today.
Vertical Formatting
This refers to how your code is organized from top to bottom. It includes
concepts such as vertical size, vertical openness, vertical density, distance,
and order, among other concepts.
When it comes to formatting your code vertically, a common analogy
programmers use to explain this is how a well-written newspaper article is
organized. Any newspaper is composed of several articles. In terms of length,
they are mostly small. Although some are still longer than others, only a few
of them are ever over a page long. Keeping all the different bits organized
this way is what makes a newspaper appear neat and readable. Imagine a
newspaper that is just one long mass of story with all the facts and details
jumbled together.
In every newspaper, information is organized in a way that makes everything
concise and neat. Usually, at the top of the page, you will find the headline
which tells you what the article is all about. Most times, the headline of an
article is enough to tell you whether you want to read it further or not. This
can be likened to the name of functions. It should be simple but still provide
sufficient information about the code. After the headline, the first paragraph
of any newspaper story serves as a synopsis of the entire story in summarized
details. In the same way, the topmost parts of your code should contain all the
high-level algorithms of the programs.
As you read down the newspaper article, subsequent paragraphs are expected
to provide more details such as important names, dates, quotes, and so on
down to the not so significant details. The same applies to your code. The
important functions should be at the top and are supposed to go down this
way until you get to the functions at the lowest levels.
Vertical Openness
Codes are made up of multiple lines with each line representing a clause or an
expression. As you go from top to bottom, each group of lines is a complete
concept or thought. To make it easier to read your code, you should separate
each of these concepts from the other with blank lines.
Take the code below; for example, the declaration, imports, and functions
have white spaces in between them to separate.
This simple action has a tremendous effect on how the code reads and its
overall visual appeal. Each blank space automatically tells you the next line is
a new concept, different from the one above. The ideas are clearly shown,
and you have no hard time reading through the code.
Now imagine all those blank lines gone. Without question, the readability of
the code will be affected. It is still the same code (even smaller with the blank
spaces gone), but you will need to pay close attention to make out individual
concepts.
The concepts no longer pop out as they did in the first example. This goes to
show the difference vertical openness can make in how your code reads and
why this rule is important to keep your code neat and organized.
Vertical Density
While blank spaces should separate individual concepts, lines of code within
the same idea should be as close to each other as possible. This is the idea of
vertical density. Adding a blank space or even useless comments in between
lines of code that are meant to be closely associated may affect the flow of
the code and make it harder to read. Rather than space-related concepts out,
there are better if they are closer to each other to improve comprehension.
Vertical Distance
It is not enough for your code to look neat, correct formatting can make it
easier to figure out what the code does as well. One of the formatting features
that affect how easy a reader can make sense of your code is the vertical
distance.
If you have ever had to hunt through chains of inheritance and check from
top to bottom to find the definition of a function or variable, youll appreciate
the importance of having the correct vertical distance between related
concepts. As a general rule of thumb, you should keep related content close.
This will make it easier to figure out what a system does as you will spend
time figuring out where the different pieces are.
Keep closely related concepts as close together as possible. They should be
kept in the same file (unless you have a very good reason to keep them in
separate files. The vertical distance between the two concepts is a measure of
how important they are to each other. This will help the easier to find related
concepts faster and more conveniently.
Conceptual Affinity
Conceptual affinity simply refers to the idea that certain parts of code tend to
want to be near each other. Conceptual affinity is one of the basis on which
vertical distance can be determined while writing code. In essence, concepts
with a stronger affinity for each other should be placed closer to each other.
This affinity may be due to a direct dependency (e.g., when a function calls
another or when a function calls a variable). conceptual affinity may also
exist between a group of functions performing a similar operation. For
instance, two functions with a common naming scheme that perform similar
tasks or a variation of the same task should be placed proximate to each other
even if they dont call each other.
Variable Declarations: As mentioned, it is expected that you declare
variables as close to where they will be used as possible. Generally, you
should place local variables at the top of every function where it will be used
since functions are expected to be very short.
If you are writing control variables for a loop, it should be declared right
within the loop statement and not elsewhere in the code. However, in some
rare cases (for longer functions), variables may be declared just before a loop
or at the top of a block of code.
Instance Variables: There is a long-standing debate about the correct
positioning of instance variables. In a well-designed class, instance variables
are used by most of the methods in the class. This is why they are best
declared at the top of the class. The rules for formatting instance variables
may vary from one language to the other. For instance, C++ programmers
might place it at the bottom of the class, following what is known as the
scissors rule. Java programmers, on the other hand, favor placing them above
the class. Either of these conventions, depending on your preference. Just
ensure that you declare the variable in a place that will be obvious. This way,
anyone reading the code will know just where to look to find the declaration.
Dependent Function: If you have a function that is being called by another,
they should be as vertically close to each other as much as possible. The
function that is being called is below the one calling it. This helps to preserve
the natural flow of your flow, ensuring the readability of your code. If they
more than one, the topmost function should call those below it, while those
below should, in turn, call the functions that are below them.
Vertical Ordering
Just like the newspaper analogy explained earlier, it is expected that your
code is arranged in a way that makes it easier to find information. The most
important concept that explains what the code does should come first. They
should be written as concisely as possible. The arrangement of concepts
vertically like this is known as ordering
Vertical ordering makes it easy to skim through source files to get the most
vital information first, while the low-level details come first. For instance,
dependencies should point downward. This implies that the function above
should be the caller.
Horizontal Formatting
In the same way that codes are read from top to down, they are read from left
to right as well. Hence, formatting your code horizontally is just as important
as vertical formatting. There are various aspects of horizontal formatting,
which include the length of a line, horizontal openness, density, alignments,
and so on.
Generally, as far as the length of individual lines of code is concerned, you
should try to maintain your code to be a maximum of about 80 characters
long. You may go above sometimes, but a figure about 120 characters is
simply out of order. These days, the length of the line can be affected by
factors such as the size of the screen or font size. But in all cases, you should
maintain your code at 100 to 120 characters per line. Asides length of the
line, other factors you should consider includes:
Horizontal Openness and Density
Like vertical openness, horizontal openness can be a great way to show the
association between various parts of your code. At the same time, limiting
openness is a perfect way to show weak relationships between various things
withing your code. Here are some tips about openness that you should keep
in mind when writing code:
One the file level, class declarations, and other statements are to be
left unindented
Methods inside a class are expected to be indented by one level to
the right
Indent the implementations within methods one level to the right
of the method.
For blocks of code withing another block, they should be indented
one level to the right of the block that contains them.
The importance of indentations like this cannot be overemphasized. This is
practically what makes your code readable at all. It is easy to see the structure
of an indented file you can easily skim through and skip parts that are not
relevant to what you are looking for. You know the new method declarations,
variables, and classes are on the left. It also easier to find variables, accessors,
constructors, and methods faster.
Work With Your Team
Every programmer has his/her own way of formatting code. If you are
working alone, you are free to set your own rules and follow them. As long as
you maintain consistency, your code should still be neat and organized.
However, since most jobs require you to work as a team, the formatting style
to be used by every member of the team should be set by the team. In a
program with multiple authors, coordinating with the team and coming to an
agreement on the right formatting style will ensure that your code remains
consistent and organized.
Prior to coding, the team should decide on simple formatting rules like the
indentation style, where braces would be used, how classes and variables will
be named, among other things. To make things easier, these rules can then be
encoded into the IDE to ensure that everyone complies with it.
Conclusion
Clean code is one that reads smoothly. To achieve this, the programmer must
learn to write with a smooth and consistent style that will make it visually
convenient for the reader to follow your code. Since any good software is
made up of multiple documents, this formatting style should be maintained
across all the documents to keep it the same at all times. This should be
maintained whether you are working on your own or with a team of
developers.
Chapter 7: Data Structures, Objects and the Law of
Demeter
Bad codes dont happen by accidents. Codes dont just get messy with the
wrong stroke of a key or any other unfortunate error of that sort. Usually, bad
code is the product of the choices you make while writing codes. All the
points and techniques mentioned so far in this book are essentially the
choices you make while writing your code. Some choices are clear, cut, and
open. It is easy to see why you should make them and why you shouldnt.
Others arent quite as clear. In this chapter, we will discuss yet another
important choice that you will most likely have to make while writing our
code. The choice between objects and data structures. It is important to
understand how each of these is used and when to use them.
Data Abstraction
Data abstraction is the process of reducing a particular body of data to a
simplified version, which still represents the whole. The process itself
involves removing certain characteristics of code and stripping it down to the
only essential features.
This is one of the first and most important steps in designing any system or
database. It is essentially a simplified specification of any entity. Abstraction
is an important vital way to conceal information while coding. This way, only
the relevant information is available to any programmer working on the code.
This makes it possible to transform a complex data structure into a code that
is simple and easier to use.
Any data type is made up of two things; properties and methods. The
properties and methods can be either private or public. In abstraction, only
public properties and methods are shown. They are expected to cover all the
possible functionalities of the data.
Keeping variables private is important. You dont want someone else to
depend on them. This privacy also comes with a level of freedom. You are
free to change the type of the variable or how it is implemented when you
keep them private.
Hiding the implementation of your variable isnt merely about adding a layer
of functions between variables. It is all about expressing your data in abstract
terms. Many developers make the mistake of pushing their variables out with
getters and setters. This is a common but bad practice. Exposing abstract
interfaces that still allows the user to manipulate the essence of the data
without revealing the implementation is a much better approach.
Objects Vs. Data structures
There is a significant difference between data structures and data. In objects,
data is hidden behind abstractions while the functions that operate on this
data is exposed. This makes it easy to add new objects to code without
changing the already existing behaviors. Conversely, it is difficult to add new
behaviors to an existing object.
Data structures, on the other hand, keep their data exposed, and they do not
have any meaningful function. This is why they have no significant existing
behavior. Hence, it is easy to add new behavior to data structure but difficult
to a new data structure to an existing function.
From these definitions, the asymmetric nature of objects and data is quite
obvious. Given the fact that these two are opposite, developers have to decide
on which one to use in any given scenario. While many developers seem to
be convinced that objects should be the main tool in software development,
not everyone is convinced of that. So how do we make the distinction and
decide on which one? Its all about understanding object-oriented coding and
procedural coding.
Procedural Code and Object-Oriented Coding
While many programmers favor object-oriented coding, if you critically
consider the nature of objects, it is easy to see why using data structures will
be a better alternative in some scenarios. This is usually the case when you
need structures that you can easily manipulate procedurally.
For instance, adding new functions to an object may be a little problematic.
To do this, you may have to modify all the objects of the same type within
the code. Of course, this isnt a problem you will experience in procedural
coding.
In procedural coding, it is easy to add new functions without altering the data
structure already in place. For object-oriented coding, you can easily add new
classes to the code while leaving the existing functions in place.
Adding new data structures is hard in procedural coding. You will have to
change all the functions to achieve this. Similarly, adding new functions is
difficult in object-oriented coding because you have to change all the classes.
So how do you decide when to choose between object implementation or
procedural coding. Here is a simple rule to follow.
1. If you expect to add more functions to your code with time while
retaining an unmodified data structure, it is recommended that you
choose procedural coding.
2. If you are expecting to add new types to your code in the future,
but you dont intend to add any new functions later, then you
should go with the object-oriented approach.
Law of Demeter
As earlier explained, an object is expected to keep data hidden while
exposing functions that operate on this data. However, some programmers
may make the mistake of exposing the internal structure of their data through
the use of accessors. When they do this, they are breaking a well-known law
known as the law of Demeter, and this is bad practice.
This law states that a module should not know the innards of an object that it
manipulates. According to the law of Demeter:
A function f of class C should only call the methods of C, an object created
by f, an object pass as an argument to f, or an object held in an instance
variable of C. The function should not invoke methods on objects that are
returned by any of the allowed functions Talk to friends, not strangers.
To put this law in simpler terms, imagine you have an object A which
requests the service (calls a method) of an object B. This is perfectly fine.
However, the object, A, is not allowed to reach through the object, B, to
request the services (call a method) of another object, C. This is because
reaching through B to get to C this way will require object A to have a
greater knowledge of the internal structure of the object B.
Rather than do this, the law says that you can either modify the interface of
object B to serve the request of object A directly or have the object A make a
direct reference to C to make its request. By following this law, the internal
structure of object B will only be known to that object.
Take the example below, for instance:
let outputDir: String =
context.getOptions().getScratchDir().getAbsolutePath()
The code calls getScratchDir()to get the return value of the function
getOptions()before calling the function getAbsolutePath().the law of Demeter
is being broken already. To make your code cleaner, you may decide to break
up the code like this.
let options: Options = context.getOptions()
let scratchDir: File = options.getScratchDir()
let outputDir: String = scratchDir.getAbsolutePath()
Even in this state, the law is being broken (sort of). This is because the
containing module now knows that options are contained within the function
context. Inside options, there is a scratch directory, which has an absolute
path. The function already knows too much about the object it calls (if
context, Options, and ScratchDir are objects). If they are data structure, then
this code is absolutely fine since there is no data to expose. But if they are
objects, then the internal structure which should be hidden is already
exposed.
The code above can be modified this way:
let bufferedOutputStream: BufferedOutputStream =
context.createScratchFileStream(classFileName).
Dont Create Hybrids
As an additional rule, when working with objects or data structures, you
should avoid creating hybrids. Hybrids are extremely difficult to work with.
They are partly objects and partly data structures. This introduces several
unique problems that are difficult to deal with.
Hybrids have functions that have no significant purpose. They also have
public variables or public accessors and mutators. This not only makes it
difficult to add new functions but adding new data structures is made harder
as well.
Chapter 8: Error Handling
Ever heard the saying, if anything can go wrong, it probably will? Thats
Murphys law, and its a reality almost every programmer can relate to. Error
handling is an inevitable part of programming. Devices fail, and inputs may
be abnormal sometimes. Knowing that theres a lot that can go wrong when a
program runs, programmers have the responsibility of ensuring that the code
they write does what it has been designed to do.
But how is this connected to writing clean code? Since error handling is an
inevitable part of programming, it only makes sense that you learn the proper
way to handle them. Programmers who have not mastered error handling the
clean coding way may end up with programs that have error-handling code
scattered all over them so much that it makes it difficult to figure out the
logic of the code. This is a way we will be going over the form of the best
clean code techniques that help you handle errors neatly.
Use Exceptions Instead of Return Codes
There was a time where most programming languages did not have
exceptions. Back then, to handle the error, you would either have to return an
error code in which the caller would check or set error flags in your code.
While this worked, it was limited in several respects. Handling error this way
also led to a lot of clutter in the caller. The calling function would have to
check the code for error after each call. This is quite easy to forget.
These days, a better and more cleaner alternative is to use exceptions. By
making the caller throw and error whenever an error is encountered, you can
write cleaning call functions whose logic is not cluttered by how it handles
the error.
De-cluttering functions this way offers several advantages, and they are not
just about the beauty of our code. The logic is also easier to follow when the
errors are better highlighted, and error handling is separated with exceptions.
Write Try-Catch-Finally Statements First
When writing codes that are likely to throw an exception, it is best, to begin
with, a try-catch-final statement. Following this rule helps you to easily
define what would happen no matter what error occurs during the execution
of the code. Try blocks are similar to transactions and should be treated like
that.
You should write them in such a way that the catch always leaves your code
in a consistent state despite what happens in the try statement. But when you
execute code within the try part of your try-catch-finally statement, you are
indirectly stating that the code execution may stop at any point only to
resume later at the catch.
When writing codes, try to write tests that will force exceptions.
Subsequently, you can add behavior to the handler that will satisfy the test
you have written. This will make it easier to build the try blocks transaction
scope first then build up the rest of the logic with TDD. The logic should be
added in such a way that it can pretend that nothing goes wrong in the code.
Use Unchecked Exceptions
There was a time when checked exceptions made a lot of sense. In the early
days of java, every method would have a list of all of its possible exceptions
that could be passed to its caller in its signature. These exceptions were also
included in the type of the method such that the code wouldnt compile if the
exceptions in the signature do not match those in the code. While this is great
and offers some unique benefits, many programmers are starting to see that it
is not necessary to build a robust software
In fact, most new programming languages dont have checked exceptions at
all. C# and C++ do not have checked exceptions. Similarly, Python and Ruby
do not have it either. The fact that one can still build robust software in these
languages using unchecked exceptions puts to rest the argument about the
relevance of checked exceptions.
The Price of Using Checked Exceptions
Although checked exceptions offer some benefits, many programmers are
now coming to terms with the fact that it comes at a high price. Using
checked exceptions violate an Open/Closed principle. Check exceptions may
not be a problem if the block throwing the code is right below the block
catching it. But this is rarely the case. In most instances, you may have the
block catching the exception several levels above. In this case, you will have
to declare the exception in the signature for each method in between the
throw and the catch block for the code to compile.
Checked exceptions break encapsulation. This is a major problem if you
consider the fact that most large codes are made up of complex hierarchical
systems. Functions at the top of the code call the ones below, and those ones
call even more functions at lower levels. Thus, if a low-level function is
modified to throw a checked exception, your code will fail to compile until
you have added a throw clause to the signature of the function. You will also
have to modify every function above to either catch the exception or add the
throw close to their signatures. These changes must be made throughout your
code from the lowest level, where the exception was thrown to the highest.
If you are building general applications, you are better off using unchecked
exceptions than checked. The cost simply outweighs the benefit. Checked
exceptions may only be beneficial if you are building a critical library.
Provide Context with Exceptions
When you throw exceptions, you will make life easier for anyone reading
your code if you provide some context about the source of an error. Hence,
you should create error messages that are informative and provide context to
your exceptions, which you can pass along with them. In Java, a stack trace
does this partly by making it possible to trace the source of an error. But it
does not tell you the intent of error. This is why you should add error
messages that mention the type of failure, where it failed, and why it did.
Passing along sufficient information like this will make error logging in the
catch a lot easier.
Using the Special Case Pattern
By following all the error handling instructions highlighted so far, you should
be able to separate the logic of your code from the error handling. The
external APIs will be wrapped to allow you to throw your exception. Then to
deal with aborted computation, you define a handler above the code. This
will make your code look neat and organized.
However, doing this also pushes error detection to the edge of your program,
and this is not always a good thing. There are times where you may not want
to abort your code. In such cases, the highlighted approach may not work. A
better alternative would be to use what is known as a special case pattern.
This involves creating a class or configuring an object which handles special
cases for you. When you take this approach, the client code does not need to
deal with exceptional behavior anymore. Instead, the behavior will be
encapsulated within the special case object.
Consider the example below:
We can modify the code such that the ExpenseReportDAO will always return
a MealExpense object. This MealExpense object then returns the per diem
total if the meals are not expensed. The code will now look like this:
While discussing error handling, we must mention some of the subtle ways
that we may inadvertently invite errors into our code. One of such seemingly
normal actions that may invite error is returning null. This is a fairly common
practice for some programmers.
Putting in a null reference can lead to several errors, system crashes, and
vulnerabilities in your code. This will make error handling a lot more difficult
both now and in the next few years when someone else is taking a look at
your code.
A lot of programmers use null to indicate that a return value is absent. While
this seems like normal standard practice. It isnt as good as it seems. In fact,
there are other better alternatives to returning a null that will make your code
cleaner but more importantly make it less error-
prone
When writing code, you can assign a null value to any object reference to
indicate that it points to nothing. When such a method with a null reference is
called, a NullPointerException will be thrown, as in the example below.
In this case, any static that is uninitialized and the instance member of the
reference type will return a null pointer exception. The example below
illustrates this better.
Almost every line on this code has a check for null. Although returning null
isnt a problem on its own, you are creating extra work for yourself and even
more issues for your call functions when you return null this way. Everything
may seem fine until a missing null check sends your application way out of
balance. So what do you do instead of calling null, here are some examples:
Return an Empty Collection
If the method is meant to return a collection class, we can substitute the null
exemption for an empty collection. Typically, there is a static method for this
purpose in the collection class.
return Collections.emptyList();
Or
return List.of();
In both instances, an immutable list will be returned such that the calling
code will not attempt to make any modifications.
Return an Optional Object
Another option is to use a class known as java.util.Optional, which was
designed to solve some of the problems with null values specifically. An
optional object class is a container that can either contain a non-null value or
be empty. This serves as a limited mechanism for library method return types
in instances where using null to represent no-result was likely to cause errors
to occur.
The class Optional.empty() can be used to represent an empty container while
Optional.of(value) can be used if there is an actual value to be represented, as
seen in the example below.
In this case, the null check has become abstracted away and is now enforced
by the type system. As with the example above, if the optional object is
empty, then nothing will be printed.
Return a Null Object
The Null Object pattern was designed as a way to identify expected behavior
when a null is encountered and encapsulate it with a static constant.
Expanding the previous Blog class code above, we have
In line 2 of this code, a constant has been declared, which represents the null
object. In this case, it is a Blog object with assumed sensible value for a blog.
Any method which returns Blog type will now use Blog.NOT_FOUND
rather than return null.
Throw an Exception
Many programmers are scared of throwing exceptions. It is not unlikely that
this fear of throwing exceptions stems from an expectation to validate user
input and recovering them elegantly if the data passed was invalid. If not
handled right and one of the values being used is in a null state, this kind of
code ma pollute your code logic and leave with a host of illogical codes.
However, it is important to note that throwing an exception is normal practice
when you have exceptional circumstances. If you always expect to find
values, then it makes perfect sense to throw any exceptions. Basically, the
decision to throw an exception if there is nothing to return depends on the
nature of your application. For instance, you may throw an unchecked
exception if the ID that is passed to your method does not exist in our
database.
You can easily substitute the code above with something like the code below
using proper exception handling at higher levels.
To wrap up this chapter, bear in mind that clean code is not only readable; it
should also be robust. You may not be able to achieve either of these goals if
we combine error handling with the main logic of our code. Instead, it should
be considered independently and handled so. How much you can do this will
go a long way to determine how maintainable your code will be in the future.
Chapter 9: Understanding Boundaries
Sometimes in programming, we make use of third party codes or open-source
software to add more functionality to a program and reduce the time spent on
coding. In some cases, it could be components built by other teams within the
same company. While maintaining clean coding principles for your own
program is important, you must also learn techniques for integrating foreign
codes into your system without compromising the organization and integrity
of your code. This is why learning about the concept of boundaries is
important. In this chapter, we will go over some of the standard clean coding
practices that will help keep your software clean at the boundary between
external pieces of code and third-party software.
What Are the Boundaries?
Boundaries are points withing your code where it meets code that has been
written by others. No matter how well-written your code is and how much
you have expended to keep it organized, the fact is that a boundary is an
unpredictable area. You have little or no control over how this part of your
code will behave especially if you are not entirely sure of what the third party
code you are adding does.
Why are Boundaries so Important?
Usually, third party packages are built in a way that makes it easy to plug
them into multiple frameworks. While this is great, this multi-applicability
introduces a range of new problems to you as a programmer when you
consider how your own users will use the framework you intend to build. It
isnt uncommon for third party packages to have plenty of hidden capabilities
that could be a liability to you in your code if not handled properly. Sure it
will serve its intended purpose, but such liabilities can also cause problems in
your own code later on.
Generally, it is recommended that you avoid passing a third party object
around within your own system. Do not return a function from it or accept it
as an argument to your public APIs. If you will be using a boundary
interface, it is best wrapped within a class or inside a close family of classes
where your system will make use of it.
Handling Change at Boundaries
Another thing a software with a clean code should be able to do well is adapt
to changes in the external systems that you have no direct control over. This
is best done at the boundaries of your code with such external codes.
Most times, changes in third party programs are done with little consideration
about how the integrating systems will be affected based on how the program
is being used. The responsibility is on you as the developer to write your code
in a way that it adapts to such changes neatly.
At the boundaries of your code, there should be clear separations and unit
tests that make it easy to define what is expected of your code. It is
recommended that you buffer code boundaries using adapters. An adapter
will help concert your own interface to the provided interface of neatly. This
way, your dependency on external software is not too widespread. Thus,
when changes occur in the external codes, you have very little changes to
make within your own code.
Limiting dependency this way is essential since you have little or no control
over the external code. This can be achieved by having very few places in
your code that make reference to them. Future maintenance of your code will
be easier this way, and you will also be able to better adapt to changes in the
external subsystems.
Learning Boundaries
Another issue with using external code is that it typically has a steep learning
curve. Lets say you want to utilize a third-party package that you are not sure
of how to use. Then you have to spend days reading through the
documentation to get an idea of how it works and the right way to use it.
But this doesnt even guarantee that your code will work as designed. You
may write the code that uses this third party component only to run into bugs
and issues when you attempt to integrate both systems. Now you have to
spend hours or days trying to figure out if the bug is from your own system,
the external system, or how you are implementing the code.
Using this approach, learning and integrating boundaries can be very difficult
work. A much better approach is to write tests that explore your
understanding of such codes based on its intended usage. Such tests are
referred to as learning tests.
What are Learning Tests?
To explore how much you understand a third-party code, you can set up a
learning test. This helps to save time that would have otherwise be spent on
experimenting on the production code. With a learning test, you can call a
third party API the same way you expect it to work in your application. This
controlled experiment will help check how much you understand the API
without spending so much time on studying the boundary documentation.
For instance, if you have a new third party code to test, after downloading
and opening the software, you can write a test case based on the intended use
of the API to quickly test it without doing too much reading on the
documentation page. When you run your test code, if it produces an error,
you can read to the documentation further to figure out the error then create
another test to check your knowledge.
Learning tests help you to learn a great deal about how third party code
works, and you can easily encode what you have learned into simple unit
tests. This is a simple and isolated way to gain knowledge, and it costs
nothing. It is a series of precise experiments that boosts your understanding
of third party code for free and see if it does what you expect it to do.
Learning tests is also important in handling new releases of third party
packages that you have been using before. When you install an update of
third-party code, you can run the learning tests to see if there are any
behavioral changes in the code before integrating it into your program. Once
integrated, it may be difficult to see if a third party code is still compatible
with your program based on what you need it to do. Authors of third-party
packages typically make changes and fix bugs based on their own needs.
These changes introduce a lot of new risks to codes making use of such
packages unless you figure out a neater way to tests the compatibility.
How to Use Code That Does Not Exist Yet
While coding, we cannot assume that we know everything that there is to
know. A large project typically consists of various systems and subsystems
that integrate and interact with each other. In many cases, these systems are
built by various teams whose work might not be clearly known to the other.
The result is that you get boundaries whose other side is unknown. Such a
boundary with the unknown is difficult to handle cleanly.
For instance, consider a team developing software for a communication
system, one of the subsystems that will be needed for the code to work is a
Transmitter and the team responsible for building this transmitter had not
defined the interface of their work yet. In order not to hold up the project, the
main team can proceed with the main system without knowing how the
transmitter code would work.
They simply need to be aware of what they want the unknown code to be to
their own systems. This will help define a clear boundary. Even if you have
no idea how the transmitter would work or what the interface would look
like, you can define your interface and decide the things you expect it to do
within your code. You may give your interface a catchy name that clearly
defines what it does in relation to the expected API, and create a method that
serves its purpose.
By designing the interface, you hope to ensure that a large part of the
unknown is still under your control. This not only ensures the readability of
your code but also helps to keep it focused on what you intend to have it do.
Also, since your transmitter API, which has not been built, is out of your
control, you should insulate the classes of your own code from this API.
Once the transmitter is defined, you can then proceed to write an Adapter,
which will serve as a bridge at the code boundary. The adapter will
encapsulate the interaction with the API at the boundary and keeping with
what we have discussed earlier; it also provides a single point of change if the
API is modified in the future. Finally, you can also set up a boundary
learning test code for the API. This ensures that the API is being used
correctly in your code.
Chapter 10: Writing Clean Unit Tests
Unit tests have grown in relevance since the early days of Test-Driven
Development. Before TDD, unit tests were nothing but short bits of code that
were written to confirm if the program worked as intended. It was typically
some dispensable ad hoc code that would be written after the classes and
methods and other parts of the code had been painstakingly put together.
The unit test is a way to interact with the program after it has been written
manually. These days, thanks to the TDD and Agile development approach,
writing unit tests have become the norm. Programmers now make it a point
of duty to write tests to ensure that every part of their code performs as
expected.
Typically, bits of code will be isolated from the operating system and set up
tests for them. Once the code passes a suite of tests, it is the responsibility of
the programmer to ensure that anyone else who would be working with the
code in the future can run those tests conveniently as well. Both the tests and
the code will then be checked into the same source package.
However, while writing unit tests have become quite popular, not all
programmers are doing it right. In a rush to add tests to code, it isnt
uncommon to find tests that are missing the points of writing good tests.
What Makes a Good Unit Test?
Before diving into some of the rules and principles for writing good unit
tests, we must identify and discuss some of the properties of good tests.
Easy to Write: as a developer, you will typically have to write a lot of tests
that cover all the different parts and cases of your applications behavior.
Hence, it should be easy to write such test routines without expending too
many efforts.
Readable: clean code should always be readable, and this applies to unit tests
as well. The intent of the test must be clear. Like your product code, the unit
test is expected to tell the story of how a part of your application behaves.
The scenario being tested should be easy to understand. Part of readability is
also being able to detect and address the problem if a test fails easily. A good
unit test should make it easy to fix a bug without necessarily debugging the
entire code.
Reliable: a unit test is expected to fail only if there is a bug in the system that
is being tested and not otherwise. While this is a simple and obvious rule, it
isnt uncommon to find programmers that right tests that fail even where there
is no bug in the system. For instance, some tests may pass when they are run
individually but fail when the whole test suit is run. There are also cases
where tests will pass on the IDE only to fail on the integration server. Such
instances are indicative of a flaw in test design. A good unit test is expected
to be reproducible despite changes in external factors like the running order
or development environment.
Fast: developers are expected to write tests that allow them to check for bugs
repeatedly. If such tests are slow, a developer working on them will likely
prefer to skip them to save time. Otherwise, you lose precious time trying to
run the tests. However, slow tests are not always a fault with the tests itself.
Sometimes, it just means that the system under test interacts with the external
system in a way that slows it down. This is not good as well as it implies that
the test may be environment-dependent.
It Must be Truly Unit: unit tests are not integration tests. This should be
clear from the design of the test. A unit test and the system it is used to test
are not expected to have access to the network resources, file systems,
databases, and so on.
Why Should You Write a Good Unit Test?
Perhaps the most important rule for writing good unit tests is to treat your test
codes just as good as your production code. Unit tests are just as important as
production code. Most developers make the mistake of treating test codes as
second-class citizens. Most programmers favor the Quick and dirty approach
to save time on writing unit tests and get more done. This is, in fact, bad
practice. Unit tests require just as much care in thought and design. You must
keep it just as clean as you keep your production code.
Of course, this is not as easy as it sounds. Writing a clean unit test code is
difficult in practice. But you must uphold the principles of clean tests. This is
because writing dirty tests is just as terrible (if not worse) as writing no test at
all. As your production code evolves, your test code is expected to change as
well. If your test code is dirty, making such changes will become increasingly
difficult, and, as the production code gets new modifications, the test code
will become more tangled. To keep up, you might have to cram new tests into
the suit to get the tests to pass. This will make the code messier even messier
until it becomes a complete liability.
The consequence of not keeping your tests clean is that you will eventually
lose them. As the test code becomes more tangled, you may have to let them
go entirely. When you do this, you also lose the very thing that helps to keep
your code flexible, reusable, and maintainable. With tests, making changes to
code is easy, but without them, you cant make changes to your code without
expecting something to break somewhere no matter how flexible the design
of your code is. Dirty test hamper your ability to improve your code, and this
eventually leads to rot.
How to Write Clean Unit Tests
Writing good unit tests is difficult, but as we have pointed out, you must
learn how to write them and so do cleanly or risk losing your entire code.
Here are some tips that can guide you in writing simple, sharp, and clean unit
tests at all times:
Arrange: Set up tests. At this stage, you should only write code
that you need to set up a specific test. This is the stage where
objects and mock setups are created, and potential expectations of
the test are setup.
Act: the act stage is where the invocation of the method being
tested takes place. Here you run the function under test.
Assert: here, you check if the expected behavior occurred or not.
Following the pattern above ensures that it is well structured and easier to
read.
Take a look at the example below:
public void testMyFunc_updatesBar() {
Bar bar;
Bar expectedBar = makeBar().setBuzzer(3);
myFunc(bar);
assertThat(bar).isEqualTo(expectedBar);
}
The code is a bit confusing despite being just four lines because the AAA
pattern is not followed and separated. The purpose of expectedBar declared at
the top is not known. It is unclear if it will be used in the myFunc() or not. It
is also difficult to tell when the setup finishes and when the tests start to run.
We can separate these three steps in the code above to clear up all the
confusion. Now we have:
public void testMyFunc_updatesBar() {
Bar bar; myFunc(bar); Bar expectedBar = makeBar().setBuzzer(3);
assertThat(bar).isEqualTo(expectedBar);
}
Following this simple pattern, no major debugging or refactoring is required
to implement the code. This structure affects the readability and
maintainability of your test suits and makes it easier to format the test units in
the present and much later in the future.
Chapter 11: Classes
Writing clean classes is one of the essential clean coding techniques to learn.
Paying attention to your classes and how they are organized is just as
important as writing single lines and blocks of codes. This is a higher level of
code organization that you must pay attention to keep your code orderly and
expressive.
Class Organization
There are standard rules and conventions for organizing classes while coding.
Following these conventions will help keep your code clean. Here are some
simple rules of class organizations to follow.
Client-Based Locking: you can make the client lock the server
before the first methods are called. Also, ensure the lock covers
the code calling the last method.
Server-Based Locking: another alternative is to create a method
to lock the server then calls the method before unlocking again.
Creating An Adapted Sever: an adapted server can serve as an
intermediary to lock the main server. This is done when the
original serve cannot be modified.
Keep Synchronized Sections Small
It is recommended that you keep the synchronized sections of your code as
small as possible. One way to introduce a lock into your code is to make use
of a synchronized keyword. At any given time, all sections of the code having
the same synchronized keyword will have a single thread executing through
them. Such locks create delays and add some overhead to your code. Hence,
you want to limit them as much as possible. Although it is important that you
keep critical sections 9sections of your code that must be protected from
simultaneous use) of your code guarded, having a code with as few critical
sections as possible is important as well. A common mistake programmers
make is to make critical sections very large to accommodate the synchronized
statement. This will only degrade the performance of your code.
Writing Correct Shut-Down Code
The nature of the shut-down code makes it difficult to write. Designing a
system that is only meant to work then shut down on its own after a while is
not easy. It can be difficult to pull off. One of the problems with writing
shutdown code is the issues of deadlock. i.e., threads may never receive a
continuous signal.
For instance, if you have a parent thread that produces several sub-threads,
then waits for them to finish running before finally releasing its resources and
shutting down. If one of the spawned threads fails to send a signal, the parent
thread will be unable to shut down forever.
A similar problem may occur in a shutdown system with children threads that
have a consumer-producer pair as one of the sub-threads. When the parent
thread sends a signal for all the children to finish, the producer code will
receive the signal and shut down immediately without sending the signal to
the consumer thread. The thread is stuck waiting for a shutdown message that
never comes. This will prevent the parent thread from shutting down as well
Hence, given how common situations like the ones described above are, it is
recommended that you think about shutdown code early on in your project.
Be prepared to spend a lot of time getting the code to shutdown correctly and
as planned.
Testing Threaded Code
Writing tests for threaded code is difficult. This is because testing only
minimizes risks. It does not guarantee the correctness of code. Things are
even more complex when you are working with multi-threaded codes with
one or more threads sharing the same data. They are a lot more complex to
deal with. You should write tests that can potentially expose problems in your
code and run them frequently as you code. If such tests fail, you can easily
track down the source of the failure and fix it until the code passes the test.
Chapter 13: Clean Design Rules
In this chapter, we will discuss some of the general techniques that you can
employ to keep your entire code base as clean and user-friendly as possible
by focusing on emergent code design. You can write simple and expressive
designs by following Kent Becks rules of simple design. These rules help to
create good and functional design. It can also help you gain insights into the
structure of code. Following these rules will also make it easier to apply some
principles we have discussed earlier in this book, such as SRP and DIP.
Kent Becks Rules of Simple Design
According to Kent Beck, a design can be said to be Simple if it agrees with
the following rules:
1. It Runs All Tests
One of the prime purposes of every design is to produce a system that works
as intended. This is not merely about having a system that looks good on
paper. There must be ways to verify if the system you have built actually
works or not. If it doesnt, then all your design effort is a mere waste.
A testable system is one that has been subjected to various comprehensive
tests and passes the tests at all times. If your system cannot be tested, there is
no way to tell if it performs as intended or not. Hence, such a system should
never be deployed in the first place.
Another advantage of a testable system is that it pushes our code towards a
design with small singular-purpose classes. Classes that align with the single
responsibility principles are easier to tests. Writing more tests will eventually
push your code into more simplicity. Hence, it is safe to conclude that
making sure your systems is testable is one of the key ways to create a better
design.
Testing code also helps to minimize coupling. Codes with tight coupling are
difficult to tests. To write more tests on coupled code, you will have to apply
principles and tools such as dependency injection, abstraction, interfaces, and
so on to reduce coupling in the code. All of these will further enhance the
design of your code.
Tests empower you to keep your code and its classes clean by incrementally
refactoring the code without fear of breaking anything. When a new line of
code is added, you should pause and reflect on how the change will affect the
design of your code. If the new change degraded the code, then you should
clean it up.
Tests should be run continually to show that nothing has been broken in the
code. While refactoring, you can apply any of the techniques you have
learned so far in this book, such as cohesion, separating concerns, shrinking
classes and function, decoupling, changing names and choosing better ones,
and so on. Tests also make it easier to apply the three other rules of simple
design that will be subsequently discussed in this chapter.
2. Contains No Duplication
One of the major hindrances that can affect a system is duplication.
Duplication introduces additional risks, more work, and complexity to your
system. Duplication can occur in various forms. The commonest and easiest
to spot is lines of code that are exactly alike. But duplication can occur in
implementation as well. To keep your system clean, you must learn to avoid
and eliminate duplication even in a few lines of code.
Extracting commonality even at the lowest levels makes it easier to recognize
places where the single responsibility principle is being violated. When this is
the case, a newly extracted method can be moved into another class. This
helps to increase the visibility and overall readability of the code. This will
also make it easier to reuse the new smaller methods in a different context,
which can reduce the complexity of the code dramatically.
3. Expresses the Intent of the Programmer
Good code is expected to clearly express the intent of its original author
without causing confusion. Writing expressive code takes time and care. But
making your code clear manes less time will be spent on trying to understand
what the code says.
If you have ever had to deal with convoluted code, it is easy to see why this
rule of keeping code expressive is important. At the time the programmer was
writing the code that you are currently finding difficult to understand, it was
easy for him or her to understand what the code does. This is because, at the
time of writing code, you have a deep understanding of the problem you are
trying to solve. It may surprise you that you will experience some level of
difficulty trying to unravel a convoluted code you wrote yourself some
months or years after.
If the code is not expressive enough, other people trying to maintain your
code will have the same issue too. The implications for code maintenance are
enormous. Any change is accompanied by the risk of creating potential
defects in the code, and more time will have to be spent to understand what
the code does. There is also the risk of getting misunderstood. All of these
reasons further support the need to write expressive code whose intent is
clear to others apart from the original author. The more expressive code is the
less time and effort that will be spent on understanding it.
Some of the ways to make code expressive include:
1. Inappropriate Information
Comments are meant to hold technical notes relating specifically to the code
design. It should not hold any irrelevant or inappropriate information.
Comment should not hold any information that can be better held in another
documentation system. For instance, some pieces of information are better
held in record-keeping systems such as your issue tracking system, source
control system, and so on. Such information will only overcrowd your code if
included in your source file. Hence, information such as meta-data (e.g.,
authors, SPR number, last modified date, etc.) should not be included in your
min comments.
2. Obsolete Comment
Keep obsolete comments out of your code as they tend to become a floating
mess of irrelevant information that will confuse a reader. Obsolete code is a
common challenge to writing clean code since comments tend to get old quite
quickly and will float away from the code they once explained. Check your
code regularly for comments that have become old and irrelevant. When you
find such comments, you can either update them with the correct information
or simply remove them entirely.
3. Redundant Comment
Redundant comments are useless as they describe things that already describe
themselves sufficiently. You should avoid adding comments just for the sake
of it. If your code already says enough about itself, adding a comment that
says nothing more (or even less) is only a waste of space and will only crowd
your program.
4. Poorly-Written Comments
Comments are not recommended. But if you will be writing them at all, then
you should take care to write a good one. Ensure that every comment you add
to your code is written in the best way you can. Your comment should be
grammatically correct with the right punctuation. Be careful with the words
you use and ensure that the information you are trying to communicate will
be clear to anyone reading the code in your absence. Dont ramble and keep
the comment as brief as possible.
5. Commented-Out Code
There are few things quite as annoying as commented out code. It drives any
developer crazy as you are left wondering if the commented out code has any
relevance to the code or if there are any plans for it. With each day that
passes commented out, code becomes more irrelevant using variables and
calling a function whose names no longer exist in the code. The rule for
dealing with commented code is simple. Just delete it! Get rid of it
immediately. The good thing is that the source code control system will still
remember it, so if anyone needs it later, they can always find the previous
version here.
Rules for Build Environments
6. Avoid Encodings
Encodings are problematic, and that is why they should be avoided. There is
no need to encode names with scope or type of information. m_ or f prefixes
have become redundant in todays coding environment. Project and subsystem
encodings have become redundant as well. Adding such encoding to your
names will only distract your reader, and they only pollute your code.
1. Insufficient Tests
What is the ideal number of tests in a test suite? Most developers simply use
their discretion and stop when they feel the tests seem to be enough. But this
shouldnt be so. Your test suite is expected to test everything that could break
in your code. Your tests are still insufficient if there are conditions in your
code that are yet to be explored or calculations that are yet to be validated.
2. Use a Coverage Tool
The purpose of a coverage tool is to report gaps within your testing strategy.
With a coverage tool, it is easier to find modules, functions, and classes that
have not been sufficiently tested. Fortunately, most IDEs come with visual
indicators for this already. A green marking line represents covered code,
while red ones indicate unchecked code.