Software development tends to be a lot like cooking. You can follow the recipe, but sometimes you end up with something that just doesn’t taste right. In code, we tend to call these bad “flavours” code smells. They do not crush your app immediately, but over time they make the codebase harder to work with.
We all feel them, but sometimes it's hard to name specifically what is wrong. Part of path to advance in your career is shifting from relying on gut-feel and intuition to justifying your choices with broadly accepted standards and paradigms. Let's dive into six lesser-known code smells you might not have heard about but should definitely keep an eye out for!
Primitive obsession
Phrase I heard one day states that primitive obsession is like attempt to build a Lego Millennium Falcon, but using only regular 2x2 bricks. Sure, you can make it work, but it’s not very elegant, comfortable nor efficient. This smell pops up when you use basic (primitive) data types (like strings, ints, or bools) to represent complex concepts data structures. Most often you can see it as storing phone numbers, email addresses, or even money amounts as plain strings or integers everywhere. Another "worthy" mention is ambiguous numbers used as feature toggles or settings.
Example:
// Before
class User {
String email;
}
// After
class User {
Email email;
}
class Email {
private String address;
// Getters, setters
// Validation and other logic here
}
Shotgun Surgery
Shotgun Surgery is a very graphical way to represent situation when changing one thing in your code means you have to tweak a million others. As if changing a lightbulb in your bedroom would force you to change or at least modify every other bulb present at your house. This usually is a sign of unwanted coupling and dependencies in your code. Good start to address those could be patterns like Strategy or Command to isolate changes. Also quick check if your logic and data are well separated may be a good hint.
Example:
// Before
class User {
String name;
String surname;
Datetime birtdate;
}
// After
class User {
Name name;
Age age;
}
class Name {
private String name;
private String surname;
// Methods to manipulate name
// For example in Asia and Balkans
// surname often goes before the name
}
class Age {
private Datetime value;
// Methods to handle age stuff
}
Speculative Generality
Speculative Generality is like packing for a week-long trip and bringing every single item of clothing you own cuz, yeah, you "might need it". This smell appears when you add complexity for future possibilities that never actually happen. End result is same as when during a travel finding that part of swim shorts make you miss the bus. Doing even the simple tasks forces you to got thru tens of pointes classes, abstractions and interfaces.
Fix for that boils down that making code open for changes is not about wrapping in layers of abstractions. Good old YAGNI & KISS principles can help.
Yo-Yo Problem
The Yo-Yo Problem makes your brain (and IDE) bounce up and down like a yo-yo. You have to jump back and forth through layers of inheritance or call chains to understand what’s going on. It’s like trying to understand a conversation by only hearing half of each sentence. Deep inheritance hierarchies. Data manipulation on various application levels. In&Out from wrappers specific implementations and such. Often also reflected in singular DB objects scattered in numerous tables.
Great answer for such trouble can be use of composition over inheritance to reduce complexity. Also seek for forced inheritances in your code or Duck typing.
Feature Envy
Feature Envy is that type of a neighbour who spends more time in your garden than their own. It happens when a class is more interested in the data and methods of another one than its own. Usually it's a prove of rather blurry line during encapsulation. Sure, tight functional connections may happen when working with inheritance or in MVC apps. Issue is more impactful when we work with classes in the similar structure.
Example:
(note like ShoppingCarts plays with Customer data)
// File: Customer.cs
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public bool ValidateEmail()
{
// Simple email validation logic
return Email.Contains("@");
}
}
// File: ShoppingCart.cs
public class ShoppingCart
{
public List<string> Items { get; set; }
public Customer Customer { get; set; }
// Shoping cart logic
public bool IsCustomerEmailValid()
{
return Customer.Email.Contains("@");
}
public void ProcessOrder()
{
if (!IsCustomerEmailValid())
{
throw new InvalidOperationException("Invalid customer email.");
}
// Process the order
}
}
// AFTER
// File: Customer.cs
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public bool IsEmailValid()
{
// Simple email validation logic
return Email.Contains("@");
}
}
// File: ShoppingCart.cs
public class ShoppingCart
{
public List<string> Items { get; set; }
public Customer Customer { get; set; }
// Shoping cart logic
public void ProcessOrder()
{
if (!Customer.IsEmailValid())
{
throw new InvalidOperationException("Invalid customer email.");
}
// Process the order
}
}
Refused Bequest
Refused Bequest is like inheriting a mansion you can’t afford to maintain. Subclasses get methods and properties they don’t need or want from their parent class, leading to messy overrides or ignored inherited features. One of most painful proofs that you overdid it with OOP. Obvious suggestion would be to rethink your approach to inheritance trees. More clever one however, states that in more complex scenarios using interfaces is easier to manage than keeping OOP consistent.
Example:
javaCopy code// Before
class Bird {
void fly() { /*...*/ }
}
class Penguin extends Bird {
void fly() {
// Can't fly
}
}
// After
interface Flight {
void fly();
}
class Bird implements Flight {
public void fly() { /*...*/ }
}
class Penguin {
// Doesn't implement Flight
}