Infinite code smells!
We see several symptoms and situations that make us doubt the quality of our development.
Let's look at some possible solutions.
Most of these smells are just hints of something that might be wrong. They are not rigid rules.
Let's continue...
Say it only once
final class SocialNetwork {
function postStatus(string $newStatus) {
if (!$user->isLogged()) {
throw new Exception('User is not logged');
}
///...
}
function uploadProfilePicture(Picture $newPicture) {
if (!$user->isLogged()) {
throw new Exception('User is not logged');
}
///...
}
function sendMessage(User $recipient, Message $messageSend) {
if (!$user->isLogged()) {
throw new Exception('User is not logged');
}
///...
}
}
<?
final class SocialNetwork {
function postStatus(string $newStatus) {
$this->assertUserIsLogged();
///...
}
function uploadProfilePicture(Picture $newPicture) {
$this->assertUserIsLogged();
///...
}
function sendMessage(User $recipient, Message $messageSend) {
$this->assertUserIsLogged();
///...
}
function assertUserIsLogged() {
if (!$this->user->isLogged()) {
throw new Exception('User is not logged');
//This is just a simplification to show the code smell
//Operations should be defined as objects with preconditions etc.
}
}
}
Some modern linters can detect repeated patterns (not just repeated code) and also while performing our code reviews we can easily detect this problem and ask for a refactor.
Adding a new feature should be straightforward it our model maps 1:1 to real world and our responsibilities are in the correct places. We should be alert for small changes spanning in several classes.
Photo by
Duplication is the primary enemy of a well-designed system.
Robert Martin
Let's break Demeter's Law.
public class Client {
Address address;
public ZipCode zipCode() {
return address.zipCode();
}
}
public class Address {
private ZipCode zipCode;
public ZipCode zipCode() {
return new ZipCode('CA90210');
}
}
public class Application {
ZipCode zipCode = client.zipCode();
}
public class Client {
Address address;
//client now has to expose its address
public address() {
return address;
}
}
public class Address {
private ZipCode zipCode;
public ZipCode zipCode() {
return new ZipCode('CA90210');
}
}
public class Application {
ZipCode zipCode = client.address().zipCode();
}
Same as its opposite smell, We can detect this small using parsing trees.
This is exactly the opposite to Message Chain. We make explicit the message chain.
Code Smell 08 - Long Chains Of Collaborations
Photo by
Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent.
Martin Fowler
Getting things is widespread and safe. But it is a very bad practice.
<?php
final class Window {
public $width;
public $height;
public $children;
public function getWidth() {
return $this->width;
}
public function getArea() {
return $this->width * $this->height;
}
public function getChildren() {
return $this->children;
}
}
<?php
final class Window {
private $width;
private $height;
private $children;
public function width() {
return $this->width;
}
public function area() {
return $this->height * $this->width;
}
public function addChildren($aChild) {
//do not expose internal attributes
return $this->children[] = $aChild;
}
}
Getters coincide in certain scenarios with a true responsibility. It will be reasonable for a window to return its color and it may accidentally store it as color. so a color() method returning the attribute color might be a good solution.
getColor() breaks bijection since it is implementative and has no real counterpart on our mappers.
Most linters can warn us if they detect anemic models with getters and setters.
Getters and Setters are a bad established practice. Instead of focusing on object behavior (essential), we are desperate to know object guts (accidental) and violate their implementation.
Code Smell 64 - Inappropriate Intimacy
Photo by Vidar Nordli-Mathisen on Unsplash
The value of a prototype is in the education it gives you, not in the code itself.
Alan Cooper
This handy operator is a trouble maker.
TL;DR: Don't mix booleans with non-booleans.
The One and Only Software Design Principle
!true // returns false
!false // returns true
isActive = true
!isActive // returns false
age = 54
!age // returns false
array = []
!array // returns false
obj = new Object;
!obj // returns false
!!true // returns true
!!false // returns false
!!isActive // returns true
!!age // returns true
!!array // returns true
!!obj // returns true
!true // returns false
!false // returns true
isActive = true
!isActive // returns false
age = 54
!age // should return type mismatch (or 54 factorial!)
array = []
!array // should return type mismatch
obj = new Object;
!obj // should return type mismatch (what is an obejct negated in a real domain?)
!!true // returns true - it is idempotent
!!false // returns false - it is idempotent
!!isActive // returns true - it is idempotent
!!age // nonsense
!!array // nonsense
!!obj // nonsense
Since this is a "feature" in some languages it would be hard to test. We can set programming policies or choose more strict languages.
We should detect ! !! usages in non-boolean objects and warn our programmers.
Languages like JavaScript divide their whole universe into true or false values. This decision hides errors when dealing with non booleans.
We should be very strict and keep booleans (and their behavior), far away from non booleans.
Code Smell 24 - Boolean Coercions
Code Smell 06 - Too Clever Programmer
Photo by
It is easier to write an incorrect program than understand a correct one.
Alan J Perlis
TL;DR: Do not create anemic objects. Much less with automatic tools.
AnemicClassCreator::create(
'Employee',
[
new AutoGeneratedField('id', '$validators->getIntegerValidator()'),
new AutoGeneratedField('name', '$validators->getStringValidator()'),
new AutoGeneratedField('currentlyWorking', '$validators->getBooleanValidator()')
]);
class Employee extends AutoGeneratedObjectEmployee {
}
//We have magic setters and getters
//getId() , setId(), getName()
//Validation is not implicit
//Class are loaded via an autoloader
$john = new Employee;
$john->setId(1);
$john->setName('John');
$john->setCurrentlyWorking(true);
$john->getName(); //returns 'John'
final class Employee {
private $name;
private $workingStatus;
public function __construct(string $name, WorkingStatus $workingStatus) {
//..
}
public function name(): string {
return $this->name;
//This is not a getter. It is Employee's responsibility to tell us her/his name
}
}
//We have no magic setters or getters
//all methods are real and can be debugged
//Validations are implicit
$john = new Employee('John', new HiredWorkingStatus());
$john->name(); //returns 'John'
Often, anemic models are generated with meta-programming.
We need to track these magic code generators.
Code Wizards, Meta-programming, and anemic models are all code smells.
We need to avoid these dark techniques.
Having to write explicitly the code makes us reflect on every piece of data we encapsulate.
Photo by
The best smells are something that's easy to spot and most of time lead you to really interesting problems. Data classes (classes with all data and no behavior) are good examples of this. You look at them and ask yourself what behavior should be in this class.
Martin Fowler
And that’s all for now…
Next article will explain 5 more code smells!