What style should Clean Code be written in?
Clean Code is not always object-oriented. Sometimes it will be written in procedural style. So what style is better: procedural or object-oriented? We should perform the choice under given conditions which facilitates its development and readability – in accordance with the principles of Clean Code.
Below is an example of the procedural code that will help me consider the purity of the code and its refactoring to the object oriented code.
public class Rectangle {
double width;
double height;
}
...
public class Geometry {
double area(Object shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.radius * circle.radius
} else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
return rectangle.width * rectangle.height;
} else if (shape instanceof Square) {
Square square = (Square) shape;
return square.size * square.size;
}
throw new IllegalArgumentException("Unknown shape");
}
}
You should choose the style in which the code will be written on the basis of observing the direction of changes that result from emerging new business requirements.
What changes does the procedural code allow?
If you mainly add new functions operating on already existing data structures, then the procedural code (new procedures) will probably remain legible. An example is the new function that returns the smallest rectangle containing given figure.
public class Geometry {
Rectange containingRectange(Object shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
Rectangle rectangle = new Rectangle();
rectangle.width = 2 * circle.radius;
rectangle.height= 2 * circle.radius;
return rectangle;
} else if (shape instanceof Rectangle) {
return (Rectangle) shape;
} else if (shape instanceof Square) {
...
}
throw new IllegalArgumentException("Unknown shape");
}
}
When will the procedural code become illegible?
But if you plan to add or modify existing data structures, it will force changes to all existing procedures. What happens when I decide to change the components in the Rectangle data structure to points describing 2 opposite corners of the square?
public class Point {
double x, y;
}
public class Rectangle {
Point topLeft;
Point bottomRight;
}
It is not difficult to notice that such a change will force many changes to existing procedures. A way to avoid many changes (or minimize them) is to place the getXSize() and getYSize() methods in the Rectangle structure that will perform the necessary calculations.
public class Rectangle {
private Point topLeft;
private Point bottomRight;
double getWidth(){
return Math.abs(topLeft.x - bottomRight.x);
}
double getHeigth(){
return Math.abs(topLeft.y - bottomRight.y);
}
}
But note that from that moment I start to hide details of the data structure. Details in the Rectangle class have been hidden and new methods calculate the necessary output. In this way, I am starting to change the code style from procedural to object oriented.
How to refactor a procedural code into an object-oriented one?
Perform self-encapsulation of data structures
At the beginning I add constructors and encapsulate details within data structures. In my case, the data in the structures are not changing, so the fields can be final.
public class Circle {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
Define a common interface / base class for existing data structures
Next, I define an empty “Shape” base class that will expand all data structures. From now on, the “area” procedure accepts only the “Shape” abstract class extension as a parameter. Alternatively, it can also be a common interface.
public abstract class Shape{
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
...
Move the logic from the procedure to the base class
In order to transfer the logic to the base class, I will make a small modification to be able to use the method transfer in the IntelliJ tool.
public class Geometry {
static double area(Shape shape) {
return new Geometry().calculateArea(shape);
}
private double calculateArea(Shape shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.getRadius() * circle.getRadius();
} else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
return rectangle.getWidth() * rectangle.getHeight();
} else if (shape instanceof Square) {
Square square = (Square) shape;
return square.getSize() * square.getSize();
}
throw new IllegalArgumentException("Unknown shape :" + shape.getClass());
}
}
I obtained the above code by extracting a new method “calculateArea”, then deleting the word static and adding a call to the constructor.
Then I move the method containing the “calculateArea” logic from “Geometry” to the “Shape” base class.
public class Geometry {
static double area(Shape shape) {
return shape.calculateArea();
}
}
public abstract class Shape {
double calculateArea() {
if (this instanceof Circle) {
Circle circle = (Circle) this;
return Math.PI * circle.getRadius() * circle.getRadius();
} else if (this instanceof Rectangle) {
Rectangle rectangle = (Rectangle) this;
return rectangle.getWidth() * rectangle.getHeight();
} else if (this instanceof Square) {
Square square = (Square) this;
return square.getSize() * square.getSize();
}
throw new IllegalArgumentException("Unknown shape :" + getClass());
}
}
After this distortion, there was a code smell: “base class is dependent on its derived classes”. Solving the problem will lead us to the next transformation.
Push method down
Majority of environments like IntelliJ, Eclipse, NetBeans has this tranformation automated.
Delete unnecessary logic in derived classes
Finally, we finish with the transformation “replace conditional expressions with polymorphism”. In each of the subclasses (i.e. our old data structures), only one condition will be true. Below you can see how we clean code in Circle class.
The final result of our refactoring is below
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double calculateArea() {
return Math.PI * circle.radius * circle.radius;
}
}
public class Square extends Shape {
private double size;
public Square(double size) {
this.size = size;
}
@Override
double calculateArea() {
return size * size;
}
}
public class Geometry {
static double area(Shape shape) {
return shape.calculateArea();
}
}
In addition, we can inline “Geometry.area” function and then change the name of “calculateArea” to “area”, so we come back to original naming.
I also recommend my article How to clean code according to refactoring pyramid? where you can see refactoring to Interpreter design pattern.