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.)
Previous Code Smells
You can find all the previous code smells (Part i - XXXVIII)Β here.
Let's continue...
Code Smell 191 - Misplaced Responsibility
You have a clear responsibility for the wrong object
TL;DR: Don't be afraid to create or overload the proper objects.
Problems
- Bijection Fault.
Solutions
- Find actual behavior on the real objects using the MAPPER.
- Answer the question: 'Whose responsibility is..?'
Context
Finding responsible objects is a tough task.
If we talk to anybody outside the software world, they will tell us where we should place every responsibility.
Software engineers, on the contrary, tend to put behavior in strange places like helpers.
Sample Code
Wrong
function add(a, b) {
return a + b;
}
// this is natural in many programming languages,
// but unnatural in real life
class GraphicEditor {
constructor() {
this.PI = 3.14;
// We shouldn't define it here
}
pi() {
return this.PI;
// Not this object responsibility
}
drawCircle(radius) {
console.log(`Drawing a circle with radius ${radius}
and circumference ${2 * this.pi() * radius}.`);
}
}
Right
class Integer {
function add(adder) {
return this + adder;
}
}
// This won't compile in many programming languages
// But it is the right place for adding responsibility
class GraphicEditor {
drawCircle(radius) {
console.log(`Drawing a circle with radius ${radius}
and circumference ${2 * Number.pi() * radius}.`);
}
}
// PI's definition is Number's responsibility
Detection
- [x]Manual
This is a semantic smell.
Exceptions
- Some languages force you to add protocol in some objects and not on everyone (like primitive integers, Strings, Arrays, etc.)
Tags
- Behavior
Conclusion
If you put the responsibilities in the proper object, you will surely find them in the same place.
Relations
More Info
Disclaimer
Code Smells are just my opinion.
Credits
Photo by Austin Neill on Unsplash
We know an object by what it does, by what services it can provide. That is to say, we know objects by their behaviors.
David West
Software Engineering Great Quotes
Code Smell 192 - Optional Attributes
You need to model something optional. Have you tried collections?
TL;DR: Collections are fantastic. And Polymorphic.
Problems
Solutions
- Change the optional attribute to a collection.
Context
If you need to model something that might be missing, some fancy languages will provide optional, nullable, and many other wrong solutions dealing with The Billion Dollar Mistake.
Empty collections and non-empty collections are polymorphic.
Sample Code
Wrong
class Person {
constructor(name, email) {
this.name = name;
this.email = email;
}
email() {
return this.email;
// might be null
}
}
// We cannot use safely person.email()
// We need to check for null explicitly
Right
class Person {
constructor(name, emails) {
this.name = name;
this.emails = emails;
// emails should always be a collection.
// even an empty one
// We can check it here
}
emails() {
return this.emails;
}
// We can mutate the emails since they are not essential
addEmail(email) {
this.emails.push(email);
}
removeEmail(email) {
const index = this.emails.indexOf(email);
if (index !== -1) {
this.emails.splice(index, 1);
}
}
}
// we can iterate the person.emails()
// in a loop without checking for null
Detection
- [x]Semi-Automatic
You can detect nullable attributes and change them when necessary.
Tags
- Null
Conclusion
This is a generalization of the null object pattern.
Relations
Code Smell 149 - Optional Chaining
Code Smell 19 - Optional Arguments
More Info
Null: The Billion Dollar Mistake
Disclaimer
Code Smells are just my opinion.
Credits
Photo by Levi Jones on Unsplash
To iterate is human, to recurse divine
Peter Deutsch
Code Smell 193 - Switch Instead of Formula
Which is better, declarative or shorter code?
TL;DR: Be declarative enough but no more.
Problems
- Readability
- Premature Optimization
Solutions
- Use a short version (or not).
- Always favor readability >> Premature optimization.
- Humans learn by examples, not by formulas.
- Shorter is not always better.
Context
Last week, a tweet went viral because of a missing formula.
It is the DigiD digital authentication iOS app in the Netherlands.
Sample Code
Wrong?
private static string GetPercentageRounds(double percentage)
{
if (percentage == 0)
return "βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ";
if (percentage > 0.0 && percentage <= 0.1)
return "π΅βͺβͺβͺβͺβͺβͺβͺβͺβͺ";
if (percentage > 0.1 && percentage <= 0.2)
return "π΅π΅βͺβͺβͺβͺβͺβͺβͺβͺ";
if (percentage > 0.2 && percentage <= 0.3)
return "π΅π΅π΅βͺβͺβͺβͺβͺβͺβͺ";
if (percentage > 0.3 && percentage <= 0.4)
return "π΅π΅π΅π΅βͺβͺβͺβͺβͺβͺ";
if (percentage > 0.4 && percentage <= 0.5)
return "π΅π΅π΅π΅π΅βͺβͺβͺβͺβͺ";
if (percentage > 0.5 && percentage <= 0.6)
return "π΅π΅π΅π΅π΅π΅βͺβͺβͺβͺ";
if (percentage > 0.6 && percentage <= 0.7)
return "π΅π΅π΅π΅π΅π΅π΅βͺβͺβͺ";
if (percentage > 0.7 && percentage <= 0.8)
return "π΅π΅π΅π΅π΅π΅π΅π΅βͺβͺ";
if (percentage > 0.8 && percentage <= 0.9)
return "π΅π΅π΅π΅π΅π΅π΅π΅π΅βͺ";
return "π΅π΅π΅π΅π΅π΅π΅π΅π΅π΅";
}
}
}
// Full source
// https://github.com/MinBZK/woo-besluit-broncode-digid-app/blob/master/Source/DigiD.iOS/Services/NFCService.cs
Right?
private static string GetPercentageRounds(double percentage)
{
string dots = "π΅π΅π΅π΅π΅π΅π΅π΅π΅π΅βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ";
int blueDots = (int) Math.Truncate (percentage* 10);
int startingPoint = 10-blueDots;
return dots. Substring(startingPoint, 10);
}
Detection
- [x]Semi-Automatic
This is a semantic smell. In this case, we can count the number of if clauses.
Tags
- Readability
Conclusion
You can read the original Twitter thread to take your own conclusions. There's some serious debate and, of course, several premature optimizators bringing obscure and unneeded solutions with (O) log(n) complexity and stupid benchmarks evidence for a loop that executes only once.
And lots of memes.
As a final conclusion, I asked ChatGPT and was not able to simplify it.
Relations
Code Smell 36 - Switch/case/elseif/else/if statements
Code Smell 20 - Premature Optimization
More Info
How to Get Rid of Annoying IFs Forever
Disclaimer
Code Smells are just my opinion.
There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies.
C. A. R. Hoare
Code Smell 194 - Missing Interval
From date should be lower than to date
TL;DR: Intervals are there. Why use plain dates?
Problems
- Missing abstraction
- Duplicated Code
- Not enforced invariants
- Primitive Obsession
- Bijection Fault
- Fail Fast principle violation
Solutions
- Create and use an Interval Object
Context
The restriction "From date should be lower than to date" means that the starting date of a certain interval should occur before the ending date of the same interval.
The "From date" should be a date that comes earlier in time than the "To date".
This restriction is in place to ensure that the interval being defined makes logical sense and that the dates used to define it are in the correct order.
We all know it. But we miss creating the Interval object.
Would you create a Date as a pair of 3 Integer numbers? Certainly, not.
This is the same.
Sample Code
Wrong
val from = LocalDate.of(2018, 12, 9)
val to = LocalDate.of(2022, 12, 22)
val elapsed = elapsedDays(from, to)
fun elapsedDays(fromDate: LocalDate, toDate: LocalDate): Long {
return ChronoUnit.DAYS.between(fromDate, toDate)
}
// We need to apply this short function
// Or the inline version many times in our code
// We don't check from Date to be less than toDate
// We can make accounting numbers with a negative number
Right
// We reify the Interval Concept
data class Interval(val fromDate: LocalDate, val toDate: LocalDate) {
init {
if (fromDate >= toDate) {
throw IllegalArgumentException("From date must be before to date")
}
// Of course the Interval must be immutable
// By using the keyword 'data'
}
fun elapsedDays(): Long {
return ChronoUnit.DAYS.between(fromDate, toDate)
}
}
val from = LocalDate.of(2018, 12, 9)
val to = LocalDate.of(2002, 12, 22)
val interval = Interval(from, to) // Invalid
Detection
- [x]Manual
This is a primitive obsession smell.
It is related to how we model things.
Tags
- Primitive
Conclusion
If you find software with missing simple validations, it certainly needs reification.
Relations
Code Smell 177 - Missing Small Objects
Code Smell 122 - Primitive Obsession
More Info
Disclaimer
Code Smells are just my opinion.
Credits
Photo by Towfiqu barbhuiya on Unsplash
At any particular point in time, the features provided by our programming languages reflect our understanding of software and programming.
R. E. Fairley
Code Smell 195 - Yoda Conditions
Best is to put the expected value last, if conditions you want to write.
TL;DR: In a natural way, write your conditions.
Problems
- Readability
- The least surprise principle violation
Solutions
- Write your conditions with the expected value as the second.
- Name the variables accordingly.
Context
Most programmers write the variable or condition first and the test value second.
In fact, this is the correct order for assertions.
In some languages, this style is used to avoid accidental assignment instead of equality comparison, which can result in a logic error in the code.
Sample Code
Wrong
if (42 == answerToLifeMeaning) {
//
}
Right
if (answerToLifeMeaning == 42) {
// might be mistaken with answerToLifeMeaning = 42
}
Detection
- [x]Semi-Automatic
We can check for constant values on the first side of the comparison.
Tags
- Readability
Conclusion
Reliable, direct, and clear be when conditions your writing.
Relations
Disclaimer
Code Smells are just my opinion.
Credits
Any man can make mistakes, but only an idiot persists in his error.
Marcus Cicero
5 more code smells are coming soonβ¦