Your code 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.)
You can find all the previous code smells (Part I - XLIII) here.
Let's continue...
You should not define too much behavior together
TL;DR: Split your interfaces
The term "Fat Interface" emphasizes that the interface is overloaded with methods, including those that may not be necessary or used by all clients.
The interface violates the principle of segregating interfaces into smaller, more focused contracts.
interface Animal {
void eat();
void sleep();
void makeSound();
// This protocol should be common to all animals
}
class Dog implements Animal {
public void eat() { }
public void sleep() { }
public void makeSound() { }
}
class Fish implements Animal
public void eat() { }
public void sleep() {
throw new UnsupportedOperationException("I do not sleep");
}
public void makeSound() {
throw new UnsupportedOperationException("I cannot make sounds");
}
}
class Bullfrog implements Animal
public void eat() { }
public void sleep() {
throw new UnsupportedOperationException("I do not sleep");
}
public void makeSound() { }
}
interface Animal {
void move();
void reproduce();
}
// You can even break these two responsibilities
class Dog implements Animal {
public void move() { }
public void reproduce() { }
}
class Fish implements Animal {
public void move() { }
public void reproduce() { }
}
class Bullfrog implements Animal {
public void move() { }
public void reproduce() { }
}
We can check the size of the interface protocol
Favoring small, reusable code components promotes code and behavior reuse.
Code Smell 61 - Coupling to Classes
Code Smell 135 - Interfaces With just One Realization
Code Smells are my opinion.
Photo by Towfiqu barbhuiya on Unsplash
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
Software Engineering Great Quotes
You create empty methods instead of failing
TL;DR: Don't fill in methods to comply
Creating an empty implementation might seem fine to jump to more interesting problems.
The code left won't fail fast so debugging it will be a bigger problem
class MerchantProcessor {
processPayment(amount) {
// no default implementation
}
}
class MockMerchantProcessor extends MerchantProcessor {
processPayment(amount) {
// Empty implementation to comply with the compiler
// Won't do anything
}
}
class MerchantProcessor {
processPayment(amount) {
throw new Error('Should be overridden');
}
}
class MockMerchantProcessor extends MerchantProcessor {
processPayment(amount) {
throw new Error('Will be implemented when needed');
}
}
// or better...
class MockMerchantProcessor extends MerchantProcessor {
processPayment(amount) {
console.log('Mock payment processed: $${amount}');
}
}
Since empty code is valid sometimes only a good peer review will find these problems.
Being lazy and deferring certain decisions is acceptable, but it's crucial to be explicit about it.
Code Smell 30 - Mocking Business
Photo by Joey Kyber on Unsplash
There is an art to knowing where things should be checked and making sure that the program fails fast if you make a mistake. That kind of choosing is part of the art of simplification.
Ward Cunningham
If you miss a comma, you are concatenating
TL;DR: Watch out for fancy language assumptions
Many programming languages favor laziness over readability and clean code.
You should use them with caution and trust your tests.
tools = [
"Amazon Codewhisperer",
"Bard" # Notice the missing comma
"ChatGPT",
"Dalle-E" # Also here
"Eliza"
]
print(len(tools))
# This will output 3
print(tools)
# ['Amazon Codewhisperer', 'BardChatGPT', 'Dalle-EEliza']
# Missing Commas act as hidden string concatenators
tools = [
"Amazon Codewhisperer",
"Bard",
"ChatGPT",
"Dalle-E",
"Eliza"
]
# We added all the missing commas
print(len(tools))
# 5
print(tools)
# ['Amazon Codewhisperer', 'Bard', 'ChatGPT', 'Dalle-E', 'Eliza']
Many linters warn about this problem.
Also, ChatGPT and Bard can detect the problem.
Many modern programming languages come with a significant amount of accidental complexity.
They are often optimized for writing code quickly, even though they may be prone to defects.
Unfortunately, when working with these languages, it is essential to exercise extreme caution.
Code Smell 84 - Max < Min (Javascript)
Code Smell 69 - Big Bang (JavaScript Ridiculous Castings)
Code Smell 06 - Too Clever Programmer
Photo by Edge2Edge Media on Unsplash
A programming language is low level when its programs require attention to the irrelevant.
Alan J. Perlis
Counting from zero seems natural. Doesn't it?
TL;DR: Start counting from one instead of zero. Like humans do.
Low-level languages force you to think at a machine level.
Hardware turning machines were designed to use binary gates and start indexing at 0.
A few programming languages use one-based indexing, where indexing starts from 1.
These languages are known for being higher level and more declarative:
package main
import "fmt"
func main() {
numbers := []int{10, 20, 30, 40, 50}
for i := 0; i < len(numbers); i++ {
// Iteration goes from zero to len-1
fmt.Println(numbers[i])
}
}
numbers = [10, 20, 30, 40, 50];
% Looping through the array using one-based indexing
% from 1 to length
for currentIndex = 1:length(numbers)
disp(numbers(currentIndex));
end
This is a language smell.
We need to think as humans when we code and not as machines.
Humans count from one.
Zero number was a brilliant discovery in math and science but it does not apply to everyday counting.
Code Smell 53 - Explicit Iteration
Code Smell 123 - Mixed 'What' and 'How'
Photo by Andy Kelly on Unsplash
Pay attention to zeros. If there is a zero, someone will divide by it.
Cem Kaner
You need to return more than one object
TL;DR: Don't return multiple values.
A function returning multiple values in languages that allow it is a problem.
Developers can use this hack to avoid reifying concepts.
Some languages are: C#, Javascript, Go, Lua, Matlab, PHP, Python, Rust, and Swift
func getNameAndAge() -> (String, Int) {
let name = "John"
let age = 30
return (name, age)
}
struct PeopleProfile {
let name: String
let age: Int
}
// You reify the PeopleProfile object
func getNameAndAge() -> PeopleProfile {
let name = "John"
let age = 30
let profile = PeopleProfile(name: name, age: age)
return profile
}
This is a language smell.
We can tell our linters to warn us.
This is yet another language feature that hinders clean code and blinds us from seeing missing abstractions in the Bijection.
Code Smell 10 - Too Many Arguments
Code Smell 122 - Primitive Obsession
Code Smell 27 - Associative Arrays
Photo by Edgar Soto on Unsplash
By relieving the brain of all unnecessary work, a good notation sets it free to concentrate on more advanced problems, and in effect increases the mental power of the race. Before the introduction of the Arabic notation, multiplication was difficult, and division even of integers called into play the highest mathematical faculties. Our modern power of easy reckoning with decimal fractions is the almost miraculous result of the gradual discovery of a perfect notation.
Alfred North Whitehead
Next week, 5 more smells