Programming and Good Programming
**_“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”—Martin Fowler_**
It works, isn't that good enough?
We're developers -- we write software and do a lot of programming. We know how to make code that works. We can even write code that works well. In fact, there are many who might say:
“My code is working well, the website I built is looking great, and my client is happy. So why would I still care about writing clean code?”
Clean Code Shows We Care
As developers, it's our goal to not just program, but to program with intent and care. Well written and cared for code can help us, and others, down the road. Well written code can speak for us when we aren't there to explain it.
Writing clean code will go a long way towards improving time management, advancing your career, and helping you become a better developer.
What is Clean Code?
-
Elegant — Clean code should be pleasing to read. Reading it should make you smile the way a well-crafted music box or well-designed car would.
-
Focused — Each function, each class, each module exposes a single-minded attitude that remains entirely undistracted and unpolluted by the surrounding details.
-
Cared For - Someone has taken the time to keep it simple and orderly. They have paid appropriate attention to details. They have cared.
-
Tested - It is high quality. It passes all the tests.
-
Simple - It contains no duplication and minimizes the number of entities such as classes, methods, and functions.
But, Really...Why Should I Care?
-
Less Wasted Time- You may understand how your code works now, but you probably won't in a few weeks. Messy code takes longer for you and your teammates to read and understand.
-
Less Documentation- If your code is clean there is minimal need for comments or extensive documentation to explain it.
-
Bugs Can't Hide- Messy, spaghetti code gives bugs places to hide. Clean code allows you to debug faster.
-
Maintainable- In the future your project will probably evolve and features will be added and changed. Clean code makes maintenance faster and easier, and therefore, less expensive.
-
Pride- You will feel good about producing quality work, and your client or employer will appreciate it.
SOLID Design Principles
SOLID is a term describing a collection of design principles for good code that was invented by Robert C. Martin, also known as Uncle Bob. It is intended to make software design more understandable, flexible and maintainable.
SOLID means:
-
Single Responsibility Principle
-
Open/Closed Principle
-
Liskov Substitution Principle
-
Interface Segregation Principle
-
Dependency Inversion Principle
Single Responsibility Principle
- A class should only have a single responsibility.
Every class should be reponsible for one thing, and that thing should be entirely contained within that class. Classes should have one reason to change.
Open/Closed Principle
- Software entities should be open for extension, but closed for modification.
In other words, we want to be able to change what the entities do, without changing the source code of the entity. How do we do this? With abstractions.
Liskov Substitution Principle
- Subclasses should be substitutable for their base classes.
The user of a base class should continue to function properly if a subclass or derived class is passed to it.
Interface Segregation Principle
- Many client-specific interfaces are better than one general-purpose interface.
A client should never be forced to implement an interface that it doesn't use or clients shouldn't be forced to depend on methods they do not use.
Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
This strategy states that modules should depend on interfaces or abstract functions and classes, rather than concrete functions and classes. Why? Because concrete things change a lot and abstract things change less frequently.
High level modules deal with the high level policies of the application and care little about the details that implement them, so we decouple the dependency.
How do we write clean code?
“It is not the language that makes programs appear simple. It is the programmer that make the language appear simple!” -Robert Martin
Meaningful Names
Choosing good names takes time but saves more than it takes. The name of a variable, function, or class, should answer all the big questions- why it exists, what it does, and how it is used.
If a name requires a comment, then the name does not reveal its intent. [[[ For example.
int d; // elapsed time in days.
]]]
[[[
A better name would be:
int elapsedTime;
For more clarity, you could use:
int elapsedTimeInDays;
]]]
Comments
"A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment."
If you are writing comments to prove your point, you're probably making your code harder to read at a glance. Ideally, comments are not required at all. Our code should explain everything. Modern programming languages allow ways through which we can easily explain our point.
[[[ For example, this:
// Check to see if the employee is eligible for full benefits
if ($employee->flags && self::HOURLY_FLAG && $employee->age > 65)
]]] [[[ Becomes this when the naming is clarified:
if ($employee->isEligibleForFullBenefits())
]]]
Classes
When we build classes there are some things we should keep in mind:
-
Classes and Objects should have noun or noun phrase names written in PascalCase, like
Customer
,WikiPage
,Account
, andAddressParser
. Avoid verbs and key words likeManager
,Processor
,Data
, orInfo
in the name of a class. -
Order Matters
- Variables, Instances, Functions, Utility methods (After functions that call them.)
-
Classes should be concise and adhere to Single Responsibility Principle
- If you can't derive a good class name, the class is probably too large.
- Multipurpose classes force us to look through code we don't need to know about.
Method Names
Methods should have verb or verb phrase names written in PascalCase, like PostPayment
, DeletePage
, or Save
.
Accessors, mutators, and predicates should be named for their value and prefixed with Get
or Set
.
When constructors are overloaded, use static factory methods with names that describe the arguments. Let's look at the TimeSpan
class in C#
[[[ Which of these time span objects represents 5 seconds?
var a = new TimeSpan(5);
var a = new TimeSpan(0, 0, 5);
var a = new TimeSpan(0,5,0);
]]]
[[[
Hard to say. How about this?
]]] [[[
var a = TimeSpan.FromTicks(5);
var a = TimeSpan.FromSeconds(5);
var a = TimeSpan.FromMinutes(5);
]]]
Pick One Word per Concept
Pick one word for each abstract concept in your classes and stick with it.
For instance, it’s confusing to have Fetch
, Retrieve
, and Get
as equivalent methods in different classes. How do you remember which method name goes with which class?
Likewise, it’s confusing to have a Controller and a Manager and a Driver in the same code base. What is the essential difference between a DeviceManager
and a Protocol- Controller?
See an example of Class, Property, and Method names below:
class VirtualPet
{
public string PetName { get; set; }
public string Type { get; set; }
public int Health { get; set; }
public int Hunger { get; set; }
public int Boredom { get; set; }
public VirtualPet()
{
...
}
public void CheckStatus()
{
...
}
public void Feed()
{
...
}
public void Play()
{
...
}
}
Small Functions
The first rule of functions is that they should be small.
The second rule of functions is that they should be smaller than that.
This implies that the blocks within if statements, else statements, while statements, and so on should be one line long. That line should probably be a function call. Not only does this keep the enclosing function small, but it also adds documentary value because the function called within the block can have a nicely descriptive name.
Few Function Arguments
A function shouldn’t have more than 3 arguments. When a function needs more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own.
public float Sum(int[] values, int startIndex, int endIndex)
{
var total = 0;
for (var index=startIndex; index<=endIndex; index++)
{
total += values[index];
}
return total;
}
This makes the logic crystal clear. Function names easily describe what we are trying to achieve.
Objects and Data Structures
First we need to clarify the difference between Object and Data Structures. One is just about storing data and other allows us to manipulate that data.
-
Objects hide their data behind abstractions and expose methods that operate on that data.
-
Data structures expose their data and have no meaningful methods.
The Geometry class below operates on the three shape classes. The shape classes are simple data structures without any behavior. All the behavior is in the Geometry class.
public class Square
{
public Point topLeft;
public double side;
}
public class Circle
{
public double radius;
}
public class Geometry
{
public sealed double PI = 3.141592653589793;
public double area(Object shape) throws NoSuchShapeException
{
if (shape is Square)
{
Square s = (Square)shape;
return s.side * s.side;
}
else if (shape is Circle)
{
Circle c = (Circle)shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}
In our code
Think about the previous example. Consider what would happen if a Perimeter()
function were added to Geometry. The Shape
classes would be unaffected. On the other hand, if I add a new shape, I must change all the functions in Geometry
to deal with it.
This code is flawed according to Liskov's Substitution Principle Which states:
[[[
If S
is a subtype of T
, then objects of type T
in a program may be replaced with objects of type S
without altering any of the desirable properties of that program.
Put plainly. We shouldn't have to make drastic changes to incorporate new functionality.
]]]
public class Shape
{
public abstract double Area()
{
return side*side;
}
}
public class Square :Shape
{
private double topLeft;
private double side;
public override double Area()
{
return side*side;
}
}
public class Circle : Shape
{
private double radius;
public final double PI = 3.141592653589793;
public override double Area()
{
return PI * radius * radius;
}
}
Now we can easily add new Shapes as compared to the previous case. If we have to add Perimeter()
function in only one Shape
, we are forced to implement that function in all the Shapes as Shape
class is an interface containing Area()
and Perimeter()
function.