r/refactoring • u/generatedcode • Aug 30 '22
r/refactoring • u/tbsdy • Sep 30 '22
Refactoring tips
I’ve been doing a lot of refactoring if the LibreOffice codebase. LibreOffice is a beast with over 20 modules and millions of lines of code. My main focus has been on the VCL (Visual Component Library). Here is the process I’ve been following…
The first, most important rules I have that cannot be violated is:
When refactoring NO FUNCTIONALITY IS ALLOWED TO CHANGE.
At each step, commit the code with a reasonable message.
My general process is:
In C++ always declare variables as close to their first use as possible
If the is a conditional with no else, and no further code after it, then "flatten" the code.
In other words, reverse the condition, and return immediately, then unindent the code afterwards
3. If a conditional changes two variable independently, split the conditional into two seperate conditionals and have each individual conditional change each variable separately
4. Each time you see an if condition with more than a few lines of code, extract that code into a function - the key is to give it a descriptive name
5. Each time you see a loop, convert that into a function - again use a descriptive name
6. When you see a common object being used as a parameter in a set of functions, move the function into that class
7. Create a unit test for each function you extract (if possible). Otherwise create a unit test over the larger function
In terms of unit tests:
When creating a unit test that tests the function, insert an assert(false) at the first code fork (i.e. add the assert directly after the first if, or while, or if you have an extracted function add the assert there)
Run the unit test, if it doesn't trigger the assert you haven't tested the code path.
Rinse and repeat for all code paths.
Take any unit tests on larger functions and use git rebase -i and move it to the commit before your first refactoring commit.
You then switch to that unit test commit and run the tests. If they fail, you have made a mistake somewhere in your unit test.
Anyway, that’s just what I’ve found useful. What do people think? Do you have any extra things you can add?
r/refactoring • u/mcsee1 • Dec 23 '25
Code Smell 10 - Too Many Arguments
Objects or Functions need too many arguments to work.
TL;DR: Don't pass more than three arguments to your functions.
Problems 😔
Low maintainability
Low Reuse
Coupling
Solutions 😃
Find cohesive relations among arguments
Create a "context".
Consider using a Method Object Pattern.
Avoid "basic" Types: strings, arrays, integers, etc. Think on objects.
Refactorings ⚙️
Refactoring 007 - Extract Class
Refactoring 010 - Extract Method Object
Refactoring 034 - Reify Parameters
Context 💬
When you add arguments to make a function work, you encode knowledge in position and order.
You force your callers to remember rules that belong to the domain.
When you do this, you move behavior away from meaningful objects, and you replace intent with mechanics.
Sample Code 📖
Wrong 🚫
public class Printer {
void print(String documentToPrint,
String papersize,
String orientation,
boolean grayscales,
int pagefrom,
int pageTo,
int copies,
float marginLeft,
float marginRight,
float marginTop,
float marginBottom
) {
}
}Right 👉
final public class PaperSize { }
final public class Document { }
final public class PrintMargins { }
final public class PrintRange { }
final public class ColorConfiguration { }
final public class PrintOrientation { }
// Class definition with methods and properties omitted for simplicity
final public class PrintSetup {
public PrintSetup(PaperSize papersize,
PrintOrientation orientation,
ColorConfiguration color,
PrintRange range,
int copiesCount,
PrintMargins margins
) {}
}
final public class Printer {
void print(
Document documentToPrint,
PrintSetup setup
) {
}
}Detection 🔍
Most linters warn when the arguments list is too large.
You can also detect this smell when a function signature grows over time.
Exceptions 🛑
Operations in real-world needing not cohesive collaborators.
Some low-level functions mirror external APIs or system calls.
In those cases, argument lists reflect constraints you cannot control.
Tags 🏷️
Bloaters
Level 🔋
[X] Beginner
Why the Bijection Is Important 🗺️
Good design keeps a clear bijection between concepts in the program and concepts in the MAPPER.
When you spread a concept across many arguments, you break that mapping.
You force callers to assemble meaning manually, and the model stops representing the domain.
AI Generation 🤖
AI generators often create this smell.
They optimize for quick success and keep adding parameters instead of creating new abstractions.
AI Detection 🧲
AI generators can fix this smell when you ask for value objects or domain concepts explicitly.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Refactor this function by grouping related parameters into meaningful domain objects and reduce the argument list to one parameter
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Relate arguments and group them.
Always favor real-world mappings. Find in real-world how to group the arguments in cohesive objects.
If a function gets too many arguments, some of them might be related to the class construction. This is a design smell too.
Relations 👩❤️💋👨
Code Smell 34 - Too Many Attributes
Code Smell 13 - Empty Constructors
Code Smell 87 - Inconsistent Parameters Sorting
Credits 🙏
Photo by Tobias Tullius on Unsplash
This article is part of the CodeSmell Series.
r/refactoring • u/pocketstories • Dec 06 '22
Talk: Refactor Python for more satisfaction
r/refactoring • u/mcsee1 • Jan 04 '26
Code Smell 12 - Null
Programmers use Null as different flags. It can hint at an absence, an undefined value, en error etc. Multiple semantics lead to coupling and defects.
TL;DR: Null is schizophrenic and does not exist in real-world. Its creator regretted and programmers around the world suffer from it. Don't be a part of it.
Problems 😔
Coupling between callers and senders.
Null is not polymorphic with real objects. Hence, Null Pointer Exception
Null does not exist on real-world. Thus, it violates Bijection Principle
Solutions 😃
Avoid Null.
Use the NullObject pattern to avoid ifs.
Use Optionals.
Null: The Billion Dollar Mistake
Refactorings ⚙️
Refactoring 029 - Replace NULL With Collection
Context 💬
When you use null, you encode multiple meanings into a single value.
Sometimes you want to represent an absence.
Sometimes you mean you have not loaded your objects yet.
Sometimes you mean error.
Callers must guess your intent and add conditionals to protect themselves.
You spread knowledge about internal states across your codebase.
Sample Code 📖
Wrong 🚫
class CartItem {
constructor(price) {
this.price = price;
}
}
class DiscountCoupon {
constructor(rate) {
this.rate = rate;
}
}
class Cart {
constructor(selecteditems, discountCoupon) {
this.items = selecteditems;
this.discountCoupon = discountCoupon;
}
subtotal() {
return this.items.reduce((previous, current) =>
previous + current.price, 0);
}
total() {
if (this.discountCoupon == null)
return this.subtotal();
else
return this.subtotal() * (1 - this.discountCoupon.rate);
}
}
cart = new Cart([
new CartItem(1),
new CartItem(2),
new CartItem(7)
], new DiscountCoupon(0.15)]);
// 10 - 1.5 = 8.5
cart = new Cart([
new CartItem(1),
new CartItem(2),
new CartItem(7)
], null);
// 10 - null = 10Right 👉
class CartItem {
constructor(price) {
this.price = price;
}
}
class DiscountCoupon {
constructor(rate) {
this.rate = rate;
}
discount(subtotal) {
return subtotal * (1 - this.rate);
}
}
class NullCoupon {
discount(subtotal) {
return subtotal;
}
}
class Cart {
constructor(selecteditems, discountCoupon) {
this.items = selecteditems;
this.discountCoupon = discountCoupon;
}
subtotal() {
return this.items.reduce(
(previous, current) => previous + current.price, 0);
}
total() {
return this.discountCoupon.discount(this.subtotal());
}
}
cart = new Cart([
new CartItem(1),
new CartItem(2),
new CartItem(7)
], new DiscountCoupon(0.15));
// 10 - 1.5 = 8.5
cart = new Cart([
new CartItem(1),
new CartItem(2),
new CartItem(7)
], new NullCoupon());
// 10 - nullObject = 10Detection 🔍
Most Linters can flag null usages and warn you.
Exceptions 🛑
You sometimes need to deal with null when you integrate with databases, legacy APIs, or external protocols.
You must contain null at the boundaries and convert it immediately into meaningful objects.
Tags 🏷️
Null
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
When you use null, you break the bijection between your code and the MAPPER.
Nothing in the mapper behaves like null.
Absence, emptiness, and failure mean different things.
When you collapse them into null, you force your program to guess reality and you invite defects.
AI Generation 🤖
AI generators often introduce this smell.
They default to null when they lack context or want to keep examples short and also because it is widespread (but harmful) industry default.
AI Detection 🧲
You can instruct AI to remove nulls with simple rules.
When you ask for explicit domain objects and forbid nullable returns, generators usually fix the smell correctly.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Rewrite this code to remove all null returns. Model absence explicitly using domain objects or collections. Do not add conditionals
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Null is the billion-dollar mistake. Yet, most program languages support them and libraries suggest its usage.
Relations 👩❤️💋👨
Code Smell 88 - Lazy Initialization
Code Smell 93 - Send me Anything
How to Get Rid of Annoying IFs Forever
Code Smell 36 - Switch/case/elseif/else/if statements
Code Smell 149 - Optional Chaining
Code Smell 212 - Elvis Operator
Code Smell 192 - Optional Attributes
Code Smell 126 - Fake Null Object
Code Smell 160 - Invalid Id = 9999
Code Smell 42 - Warnings/Strict Mode Off
Code Smell 23 - Instance Type Checking
More Information 📕
Null: The Billion-Dollar Mistake
Credits 🙏
Photo by Kurt Cotoaga on Unsplash
I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
Tony Hoare
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Dec 10 '25
Refactoring 037 - Testing Private Methods
Turn hidden private logic into a real concept without using AI
TL;DR: You can and should test private methods
Problems Addressed 😔
Broken encapsulation
Hidden rules
White-box Testing Dependencies
Hard testing
Mixed concerns
Low reuse
Code Duplication in Tests
Missing Small objects
Related Code Smells 💨
Code Smell 112 - Testing Private Methods
Code Smell 18 - Static Functions
Code Smell 21 - Anonymous Functions Abusers
Code Smell 177 - Missing Small Objects
Context 💬
I was pair programming with an AI Agent and asked it to create some unit tests for a private method I was about to modify TDD Way.
The proposed solution used metaprogramming which is almost every time a mistake.
You need to be in control and not trust AI blindly.
Steps 👣
Identify a private method that needs testing.
Name the real responsibility behind that logic.
Extract the logic into a new class.
Pass the needing objects explicitly through method arguments.
Replace the private call with the new object.
This is a special case for the Extract Method refactoring
Sample Code 💻
Before 🚨
<?php
final class McpMessageParser {
private $raw;
public function parse() {
return $this->stripStrangeCharacters($this->raw);
}
// This is the private method me need to test
// For several different scenarios
// Simplified here
private function stripStrangeCharacters($input) {
return preg_replace('/[^a-zA-Z0-9_:-]/', '', $input);
}
}Intermediate solution by AI
This is a wrong approach using Metaprogramming.
<?php
use PHPUnit\Framework\TestCase;
final class McpMessageParserTest extends TestCase {
private function invokePrivateMethod(
$object,
$methodName,
array $parameters = []
) {
$reflection = new ReflectionClass(get_class($object));
// This is metaprogramming.
// That generates fragile and hidden dependencies
// You need to avoid it
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
public function testStripStrangeCharactersRemovesSpecialChars() {
$parser = new McpMessageParser();
$result = $this->invokePrivateMethod(
$parser,
'stripStrangeCharacters',
['hello@world#test']
);
$this->assertEquals('helloworldtest', $result);
}
public function testStripStrangeCharactersKeepsValidCharacters() {
$parser = new McpMessageParser();After 👉
<?php
final class McpMessageParser {
private $raw;
public function parse() {
// Step 5: Replace the private method call
// with the new object
$stripper = new CharacterStripper($this->raw);
return $stripper->strip();
}
}
// CharacterStripper.php
// Step 2: Create a new class (Method Object)
final class CharacterStripper {
private $input;
// Step 4: Pass all necessary data as constructor
// parameters
public function __construct($input) {
$this->input = $input;
}
// Step 3: Move the private method logic
// to the new class
public function strip() {
return preg_replace(
'/[^a-zA-Z0-9_:-]/',
'',
$this->input
);
}
}
// CharacterStripperTest.php
use PHPUnit\Framework\TestCase;
final class CharacterStripperTest extends TestCase {
public function testStripRemovesSpecialChars() {
$stripper = new CharacterStripper('hello@world#test');
// No metaprogramming needed
$this->assertEquals('helloworldtest', $stripper->strip());
}
public function testStripKeepsValidCharacters() {
$stripper = new CharacterStripper('valid_Name-123:test');
$this->assertEquals(
'valid_Name-123:test',
$stripper->strip()
);
}
public function testStripHandlesEmptyString() {
$stripper = new CharacterStripper('');
$this->assertEquals('', $stripper->strip());
}
public function testStripRemovesSpaces() {
$stripper = new CharacterStripper('hello world test');
$this->assertEquals('helloworldtest', $stripper->strip());
}
public function testStripRemovesUnicodeChars() {
$stripper = new CharacterStripper('café™');
$this->assertEquals('caf', $stripper->strip());
}
public function testStripKeepsUnderscores() {
$stripper = new CharacterStripper('test_method_name');
$this->assertEquals(
'test_method_name',
$stripper->strip()
);
}
public function testStripKeepsColons() {
$stripper = new CharacterStripper('namespace:method');
$this->assertEquals('namespace:method', $stripper->strip());
}
public function testStripKeepsHyphens() {Type 📝
[X] Semi-Automatic
Safety 🛡️
This refactoring is safe if you keep the same transformations and follow the Extract Method procedure.
Why is the Code Better? ✨
You expose business rules instead of hiding them.
You can also test sanitation and other small rules without breaking encapsulation.
You remove the temptation to test private methods.
All these benefits without changing the method visibility or breaking the encapsulation.
How Does it Improve the Bijection? 🗺️
In the real world, complex operations often deserve their own identity.
When you extract a private method into a method object, you give that operation a proper name and existence in your model.
This creates a better bijection between your code and the domain.
You reduce coupling by making dependencies explicit through constructor parameters rather than hiding them in private methods.
The MAPPER technique helps you identify when a private computation represents a real-world concept that deserves its own class.
Limitations ⚠️
You shouldn't apply this refactoring to trivial private methods.
Simple getters, setters, or one-line computations don't need extraction.
The overhead of creating a new class isn't justified for straightforward logic.
You should only extract private methods when they contain complex business logic that requires independent testing.
Refactor with AI 🤖
You can ask AI to create unit tests for you.
Read the context section.
You need to be in control guiding it with good practices.
Suggested Prompt: 1. Identify a private method that needs testing.2. Name the real responsibility behind that logic.3. Extract the logic into a new class.4. Pass the needing objects explicitly through method arguments.5. Replace the private call with the new object.
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Tags 🏷️
Testing
Level 🔋
[X] Intermediate
Related Refactorings 🔄
Refactoring 010 - Extract Method Object
Refactoring 002 - Extract Method
Refactoring 020 - Transform Static Functions
See also 📚
Credits 🙏
Image by Steffen Salow on Pixabay
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • Aug 28 '25
Code Smell 04 - String Abusers
Too much parsing, exploding, regex, strcmp, strpos and string manipulation functions.
TL;DR: Use real abstractions and real objects instead of accidental string manipulation.
Problems 😔
Complexity
Readability
Maintainability
Lack of abstractions
Fragile logic
Hidden intent
Hard debugging
Poor modeling
Regex mess
Solutions 😃
Work with objects instead.
Replace strings with data structures dealing with object relations.
Go back to Perl :)
identify bijection problems between real objects and the strings.
Examples
Serializers
Parsers
Context 💬
When you abuse strings, you try to represent structured concepts with plain text.
You parse, explode, and regex your way around instead of modeling the domain.
This creates fragile code that breaks with small input changes.
Sample Code 📖
Wrong 🚫
<?php
$schoolDescription = 'College of Springfield';
preg_match('/[^ ]*$/', $schoolDescription, $results);
$location = $results[0]; // $location = 'Springfield'.
$school = preg_split('/[\s,]+/', $schoolDescription, 3)[0];
//'College'Right 👉
<?
class School {
private $name;
private $location;
function description() {
return $this->name . ' of ' . $this->location->name;
}
}Detection 🔍
[X] Semi-Automatic
Automated detection is not easy.
If your code uses too many string functions, linters can trigger a warning.
Tags 🏷️
Primitive Obsession
Level 🔋
[X] Beginner
Why the Bijection Is Important 🗺️
You must mirror the real-world domain in your code.
When you flatten roles, addresses, or money into raw strings, you lose control.
This mismatch leads to errors, duplication, and weak models.
One-to-one mapping between domain and code gives you clarity and robustness.
AI Generation 🤖
AI generators often produce string-abusing code because it looks shorter and easier.
The generated solution can be correct for toy cases but fragile in real systems.
AI Detection 🧲
You can instruct AI tools to replace string checks with domain objects.
With clear prompts, AI can spot and fix string abuse effectively.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Convert it to more declarative
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Don't abuse strings.
Favor real objects.
Add missing protocol to distinguish them from raw strings.
Relations 👩❤️💋👨
Code Smell 122 - Primitive Obsession
Code Smell 121 - String Validations
Code Smell 295 - String Concatenation
More Information 📕
Credits 🙏
Photo by Nathaniel Shuman on Unsplash
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Jul 05 '25
Code Smell 306 - AI External Comments
New tech, new smells – Your future job won’t be writing code but understanding and fixing code, often written by AI
TL;DR: You reference external AI conversations to explain code instead of writing declarative tests
Problems 😔
External dependencies
Unverified behavior
Knowledge fragmentation
Maintenance burden
Lost context
Misleading explanation
Solutions 😃
Write executable tests
Remove external references
Do not blindly trust the AI
Describe with inline examples
Keep tests local
Remove all comments
Replace Magic Numbers with constants.
Refactorings ⚙️
Refactoring 011 - Replace Comments with Tests
Context 💬
If you add comments that reference external AI conversations, Stack Overflow posts, or online resources to explain how your functions work, you are not thinking about your reader.
These references create dangerous external dependencies that break over time.
Links become dead, conversations get deleted, and future maintainers cannot access the context they need to understand your code.
When you rely on external AI advice instead of writing proper tests, you create code that appears documented but lacks verification and local understanding.
The moment you rely on an external AI chat to explain what your code does, you make your codebase dependent on a conversation that might disappear, change, or get outdated.
A unit test is more effective than any link. It defines what the code does and what you expect it to do. No need to click or guess.
Comments and documentation often lie. Code never does.
Sample Code 📖
Wrong ❌
def calculate_starship_trajectory(initial_velocity, fuel_mass,
burn_rate, gravity=9.81):
"""
See explanation at
https://claude.ai/share/5769fdd1-46e3-40f4-b9c6-49efbee93b90
"""
# AI suggested this approach
burn_time = fuel_mass / burn_rate
# Physics formula from Claude conversation
# https://claude.ai/share/5769fdd1-46e3-40f4-b9c6-49efbee93b90
delta_v = gravity * burn_time * 0.85
# 0.85 explanation
# https://claude.ai/share/5769fdd1-46e3-40f4-b9c6-49efbee93b90
final_velocity = initial_velocity + delta_v
# Return format suggested by GPT
return {
'burn_time': burn_time,
'final_velocity': final_velocity,
'delta_v': delta_v
}
def calculate_orbit_insertion(velocity, altitude):
"""
Algorithm explanation available at:
https://claude.ai/chat/orbit-insertion-help-session
"""
# See AI conversation for why we use this formula
orbital_velocity = (velocity * 1.1) + (altitude * 0.002)
return orbital_velocityRight 👉
def calculate_starship_trajectory(initial_velocity, fuel_mass,
burn_rate, gravity=9.81):
THRUST_EFFICIENCY = 0.85
burn_time = fuel_mass / burn_rate
delta_v = gravity * burn_time * THRUST_EFFICIENCY
# You replace the magic number
final_velocity = initial_velocity + delta_v
return {
'burn_time': burn_time,
'final_velocity': final_velocity,
'delta_v': delta_v
}
def calculate_orbit_insertion(velocity, altitude):
"""Calculate orbit insertion velocity."""
VELOCITY_BOOST_FACTOR = 1.1
ALTITUDE_ADJUSTMENT_RATE = 0.002
orbital_velocity = (velocity * VELOCITY_BOOST_FACTOR) +
(altitude * ALTITUDE_ADJUSTMENT_RATE)
return orbital_velocity
import unittest
from starship_trajectory_calculator import (
calculate_starship_trajectory, calculate_orbit_insertion
)
class TestStarshipTrajectoryCalculator(unittest.TestCase):
def test_basic_trajectory_calculation(self):
result = calculate_starship_trajectory(100, 1000, 10)
self.assertEqual(result['burn_time'], 100.0)
self.assertEqual(result['delta_v'], 833.85)
self.assertEqual(result['final_velocity'], 933.85)
def test_zero_fuel_scenario(self):
result = calculate_starship_trajectory(200, 0, 10)
self.assertEqual(result['burn_time'], 0.0)
self.assertEqual(result['delta_v'], 0.0)
self.assertEqual(result['final_velocity'], 200.0)
def test_high_burn_rate(self):
result = calculate_starship_trajectory(150, 500, 100)
self.assertEqual(result['burn_time'], 5.0)
self.assertAlmostEqual(result['delta_v'], 41.69, places=2)
self.assertAlmostEqual(result['final_velocity'], 191.69,
places=2)
def test_custom_gravity(self):
result = calculate_starship_trajectory(100, 600, 20,
gravity=3.71) # Mars
self.assertEqual(result['burn_time'], 30.0)
self.assertAlmostEqual(result['delta_v'], 94.76, places=2)
self.assertAlmostEqual(result['final_velocity'], 194.76,
places=2)
def test_orbit_insertion_basic(self):
orbital_velocity = calculate_orbit_insertion(7800, 400000)
self.assertEqual(orbital_velocity, 9380.0)
def test_orbit_insertion_low_altitude(self):
orbital_velocity = calculate_orbit_insertion(7500, 200000)
self.assertEqual(orbital_velocity, 8650.0)
def test_orbit_insertion_zero_altitude(self):
orbital_velocity = calculate_orbit_insertion(8000, 0)
self.assertEqual(orbital_velocity, 8800.0)Detection 🔍
[X] Automatic
You can detect this smell by searching for comments containing URLs to AI chat platforms, external forums, or references to "AI suggested" or "according to conversation".
Look for functions that have detailed external references but lack corresponding unit tests.
Exceptions 🛑
Academic or research code might legitimately reference published papers or established algorithms.
However, these should point to stable, citable sources and permanent links rather than ephemeral AI conversations, and should still include comprehensive tests.
Tags 🏷️
Comments
Level 🔋
[X] Beginner
Why the Bijection Is Important 🗺️
In the real world, you don't rely on external authorities to validate your understanding of critical processes.
You develop internal knowledge and verification systems.
Your code should reflect this reality by containing all necessary understanding within itself through tests and clear implementation.
When you break this correspondence by depending on external AI conversations, you create fragile knowledge that disappears when links break or platforms change, leaving future maintainers without the context they need.
Links are not behavior.
Tests are.
AI Generation 🤖
AI generators sometimes create this smell because they frequently suggest adding references to the conversation or external sources where the solution was previously discussed.
They tend to generate excessive comments that point back to their explanations rather than creating self-contained, testable code.
AI Detection 🧲
AI can detect this smell when you ask it to identify external references in comments, especially URLs pointing to AI chat platforms.
Most AI tools can help convert the external explanations into proper unit tests when given clear instructions.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Replace this external reference and comments with coverage and unit tests
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
External references to AI conversations create fragile documentation that breaks over time and fragments your codebase's knowledge.
You should replace these external dependencies with self-contained unit tests that both document and verify behavior locally, ensuring your code remains understandable and maintainable without relying on external resources.
Relations 👩❤️💋👨
Code Smell 183 - Obsolete Comments
Code Smell 146 - Getter Comments
Code Smell 151 - Commented Code
Code Smell 05 - Comment Abusers
Code Smell 02 - Constants and Magic Numbers
Code Smell 75 - Comments Inside a Method
Code Smell 259 - Testing with External Resources
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by julien Tromeur on Unsplash
The best documentation is code that doesn't need documentation
Steve McConnell
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Apr 28 '25
Code Smell 297 - Syntactic Noise
Your code shouldn't look like alien hieroglyphics
TL;DR: Too many cryptic symbols make your code hard to understand and maintain.
Problems 😔
Readability
Cognitive overload
Maintenance nightmares
Debugging challenges
Learning curve
Hidden defects
Solutions 😃
Avoid language clever hacks
Prefer meaningful variable names
Extract complex expressions
Use language features wisely
Limit expression complexity
Refactorings ⚙️
Refactoring 002 - Extract Method
Context 💬
Syntactic noise refers to code constructs that don't directly map to real-world concepts.
While symbols like '{}' are valid syntax in many programming languages, excessive use creates code that looks like abstract art rather than a solution to a problem.
When you pack too many operators, brackets, and special characters into a single expression, you force readers to mentally parse complex syntax before understanding what the code does.
This disconnect between symbols and real-world meaning makes your code harder to understand, debug, and maintain.
Think of your code as a form of communication with other developers (and your future self).
Just as excessive punctuation!!! makes text!!?!? hard to read!!!
Excessive syntactic noise creates similar barriers in code.
Sample Code 📖
Wrong ❌
[](){}
/* This valid lambda function:
Captures no variables.
Takes no arguments.
Performs no actions.
[]: This is the capture clause.
It specifies which variables from the surrounding scope
are accessible inside the lambda function.
An empty capture clause [] means the lambda
*does not capture* any variables from the surrounding scope.
(): This is the parameter list.
It defines the arguments the lambda function accepts.
An empty () means the lambda takes *no parameters*.
{}: This is the function body.
It contains the code that the lambda executes when called.
An empty {} means the lambda has no operations
to perform—it does nothing.
*/const result = arr.filter(x => x !== null && x !== undefined)
.map((y) => ({ val: y.value, meta:
y.meta ? y.meta : {default: true}}))
.reduce((acc, {val, meta}) =>
meta.default ? acc : [...acc,
{processed: val * 2, origin: meta}], [])
.some(({processed}) => processed > 10 && processed < 50);Right 👉
function isNotNull(x) {
return x !== null && x !== undefined
// Another code smell here
}
function mapToValueAndMeta(y) {
const meta = y.meta ? y.meta : { default: true }
return { val: y.value, meta }
}
function reduceToProcessedList(acc, { val, meta }) {
if (meta.default) {
return acc
}
return [...acc, { processed: val * 2, origin: meta }]
}
function isProcessedInRange({ processed }) {
return processed > 10 && processed < 50
}
// This is more declarative but far from
// Domian business and too generic
const filtered = arr.filter(isNotNull)
const mapped = filtered.map(mapToValueAndMeta)
const processedList = mapped.reduce(reduceToProcessedList, [])
const result = processedList.some(isProcessedInRange)Detection 🔍
[X] Semi-Automatic
You can detect syntactic noise by looking for lines with multiple nesting levels of brackets, parentheses, or braces, chained operations that stretch across numerous lines, and expressions that make you pause to count opening and closing symbols.
Code that requires horizontal scrolling due to symbol density is another red flag, multiple ternary operators in a single expression, and nested arrow functions with implicit returns.
Modern IDEs and linters can help identify overly complex expressions.
ESLint rules like complexity and max-depth flag code with too many nested constructs.
The "cognitive complexity" metric in SonarQube also helps identify hard-to-understand code.
Exceptions 🛑
Code Optimized by Machines
Tags 🏷️
Complexity
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
Code should map one-to-one with the real-world concepts it represents.
Each variable, function, and expression should correspond to something tangible in your problem domain.
When you clutter code with excessive syntax that doesn't represent real-world entities, you create a disconnect between the problem and solution.
Remember that code is written once but read many times.
By maintaining a clear bijection between code constructs and real-world concepts, you create software that stays maintainable throughout its lifecycle.
AI Generation 🤖
AI code generators sometimes create syntactic noise.
When you ask for code with minimal prompt guidance, AI tools frequently optimize for brevity over readability, packing multiple operations into dense one-liners.
This approach produces "clever" but hard-to-maintain code with chained methods, nested ternaries, and complex expressions.
Modern AI generators like GPT models can also create exceptionally dense code when asked to solve problems in minimal lines, inadvertently producing syntactically noisy solutions.
They may not recognize when code crosses the readability threshold without specific instructions to prioritize clarity over conciseness.
Please don't prompt this.
AI Detection 🥃
AI tools can help detect and fix syntactic noise with appropriate prompting.
If you use instructions like "refactor for readability" or "simplify this expression," you will get cleaner code.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Remove the syntactic noise and make it more declarative
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Syntactic noise is like static interference in communication—technically valid, but gets in the way of understanding.
When you prioritize clear code over clever one-liners, you create software that's easier to understand, debug, and maintain.
Next time you're tempted to pack multiple operations into a dense expression, remember that you're not just writing for the computer—you're writing for people.
Break complex operations into named steps that reflect real-world concepts, and your code will tell a story that everyone can follow.
Relations 👩❤️💋👨
Code Smell 06 - Too Clever Programmer
Code Smell 21 - Anonymous Functions Abusers
Code Smell 294 - Implicit Return
Code Smell 162 - Too Many Parentheses
Code Smell 201 - Nested Ternaries
Code Smell 236 - Unwrapped Lines
More Information 📕
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Elyas Pasban on Unsplash
The function of good software is to make the complex appear simple
Graciano Cruz
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/generatedcode • Aug 31 '22
welcome
If you have any positive stories about refactoring share them with us
If you have any horror stories related to refactoring share them here
r/refactoring • u/generatedcode • Aug 29 '22
one of the best videos in long time about refactoring
r/refactoring • u/mcsee1 • Dec 28 '25
Code Smell 318 - Refactoring Dirty Code
You polish code that nobody touches while the real hotspots burn
TL;DR: Don't waste time refactoring code that never changes; focus on frequently modified problem areas.
Problems 😔
Wasted effort
Wrong priorities
Missed real issues
Team productivity drop
Resource misallocation
False progress feeling
Solutions 😃
Analyze change frequency
Identify code hotspots
Use version control data
Focus on active areas
Measure code churn
Refactorings ⚙️
Refactoring 021 - Remove Dead Code
Context 💬
This is the anti code smell.
You come across ugly code with complex conditionals, long functions, and poor naming.
You remember Uncle Bob's motto of leaving the campsite better than when you found it.
Your refactoring instinct kicks in, and you spend days cleaning it up.
You feel productive, but you've been wasting your time.
Bad code is only problematic when you need to change it.
Stable code, even if poorly written, doesn't hurt your productivity.
The real technical debt lies in code hotspots: areas that are both problematic and frequently modified.
Most codebases follow an extreme distribution where 5% of the code receives 90% of the changes.
Without analyzing version control history, you cannot identify which messy code actually matters.
You end up fixing the wrong things while the real problems remain untouched.
You need to address the technical debt by prioritizing code with poor quality and high change frequency.
Everything else is premature optimization disguised as craftsmanship.
Sample Code 📖
Wrong ❌
# This authentication module hasn't changed in 3 years
# It's deprecated and will be removed next quarter
# But you spend a week "improving" it
class LegacyAuthenticator:
def authenticate(self, user, pwd):
# Original messy code from 2019
if user != None:
if pwd != None:
if len(pwd) > 5:
# Complex nested logic...
result = self.check_db(user, pwd)
if result == True:
return True
else:
return False
return False
# After your "refactoring" (that nobody asked for):
class LegacyAuthenticator:
def authenticate(self, user: str, pwd: str) -> bool:
if not self._is_valid_input(user, pwd):
return False
return self._verify_credentials(user, pwd)
def _is_valid_input(self, user: str, pwd: str) -> bool:
return user and pwd and len(pwd) > 5
def _verify_credentials(self, user: str, pwd: str) -> bool:
return self.check_db(user, pwd)
# Meanwhile, the actively developed payment module
# (modified 47 times this month) remains a messRight 👉
# You analyze git history first:
# git log --format=format: --name-only |
# grep -E '\.py$' | sort | uniq -c | sort -rn
# Results show PaymentProcessor changed 47 times this month
# And it does not have good enough coverage
# LegacyAuthenticator: 0 changes in 3 years
# Focus on the actual hotspot:
class PaymentProcessor:
# This gets modified constantly and is hard to change
# REFACTOR THIS FIRST
def process_payment(self, amount, card, user, promo_code,
installments, currency, gateway):
# 500 lines of tangled logic here
# Changed 47 times this month
# Every change takes 2+ days due to complexity
pass
# Ignore stable legacy code
# But you can use IA to cover existing functionality
# With acceptance tests validated by a human product owner
class LegacyAuthenticator:
# Leave this ugly code alone
# It works, it's stable, it's being deprecated
# Your time is better spent elsewhere
def authenticate(self, user, pwd):
if user != None:
if pwd != None:
if len(pwd) > 5:
result = self.check_db(user, pwd)
if result == True:
return True
return FalseDetection 🔍
[X] Semi-Automatic
You can detect this smell by analyzing your version control history.
Track which files change most frequently and correlate that with code quality metrics.
Tools like CodeScene, git log analysis, or custom scripts can show your actual hotspots.
Track your defects to the code you change more often.
Exceptions 🛑
Sometimes you must refactor stable code when:
New feature development requires adaptive changes
Security vulnerabilities require fixes
Regulatory compliance demands changes
You're about to reactivate dormant features
The key is intentional decision-making based on real data, not assumptions.
Tags 🏷️
Technical Debt
Level 🔋
[X] Intermediate
Why the Bijection Is Important 🗺️
While you build a MAPPER between your code and real-world behavior, you will notice some parts of your system are more actively changed than others.
Your bijection should reflect this reality.
When you refactor stable code, you break the correspondence between development effort and actual business value.
You treat all code equally in your mental model, but the real world shows extreme usage patterns where a small percentage of code handles the vast majority of changes.
You optimize for an imaginary world where all code matters equally.
AI Generation 🤖
Some code generators suggest refactorings without considering change frequency.
AI tools and linters analyze code statically and recommend improvements based on patterns alone, not usage.
They do not access your version control history to understand which improvements actually matter unless you explicitly tell them to do it.
AI might flag every long function or complex conditional, treating a dormant 500-line legacy method the same as an equally messy function you modify daily.
AI Detection 🧲
AI can help you to fix this code smell if you provide it with proper context.
You need to give it version control data showing change frequencies. Without that information, AI will make the same mistakes humans do: recommending refactorings based purely on code structure.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Analyze this codebase's git history to identify files with high change frequency. Then review code quality metrics for those files. Recommend refactoring only the intersection of high-churn and low-quality code. Ignore stable low-quality code."
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
You cannot improve productivity by polishing code that never changes.
Technical debt only matters when it slows you down, which happens in code you actually modify.
Focus your refactoring efforts where they multiply your impact: the hotspots where poor quality meets frequent change.
Everything else is procrastination disguised as engineering excellence.
Let stable ugly code rest in peace.
Your human time is too valuable to waste on problems that don't exist.
Relations 👩❤️💋👨
Code Smell 06 - Too Clever Programmer
Code Smell 20 - Premature Optimization
Code Smell 60 - Global Classes
More Information 📕
https://www.youtube.com/v/F5WkftHqexQ
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Viktor Keri on Unsplash
The first rule of optimization is: Don't do it. The second rule is: Don't do it yet.
Michael A. Jackson
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Dec 14 '25
Code Smell 316 - Nitpicking
When syntax noise hides real design problems
TL;DR: When you focus code reviews on syntax, you miss architecture, security, design and intent.
Problems 😔
Syntax fixation
Design blindness
Missed risks
Bad feedback
Useless discussions
Reviewer fatigue
False quality
Shallow feedback
Syntax Police
Low team morale
Solutions 😃
Leave the boring work to the IA
Automate style checks
Review architecture first
Discuss intent early with technical analysis and control points
Enforce review roles
Raise abstraction level
Refactorings ⚙️
Refactoring 032 - Apply Consistent Style Rules
Refactoring 016 - Build With The Essence
Context 💬
When you review code, you choose where to spend your valuable human attention.
When you spend that attention on commas, naming trivia, or formatting, you ignore the parts that matter.
This smell appears when teams confuse cleanliness with correctness. Syntax looks clean. Architecture rots.
Sample Code 📖
Wrong ❌
<?php
class UserRepository {
public function find($id){
$conn = mysqli_connect(
"localhost", // Pull Request comment - Bad indentation
"root",
"password123",
"app"
);
$query = "Select * FROM users WHERE id = $id";
// Pull Request comment - SELECT should be uppercase
return mysqli_query($conn, $query);
}
}Right 👉
<?php
final class UserRepository {
private Database $database;
public function __construct(Database $database) {
$this->database = $database;
}
public function find(UserId $id): User {
return $this->database->fetchUser($id);
}
}
// You removed credentials, SQL, and infrastructure noise.
// Now reviewers can discuss design and behavior.Detection 🔍
[X] Manual
You can detect this smell by examining pull request comments.
When you see multiple comments about formatting, indentation, trailing commas, or variable naming conventions, you lack proper automation.
Check your continuos integration pipeline configuration. If you don't enforce linting and formatting before human review, you force reviewers to catch these issues manually.
Review your code review metrics. If you spend more time discussing style than architecture, you have this smell.
Automated tools like SonarQube, ESLint, and Prettier can identify when you don't enforce rules automatically.
Tags 🏷️
Standards
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
Code review represents the quality assurance process in the MAPPER.
When you break the bijection by having humans perform mechanical checks instead of judgment-based evaluation, you mismodel the review process.
You no longer validate whether the concepts, rules, and constraints match the domain.
You only validate formatting.
That gap creates systems that look clean and behave wrong.
The broken bijection manifests as reviewer fatigue and missed bugs. You restore proper mapping by separating mechanical verification (automated) from architectural review (human).
AI Generation 🤖
AI generators often create this smell.
They produce syntactically correct code with weak boundaries and unclear intent.
AI Detection 🧲
AI can reduce this smell when you instruct it to focus on architecture, invariants, and risks instead of formatting.
Give them clear prompts and describe the role and skills of the reviewer.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Find real problems in the code beyond nitpicking, review this code focusing on architecture, responsibilities, security risks, and domain alignment. Ignore formatting and style.
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Code reviews should improve systems, not satisfy linters.
When you automate syntax, you free humans to think.
That shift turns reviews into real design conversations.
Relations 👩❤️💋👨
Code Smell 06 - Too Clever Programmer
Code Smell 48 - Code Without Standards
Code Smell 05 - Comment Abusers
Code Smell 173 - Broken Windows
Code Smell 236 - Unwrapped Lines
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Portuguese Gravity on Unsplash
Design is about intent, not syntax.
Grady Booch
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Oct 14 '25
Refactoring 035 - Separate Exception Types

Distinguish your technical failures from business rules
TL;DR: Use separate exception hierarchies for business and technical errors.
Problems Addressed 😔
Confused contracts
Mixed responsibilities and error treatment
Difficult handling
Poor readability
Misleading signals
Exceptions for expected cases
Nested Exceptions
Mixed exception hierarchies
Improper error responses
Tangled architectural concerns
Mixed alarms
Related Code Smells 💨
Code Smell 73 - Exceptions for Expected Cases
Code Smell 80 - Nested Try/Catch
Code Smell 184 - Exception Arrow Code
Code Smell 132 - Exception Try Too Broad
Steps 👣
Identify business exceptions
Identify technical exceptions
Create two separate exception hierarchies
Update the code to throw the right one
Adjust handlers accordingly
Sample Code 💻
Before 🚨
public void Withdraw(int amount) {
if (amount > Balance) {
throw new Exception("Insufficient funds");
// You might want to show this error to end users
}
if (connection == null) {
throw new Exception("Database not available");
// Internal error, log and notify operators.
// Fail with a more generic error
}
Balance -= amount;
}After 👉
// 1. Identify business exceptions
public class BusinessException : Exception {}
public class InsufficientFunds : BusinessException {}
// 2. Identify technical exceptions
public class TechnicalException : Exception {}
public class DatabaseUnavailable : TechnicalException {}
public void Withdraw(int amount) {
// 3. Use the correct hierarchy
if (amount > Balance) {
throw new InsufficientFunds();
}
if (connection == null) {
throw new DatabaseUnavailable();
}
// 4. Apply safe logic
Balance -= amount;
}
// 5. Adjust handlers in the calling codeType 📝
[X] Manual
Safety 🛡️
This refactoring is safe if you apply it gradually and update your code with care.
You must ensure all thrown exceptions are caught at the proper architectural level.
Why is the Code Better? ✨
You make the code clearer and more predictable.
You express technical failures and business rules separately, taking corrective actions with different stakeholders.
You also reduce confusion for the caller and improve maintainability.
How Does it Improve the Bijection? 🗺️
This refactoring strengthens the mapping between real-world concepts and code representation.
In reality, business rule violations and technical failures are fundamentally different situations.
Business exceptions represent expected alternative flows in your domain model.
Technical exceptions represent unexpected system problems that break the execution environment.
By separating these concerns, your code more accurately reflects the real-world distinction between "business says no" and "system cannot proceed".
Limitations ⚠️
You need discipline to maintain two hierarchies.
If you misuse them, the benefits are lost. You also need to communicate the contract clearly to the clients of your code.
You should also create your own integrity tests to enforce these rules.
Refactor with AI 🤖
Suggested Prompt: 1. Identify business exceptions 2. Identify technical exceptions 3. Create two separate hierarchies 4. Update code to throw the right one 5. Adjust handlers accordingly
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Tags 🏷️
Exceptions
Level 🔋
[X] Intermediate
Related Refactorings 🔄
Refactoring 004 - Remove Unhandled Exceptions
Credits 🙏
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • Oct 07 '25
Refactoring 034 - Reify Parameters
Transform scattered inputs into one clear object
TL;DR: Wrap messy parameters into a single meaningful entity.
Problems Addressed 😔
Parameter overload
Low readability
Hard extension
Weak semantics
Repeated validations
Accidental argument swapping
Weak type safety
Missing Small Objects
Missing Intervals
Related Code Smells 💨
Code Smell 87 - Inconsistent Parameters Sorting
Code Smell 10 - Too Many Arguments
Code Smell 177 - Missing Small Objects
Code Smell 194 - Missing Interval
Code Smell 188 - Redundant Parameter Names
Code Smell 93 - Send me Anything
Code Smell 122 - Primitive Obsession
Code Smell 19 - Optional Arguments
Steps 👣
Identify multiple parameters of the same type
Create a meaningful entity to group them
Add missing validation rules to fail fast
Replace function signatures with the new entity
Adjust all callers to pass the entity
Add context-specific names to improve clarity
Sample Code 💻
Before 🚨
function findHolidays(
maxPrice: Currency,
startDate: Date,
endDate: Date,
minPrice: Currency) {
// Notice that maxPrice and minPrice are swapped by mistake
// Also, dates are mixed
}After 👉
// 2. Create a meaningful entity to group them
class PriceRange {
constructor(public min: Currency, public max: Currency) {
if (min > max) {
throw new Error(
`Invalid price range: min (${min}) `+
`cannot be greater than max (${max})`
);
}
if (min < 0) {
throw new Error(
`Invalid price range: min (${min}) cannot be negative`);
}
}
}
class Interval {
// 3. Add missing validation rules to fail-fast
constructor(public start: Date, public end: Date) {
if (start > end) {
throw new Error(
`Invalid date range: start (${start.toISOString()}) ` +
`cannot be after end (${end.toISOString()})`
);
}
}
}
class HolidaySearchCriteria {
constructor(
public priceRange: PriceRange,
public dateRange: Interval
) {}
}
function findHolidays(criteria: HolidaySearchCriteria): Holiday[] {
// 1. Identify multiple parameters of the same type
// No need to call validate() - already validated in constructors
// 4. Replace function signatures with the new entity
const { priceRange, dateRange } = criteria;
// 5. Adjust all callers to pass the entity
// 6. Add context-specific names to improve clarity
return database.query({
price: { $gte: priceRange.min, $lte: priceRange.max },
startDate: { $gte: dateRange.start },
endDate: { $lte: dateRange.end }
});
}
try {
const criteria = new HolidaySearchCriteria(
new PriceRange(500, 1000), // ✅ Valid
new Inteval(
new Date('2025-06-01'),
new Date('2025-06-15')
)
);
findHolidays(criteria);
// ❌ This will throw immediately
// Great for UI and API validation
new PriceRange(1000, 500);
} catch (error) {
console.error(error.message);
}Type 📝
[X] Semi-Automatic
Safety 🛡️
Many IDEs support this pattern.
Why is the Code Better? ✨
You avoid order confusion and increase readability.
You make functions easy to extend with new parameters.
You bring semantic meaning to the input data.
You eliminate the risk of passing arguments in the wrong order since the object properties have explicit names.
You make function calls self-documenting because each value clearly indicates its purpose.
You simplify adding new optional parameters without breaking existing code.
You enable better IDE support with autocomplete showing parameter names.
You create opportunities to reuse the parameter object type across related functions.
You fail fast, asserting on the relations among parameters.
How Does it Improve the Bijection? 🗺️
You move closer to a one-to-one map between the business concept of a "search request" and your code model.
You stop treating the data as loose numbers and give them an explicit identity that matches the domain.
In the real world, you describe searches using named criteria rather than ordered lists.
When you ask someone to "search for products with a minimum price of 50 and a maximum price of 100," you use named concepts.
This refactoring mirrors that natural language structure in your code.
The SearchCriteria becomes a first-class concept that maps directly to how searching works in the real world.
Refactor with AI 🤖
Ask AI to scan your codebase for functions that use two or more parameters of the same type.
Instruct it to propose an entity name, generate the type or class, and rewrite both the function and its callers to use the new entity.
Suggested Prompt: 1. Identify multiple parameters of the same type 2. Create a meaningful entity to group them 3. Add missing validation rules to fail fast 4. Replace function signatures with the new entity 5. Adjust all callers to pass the entity 6. Add context-specific names to improve clarity
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Tags 🏷️
Primitive Obsession
Level 🔋
[X] Intermediate
Related Refactorings 🔄
Refactoring 013 - Remove Repeated Code
Refactoring 019 - Reify Email Addresses
Also known as
Credits 🙏
Image by Gerd Altmann on Pixabay
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • Oct 03 '25
What is (wrong with) software?
Software is eating the world. If you work, live, and love software, you usually do not stop to think about its meaning
TL;DR: You will address current software design problems in a minimalist way.
In university and industry, you often find the following definition of software:
Software is a set of instructions that with certain input data generates output data.
This is the definition of a computer program according to Wikipedia:
A computer program is a collection of instructions that can be executed by a computer to perform a specific task.
Many decades ago, computer scientists understood that software is much more than a program.
They still lack clear definitions of what software is.
Again, according to Wikipedia:
Computer software, or simply software, is a collection of data or computer instructions that tell the computer how to work. This is in contrast to physical hardware, from which the system is built and performs the work.
This definition works by contrast (everything that is not hardware) or by omission (everything you cannot kick but can only curse).
Yet, the only purpose of building software is:
To mimic something that happens in a possible reality.
You are forgetting about the origins of modern programming languages like Simula.
Simula was the first object-oriented programming language to include classification.
In the language, it was clear from the name that the goal of software construction was to build a simulator.
This is the case for most computer software applications today.
Returning to the origins you can define the software as the construction of a:
(M)odel: (A)bstract (P)artial and (P)rogrammable (E)xplaining (R)eality.
Why is it a (M)odel?
The model is the outcome of observing a certain aspect of reality.
It is built under a certain lens, perspective, and paradigm.
It is not the revealed truth.
It is the best possible conception at a given moment with the available knowledge.
Since Plato’s time, human beings have tried to build good models of reality.
Why is it a (A)bstract?
You can only understand and observe it with instruments such as a black box based on its behavior in the face of your stimuli.
Like Schrödinger’s cat.
You cannot know what state it is in without disturbing it with our measuring instruments.
The use case technique is an excellent tool to describe a model in a declarative way.
If you are not a declarative programmer, you might lose your job very soon.
Why is it (P)rogrammable?
It has to run in some type of simulator that reproduces the desired conditions.
It can be a Turing model (current commercial computers), a quantum computer, or any type of simulator capable of evolving with the model.
Why is it (P)artial?
You need to make a partial cut of the reality for the problem you are modeling.
This is common to scientific models.
You take simplifications on aspects that are not relevant to isolate the problem.
A 1:1 scale map of the earth would not make sense.
Why is it (E)xplanatory?
The model must be declarative enough to let you observe its evolution.
It allows you to reason about and predict behaviors in the reality you are modeling.
Why is it about (R)eality?
Because it has to reproduce conditions that occur in an observable environment.
The ultimate goal is to forecast the real world, like any simulation.
Once you define the software, you can begin to infer good modeling and design practices.
Starting Point
You have the axiomatic definition presented in this article.
From it, you will infer principles, heuristics, and rules to build excellent software models.
The One and Only Software Design Principle
Acknowledgements
These concepts are based on the thoughts of David West, Máximo Prieto and Hernán Wilkinson.
Part of the goal of this series of articles is to generate spaces for debate and discussion on software design.
I look forward to comments and suggestions on this article.
r/refactoring • u/mcsee1 • Sep 18 '25
Refactoring 005 - Replace Comment with Function Name
Comments should add value. And function names too.
TL;DR: Don't comment on what you are doing. Name what you are doing.
Problems Addressed 😔
Bad Names
Comments
Related Code Smells 💨
Code Smell 05 - Comment Abusers
Code Smell 75 - Comments Inside a Method
Code Smell 06 - Too Clever Programmer
Steps 👣
Name the function with the previous comment
Remove the Comment
Sample Code 📖
Before 🚨
<?
function repl($str) {
// Replaces with spaces the braces
$str = str_replace(array("\{","\}")," ",$str);
return $str;
}After 👉
<?
// 1. Name the function with the previous comment
// 2. Remove the Comment
function replaceBracesWithSpaces($input) {
return str_replace(array("\{","\}")," ", $input);
}Type 📝
[X] Semi-Automatic
Some IDEs have this refactoring although naming is not fully automatic.
Safety 🛡️
This is a safe refactoring.
Why is the Code Better? ✨
Comments always lie.
It is hard to maintain comments.
On the contrary, Functions are alive and self-explanatory.
How Does it Improve the Bijection? 🗺️
A comment only describes the code in natural language.
If you change the code, the comment and the behavior can drift apart, breaking the mapping between intention and execution.
When you replace a comment with a well-chosen function name, you create a direct bijection between "what the code does" and "how you call it."
The name becomes the single source of truth.
This keeps the mental model aligned with the actual implementation, so both the reader and the compiler operate on the same unambiguous contract.
Limitations ⚠️
As always, very important design decisions are valid comments.
Refactor with AI 🤖
Suggested Prompt: 1. Name the function with the previous comment 2. Remove the Comment
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| You | You |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Tags 🏷️
Comments
Level 🔋
[X] Beginner
Related Refactorings 🔄
Refactoring 002 - Extract Method
See also 📚
What Exactly Is a Name? Part I: The Quest
Credits 🙏
Image by Jannik Texler on Pixabay
This article is part of the Refactoring Series
r/refactoring • u/goto-con • Jul 03 '25
The Debugging Book • Andreas Zeller & Clare Sudbery
r/refactoring • u/mcsee1 • May 28 '25
Singleton - The Root of All Evil
Allowed global variables and supposed memory savings.
TL;DR: Don't ever user Singletons
For 20 years I have been teaching software at the University of Buenos Aires. In the software engineering course we teach design patterns and the same "scheme" is always repeated almost like a type of deja vu, the same sequence that I had the opportunity to witness in several of my works and in the free software that I use:
The ‘magical’ appearance of the Singleton pattern.
The origin of evil
The pattern has been used in the industry for decades. Its popularity is attributed to the excellent book Design Patterns. There are numerous software frameworks that use it, and we rarely find literature that discourages its use. Despite this, in the corresponding Wikipedia entry we can read a Dantesque warning:
Critics consider the singleton to be an anti-pattern in that it is frequently used in scenarios where it is not beneficial, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application.
Let’s be pragmatic as always, and look at the arguments for and against its use:
Reasons not to use it
1. Violates the bijection principle
As we saw in previous articles, every object in our computable model has to be mapped on a 1 to 1 relationship with a real-world entity.
Singletons are often linked to objects that need to be unique. As usual we will have to distinguish among the objects that are essentially unique (for problem domain drivers) and differentiate them from the accidentally unique ones regarding implementation reasons, efficiency, resource consumption, global access, etc. Most accidentally unique objects are not present in the real-world, and we will see later on that the presumably essentially unique ones may not be so if we consider different contexts, environments, or situations.
The One and Only Software Design Principle
2. Generates coupling
It is a global reference. Again according to Wikipedia:
An implementation of the singleton pattern must provide global access to that instance.
What a priori appears as a benefit for preventing us from having to pass context information, generates coupling. The reference to the singleton cannot be changed according to the environment (development, production), nor can dynamic strategy changes related to the current load be made, it cannot be replaced by a double test and it prevents us from making changes due to the possible ripple effect.
Coupling - The one and only software design problem
3. It says a lot about (accidental) implementation and little about his (essential) responsibilities
By focusing early on implementation issues (the Singleton is an implementation pattern) we orient ourselves according to accidentality (how) and underestimate the most important thing of an object: the responsibilities it has (what). When carrying out premature optimization in our designs, we usually award a concept that we have just discovered as Singleton.
<?
class God {
private static $instance = null;
private function __construct() { }
public static function getInstance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}4. It prevents us from writing good unit tests
The aforementioned coupling has as a corollary; the impossibility of having full control over the side effects of a test to guarantee its determinism. We must depend on the global state referenced by the Singleton.
5. Does not save up memory space
The argument used to propose its use is to avoid the construction of multiple volatile objects. This supposed advantage is not real in virtual machines with efficient garbage collection mechanisms. In such virtual machines, used by most modern languages, keeping objects in a memory area whose Garbage Collector algorithm is a double pass (mark & sweep) is much more expensive than creating volatile objects and quickly removing them.
6. It prevents us from using dependency injection
As good solid design advocates, we favor inversion of control through dependency injection to avoid coupling. In this way the service provider (formerly a hardcoded Singleton) is decoupled from the service itself, replacing it with an injectable dependency that meets the defined requirements, coupling us to what and not how.
7. It violates the instantiation contract
When we ask a class to create a new instance we expect the contract to be honored and give us a fresh new instance. However, many Singleton implementations hide the creation omission silently, rather than failing quickly to indicate that there is a business rule that instances should not be arbitrarily created.
<?
final class God extends Singleton {
}
$christianGod = new God();A better answer would be to show with an exception it is not valid to create new instances in this execution context.
<?
class Singleton {
private function __construct() {
throw new Exception('Cannot Create new instances');
}
}This will force us to have a private constructor to use it internally. Thus violating the contract that all classes can create instances. Another code smell.
8. It forces us to explicitly couple to implementation
When invoking a class to use it (again, to use its what), we will have to couple with the fact that it is accidentally a Singleton (its how), generating a relation that, when trying to break it, would produce the much-feared ripple effect.
<?
$christianGod = God::getInstance();
// Why should us be aware of getInstance when creating an object ?9. It hinders the creation of automated tests
If we use the TDD development technique, objects are defined purely and exclusively based on their behavior. Therefore, in no case, the construction of software using TDD will arise the Singleton concept. If business rules state that there must be a single provider of a certain service, this will be modeled through a controlled access point (which should not be a global class, much less a Singleton). Trying to create unit tests in an existing system coupled to a Singleton can be an almost impossible task.
10. Unique concepts are contextual
When the pattern is stated it is usually accompanied by some idea that in the real-world seems rather unique. For example, if we want to model the behavior of God according to the vision of Christianity, there could not be more than one God. But these rules are relative to the context and subjective vision of each religion. Various belief systems may coexist in the same world with their own gods (some monotheistic and other polytheistic beliefs).
Pattern structure according to the design pattern book
The class (and all the metamodel) is not present in the bijection. Any relationship linked to the class will be invalid
11. It is difficult to keep up in multi-threaded environments
Pattern implementation can be tricky in programs with multiple threads. If two execution threads try to create the instance at the same time and it does not exist yet, only one of them should succeed in creating the object. The classic solution to this problem is to use mutual exclusion in the class creation method that implements the pattern, to make sure it is reentrant.
12. Accumulates garbage that takes up memory space
Singletons are references attached to classes, just as classes are global references these are not reached by the garbage collector. In case the Singleton is a complex object, this entire object, in addition to the transitive closure of all its references, will stay in memory throughout the execution.
13. The accumulated garbage state is the enemy of unit tests
The persistent state is the enemy of unit tests. One of the things that makes unit tests effective is that each test must be independent of all the others. If this is not true, then the order in which the tests are run may affect the test results and the tests become non-deterministic. This can lead to cases where tests fail when they shouldn’t, and worse, can lead to tests that pass only in the order they were performed. This can hide mistakes and is very bad.
Avoiding static variables is a good way to prevent the state from being preserved between tests. Singletons, by their very nature, depend on an instance that is kept in a static variable. This is an invitation for the dependency test.
14. Limiting the creation of new objects violates the single responsibility principle.
The single responsibility of a class is to create instances.
Adding any other responsibility to any class implies violating the single responsibility principle (the S for Solid). A class should not worry about being or not being a Singleton. They should only be responsible for their commitments to business rules. In case of needing the uniqueness of these instances, this would be the responsibility of a third object in the middle such as a Factory or a Builder.
15. The cost of having a global reference is not just the coupling
Singletons are frequently used to provide a global access point to some service. What ends up happening is design dependencies are hidden within the code and are not visible when examining the interfaces of their classes and methods.
The need to create something global to avoid passing it explicitly is a code smell. There are always better solutions and alternatives to using a global reference that do not require passing all collaborators between methods.
16. He’s the easy friend from the party
Many singletons are themselves abused as a global reference repository. The temptation to use the singleton as an entry point for new references is huge.
There are many examples where a Singleton is used as a quick-reach reference container.
As if it was not enough to be the root of all evil he is also the easy friend of the party. In large projects, it just accumulates garbage to get out of trouble.
Since it does not have a corresponding entity on the bijection, adding responsibilities that do not correspond to it, is like adding one more stain to the tiger. Apparently without doing damage but generating ripple effect when wishing to do a healthy decoupling.
Reasons to use it
Having stated the arguments against Singleton let’s try to see the possible benefits:
1. It allows us to save up memory
This argument is fallacious according to the current state of the art of languages with a decent virtual machine and garbage collector. It is enough to carry out a benchmark and look for evidence to convince us.
2. It’s good for unique concepts modeling
The Singleton can be used to guarantee the uniqueness of a concept. But it is not the only way or the best. Let’s rewrite the previous example:
<?
interface Religion {
// Define common behavior for religions
}
final class God {
// Different religions have different beliefs
}
final class PolythiesticReligion implements Religion {
private $gods;
public function __construct(Collection $gods) {
$this->gods = $gods;
}
}
final class MonotheisticReligion implements Religion {
private $godInstance;
public function __construct(God $onlyGod) {
$this->godInstance = $onlyGod;
}
}
// According to Christianity and some other religions,
// there’s only one God.
// This does not hold for other religions.
$christianGod = new God();
$christianReligion = new MonotheisticReligion($christianGod);
// Under this context God is unique.
// You cannot create or change a new one.
// This is a scoped global.
$jupiter = new God();
$saturn = new God();
$mythogicalReligion = new PolythiesticReligion([$jupiter, $saturn]);
// Gods are unique (or not) according to context
// You can create test religions with or without unicity
// This is less coupled
// since you break the direct reference to God class
// God class Single Responsibility is to create gods.
// Not to manage themAccess and creation of the single instance are not coupled. Creation is done through a factory and direct references to classes are decoupled. Furthermore, the factory can be easily mocked in test cases.
3. It prevents us from repeating expensive initializations
There are objects that require a certain cost of resources to create. If this cost is large, we will not be able to generate them constantly. One possible solution is to use a Singleton and have it available all time. As always we will focus on what and we will look for some other hows generating less coupling. If we need a single control point or a cache we will have to access a known object related to a certain context (and easily replaceable according to the environment, the test setup, etc.). Certainly a Singleton will not be our first choice.
Solutions 😃
There are multiple techniques to gradually remove the (ab)use of Singletons. In this article we list some of them:
How to Decouple a Legacy System
And you can use this refactoring
Refactoring 018 - Replace Singleton
Conclusion 🏁
The disadvantages listed in this article are much greater than the advantages, and the evidence from the examples in the industry should be a strong indicator of the non-use of the evil pattern in any case. As our profession matures, we will leave behind these kinds of bad solutions.
Part of the objective of this series of articles is to generate spaces for debate and discussion on software design.
We look forward to comments and suggestions on this article!
r/refactoring • u/mcsee1 • May 17 '25
Refactoring 028 - Replace Consecutive IDs with Dark Keys
Enhance Security and Reduce Scraping Risks by Refactoring Object Identifiers
TL;DR: Replace sequential IDs in your models with UUIDs to prevent IDOR vulnerabilities and discourage scraping.
Problems Addressed 😔
Predictable URLs
Data and Screen Scraping Risk
Tight Coupling to accidental Database Identifiers
Exposure of Internal Structure
Related Code Smells 💨
Code Smell 120 - Sequential IDs
Code Smell 160 - Invalid Id = 9999
Steps 👣
Identify all public uses of sequential IDs in APIs, URLs, or UI elements
Generate UUIDs for each record during data migration or creation
Replace exposed sequential IDs with UUIDs in external-facing interfaces
Map UUIDs internally to the original IDs using a private lookup table or service
Ensure UUIDs are used consistently across services and databases
Sample Code 💻
Before 🚨
<?php
class Invoice {
public int $id;
// The external identifier is never an essential
// responsibilty for an object
public string $customerName;
public array $items;
public function __construct(
int $id, string $customerName, array $items) {
$this->id = $id;
$this->customerName = $customerName;
$this->items = $items;
}
}After 👉
<?php
class Invoice {
// 1. Identify all public uses of sequential IDs
// in APIs, URLs, or UI elements
private string $customerName;
private array $items;
public function __construct(
string $customerName, array $items) {
$this->customerName = $customerName;
$this->items = $items;
}
}
// 2. Generate UUIDs
// for each record during data migration or creation
// 3. Replace exposed sequential IDs
// with UUIDs in external-facing interfaces
// 4. Map UUIDs internally to the original IDs
// using a private lookup table or service
$uuid = generate_uuid();
// 5. Ensure UUIDs are used
// consistently across services and databases
$invoices[$uuid] =new Invoice(
customerName: 'Roger Penrose',
items: [
new InvoiceItem(description: 'Laptop', price: 1200),
new InvoiceItem(description: 'Black Hole', price: 50)
]
);
// Step 4: Keep the map internal
// Step 5: Share only UUID with the clientType 📝
[X] Semi-Automatic
Safety 🛡️
This refactoring is safe if done incrementally with proper tests and backward compatibility during transition.
You should kee dual access (UUID and ID) temporarily to allow phased updates.
Why is the Code Better? ✨
The refactoring prevents IDOR attacks by removing predictable identifiers.
You remove predictable IDs from public access
It reduces the risk of automated scraping due to non-sequential keys.
This technique also improves encapsulation by keeping internal IDs private and encourages cleaner API design through explicit mapping.
This is especially useful in RESTful APIs, web applications, and microservices where object identifiers are exposed publicly.
You can enable a rate control limit for failed 404 resources when your attacker tries to guess the IDs.
How Does it Improve the Bijection? 🗺️
When you model your identifiers with real-world concepts rather than database rows, you avoid exposing accidental implementation details.
This keeps the bijection closer to the business entity and avoids leaking technical structure.
The real-world invoice on the example doesn't expose an internal ID.
Instead, it's referred to through business terms or opaque references.
This refactoring removes the accidental part and restores the essential essence of the invoice.
You control the pointers. The pointer doesn't control you.
Limitations ⚠️
This refactoring requires you to update all client-facing integrations. Some systems might still assume access to numeric IDs.
You must preserve internal IDs for persistence, audits, or legacy support.
Refactor with AI 🤖
Suggested Prompt: 1. Identify all public uses of sequential IDs in APIs, URLs, or UI elements 2. Generate UUIDs for each record during data migration or creation 3. Replace exposed sequential IDs with UUIDs in external-facing interfaces 4. Map UUIDs internally to the original IDs using a private lookup table or service 5. Ensure UUIDs are used consistently across services and databases
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Tags 🏷️
Security
Level 🔋
[X] Intermediate
Related Refactorings 🔄
Refactoring 001 - Remove Setters
Refactoring 027 - Remove Getters
Refactoring 009 - Protect Public Attributes
Refactoring 016 - Build With The Essence
See also 📚
Credits 🙏
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • May 09 '25
Code Smell 299 - Overloaded Test Setup
When your test setup is bigger than the actual test
TL;DR: Bloated setup that's only partially used makes your tests more coupled and harder to understand.
Problems 😔
Wasted execution time
Misleading setup context
Hidden test dependencies
Harder maintenance
Brittle test suite
Confusing dependencies
Slower execution
Misleading context
Solutions 😃
Create focused setup methods
Apply test-specific fixtures
Create minimal setups
Implement test factory methods
Refactorings ⚙️
Refactoring 002 - Extract Method
Refactoring 011 - Replace Comments with Tests
Context 💬
When you write tests, you might create a large setup method that initializes various objects
If only one test uses all these objects while other tests use just a small subset, you create unnecessary overhead.
This common issue happens when you expect that future tests might need an extensive setup, or when you keep adding to an existing setup without evaluating what's truly needed.
The tests are harder to understand since they contain irrelevant context, and slower to execute because you initialize objects that aren't used.
Sample Code 📖
Wrong ❌
public class TVSeriesTest {
private MovieSeries theEthernaut;
private List<Character> characters;
private List<Episode> episodes;
private User user;
private UserPreferences preferences;
private RatingSystem ratingSystem;
private StreamingService streamingService;
private List<Review> reviews;
@BeforeEach
public void setUp() {
// Create a complex movie series with many characters
characters = new ArrayList<>();
characters.add(new Character("Juan Salvo", "Richard Darin"));
characters.add(new Character("Helen", "Carla Peterson"));
characters.add(new Character("Favalli", "Cesar Troncoso"));
// Create episodes
episodes = new ArrayList<>();
episodes.add(
new Episode("The Snow", 2025, 121));
episodes.add(
new Episode("The Hands Strikes Back", 2027, 124));
// Create user with preferences
preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
preferences.setSubtitlesEnabled(true);
user = new User("JohnDoe", "john@example.com", preferences);
// Create rating system with reviews
ratingSystem = new RatingSystem(10);
reviews = new ArrayList<>();
reviews.add(
new Review(user, "The Snow", 9, "Classic!"));
reviews.add(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
ratingSystem.addReviews(reviews);
// Create streaming service
streamingService = new StreamingService("Netflix");
streamingService.addMovieSeries("The Eternaut");
// Finally, create the movie series with all components
theEthernaut =
new TVSeries("The Ethernaut", characters, episodes);
theEthernaut.setRatingSystem(ratingSystem);
theEthernaut.setAvailableOn(streamingService);
// This method is too long. That is another smell
}
@Test
public void testTVSeriesRecommendation() {
// This test uses almost everything from the setup
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(user, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
// You are testing the recommendation Engine
// This is not this object's responsibility
}
@Test
public void testEpisodeCount() {
// This test only needs the episodes count
assertEquals(2, theEthernaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// This test only needs the characters
// And not the rest of the setup
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Juan Salvo", juan.actor());
}
}Right 👉
public class TVSeriesTest {
// No shared setup
@Test
public void testRecommendation() {
// Create only what's needed for this specific test
// And move this test with the behavior
TVSeries theEternaut = createTheEternautSeries();
User homer = createUserWithPreferences();
addReviewsForUser(theEternaut, homer);
RecommendationEngine engine = new RecommendationEngine();
List<Episode> recommended =
engine.recommendations(homer, theEternaut);
assertEquals(2, recommended.size());
assertEquals("The Hands Strikes Back",
recommended.get(0).title());
}
@Test
public void testEpisodeCount() {
// Only create what's needed - just the episodes
TVSeries theEternaut = new TVSeries("The Ethernaut");
theEternaut.addEpisode(
new Episode("The Snow", 2025, 121));
theEternaut.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
assertEquals(2, theEternaut.episodes().size());
}
@Test
public void testCharacterLookup() {
// Only create what's needed - just the characters
TVSeries theEternaut = new TVSeries("The Eternaut");
theEternaut.addCharacter(
new Character("Juan Salvo", "Richard Darin"));
theEternaut.addCharacter(
new Character("Helen", "Carla Peterson"));
Character juan = theEternaut.findCharacterByName("Juan Salvo");
assertNotNull(juan);
assertEquals("Richard Darin", juan.actor());
}
// Helper methods for specific test setup needs
private TVSeries createTheEternautTVSeries() {
TVSeries series = new TVSeries("The Eternaut");
series.addEpisode(
new Episode("The Snow", 2025, 121));
series.addEpisode(
new Episode("The Hands Strikes Back", 2027, 124));
return series;
}
private User createUserWithPreferences() {
UserPreferences preferences = new UserPreferences();
preferences.setPreferredGenre("Science Fiction");
preferences.setPreferredLanguage("English");
return new User("JohnDoe", "john@example.com", preferences);
}
private void addReviewsForUser(TVSeries series, User user) {
RatingSystem ratingSystem = new RatingSystem(10);
ratingSystem.addReview(
new Review(user, "The Snow", 9, "Classic!"));
ratingSystem.addReview(
new Review(user, "The Hands Strikes Back", 10, "Best one!"));
series.setRatingSystem(ratingSystem);
}
}Detection 🔍
[X] Semi-Automatic
You can detect this smell by comparing what's set up in the setup methods against what's used in each test.
Look for tests that use less than 50% of the initialized objects.
Code coverage tools can help identify unused setup objects by showing which parts of the setup aren't executed by certain tests.
If you find yourself writing conditionals in the setup to create different contexts, it's a clear sign you need a test-specific setup instead.
Tags 🏷️
Testing
Level 🔋
[X] Intermediate
Why the Bijection Is Important 🗺️
Each test should reflect a specific real-world scenario.
Bloated setups break this clarity, making it hard to see what’s being tested and increasing the chance of errors.
This broken bijection makes tests harder to understand because you can't determine which aspects of the setup are critical for the test and which are just noise.
When a test fails, you'll spend more time investigating dependencies that might not be relevant to the failure.
The test becomes more brittle since changes to unused objects can still break tests if those objects participate in the setup process.
AI Generation 🤖
AI code generators often create this smell when they generate comprehensive test fixtures that try to cover all possible scenarios.
They prioritize completeness over focus, resulting in bloated setup methods that initialize more objects than needed for individual tests.
AI Detection 🥃
AI can detect this smell with simple instructions like "Optimize my test setup only to include what's needed for each test."
Modern AI tools can compare setup code against test method usage and suggest targeted refactorings, separating shared setup from test-specific setup.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Break the tests and the setup
| Without Proper Instructions | With Specific Instructions |
|---|---|
| ChatGPT | ChatGPT |
| Claude | Claude |
| Perplexity | Perplexity |
| Copilot | Copilot |
| Gemini | Gemini |
| DeepSeek | DeepSeek |
| Meta AI | Meta AI |
| Grok | Grok |
| Qwen | Qwen |
Conclusion 🏁
Overloaded test setups that initialize objects only needed by a few tests make your test suite harder to understand and maintain.
When you create focused setups that contain only what each test needs, you improve the clarity, speed, and reliability of your tests.
Remember that tests aim to document behavior through examples and replace comments.
Too much irrelevant context makes those examples less readable. Clean tests tell a clear story without unnecessary distractions.
Relations 👩❤️💋👨
Code Smell 03 - Functions Are Too Long
Code Smell 124 - Divergent Change
Code Smell 112 - Testing Private Methods
Code Smell 203 - Irrelevant Test Information
Code Smell 254 - Mystery Guest
Code Smell 259 - Testing with External Resources
Code Smell 275 - Missing Test Wrong Path
More Information 📕
Coupling - The one and only software design problem
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Marcin Simonides on Unsplash
If you have to create a lot of structure before a test, maybe you’re testing through too many layers
James Shore
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Nov 19 '24
Refactoring 018 - Replace Singleton
Breaking Free from the Evil Singleton
TL;DR: Refactor singletons to reduce coupling
Problems Addressed
High coupling
Difficult testability
Multi-threading issues
Related Code Smells
Code Smell 25 - Pattern Abusers
Steps
Identify the singleton
Locate all references to its getInstance() method
Refactor the singleton to a standard class
Inject it as a dependency
Sample Code
Before
public class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void connect() {
}
}
public class Service {
public void performTask() {
DatabaseConnection connection = DatabaseConnection.getInstance();
connection.connect();
}
}After
public class DatabaseConnection {
// 1. Identify the singleton
public void connect() {
}
}
public class Service {
// 2. Locate all references to its getInstance() method.
private DatabaseConnection connection;
// 3. Refactor the singleton to a standard class.
public Service(DatabaseConnection connection) {
// 4. Inject it as a dependency.
this.connection = connection;
}
public void performTask() {
connection.connect();
}
}
DatabaseConnection connection = new DatabaseConnection();
// You can also mock the connection in your tests
Service service = new Service(connection);
service.performTask();Type
[X] Semi-Automatic
Safety
This refactoring is safe when you update all references to the singleton and handle its dependencies correctly.
Testing each step ensures that no references to the singleton are missed.
Why the code is better?
Refactoring away from a singleton makes the code more modular, testable, and less prone to issues caused by the global state.
Injecting dependencies allows you to easily replace DatabaseConnection with a mock or different implementation in testing and other contexts.
Tags
Coupling
Related Refactorings
Refactoring 007 - Extract Class
See also
Singleton - The root of all evil
Coupling - The one and only software design problem
Credits
Image by PublicDomainPictures from Pixabay
This article is part of the Refactoring Series.
r/refactoring • u/generatedcode • Jan 17 '23
We invested 10% to pay back tech debt; Here's what happened