It smells because there are likely many instances where it could be edited or improved.
Most of these smells are just hints of something that might be wrong. Therefore, they are not required to be fixed per se… (You should look into it, though.)
Let's continue...
Null Objects are great alternatives to The Billion Dollar Mistake. Sometimes we don't need them
TL;DR: Don't abuse patterns. Even NullObject.
Null Object pattern is an excellent alternative to Nulls and IFs (Both are code smells).
The structure of the pattern tells us to create a hierarchy.
This is not necessary; we need natural objects to be polymorphic to null objects.
Inheritance is not a proper way to achieve polymorphism.
A simple solution is to create a real object behaving like a null one.
For example: '0' is the numbers' null object.
“(or "") is String's null object
An empty collection is the collection's null object.
abstract class Address {
public abstract String getCity();
public abstract String getState();
public abstract String getZipCode();
}
// Using inheritance for null objects is a mistake
// We should use interfaces (when available)
public class NullAddress extends Address {
public NullAddress() { }
public String getCity() {
return Constants.EMPTY_STRING;
}
public String getState() {
return Constants.EMPTY_STRING;
}
public String getZipCode() {
return Constants.EMPTY_STRING;
}
}
public class RealAddress extends Address {
private String zipCode;
private String city;
private String state;
public RealAddress(String city, String state, String zipCode) {
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String getZipCode() {
return zipCode;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
}
// There are just "addresses"
public class Address {
private String zipCode;
private String city;
private String state;
public Address(String city, String state, String zipCode) {
// Looks anemic :(
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
public String zipCode() {
return zipCode;
}
public String city() {
return city;
}
public String state() {
return state;
}
}
Address nullAddress = new Address(Constants.EMPTY_STRING, Constants.EMPTY_STRING, Constants.EMPTY_STRING);
// we have our null object
// we should NOT assign it to a singleton, static or global
// It behaves like a null object. That's enough
// No premature optimizations
This is a semantic smell.
Creating Null Object classes is sometimes overdesign.
We need to create a real object.
This real object should never be a global, singleton, or static.
Too many smells to avoid.
Code Smell 18 - Static Functions
Code Smell 17 - Global Functions
Photo by Juan Davila on Unsplash
Thanks to Hernan Wilkinson for this idea on his course Diseño a la Gorra (in Spanish)
All models are wrong but some models are useful
George Box
Software Engineering Great Quotes
You declare something a constant. But you can mutate it.
TL;DR: Use inmutable constants
We learned to declare constants in our first course on computer programming.
As always, it is not important if something is constant.
It is important if it does not mutate.
const DISCOUNT_PLATINUM = 0.1;
const DISCOUNT_GOLD = 0.05;
const DISCOUNT_SILVER = 0.02;
// Since variables are constants we cannot reassign them
const DISCOUNT_PLATINUM = 0.05; // Error
// We can group them
const ALL_CONSTANTS = {
DISCOUNT: {
PLATINUM = 0.1;
GOLD = 0.04;
SILVER = 0.02;
},
};
const ALL_CONSTANTS = 3.14; // Error
ALL_CONSTANTS.DISCOUNT.PLATINUM = 0.08; // NOT AN ERROR. WTF!
const ALL_CONSTANTS = Object.freeze({
DISCOUNT:
PLATINUM = 0.1;
GOLD = 0.05;
SILVER = 0.02;
});
const ALL_CONSTANTS = 3.14; // Error
ALL_CONSTANTS.DISCOUNT.PLATINUM = 0.12; // NOT AN ERROR. WTF!
export const ALL_CONSTANTS = Object.freeze({
DISCOUNT: Object.freeze({
PLATINUM = 0.1;
GOLD = 0.05;
SILVER = 0.02;
}),
});
const ALL_CONSTANTS = 3.14; // Error
ALL_CONSTANTS.DISCOUNT.PLATINUM = 0.12; // ERROR
// Code works, but it is coupled and we cannot test it
Class TaxesProvider {
applyPlatinum(product);
}
// Now we can couple to a interface (the protocol of taxes provider)
// Since class has no setters it is constant an immutable
// And we can replace it on tests
We can perform mutation testing to find changed values.
Mutability is essential.
We need to enforce it with the right tools.
Code Smell 86 - Mutable Const Arrays
Code Smell 107 - Variables Reuse
Code Smell 02 - Constants and Magic Numbers
This smell was inspired by This
Photo by Sangharsh Lohakare on Unsplash
You start digging in the code. The more you dig, the more stuff you turn up. Eventually you dig yourself into a hole you can’t get out of. To avoid digging your own grave, refactoring must be done systematically.
Eric Gamma
Software Engineering Great Quotes
Using your local language is an excellent idea because domain naming is easier. Not
TL;DR: Stick to English. Always.
All programming languages are written in English.
Unless for a few failed experiments during the '90s, all modern languages use English for their primitives and their frameworks.
If you wanted to read or write in medieval Europe, you had to acquire a new language at the same time. Writing meant Latin.
This is true for programming languages nowadays.
I am not a Native English speaker.
My code (tries to be) is in English.
const elements = new Set();
elements.add(1);
elements.add(1);
echo elements.size() yields 1
// This is the standard set
var moreElements = new MultiConjunto();
// We defined a multiset in Spanish
// because we are extending the domain
moreElements.agregar('hello');
moreElements.agregar('hello');
// 'agregar' is the Spanish word for 'add'
echo moreElements.size() yields 2 // Since it is a multiset
// elements and moreElements are NOT polymorphic
// I cannot exchange their implementation
const elements = new Set();
elements.add(1);
elements.add(1);
echo elements.size() yields 1
// This is the standard set
var moreElements = new MultiSet();
// We defined a multiset in English
moreElements.add('hello');
moreElements.add('hello');
echo moreElements.size() yields 2 // Since it is a multiset
// elements and moreElements are polymorphic
// I can exchange their implementation anytime
Most IDEs and linters have a thesaurus.
We can search for foreign words.
Don't mix Non-English domain words with English primitives.
Even when Mapping your real-world entities, use plain English.
Photo by Anna Vander Stel on Unsplash
A programming language is a tool that has a profound influence on our thinking habits.
Edsger Dijkstra
Software Engineering Great Quotes
We love to improve time and space complexity by guessing not real scenarios
TL;DR: Don't optimize anything until you have a real use scenario benchmark.
In university and in online courses, we learn algorithms, data structures, and computational complexity before good design rules.
We tend to overestimate the (possible) performance problems and underestimate code readability and software lifetime.
Premature optimization often has no evidence of solving real problems.
We need to surgically improve our code when the facts tell us we have a real issue.
for (k = 0; k < 3 * 3; ++k) {
i = Math.floor(k / 3);
j = k % 3;
console.log(i + ' ' + j);
}
// This cryptic piece of code iterates a
// two dimensional array
// We don't have proofs this will be useful
// In real contexts
for (innerIterator = 0; innerIterator < 3; innerIterator++) {
for (outerIterator = 0; outerIterator < 3; outerIterator++) {
console.log(innerIterator + ' ' + outerIterator);
}
}
// This is a readable double for-loop
// 3 is a small number
// No performance issues (by now)
// We will wait for real evidence
This is a semantic smell.
We might find the code harder to read.
We need to stop optimizing for machines and start optimizing for human readers and code maintainers.
We need to avoid programming languages designed for premature optimization and favor robust ones.
Code Smell 06 - Too Clever Programmer
Code Smell 20 - Premature Optimization
Photo by Priscilla Du Preez on Unsplash
Optimism is an occupational hazard of programming: feedback is the treatment.
Kent Beck
Software Engineering Great Quotes
It is nice to see a class implementing Interfaces. It is nicer to understand what it does
TL;DR: Name your classes after real-world concepts.
Some languages bring idioms and common usages against good model naming.
We should pick our names carefully.
public interface Address extends ChangeAware, Serializable {
/**
* Gets the street name.
*
* @return the street name
*/
String getStreet();
// ...
}
// Wrong Name - There is no concept 'AddressImpl' in real world
public class AddressImpl implements Address {
private String street;
private String houseNumber;
private City city;
// ..
}
// Simple
public class Address {
private String street;
private String houseNumber;
private City city;
// ..
}
// OR
// Both are real-world names
public class Address implements ContactLocation {
private String street;
private String houseNumber;
private City city;
// ..
}
Since this is a naming smell.
We can search using regular expressions and rename these concepts.
We should pick class names according to essential bijection and not follow accidental implementation.
Do not call “I” to your interfaces.
Code Smell 65 - Variables Named after Types
Code Smell 38 - Abstract Names
Photo by Paula Hayes on Unsplash
Encoded names are seldom pronounceable and are easy to miss-type.
Robert C. Martin
Software Engineering Great Quotes
And that’s all for now…
The next article will explain five more code smells!