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 - XLVI) here.
Let's continue...
Where are your sources of truth?
TL;DR: Say it only once
The principle of "Don't Repeat Yourself" (DRY) encourages you to avoid redundancy and duplication of behavior.
Redundant data can lead to inconsistencies because updates or changes need to be made in multiple places.
If you update one instance of the data and forget to update another, your system can become inconsistent, which can lead to errors and unexpected behavior.
Maintaining redundant data can be a nightmare when it comes to making changes or updates since It increases the workload and the likelihood of introducing errors during maintenance.
With a single source of truth, you only need to make changes in one place, simplifying the maintenance process.
When data is repeated in multiple places, it becomes difficult to identify the authoritative source of that data, leading to confusion for developers.
class Transfer:
def __init__(self, amount, income, expense):
self.amount = amount
self.income = income
self.expense = expense
class Income:
def __init__(self, amount):
self.amount = amount
# amount is the same for party and counterparty
class Expense:
def __init__(self, amount):
self.amount = amount
transfer_amount = 1000
# simplification: should be a money object with the currency
income = Income(transfer_amount)
expense = Expense(transfer_amount)
transfer = Transfer(transfer_amount, income, expense)
print("Transfer amount:", transfer.amount)
print("Income amount:", transfer.income.amount)
print("Expense amount:", transfer.expense.amount)
class Transfer:
def __init__(self, amount):
self.amount = amount
self.income = Income(self)
self.expense = Expense(self)
class Income:
def __init__(self, transfer):
self.transfer = transfer
def get_amount(self):
return self.transfer.amount
class Expense:
def __init__(self, transfer):
self.transfer = transfer
def get_amount(self):
return self.transfer.amount
transfer_amount = 1000
transfer = Transfer(transfer_amount)
print("Transfer amount:", transfer.amount)
print("Income amount:", transfer.income.get_amount())
print("Expense amount:", transfer.expense.get_amount())
This is a semantic smell
In larger and more complex systems, redundancy becomes a significant problem.
As your system grows, the challenges associated with maintaining and synchronizing redundant data also increase.
Redundant data also increases the surface area for testing and debugging.
You need to ensure that all copies of the data behave consistently, which can be a challenging task.
Code Smells are my opinion.
Photo by Jørgen Håland on Unsplash
Everything will ultimately fail. Hardware is fallible, so we add redundancy. This allows us to survive individuals hardware failures, but increases the likelihood of having at least one failure at any given time.
Michael Nygard
Don't Repeat Yourself. Don't Repeat Yourself
TL;DR: You can find missing abstractions by looking at repeated code
Repeated code is a symptom of missing abstractions.
This is natural in the learning process since we cannot foresee those abstractions.
def calculate_area(length, width):
return length * width
def calculate_volume(length, width, height):
return length * width * height
def calculate_area(length, width):
return length * width
def calculate_volume(length, width, height):
base_area = calculate_area(length, width)
return base_area * height
Some linters can find repeated code patterns.
The abstraction must have a dependency correspondence on the Bijection
Repeated code is a problem and a hint for a missing abstraction.
Remember you don't need to avoid copying and pasting.
You must explicitly write the repeated code and remove the duplication by introducing an abstraction.
Avoiding the cut and paste is a shortcut and a symptom of premature optimization.
Code Smell 182 - Over Generalization
Photo by Mitchell Griest on Unsplash
Pasting code from the internet into production code is like chewing gum found in the street.
Mike Johnson
You count collections or collections.count?
TL;DR: Chose narrow names
Names are significant and should not deceive the reader. f You name things and lose the scope of the name.
It is important to be accurate of the expected reference on the names.
const standardModelParticles = {
quarks: [
{
name: "Up",
charge: "2/3",
type: "Quark",
},
{
name: "Down",
charge: "-1/3",
type: "Quark",
},
// ...
],
leptons: [
{
name: "Electron",
charge: "-1",
type: "Lepton",
},
{
name: "Muon",
charge: "-1",
type: "Lepton",
},
// ...
],
gaugeBosons: [
{
name: "Photon",
charge: "0",
type: "Boson",
},
{
name: "W Boson",
charge: "±1",
type: "Boson",
},
// ...
],
higgsBoson: [
{
name: "Higgs Boson",
charge: "0",
type: "Scalar Boson",
},
],
};
const quarks = standardModelParticles.quarks.length;
// Bad name. It does not represent a count
// But a Collection of things
const standardModelParticles = {
}; // Same as the "Wrong" example
const quarksCount = standardModelParticles.quarks.length;
Some linters can check the types and names and infer a mistake
Take care of your names.
Use automatic refactor tools whenever you come across a bad name.
Code Smell 163 - Collection in Name
Code Smell 134 - Specialized Business Collections
Photo by Sandy Millar on Unsplash
Some people are good programmers because they can handle many more details than most people. But there are a lot of disadvantages in selecting programmers for that reason - it can result in programs that no one else can maintain.
Butler Lampson
Be smart (and lazy) with low performant conditions
TL;DR: Premature Optimization is Evil. Optimization is Good.
Readability is always essential and you should avoid premature optimization.
Non-premature optimization happens when you have actual evidence you can improve your code execution time without much readability penalizations.
def is_warm():
# This is a fast api call to your thermometer
response = requests.get
("https://iot-device-api.example.com/current_temperature")
temperature_data = response.json()
return temperature_data.get('temperature', 0) > 25
def is_weekend():
# This function checks if today is a weekend
# based on a slow calendar API call
response = requests.get
("https://calendar-api.example.com/today")
calendar_data = response.json()
return calendar_data.get('day_of_week', '').lower()
in ['saturday', 'sunday']
def is_sunny():
# Very slow function to a low performant weather API call
response = requests.get
("https://weather-api.example.com/current")
weather_data = response.json()
return weather_data.get('weather', '') == 'sunny'
is_sunny_value = is_sunny()
is_warm_value = is_warm()
is_weekend_value = is_weekend()
if is_sunny_value and is_warm_value and is_weekend_value:
# the 3 conditions are always evaluated
print("Let's go outside!")
else:
print("Stay at home.")
if is_warm() and is_weekend() and is_sunny():
# the 3 conditions are evaluated in short circuit
# and sorted from fastest to slowest
# for a fast exit
print("Let's go outside!")
else:
print("Stay at home.")
You can detect slow calls using actual benchmarks.
Do not consider algorithm complexity since sometimes it is unrelated to actual data distribution. (for example, optimizing an array with a few elements).
Find bottlenecks using Pareto rules.
Optimize your code-critical sections.
Code Smell 140 - Short Circuit Evaluation
Code Smell 145 - Short Circuit Hack
Photo by Nick Abrams on Unsplash
The key to performance is elegance, not battalions of special cases.
Jon Bentley and Douglas McIlroy
You compute and log in the same place
TL;DR: Avoid side effects
Outputting to the console within an internal function generates coupling and side effects
[X] Automatic
Several linters warn for this usage
- Globals
Instead of logging directly within internal functions, a more modular and flexible approach is to have functions return values or throw exceptions when errors occur.
The calling code can then decide how to handle and log these results based on the application's logging strategy.
[Code Smell 17 - Global Functions])
Image generated by Midjourney
Memory is like an orgasm. It's a lot better if you don't have to fake it.
Seymour Cray
Next week, 5 more smells.