Dear Dev,
One essay recommended to me early on in my career was “Object Calisthenics” (OC) and since that time I recommend it to every developer I cross paths with. “Object Calisthenics” describes a set of 9 rules credited to Jeff Bays in the book The ThoughtWork Anthology. The goal of these rules is to empower a developer to write cleaner, more maintainable code.
There is a lot of value in being familiar with the OC rules, however there is also value in conversation with other developers, and hearing their opinions on the rules. I’d like to share my opinions over the Object Calisthenics ruleset.
Here are the rules –
Although they are called rules, my mentor told me to consider them guidelines. As such, I consider them more of a warning sign to look up from your keyboard. If you find yourself trying these exercises and you violate one of these rules, take a moment to think about the code you are writing instead of trying to perfectly fit the rule.
I tend to agree with this rule in the sense that too many indentions make code hard to read. Consider the following examples:
makeHtmlTable() {
/* output <table> */
for (var i = 0; i <5; i++) {
/* output <tr> */
for (var j = 0; j <5; j++) {
/* output <td> */
createContent();
/* output <td> */
}
/* output <tr> */
}
/* output </table> */
}
Now let’s see what an example would be following the One Level of Indention per Method rule.
createHTMLTable() {
/* output <table> */
createTableRows();
/* output </table> */
}
createTableRows() {
for (var i = 0; i <5; i++) {
/* output <tr> */
createTableData();
/* output <tr> */
}
}
createTableData() {
for (var j = 0; j <5; j++) {
/* output <td> */
createContent();
/* output <td> */
}
}
In the first example there are two loops spewing out a lot of html. The function makeHtmlTable() is generating the HTML table, and it is also outputting TableRows and all of the TableData. This muddies the water a bit on what exactly the function does. By keeping one indentation per method, we have the opportunity to name other methods to better describe what is happening in the code. This helps keep the methods shorter, cleaner, and easier to read for the next person.
Besides most people finding the first example harder to read, when you have an encounter with this kind of code in the wild, it should be a stop and think moment. Something to keep in mind when loops are the cause of the indentations is that you may run the risk for nonlinear time to perform the function.
I agree with this rule, as well as using a guard clause on functions. If you can’t, it’s more than likely the function is too big. Here is an example of this rule:
showContent(user) {
if (user.isAdmin()) {
return user.getAdminContent();
} else {
return user.getContent();
}
}
showContent() {
if (user.isAdmin()) {
return user.getAdminContent();
}
return user.getContent();
}
This rule is pretty straightforward with its benefit; fewer keywords to read, fewer keywords for the brain to process, with the added benefit that it prevents unnecessary indentation as well.
I would add to this rule that if the logic in the “Is admin” path has more logic, you should invert it and treat it as a guard clause, like so:
showContent() {
if (!user.isAdmin()) {
return user.getContent();
}
user.loadAdminContent();
return user.getAdminContent();
}
This rule is a bit of overkill, and most devs will get along fine using primitives, like an int, for simple purposes such as counters.
class FooMachine {
int counter = 0;
fooFight() {
/* Fight the Foo */
counter++;
}
}
The major takeaway from the rule is that if your primitive (in this case the counter) starts creating a lot of behavior based around its value, it might be a good idea to wrap it in its own class.
class FooMachine {
int counter = 0;
fooFight() {
/* Fight the Foo */
if (counter > 5) {
throw Exception("Ran out of Fingers");
}
if (counter == 2) {
/* send warning email */
}
counter++;
}
}
class FooMachine {
Counter counter;
fooFight() {
/* Fight the Foo */
counter.increment();
}
}
class Counter {
int count = 0;
increment() {
if (counter > 5) {
throw Exception("Ran out of Fingers");
}
if (counter == 2) {
/* send warning email */
}
count++;
}
}
To put this rule into simple terms, each array or list should be put into a class. Similar to the rule above, I don’t always follow it, but I am typically pleased when I do. The reason to do this is so other parts of an application that use this array or list don’t have to know how to sort specific parts of the list or the array. Consider the following:
class AccountManager {
list<BankAccount> accounts = getBankAccounts();
getCheckBalance() {
var sum = 0;
for (var account in accounts) {
if (account.isDebt) continue;
sum += account.balance;
}
return sum;
}
}
The example above is basically doing a sort in the getCheckBalance function. Now, if this is just a one off, it would probably be fine. There is a much cleaner and more maintainable way to manage it, though - move the behavior of sorting and housing the list to its own class (a first class Collection).
class BankAccountCollection {
list<BankAccount> accounts = getBankAccounts();
getNonDebtAccounts() {
list<BankAccount> nonDebtAccounts = [];
for (var account in accounts) {
if (account.isDebt) continue;
nonDebtAccounts.add(account);
}
}
}
class AccountManager {
BankAccountCollection accounts = BankAccountCollection();
getCheckBalance() {
var sum = 0;
for (var account in accounts.getNonDebtAccounts()) {
sum += accounts.balance;
}
return sum;
}
}
Now if another part of the application needs to use that sorted list, it has a function ready to go. It’s also easier to add another sorting method for the list, just by adding it to the collection class! The takeaway for this rule is that if you see or could see yourself repeating sort logic on a list or array, it is probably a good idea to wrap it into its own class.
This rule seems like it’s basically just to keep code readable. Sometimes it will be more convenient, and arguably more readable, to use multiple accessors in a single line than to break it out into multiple variables.
getNumber() {
var number = user.getAccount().getNumber();
/* vs */
var account = user.getAccount();
var number = account.getNumber();
return number;
}
I’d recommend using what you think is more readable, and trying to avoid too many dots per line. The only time I recommend completely ignoring this rule is when the return types of the functions are all the same, the way they are in this builder pattern:
class AccountBuilder {
buildAccount() {
return initializeAccount()
.addChecking()
.addSavings();
}
BankAccount initializeAccount() {
/* do setup logic */
BankAccount account = BankAccount();
return account;
}
BankAccount addChecking() {
account.activateChecking();
return account;
}
BankAccount addSavings() {
account.activateSavings();
return account;
}
}
“Don’t abbreviate” is pretty self explanatory–just don’t do it. If you cannot be kind to others, at least be kind to your future self. Ask yourself which one of these examples would you rather read when troubleshooting 6 months after writing.
class A {
p() {
u.a();
return u;
}
}
/* or */
class AccessControl {
permit() {
user.activate();
return user;
}
}
The only times I would say this is acceptable are those weird instances where it is a convention like in golang’s methods on struct types.
Example provided by https://gobyexample.com/methods
A wise application developer once told me, “Joe, class files are for free.” What he meant by that was there is no point in jamming everything into a single file, a single class, or even a single function. The rule states “No Classes over 50 lines,” and some devs who have written their opinion on this rule recommend “50-150 lines.”
I would offer you an alternative opinion. Like I said earlier, these rules are guidelines. Once you’ve passed the 250 line mark in a class, consider whether that class is doing too much. If you feel it is, make separate classes, and move some of the methods over. The same goes for methods, but the limit should be closer to 50 lines. Over that? Think about breaking them into smaller units of work.
As an amendment to the wise application developer’s saying, I’d say “Remember, classes, functions, and files are for free.”
I do not follow this rule, and find it contradictory to a main objective of the Object Calisthenic: readability. Being truly dogmatic about the rules would mean having classes that each have one or two instance variables, and wrapping every primitive and list in a class. That would create some extremely layered objects. One might argue that it’s for high cohesion, but I find it pedantic.
Consider what a user class might look like under this approach. A user can have a profile, a settings configuration, and a security configuration before even getting into the application data. The dogmatic dev would have to delineate between all of these attributes of a user, two at a time.
What I hope you take away from this rule is that you could entertain the idea, and think about the best way to break down your class object. Again, I would use this rule more as a guideline: if you have more than 5 instance variables, then maybe it is time to think about what your object’s responsibility is.
The main idea behind this rule is that nothing should be modifying an object besides itself. Therefore, if an object has a setter, it is exposing a method in which another entity could define its property. Like so:
class BankAccount {
int balance = 0;
getBalance() {
return balance;
}
setBalance(int amount) {
balance = amount
}
}
class ATM {
BankAccount account = BankAccount();
depositMoney(int amount) {
var balance = account.getBalance();
account.setBalance(amount + balance);
}
withdrawMoney(int amount) {
var balance = account.getBalance();
account.setBalance(balance - amount);
}
}
At surface level, this looks okay, but if another entity also needs to deposit and withdraw money, all the logic would need to be duplicated. Removing the account logic from ATM and remove the setter from BankAccount lets the code tell it exactly what it’s supposed to do, like so:
class BankAccount {
int balance = 0;
getBalance() {
return balance;
}
deposit(int amount) {
balance += amount;
}
withdraw(int amount) {
/* could also do checks for issues here */
balance -= amount;
}
}
class ATM {
BankAccount account = BankAccount();
depositMoney(int amount) {
account.deposit(amount);
}
withdrawMoney(int amount) {
account.withdrawl(amount);
}
}
Now other entities can utilize the behavior of depositing and withdrawing money from the account without having to access or use properties outside the BankAccount Class.
I generally agree with this rule with the caveat that getters are allowed - but only for read only purposes.
Although I agree with a majority of the Object Calisthenics ruleset, sticking to some of them too strictly could be more of a burden than they are worth. Overall, my opinion on object calisthenics is that they are more helpful as guidelines, or as thought exercises–not rules. I hope this article helped inspire some thought, as well as helped you improve your code awareness and develop a bit of opinion over software development practices.
Until next time, remember: classes, functions, and files are for free.
Best Regards,
Joe