How to Find the Stinky Parts of Your Code [Part XXXIX]

Written by mcsee | Published 2023/06/20
Tech Story Tags: programming | security | clean-code | software-engineering | refactoring | refactor-legacy-code | pixel-face | software-development | web-monetization

TLDRIt 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.)via the TL;DR App

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

Solutions

  1. Find actual behavior on the real objects using the MAPPER.
  2. 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

Code Smell 22 - Helpers

Code Smell 63 - Feature Envy

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

  1. 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 12 - Null

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

Solutions

  1. Use a short version (or not).
  2. Always favor readability >> Premature optimization.
  3. Humans learn by examples, not by formulas.
  4. Shorter is not always better.

Context

Last week, a tweet went viral because of a missing formula.

Twitter

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

Twitter

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

Solutions

  1. 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 46 - Repeated Code

Code Smell 122 - Primitive Obsession

More Info

Fail Fast

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

  1. Write your conditions with the expected value as the second.
  2. 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

Code Smell 99 - First Second

Disclaimer

Code Smells are just my opinion.

Credits

Photo by Lia on Unsplash


Any man can make mistakes, but only an idiot persists in his error.

Marcus Cicero


5 more code smells are coming soon…


Written by mcsee | I’m senior software engineer specialized in declarative designs and S.O.L.I.D. and Agile lover.
Published by HackerNoon on 2023/06/20