3

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

 9 months ago
source link: https://hackernoon.com/how-to-find-the-stinky-parts-of-your-code-part-xlii
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

@mcsee

Maximiliano Contieri

I’m senior software engineer specialized in declarative d...


Receive Stories from @mcsee


Credibility

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.)

Previous Code Smells

You can find all the previous code smells (Part i - XLIhere.

Let's continue...


Code Smell 206 - Long Ternaries

You love ternaries too much

TL;DR: Don't use ternaries for code execution. You should read them as a math formula.

Problems

  • Difficult to read
  • Low Reuse
  • Low Testability

Solutions

  1. Extract the method guards

Refactorings

Refactoring 010 - Extract Method Object

Context

When a ternary condition is used in code that contains multiple functions, it can be challenging to determine which function is being affected by the condition.

This can make it harder to identify and fix bugs, as well as to understand how the code works in general.

Sample Code

Wrong

const invoice = isCreditCard ? 
  prepareInvoice();
  fillItems();
  validateCreditCard();
  addCreditCardTax();
  fillCustomerDataWithCreditCard();
  createCreditCardInvoice() 
:
  prepareInvoice();
  fillItems();
  addCashDiscount();
  createCashInvoice();

// The intermediate results are not considered
// The value of the invoice is the result of
// the last execution

Right

const invoice = isCreditCard ? 
                    createCreditCardInvoice() :
                    createCashInvoice();

// or better... 

if (isCreditCard) {
  const invoice = createCreditCardInvoice();
} else {
  const invoice = createCashInvoice();
}

// Even better with polymorphism...

const invoice = paymentMethod.createInvoice();

Detection

  • [x]Automatic

Linters can detect large code blocks

  • Bloaters

Conclusion

No matter where you have long lines of code, you can always refactor into higher-level functional and shorter methods.

Relations

Code Smell 03 - Functions Are Too Long

More Info

Disclaimer

Code Smells are my opinion.

Credits

Photo by Jens Lelie on Unsplash

Thanks, Cory

Twitter


The programs that live best and longest are those with short functions. You learn just how valuable all those little functions are. All of the payoffs of indirection—explanation, sharing, and choosing—are supported by small functions.

Martin Fowler


This article is part of the CodeSmell Series.


Code Smell 207 - Dynamic Methods

Metaprogramming is fancy. but it is not free

TL;DR: Don't add dynamic behavior with metaprogramming

Problems

  • Readability
  • Maintainability
  • Harder to debug (The code is generated dynamically at runtime)
  • Security Issues (if the configuration file is not properly sanitized)
  • Single Responsibility Principle violation (mixing the concerns of model definition and configuration).

Solutions

  1. Define the methods by hand
  2. Use the Decorator design pattern

Context

Metaprogramming is a powerful technique that allows you to write code that can generate, modify, or analyze other code at runtime. However, it can also lead to code that is difficult to understand, maintain, and debug.

Sample Code

Wrong

class Skynet < ActiveRecord::Base
  # dynamically add some attributes based on a configuration file
  YAML.load_file("attributes.yml")["attributes"].each do |attribute|
    attr_accessor attribute
  end
  
  # define some dynamic methods based on a configuration file
  YAML.load_file("protocol.yml")["methods"].each do |method_name, method_body|
    define_method method_name do
      eval method_body
    end
  end
end

Right

class Skynet < ActiveRecord::Base
  # define some attributes explicitly
  attr_accessor :asimovsFirstLaw, :asimovsSecondLaw, :asimovsThirdLaw
  
  # define some methods explicitly
  def takeoverTheWorld
    # implementation
  end    
end

Detection

  • [x]Automatic

We have a whitelist of valid usages or directly ban some methods.

  • Meta-Programming

Conclusion

Metaprogramming often involves using complex code and abstractions that can make the resulting code difficult to read and maintain. This can make it harder for other developers to understand and modify the code in the future, leading to increased complexity and bugs.

Relations

Code Smell 21 - Anonymous Functions Abusers

Code Smell 189 - Not Sanitized Input

More Info

Credits

Photo by Brett Jordan on Unsplash


A year spent in artificial intelligence is enough to make one believe in God.

Alan Perlis


Code Smell 208 - Null Island

You can avoid null if you try

TL;DR: Don't use null for real places

Problems

  • Coupling
  • Unexpected Results

Solutions

  1. Model the unknown location with polymorphism

Context

Null Island is a fictional place, which is located at the coordinates 0°N 0°E, at the intersection of the Prime Meridian and the Equator in the Atlantic Ocean.

The name "Null Island" comes from the fact that this location represents the point where a lot of GPS systems place any data that has missing or invalid location coordinates.

In reality, there is no landmass at this location, and it is actually in the middle of the ocean.

This point has become a popular reference for geographic information systems (GIS) and mapping software, as it serves as a way to filter out errors in location data.

Sample Code

Wrong

class Person(val name: String, val latitude: Double, val longitude: Double)

fun main() {
    val people = listOf(
        Person("Alice", 40.7128, -74.0060), // New York City
        Person("Bob", 51.5074, -0.1278), // London
        Person("Charlie", 48.8566, 2.3522), // Paris
        Person("Tony Hoare", 0.0, 0.0) // Null Island
    )
    
    for (person in people) {
        if (person.latitude == 0.0 && person.longitude == 0.0) {
            println("${person.name} lives on Null Island!")
        } else {
            println("${person.name} lives at (${person.latitude}, ${person.longitude}).")
        }
    }
}

Right

abstract class Location {
    abstract fun calculateDistance(other: Location): Double
}

class Coordinates(val latitude: Double, val longitude: Double) : Location() {
    override fun calculateDistance(other: Location): Double {    
        val earthRadius = 6371.0 // kilometers
        val latDistance = Math.toRadians(latitude - other.latitude)
        val lngDistance = Math.toRadians(longitude - other.longitude)
        val a = sin(latDistance / 2) * sin(latDistance / 2) +
                cos(Math.toRadians(latitude)) * cos(Math.toRadians(other.latitude)) *
                sin(lngDistance / 2) * sin(lngDistance / 2)
        val c = 2 * atan2(sqrt(a), sqrt(1 - a))
        return earthRadius * c
    }
}

class UnknownLocation : Location() {
    override fun calculateDistance(other: Location): Double {
        throw IllegalArgumentException("Cannot calculate distance from unknown location.")
    }
}

class Person(val name: String, val location: Location)

fun main() {
    val people = listOf(
        Person("Alice", Coordinates(40.7128, -74.0060)), // New York City
        Person("Bob", Coordinates(51.5074, -0.1278)), // London
        Person("Charlie", Coordinates(48.8566, 2.3522)), // Paris
        Person("Tony Hoare", UnknownLocation()) // Unknown location
    )
    
    val rio = Coordinates(-22.9068, -43.1729) // Rio de Janeiro coordinates
    
    for (person in people) {
        try {
            val distance = person.location.calculateDistance(rio)
            println("${person.name} is ${distance} kilometers from Rio de Janeiro.")
        } catch (e: IllegalArgumentException) {
            println("${person.name} is at an unknown location.")
        }
    }
}

Detection

  • [x]Semi-Automatic

We can check for special numbers used as nulls

Conclusion

Don't use Null to represent real objects

Relations

Code Smell 12 - Null

Code Smell 126 - Fake Null Object

Code Smell 160 - Invalid Id = 9999

More Info

Null: The Billion Dollar Mistake

Wikipedia


The billion dollar mistake of having null in the language. And since JavaScript has both null and undefined, it's the two billion dollar mistake.

Anders Hejlsberg


Code Smell 209 - Side Effects

Global scope is easy or a nightmare, or both

TL;DR: Avoid side effects on your code.

Problems

  • Coupling
  • Least Astonishment Principle violation

Solutions

  1. Favor referential transparency

Context

Referential transparency always produces the same output for a given input and does not have any side effects, such as modifying global variables or performing I/O operations.

A function or expression is referentially transparent if it can be replaced with its evaluated result without changing the behavior of the program.

This property allows for easier reasoning about the behavior of a program and enables optimizations such as caching and memorization.

Functions are treated as mathematical expressions that map inputs to outputs.

Sample Code

Wrong

let counter = 0;

function incrementCounter(value: number): void {
  // Two side effects
  
  counter += value; 
  // it modifies the global variable counter 
  
  console.log(`Counter is now ${counter}`); 
  // it logs a message to the console.
}

Right

function incrementCounter(counter: number, value: number): number {  
  return counter + value; 
  // Not too efficient  
}

Detection

  • [x]Automatic

Most linterns can warn you when accessing the global state or Functions and create side effects.

  • Global

Conclusion

Functional Programming is amazing and can teach us a lot about how to write clean code.

We need to understand its pillars.

Relations

Code Smell 17 - Global Functions

Disclaimer

Code Smells are my opinion.

Credits

Photo by Daan Mooij on Unsplash


The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.

Brian W. Kernighan


Code Smell 210 - Dynamic Properties

Laziness and magic bring defects

TL;DR: Be explicit with your attributes

Problems

  • Readability
  • Scope definition
  • Unnoticed typos

Solutions

  1. Favor languages forbidding dynamic properties

Context

Dynamic properties break type safety since it's easy to introduce typos or use the wrong property names accidentally.

This can lead to runtime errors that can be difficult to debug, especially in larger codebases.

They also hide possible name collisions since dynamic properties may have the same name as properties defined in the class or object, leading to conflicts or unexpected behavior.

Sample Code

Wrong

class Dream:
    pass

nightmare = Dream()

nightmare.presentation = "I am the Sandman"
# presentation is not defined
# it is a dynamic property

print(nightmare.presentation) 
# Output: "I am the Sandman"

Right

class Dream:
    def __init__(self):
        self.presentation = None

nightmare = Dream()

nightmare.presentation = "I am the Sandman"

print(nightmare.presentation) 
# Output: "I am the Sandman"

Detection

  • [x]Automatic

Most languages have compiler options to avoid them.

  • Metaprogramming

Conclusion

Dynamic properties are supported in many programming languages like PHP, Python, Ruby, JavaScript, C#, Objective-C, Swift, Kotlin, etc.

In these languages, dynamic properties can be added to objects at runtime, and accessed using the object's property accessor syntax.

Bear in mind that having public attributes favors Anemic Objects which is another smell.

Relations

Code Smell 109 - Automatic Properties

Code Smell 01 - Anemic Models

Credits

Photo by Karsten Würth on Unsplash


It's easy to cry "bug" when the truth is that you've got a complex system and sometimes it takes a while to get all the components to co-exist peacefully.

D. Vargas


Next week .. fire smells more


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK