The vision of "Better Code Saul" is to advocate good code practices and good coding design techniques in order to inspire others to adopt them and increment software's code quality.
The season one's mission of "Better Code Saul" is to provide a short-term benefit to your code quality.
The episodes succession is meant to be of incremental difficulty and each episode is useful for the following.
Calisthenics is a word that derives from the greek and means "exercise" in a sport context.
Object is a word from Object Oriented Programming.
So Object Calisthenics are programming exercises, formalised to be executed following a set of 9 rules.
These 9 rules have been written by Jeff Bay
Only One Level Of Indentation Per Method
Don’t Use The ELSE Keyword
Wrap All Primitives And Strings
First Class Collections
One Dot Per Line
Don’t Abbreviate
Keep All Entities Small
No Classes With More Than Two Instance Variables
No Getters/Setters/Properties
class Board {
String board() {
StringBuffer buf = new StringBuffer();
// 1st level
for (int i = 0; i < 10; i++) {
// 2nd level
for (int j = 0; j < 10; j++) {
// 3rd level
buf.append(data[i][j]);
}
buf.append(“\n”);
}
return buf.toString();
}
}
Too many nested levels
A possible fix
class Board {
public String board() {
StringBuilder buf = new StringBuilder();
collectRows(buf);
return buf.toString();
}
private void collectRows(StringBuilder buf) {
for (int i = 0; i < 10; i++) {
collectRow(buf, i);
}
}
private void collectRow(StringBuilder buf, int row) {
for (int i = 0; i < 10; i++) {
buf.append(data[row][i]);
}
buf.append("\n");
}
}
Suggestion: Make use of ExtractMethod pattern
public void login(String username, String password) {
if (userRepository.isValid(username, password)) {
redirect("homepage");
} else {
addFlash("error", "Bad credentials");
redirect("login");
}
}
with ELSE
public void login(String username, String password) {
if (userRepository.isValid(username, password)) {
return redirect("homepage");
}
addFlash("error", "Bad credentials");
return redirect("login");
}
without ELSE
Suggestion: Use early returns or flags
Suggestion: Use ValueObjects
Principle
If you have a primitive variable that has some behaviour
you need to encapsulate
class Product {
public function getPrice(): float {
return $this->price;
}
public function setPrice(float $price) {
$this->price = $price;
}
}
class Product {
public function getPrice(): Money {
return $this->price;
}
public function setPrice(Money $price) {
$this->price = $price;
}
}
If you have a class that contains one collection, that class should not contain any other member
class Order {
public ArrayCollection $products;
public ArrayCollection $discountsApplied;
function productsCustomMethod() {
...
}
function discountsCustomMethod() {
...
}
}
class Order {
public ProductsCollection $products;
public DiscountCollection $discountsApplied;
}
// ProductsCollection
class ProductsCollection {
public ArrayCollection $products;
function productsCustomMethod() {
...
}
}
// DiscountCollection
class DiscountCollection {
public ArrayCollection $products;
function discountCustomMethod() {
...
}
}
not good
better
Suggestion: Make use of SOLID's SRP
In C# you have a dot, in JS you have a dot, in PHP you have the arrow operator.
You use this when you want to use a class method or property.
You should not have more than one of these in your code.
This does not apply for fluent interfaces.
// I want to get a certain
// product price starting from an order
// NOT OK
$order->orderDetail->product->getPrice();
// I want to get a certain
// product price starting from an order
// MORE OKish
$orderDetail = $order->orderDetail;
$product = $orderDetail->product
$price = $product->getPrice();
// OK Fluent example
$widget
->addSectionFoo()
->addSectionBar();
$gt = $t + $tx - $d;
Never abbreviate variable names or methods names.
Why do you need to do that?
$grandTotal = $total + $taxes - $discounts;
Why?
Oh ok, now better
Suggestion: If you have no answer from the above question, then you don't need abbreviations. If you I've found an answer your code is probably duplicated or you are crunching code and getting stresses. Ask a collegue for help
An entity here is a class.
All classes should not be longer than 150 lines *.
Classes longer than 150 lines are probably doing more than one thing.
This is a rule. The principle above is to keep things simple, split responsibilities and keep your classes testable.
This is hard, but it will benefit your coding quality.
* the original rule says 50 lines. 150 is still reasonable tho.
Up to you decide to which one abide to.
class Person {
public string $firstName;
public string $middleName;
public string $lastName;
}
class Person {
public GivenNames $names;
public FamilyName $lastName;
}
class FamilyName {
public string $familyName;
}
class GivenNames {
/** array<string> **/
public array $names;
}
Suggestion: Use encapsulation 'til death
class Person {
public string $firstName;
public string $lastName;
public function setFirstName(string $firstName) {
...
}
public function getFirstName(): string {
...
}
public function setLastName(string $lastName) {
...
}
public function getLastName(): string {
...
}
}
Wrong
class Person {
private string $firstName;
private string $lastName;
public function __construct(string $firstName, string $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}
public function getFirstName(): string {
...
}
public function getLastName(): string {
...
}
}
Correct
Never change an object's state
Make objects immutable
To init some properties use class' constructor
Don't write unnecessary getters
I'd love to see these rules applied in some programming exercises.
Some of them are also doable on refactoring PRs on a real project.
Yes, these are rules for exercises, but they will change your way to write code and you will question yourself when you will write production code.
You will produce better quality code and break your old bad habits.
Theres no reason to use these rules in a live projects, but there are a lot of reasons to use the principles behind them.
As Jeff Bay said, 7 out of 9 of these rules are just making wise use of encapsulation.