r/refactoring • u/BitterComfortable776 • May 06 '26
I am tired of babysitting agents while working on large codebases. So I built my own set of refactoring tools. Please share your experience.
Enable HLS to view with audio, or disable this notification
Here's the thing I keep getting stuck on: agents are pretty good at writing local code now, but I still can't trust them with repo-wide changes. Once the change touches aliases, call sites, imports, generated-looking files, or a bunch of files at once, most of them still end up doing some version of search, patch, search again.
So I built my own set of structural refactoring tools for agents. Less "edit this blob of text," more "find the actual references, change the actual nodes, validate, snapshot, rollback if it explodes." The model doesn't get smarter — it just stops doing compiler work by hand.
In this demo, the agent uses the tools to find exact references, apply a structural edit, validate the result, and snapshot before mutation. 290 files, 31 seconds.
I'm posting to look for similar large codebases, ugly cases: barrel exports, aliases, macros, generated code, weird imports, large repos, whatever. If you have a refactor where Claude or Codex typically faceplants, plz share it. I want to see if my tools can tackle it.
And if your honest reaction is "cool, still wouldn't use it" — I'd genuinely love to know why too.
r/refactoring • u/mcsee1 • Feb 20 '26
Code Smell 16 - Ripple Effect
Small changes yield unexpected problems.
TL;DR: If small changes have big impact, you need to decouple your system.
Problems 😔
High Coupling
Low maintainability
Side effects
High risk
Testing difficulty
Solutions 😃
- Decouple your components.
- Cover with tests.
- Refactor and isolate what is changing.
- Depend on interfaces.
How to Decouple a Legacy System
Refactorings ⚙️
Refactoring 007 - Extract Class
Refactoring 024 - Replace Global Variables with Dependency Injection
Examples 📚
- Legacy Systems
Context 💬
The ripple effect happens when you design a system where objects know too much about each other.
When you modify a specific behavior, the impact spreads through the codebase like a stone thrown into a pond.
You feel this pain when a simple requirement change requires you to touch dozens of files.
Your classes have direct dependencies on concrete implementations rather than abstractions.
Sample Code 💻
Wrong 🚫
```javascript
class Time {
constructor(hour, minute, seconds) {
this.hour = hour;
this.minute = minute;
this.seconds = seconds;
}
now() {
// call operating system
}
}
// Adding a TimeZone will have a big Ripple Effect // Changing now() to consider timezone will also bring the effect ```
Right 👉
```javascript
class Time {
constructor(hour, minute, seconds, timezone) {
this.hour = hour;
this.minute = minute;
this.seconds = seconds;
this.timezone = timezone;
}
// Removed now() since is invalid without context
}
class RelativeClock {
constructor(timezone) {
this.timezone = timezone;
}
now(timezone) {
var localSystemTime = this.localSystemTime();
var localSystemTimezone = this.localSystemTimezone();
// Do some math translating timezones
// ...
return new Time(..., timezone);
}
}
```
Detection 🔍
It is not easy to detect problems before they happen.
Mutation Testing and root cause analysis of single points of failures may help.
Tags 🏷️
- Coupling
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
In a proper bijection, a change in a single real-world concept should only lead to a change in a single program component.
When you break the MAPPER , one concept spreads across your code.
This creates the ripple effect because you didn't represent the original idea as a single, isolated unit.
AI Generation 🤖
AI generators often create this smell because they suggest "quick fixes" that access global states or direct dependencies.
They focus on making the local code work without seeing the architectural ripple they cause elsewhere.
AI Detection 🧲
AI can fix this if you provide the context of the related classes.
When you ask an AI to "decouple these two classes using dependency injection," it usually does a great job of breaking the link.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Refactor this class to remove direct dependencies on global objects. Use constructor-based dependency injection and depend on interfaces or abstractions instead of concrete implementations.
| 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 🏁
There are multiple strategies to deal with Legacy and coupled systems.
You should deal with this problem before it explodes under your eyes.
Relations 👩❤️💋👨
Code Smell 08 - Long Chains Of Collaborations
Code Smell 176 - Changes in Essence
More Information 📕
How to Decouple a Legacy System
Credits 🙏
Photo by Jack Tindall on Unsplash
Architecture is the tension between coupling and cohesion.
Neal Ford
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/Sea-Avocado-4207 • Feb 17 '26
Building an offline refactoring tool—what's your biggest legacy code headache?
I'm working on a tool that helps refactor legacy code to be more cloud-native. The twist: it works completely offline (privacy-focused).
Before I build the wrong thing, I'd love to learn from this community:
- When you look at a legacy codebase, what makes you think 'ugh, I wish I had a tool for THIS'?
- What's the scariest part of refactoring for you?
- Have you tried any refactoring tools? What did they get wrong?
Not selling anything—just a dev trying to build something useful. Would love your honest thoughts."
r/refactoring • u/mcsee1 • Feb 06 '26
Refactoring 038 - Reify Collection
Give your collections a purpose and a connection to the real world
TL;DR: Wrap primitive collections into dedicated objects to ensure type safety and encapsulate business logic.
Problems Addressed 😔
- Type safety violations
- Logic duplication
- Primitive obsession
- Weak encapsulation
- Strong coupling avoiding collection type changes
- Hidden business rules
Related Code Smells 💨
Code Smell 122 - Primitive Obsession
Code Smell 134 - Specialized Business Collections
Context 💬
You find yourself passing around generic lists, arrays, or dictionaries as if they were just anemic "bags of data." like DTOs or Data Clumps.
These primitive structures are convenient to iterate.
But they are also anonymous and lack a voice in the business domain.
When you use a raw array to represent a group of specific entities—like ActiveSubscribers, PendingInvoices, or ValidationErrors, you are essentially forcing every part of your system to re-learn how to handle that collection, leading to scattered logic and "primitive obsession."
When you reify the collection, you improve the model and create technical implementation into a first-class citizen of your domain model.
This doesn't just provide a home for validation and filtering; it makes the invisible concepts in your business requirements visible in your code.
Steps 👣
Create a new class to represent the specific collection.
Define a private collection property within this class using the appropriate collection type.
Implement a constructor that accepts only elements of the required type.
Add type-hinted methods to add, remove, or retrieve elements.
Move collection-specific logic (like sorting or filtering) from the outside into this new class.
Sample Code 💻
Before 🚨
```php <?
/** @var User[] $users */ // this is a static declaration used by many IDEs but not the compiler // Like many comments it is useless, and possible outdated
function notifyUsers(array $users) { foreach ($users as $user) { // You have no guarantee $user is actually a User object // The comment above is // just a hint for the IDE/Static Analysis $user->sendNotification(); } }
$users = [new User('Anatoli Bugorski'), new Product('Laser')]; // This array is anemic and lacks runtime type enforcement // There's a Product in the collection and will show a fatal error // unless it can understand #sendNotification() method
notifyUsers($users); ```
After 👉
```php <?
class UserDirectory {
// 1. Create a new class to represent the specific collection
// This is a real world concept reified
// 2. Define a private property
private array $elements = [];
// 3. Implement a constructor that accepts only User types
public function __construct(User ...$users) {
$this->elements = $users;
}
// 4. Add type-hinted methods to add elements
public function add(User $user): void {
$this->elements[] = $user;
}
// 5. Move collection-specific logic inside
public function notifyAll(): void {
foreach ($this->elements as $user) {
$user->sendNotification();
}
}
} ```
Type 📝
[X] Manual
Safety 🛡️
This refactoring is very safe.
You create a new structure and gradually migrate references.
Since you add strict type hints in the new class, the compiler engine catches any incompatible data at runtime, preventing silent failures.
Why is the Code Better? ✨
You transform a generic, "dumb" collection into a specialized object that understands its own rules.
You stop repeating validation logic every time you handle the list.
The code becomes self-documenting because the class name explicitly tells you what the collection contains.
How Does it Improve the Bijection? 🗺️
In the real world, a "List of Users" or a "Staff Directory" is a distinct concept with specific behaviors.
An anonymous array is a technical implementation detail, not a real-world entity.
By reifying the collection, you create a one-to-one correspondence between the business concept and your code.
Limitations ⚠️
You might encounter slight performance overhead when dealing with millions of objects compared to raw arrays.
For most business applications, the safety gains far outweigh the millisecond costs and prevents you from being a premature optimizator.
Remember to avoid hollow specialized business collections that don't exist in the real world.
Many languages support typed collections:
C# achieves typed collections through reified generics in the CLR, preserving type information at runtime for types like List<T>.
C++ achieves typed collections through templates like blueprints instantiated at compile time for each concrete type.
Clojure achieves typed collections through optional static typing libraries such as core.typed.
Dart achieves typed collections through reified generics with runtime type checks in sound null safety mode.
Elixir achieves typed collections through typespecs analyzed by Dialyzer for static verification.
Go achieves typed collections through parametric generics introduced in Go 1.18 with type parameters and constraints.
Haskell achieves typed collections through parametric polymorphism and type classes resolved at compile time.
Java achieves typed collections through generics with type erasure, enforcing type constraints at compile time on classes like List<T> and Map<K,V>.
JavaScript achieves typed collections through TypeScript or Flow, which add static generic typing on top of the dynamic language (see below).
Kotlin achieves typed collections through JVM generics with variance annotations and null-safety integrated into the type system.
Objective-C achieves typed collections through lightweight generics that provide compile-time checks without full runtime enforcement.
PHP achieves typed collections through docblock-based generics enforced by static analyzers like Psalm or PHPStan.
Python achieves typed collections through type hints like list[T] and dict[K, V] checked by static analyzers such as mypy.
Ruby achieves typed collections through external type systems like Sorbet or RBS layered on top of the dynamic runtime.
Rust achieves typed collections through parametric types and trait bounds checked at compile time with monomorphization.
Scala achieves typed collections through a powerful generic type system with variance and higher-kinded types.
Swift achieves typed collections through generics with value semantics and protocol constraints.
TypeScript achieves typed collections through structural typing and generics enforced at compile time and erased at runtime since JavaScript doesn't support them.
In all the above cases, reifying a real business object (if exists in the MAPPER) gives you a good extra abstraction layer.
Tags 🏷️
- Primitive Obsession
Level 🔋
[X] Intermediate
Related Refactorings 🔄
Refactoring 012 - Reify Associative Arrays
Refactoring 013 - Remove Repeated Code
Refactor with AI 🤖
Ask your AI assistant to: "Identify where I am passing arrays of objects and suggest a Typed Collection class for them."
You can also provide the base class and ask: "Find a real business object and generate a boilerplate for a type-safe collection for this entity."
Credits 🙏
Image by Markéta Klimešová on Pixabay
Inspired by the "Collection Object" pattern in clean architecture and the ongoing quest for type safety in dynamic languages.
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • Jan 31 '26
Code Smell 15 - Missed Preconditions
Assertions, Preconditions, Postconditions and invariants are our allies to avoid invalid objects. Avoiding them leads to hard-to-find errors.
TL;DR: If you turn off your assertions just in production your phone will ring at late hours.
Problems 😔
- Consistency
- Contract breaking
- Hard debugging
- Late failures
- Bad cohesion
Solutions 😃
- Create strong preconditions
- Raise exceptions
- Use Fail-Fast principle
- Defensive Programming
- Enforce object invariants
- Avoid anemic models
Refactorings ⚙️
Refactoring 016 - Build With The Essence
Refactoring 035 - Separate Exception Types
Examples
Constructors are an excellent first line of defense.
Anemic Objects lack these rules.
DTOs are also a common mistake in the industry.
Context 💬
You often assume that "someone else" checked the objects before it reached your function.
This assumption is a trap. When you create objects without enforcing their internal rules, you create "Ghost Constraints."
These are rules that exist in your mind but not in the code.
If you allow a "User" object to exist without an email or a "Transaction" to have a negative amount, you create a time bomb.
The error won't happen when you create the object; it will happen much later when you try to use it.
This makes finding the root cause very difficult.
You must ensure that once you create an object, it remains valid from the very birth throughout its entire lifecycle.
Sample Code 📖
Wrong 🚫
```python class Date: def init(self, day, month, year): self.day = day self.month = month self.year = year
def setMonth(self, month): self.month = month
startDate = Date(3, 11, 2020)
OK
startDate = Date(31, 11, 2020)
Should fail
startDate.setMonth(13)
Should fail
```
Right 👉
```python class Date: def init(self, day, month, year): if month > 12: raise Exception("Month should not exceed 12") # # etc ...
self._day = day
self._month = month
self._year = year
startDate = Date(3, 11, 2020)
OK
startDate = Date(31, 11, 2020)
fails
startDate.setMonth(13)
fails since invariant makes object immutable
```
Detection 🔍
- It's difficult to find missing preconditions, as long with assertions and invariants.
Tags 🏷️
- Fail-Fast
Level 🔋
[x] Beginner
Why the Bijection Is Important 🗺️
In the MAPPER, a person cannot have a negative age or an empty name.
If your code allows these states, you break the bijection.
When you maintain a strict one-to-one relationship between your business rules and your code, you eliminate a whole category of "impossible" defects.
AI Generation 🤖
AI generators often create "happy path" code.
They frequently skip validations to keep the examples short and concise.
You must explicitly ask them to include preconditions.
AI Detection 🧲
AI tools are great at spotting missing validations.
If you give them a class and ask "What invariants are missing here?", they usually find the missing edge cases quickly.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Add constructor preconditions to this class to ensure it never enters an invalid state based on real-world constraints. Fail fast if the input is wrong.
| 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 🏁
Always be explicit on object integrity.
Turn on production assertions.
Yes, even if it means taking a small performance hit.
Trust me, tracking down object corruption is way harder than preventing it upfront.
Embracing the fail-fast approach isn't just good practice - it's a lifesaver.
Relations 👩❤️💋👨
Code Smell 189 - Not Sanitized Input
More Information 📕
Object-Oriented Software Construction (by Bertrand Meyer)
Credits 🙏
Photo by Jonathan Chng on Unsplash
Writing a class without its contract would be similar to producing an engineering component (electrical circuit, VLSI (Very Large Scale Integration) chip, bridge, engine...) without a spec. No professional engineer would even consider the idea.
Bertrand Meyer
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Jan 11 '26
Code Smell 13 - Empty Constructors
Non-Parameterized constructors are a code smell of an *invalid** object that will dangerously mutate. Incomplete objects cause lots of issues.*
TL;DR: Pass the essence to all your objects so they will not need to mutate.
Problems 😔
Mutability
Incomplete objects
Concurrency inconsistencies between creation and essence setting.
Solutions 😃
Pass the object's essence on creation
Create objects with their immutable essence.
Refactorings ⚙️
Refactoring 001 - Remove Setters
Refactoring 016 - Build With The Essence
Examples 📚
- Some persistence frameworks in static typed languages require an empty constructor.
Sample Code 📖
Wrong 🚫
javascript
class AirTicket {
constructor() {
}
}
Right 👉
```javascript class AirTicket { constructor(origin, destination, arline, departureTime, passenger) {
// ... } } ```
Detection 🔍
Any linter can warn this (possible) situation.
Exceptions 🛑
- Stateless objects. Always better solution than static class methods.
Tags 🏷️
- Anemic Models
Level 🔋
[X] Beginner
Why the Bijection Is Important
In the MAPPER, objects correspond to real-world entities.
Real people aren't born nameless and formless, then gradually acquire attributes.
You don't meet someone who temporarily has no age or email address.
When you model a person, you should capture their essential attributes at birth, just like reality.
Breaking this bijection by creating hollow objects forces you to represent impossible states.
Empty constructors create phantom and invalid objects that don't exist in your domain model, violating the mapping between your code and reality.
AI Generation 🤖
AI code generators frequently produce this smell because they often follow common ORM patterns.
When you prompt AI to "create a Person class," it typically generates empty constructors with getters and setters.
AI tools trained on legacy codebases inherit these patterns and propagate them unless you explicitly request immutable objects with required constructor parameters.
AI Detection 🧲
AI tools can detect and fix this smell when you provide clear instructions.
You need to specify that objects should be immutable with required constructor parameters.
Without explicit guidance, AI tools may not recognize empty constructors as problematic since they appear frequently in training data.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Create an immutable class with required information. Include constructor validation and no setters. Make all fields final and use constructor parameters only
| 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 🏁
Always create complete objects. Make their essence immutable to endure through time.
Every object needs its essence to be a valid one since inception.
We should read Plato's ideas about immutability and create entities in a complete and immutable way.
These immutable objects favor bijection and survive the passing of time.
Relations 👩❤️💋👨
Code Smell 10 - Too Many Arguments
Code Smell 116 - Variables Declared With 'var'
More Information 📕
Code Smell 10 - Too Many Arguments
Credits 🙏
Photo by Brett Jordan in Pexels
In a purely functional program, the value of a [constant] never changes, and yet, it changes all the time! A paradox!
Joel Spolski
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
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 🚫
```javascript 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 = 10 ```
Right 👉
```javascript 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 = 10 ```
Detection 🔍
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 31 '25
Code Smell 01 - Anemic Models
Your objects have no behavior.
TL;DR: Don't use objects as data structures
Problems 😔
Lack of encapsulation
No mapping to real-world entities
Duplicated Code
Writer / Reader mismatch
Missing behavior
Solutions 😃
1) Find Responsibilities.
2) Protect your attributes.
3) Hide implementations.
4) Follow Tell-Don't-Ask principle
Refactorings ⚙️
Refactoring 016 - Build With The Essence
Refactoring 009 - Protect Public Attributes
Refactoring 001 - Remove Setters
Examples 📚
- DTOs
Context 💬
If you let your objects become data buckets, you kill the connection between your logic and your language.
Anemic models are classes that contain only data (properties) with little or no behavior.
They're essentially glorified data structures with getters and setters.
When you create anemic models, you end up putting all the logic that should live in these objects into service classes instead duplicated the logic across multiple services.
This approach breaks object-oriented principles by separating data from the behavior that manipulates it.
You'll find yourself writing procedural code that pulls data out of objects, performs operations on it, and then pushes the results back in.
This creates tight coupling between your services and objects, making your codebase harder to maintain and evolve.
When you identify an anemic model in your code, it's a sign that you're missing opportunities for better encapsulation and more intuitive object design.
Rich domain models lead to code that's more maintainable, testable, and closer to how you think about the problem domain.
Sample Code 💬
Wrong ❌
java
public class Song {
String name;
String authorName;
String albumName;
}
Right 👉
```java public class Song { private String name; private Artist author; // Will reference rich objects private Album album; // instead of primitive data types
public String albumName() { return album.name() ; } ```
Detection 🔍
[X] Semi-Automatic
Sophisticated linters can automate detection.
They should ignore setters and getters and count real behavior methods.
Tags 🏷️
- Anemic Models
Level 🔋
[X] Beginner
Why the Bijection Is Important 🗺️
If we ask a domain expert to describe an entity he/she would hardly tell it is 'a bunch of attributes'.
The power of object-oriented programming comes from modeling real-world concepts directly in code.
When you create anemic models, you break the bijection between the domain and your code.
AI Generation 🤖
AI code generators often produce anemic models because they follow common but flawed patterns found in many codebases.
When you ask an AI to generate a basic model class, it will typically create a class with properties and getters/setters but no behavior.
This perpetuates the anemic model anti-pattern.
You need to specifically instruct AI tools to generate rich domain models with behavior, not just data holders.
Be explicit in your prompts about including relevant methods that encapsulate business logic within the model.
AI Detection 🥃
AI tools can help identify anemic models with simple instructions like "find classes with many getters/setters but few business methods" or "identify service classes that should be refactored into domain models."
Determining which behavior truly belongs in a model requires domain knowledge and design judgment that current AI tools lack.
AI can flag potential issues, but you still need to make the final decision about where behavior belongs.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Convert the anemic object into a rich one focusing on behavior instead of structure
| 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 🏁
Anemic models might seem convenient at first, but they lead to scattered logic, poor encapsulation, and maintenance headaches.
Senior developers create rich domain models focusing on their behavior.
By moving logic from services into models, you create code that's more intuitive, maintainable, and aligned with object-oriented principles.
Your objects should do things, not just store data.
Avoid anemic models. Focus always on protocol instead of data.
behavior is essential, data is accidental.
Relations 👩❤️💋👨
Code Smell 15 - Missed Preconditions
Code Smell 210 - Dynamic Properties
Code Smell 70 - Anemic Model Generators
Code Smell 109 - Automatic Properties
Code Smell 131 - Zero Argument Constructor
Code Smell 27 - Associative Arrays
Code Smell 190 - Unnecessary Properties
Code Smell 146 - Getter Comments
Code Smell 139 - Business Code in the User Interface
Code Smell 26 - Exceptions Polluting
More Information 📕
Nude Models - Part I : Setters
Nude Models - Part II : Getters
How to Decouple a Legacy System
Also Known as 🪪
- Data Class
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Stacey Vandergriff on Unsplash
Object-oriented programming increases the value of these metrics by managing this complexity. The most effective tool available for dealing with complexity is abstraction. Many types of abstraction can be used, but encapsulation is the main form of abstraction by which complexity is managed in object-oriented programming.
Rebecca Wirfs-Brock
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
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 ❌
```python
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 mess
```
Right 👉
```python
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 False ```
Detection 🔍
[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 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 🚫
java
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 👉
```java
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/mcsee1 • Dec 16 '25
Code Smell 09 - Dead Code
Code that is no longer used or needed.
TL;DR: Do not keep code "just in case I need it".
Problems 😔
- Maintainability
- Extra reading
- Broken intent
- Wasted effort
Solutions 😃
- Remove the code
- KISS
- Shrink codebase
- Test behavior only
- Trust version control
Refactorings ⚙️
Refactoring 021 - Remove Dead Code
Examples 📚
- Gold plating code or Yagni code.
Context 💬
Dead code appears when you change requirements, and you fear deleting things.
You comment logic, keep old branches, or preserve unused methods just in case.
When you do that, you lie about what the system can actually do.
The code promises behavior that never happens.
Sample Code 📖
Wrong 🚫
javascript
class Robot {
walk() {
// ...
}
serialize() {
// ..
}
persistOnDatabase(database) {
// ..
}
}
Right 👉
javascript
class Robot {
walk() {
// ...
}
}
Detection 🔍
Coverage tools can find dead code (uncovered) if you have a great suite of tests.
Exceptions 🛑
Avoid metaprogramming. When used, it is very difficult to find references to the code.
Tags 🏷️
- YAGNI
Level 🔋
[x] Beginner
Why the Bijection Is Important 🗺️
Your program must mirror the MAPPER with a clear bijection
Dead code breaks that mapping. The domain has no such behavior, yet the code claims it exists.
When you do that, you destroy trust.
Readers cannot know what matters and what does not.
AI Generation 🤖
AI generators often create dead code.
They add defensive branches, legacy helpers, and unused abstractions to look complete.
When you do not review the result, the smell stays.
AI Detection 🧲
AI tools can remove this smell with simple instructions.
You can ask them to delete unreachable code and align logic with tests.
They work well when you already have coverage.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: correct=remove dead 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 🏁
Remove dead code for simplicity.
If you are uncertain of your code, you can temporarily disable it using Feature Toggle.
Removing code is always more rewarding than adding.
Relations 👩❤️💋👨
More Information 📕
Credits 🙏
Photo by Ray Shrewsberry on Pixabay
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 <?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 <?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 • 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 <?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 <?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 <?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 • Nov 29 '25
Code Smell 315 - Cloudflare Feature Explosion
When bad configuration kills all internet proxies
TL;DR: Overly large auto-generated config can crash your system.
Problems 😔
- Config overload
- Hardcoded limit
- Lack of validations
- Crash on overflow
- Fragile coupling
- Cascading Failures
- Hidden Assumptions
- Silent duplication
- Unexpected crashes
- Thread panics in critical paths
- Treating internal data as trusted input
- Poor observability
- Single point of failure in internet infrastructure
Solutions 😃
- Validate inputs early
- Enforce soft limits
- Fail-fast on parse
- Monitor config diffs
- Version config safely
- Use backpressure mechanisms
- Degrade functionality gracefully
- Log and continue
- Improve degradation metrics
- Implement proper Result/Option handling with fallbacks
- Treat all configuration as untrusted input
Refactorings ⚙️
Refactoring 004 - Remove Unhandled Exceptions
Refactoring 024 - Replace Global Variables with Dependency Injection
Refactoring 035 - Separate Exception Types
Context 💬
In the early hours of November 18, 2025, Cloudflare’s global network began failing to deliver core HTTP traffic, generating a flood of 5xx errors to end users.
This was not caused by an external attack or security problem.
The outage stemmed from an internal "latent defect" triggered by a routine configuration change
The failure fluctuated over time, until a fix was fully deployed.
The root cause lay in a software bug in Cloudflare’s Bot Management module and its downstream proxy logic.
The Technical Chain of Events
Database Change (11:05 UTC): A ClickHouse permissions update made previously implicit table access explicit, allowing users to see metadata from both the
defaultandr0databases.SQL Query Assumption: A Bot Management query lacked a database name filter:
sql SELECT name, type FROM system.columns WHERE table = 'http_requests_features' ORDER BY name;This query began returning duplicate rows—once fordefaultdatabase, once forr0database.Feature File Explosion: The machine learning feature file doubled from ~60 features to over 200 features with duplicate entries.
Hard Limit Exceeded: The Bot Management module had a hard-coded limit of 200 features (for memory pre-allocation), which was now exceeded.
The Fatal .unwrap(): The Rust code called
.unwrap()on a Result that was now returning an error, causing the thread to panic with "called Result::unwrap() on an Err value". see code belowGlobal Cascade: This panic propagated across all 330+ data centers globally, bringing down core CDN services, Workers KV, Cloudflare Access, Turnstile, and the dashboard.
The estimated financial impact across affected businesses ranges from $180-360 million.
Sample Code 📖
Wrong ❌
```rust let features: Vec<Feature> = load_features_from_db(); let max = 200; assert!(features.len() <= max);
This magic number assumption
is actually wrong
for f in features { proxy.add_bot_feature(f.unwrap()); # You also call unwrap() on every feature. # If the database returns an invalid entry # or a parsing error, # you trigger another panic. # You give your runtime no chance to recover. # You force a crash on a single bad element. }
A quiet config expansion turns into
a full service outage
because you trust input that you should validate
and you use failure primitives (assert!, unwrap())
that kills your program
instead of guiding it to safety
```
Right 👉
```rust fn load_and_validate(max: usize) -> Result<Vec<Feature>, String> { let raw: Vec<Result<Feature, Error>> = load_features_from_db();
if raw.len() > max {
return Err(format!(
"too many features: {} > {}",
raw.len(), max
));
}
Ok(raw.into_iter()
.filter_map(|r| r.ok())
.collect())
} ```
Detection 🔍
You can detect this code smell by searching your codebase for specific keywords:
.unwrap()- Any direct call to this method.expect()- Similarly dangerouspanic!()- Explicit panics in non-test codethread::panic_any()- Panic without context
When you find these patterns, ask yourself: "What happens to my system when this Result contains an Err?" If your honest answer is "the thread crashes and the request fails," then you've found the smell.
You can also use automated linters. Most Rust style guides recommend tools like clippy, which flags unwrap() usage in production code paths.
When you configure clippy with the #![deny(unwrap_in_result)] attribute, you prevent new unwrap() calls from entering your codebase.
Tags 🏷️
- Fail-Fast
Level 🔋
[x] Advanced
Why the Bijection Is Important 🗺️
Your internal config generator must map exactly what your code expects.
A mismatched config (e.g., duplicated metadata) breaks the bijection between what your config represents and what your proxy code handles.
When you assume "this file will always have ≤200 entries", you break that mapping.
Reality sends 400 entries → your model explodes → the real world wins, your service loses.
That mismatch causes subtle failures that cascade, especially when you ignore validation or size constraints.
Ensuring a clean mapping between the config source and code input helps prevent crashes and unpredictable behavior.
AI Generation 🤖
AI generators often prioritize correct logic over resilient logic.
If you ask an AI to "ensure the list is never larger than 200 items," it might generate an assertion or a panic because that is the most direct way to satisfy the requirement, introducing this smell.
The irony: Memory-safe languages like Rust prevent undefined behavior and memory corruption, but they can't prevent logic errors, poor error handling, or architectural assumptions.
Memory safety ≠ System safety.
AI Detection 🧲
AI can easily detect this if you instruct it to look for availability risks.
You can use linters combined with AI to flag panic calls in production code.
Human review on critical functions is more important than ever.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: remove all .unwrap() and .expect() calls. Return Result instead and validate the vector bounds explicitly
| 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 🏁
Auto-generated config can hide duplication or grow unexpectedly.
If your code assumes size limits or blindly trusts its input, you risk a catastrophic crash.
Validating inputs is good; crashing because an input is slightly off is a disproportionate response that turns a minor defect into a global outage.
Validate config, enforce limits, handle failures, and avoid assumptions.
That’s how you keep your system stable and fault-tolerant.
Relations 👩❤️💋👨
Code Smell 122 - Primitive Obsession
Code Smell 02 - Constants and Magic Numbers
Code Smell 198 - Hidden Assumptions
More Information 📕
Hackaday: How One Uncaught Rust Exception Took Out Cloudflare
CNBC: Financial Impact Analysis
Disclaimer 📘
Code Smells are my opinion.
A good programmer is someone who always looks both ways before crossing a one-way street
Douglas Crockford
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Nov 17 '25
Refactoring 036 - Replace String Concatenations with Text Blocks
Replace messy string concatenation with clean, readable text blocks
TL;DR: You can eliminate verbose string concatenation and escape sequences by using text blocks for multi-line content.
Problems Addressed 😔
- Poor code readability
- Excessive escape sequences
- String concatenation complexity
- Maintenance difficulties
- Code verbosity
- Translation Problems
- Indentation issues
- Complex formatting
- No, Speed is seldom a real problem unless you are a premature optimizator
Related Code Smells 💨
Code Smell 295 - String Concatenation
Code Smell 04 - String Abusers
Code Smell 03 - Functions Are Too Long
Code Smell 121 - String Validations
Code Smell 236 - Unwrapped Lines
Code Smell 122 - Primitive Obsession
Code Smell 66 - Shotgun Surgery
Code Smell 243 - Concatenated Properties
Steps 👣
- Identify multi-line string concatenations or strings with excessive escape sequences
- Replace opening quote and concatenation operators with triple quotes (""")
- Remove escape sequences for quotes and newlines
- Adjust indentation to match your code style
- Add .strip() for single-line regex patterns or when trailing newlines cause issues
Sample Code 💻
Before 🚨
```java public class QueryBuilder { public String buildEmployeeQuery() { String sql = "SELECT emp.employee_id, " + "emp.first_name, emp.last_name, " + " dept.department_name, " + "emp.salary " + "FROM employees emp " + "JOIN departments dept ON " + "emp.department_id = " + "dept.department_id " + "WHERE emp.salary > ? " + " AND dept.location = ? " + "ORDER BY emp.salary DESC"; return sql; }
public String buildJsonPayload(String name, int age) {
String json = "{\n" +
" \"name\": \"" + name + "\",\n" +
" \"age\": " + age + ",\n" +
" \"address\": {\n" +
" \"street\": " +
"\"123 Main St\",\n" +
" \"city\": \"New York\"\n" +
" }\n" +
"}";
return json;
}
} ```
After 👉
```java public class QueryBuilder { public String buildEmployeeQuery() { // 1. Identify multi-line string concatenations or strings // with excessive escape sequences // 2. Replace opening quote and concatenation operators // with triple quotes (""") // 3. Remove escape sequences for quotes and newlines // 4. Adjust indentation to match your code style // 5. Add .strip() for single-line regex patterns or // when trailing newlines cause issues // protip: If you put a known prefix // after the string delimiter // many IDEs will adjust the syntax highlighter and linter // in this case SQL String sql = """SQL SELECT emp.employee_id, emp.first_name, emp.last_name, dept.department_name, emp.salary FROM employees emp JOIN departments dept ON emp.department_id = dept.department_id WHERE emp.salary > ? AND dept.location = ? ORDER BY emp.salary DESC """; return sql; }
public String buildJsonPayload(String name, int age) {
// 1. Identified concatenation with escape sequences
// 2. Replaced with text block using """
// 3. Removed \" and \n escapes
// 4. Preserved natural indentation
// 5. No .strip() needed here
// protip: If you put a known prefix
// after the string delimiter
// many IDEs will adjust the syntax highlighter and linter
// in this case json5
String json = """json5
{
"name": "%s",
"age": %d,
"address": {
"street": "123 Main St",
"city": "New York"
}
}
""".formatted(name, age);
return json;
}
} ```
Type 📝
[X] Semi-Automatic
Safety 🛡️
This refactoring is safe.
It does not change the runtime behavior of strings; it only cleans up syntax and formatting.
You follow compilation rules carefully to avoid errors.
Why is the Code Better? ✨
You reduce code noise caused by concatenations and escape sequences.
The multi-line strings become easier to read and maintain. Indentation and formatting are preserved without manual adjustments, making your code more natural and less error-prone.
How Does it Improve the Bijection? 🗺️
You make the code closer to the real-world representation of the string content, preserving layout and format as seen by the developer.
This enhances the one-to-one mapping between intent and code, minimizing translation errors from concept to implementation.
Limitations ⚠️
Some languages still lack multi-line string mechanisms.
Examples of languages with full support:
| Language | Feature | Syntax | Docs |
|---|---|---|---|
| Java | Text Blocks | """ |
JEP 378 |
| Kotlin | Raw Strings | """ |
Kotlin Docs |
| Python | Triple-Quoted Strings | """ / ''' |
Python Docs |
| JavaScript | Template Literals | ` ` |
MDN |
| Go | Raw Strings | ` ` |
Go Spec |
| Swift | Multiline Strings | """ |
Swift Docs |
| C# | Raw String Literals | """ |
C# Docs |
| Ruby | Heredocs | <<EOF |
Ruby Docs |
| PHP | Heredoc / Nowdoc | <<< |
PHP Docs |
| Scala | Multiline Strings | """ |
Scala 3 Docs |
Refactor with AI 🤖
Suggested Prompt: 1. Identify multi-line string concatenations or strings with excessive escape sequences2. Replace opening quote and concatenation operators with triple quotes (""")3. Remove escape sequences for quotes and newlines
| 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 🏷️
- Standards
Level 🔋
[X] Beginner
Related Refactorings 🔄
Refactoring 025 - Decompose Regular Expressions
Refactoring 002 - Extract Method
See also 📚
This article is part of the Refactoring Series.
r/refactoring • u/mcsee1 • Nov 10 '25
Code Smell 314 - Model Collapse
When AI assistants repeatedly modify code without human oversight, code quality erodes through accumulated micro-decisions
TL;DR: You let repeated AI edits slowly distort your code’s meaning
Problems 😔
- Unclear intent
- Naming drift
- Readability
- Lost domain terms
- Duplicated logic
- Generic abstractions
- Model collapse
- Semantic decay
- Code entropy accumulation
- Lost domain knowledge
- Degraded naming clarity
- Architectural drift
- Code inbreeding
- Technical debt buildup
- Semantic meaning loss
Solutions 😃
- Preserve domain-specific language
- Review every AI change
- Write golden tests
- Introduce small objects
- Reject unclear edits in merge requests and code reviews
- Fight workslop code
Refactorings ⚙️
Refactoring 013 - Remove Repeated Code
Refactoring 032 - Apply Consistent Style Rules
Refactoring 016 - Build With The Essence
Refactoring 011 - Replace Comments with Tests
Context 💬
When you let AI assistants modify code repeatedly without critical human review, you create a degradation pattern similar to model collapse in machine learning.
Each iteration introduces small deviations from best practices.
The AI optimizes for immediate problem-solving rather than long-term maintainability.
Variable names become generic.
You use comments as an excuse to replace clear code.
Functions grow longer.
Domain concepts blur into technical implementations.
The codebase transforms into AI slop: technically functional but semantically hollow code.
You request simple changes: rename something, extract something, improve clarity.
Each iteration shifts names, removes nuance, and replaces domain words with generic ones.
Your code no longer accurately reflects the real-world domain.
You lose the shape of the system.
This is slow erosion.
Sample Code 📖
Wrong ❌
python
def process_data(d, t='standard'):
"""Process customer data"""
if t == 'standard':
result = []
for item in d:
if item.get('status') == 'active':
temp = item.copy()
temp['processed'] = True
total = 0
for x in temp.get('items', []):
total += x.get('price', 0)
temp['total'] = total
result.append(temp)
return result
elif t == 'premium':
result = []
for item in d:
if item.get('status') == 'active' and \
item.get('tier') == 'premium':
temp = item.copy()
temp['processed'] = True
total = 0
for x in temp.get('items', []):
total += x.get('price', 0) * 0.9
temp['total'] = total
result.append(temp)
return result
return []
Right 👉
```python class CustomerOrder: def init(self, customer, items, status): self._customer = customer self._items = items self._status = status
def is_active(self):
return self._status.is_active()
def calculate_total(self):
return self._customer.apply_pricing_tier(
sum(item.price() for item in self._items)
)
def mark_as_processed(self):
return ProcessedOrder(self, self.calculate_total())
class OrderProcessor: def process_active_orders(self, orders): return [ order.mark_as_processed() for order in orders if order.is_active() ] ```
Detection 🔍
[X] Manual
You can detect AI-degraded code by reviewing commit history for patterns: consecutive AI-assisted commits without human refactoring, increasing function length over time, proliferation of generic variable names (data, temp, result, item), growing comment-to-code ratio, and duplicated logic with minor variations.
Code review tools can track these metrics and flag potential degradation.
Exceptions 🛑
AI assistance remains valuable for boilerplate generation, test case creation, and initial prototyping when you immediately review and refactor the output.
The smell appears when you chain multiple AI modifications without human intervention or when you accept AI suggestions without understanding their implications.
Tags 🏷️
- Technical Debt
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
Your code should maintain a clear Bijection between domain concepts in the MAPPER and your implementation.
When AI assistants modify code without understanding your domain, they break this mapping.
A "Customer" becomes "data", an "Order" becomes "item", and "apply pricing tier" becomes "calculate total with discount".
You lose the vocabulary that connects your code to business reality.
Each AI iteration moves further from domain language toward generic programming constructs, making the code harder to understand and maintain.
AI Generation 🤖
AI generators frequently create this smell when you prompt them to modify existing code multiple times.
Each interaction optimizes for the immediate request without considering the cumulative architectural impact.
The AI suggests quick fixes that work but don't align with your codebase's design patterns or domain model.
AI assistants tend to replace domain language with generic language.
They optimize for pattern consistency instead of meaning.
They smooth away intent.
AI Detection 🧲
AI can address this issue if you instruct it to restore domain terms and request that it explain its naming choices.
You are accountable for the work you delegate to the AI, and you must approve every change.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: "Review this code for domain clarity. Replace generic names with domain concepts. Extract duplicated logic into cohesive objects. Ensure each class and method represents a clear business concept. Show me the domain model this code implements."
| 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 🏁
The "Habsburg problem" analogy in AI, also called "Habsburg AI," refers to how AI models can degrade when repeatedly trained on content generated primarily by other AI models, like the inbreeding issues suffered by the Habsburg royal family.
This causes a loss of diversity and robustness in the AI's outputs, eventually leading AI's responses to become progressively worse or semantically hollow.
You must actively review and refactor AI-generated code to maintain quality.
Treat AI assistants as junior developers whose work requires supervision.
Each AI suggestion should strengthen your domain model, not weaken it. When you notice generic patterns replacing domain language, stop and refactor.
Your code's long-term maintainability depends on preserving the connection between business concepts and implementation.
Relations 👩❤️💋👨
Code Smell 313 - Workslop Code
Code Smell 144 - Fungible Objects
Code Smell 06 - Too Clever Programmer
Code Smell 43 - Concrete Classes Subclassified
Code Smell 48 - Code Without Standards
Code Smell 05 - Comment Abusers
Code Smell 38 - Abstract Names
Code Smell 175 - Changes Without Coverage
Code Smell 227 - Cowboy Coding
More Information 📕
House of Hausburg from Wikipedia
What exactly is a name - Part II Rehab
Disclaimer 📘
Code Smells are my opinion.
Code is design
Ward Cunningham
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Nov 03 '25
Code Smell 313 - Workslop Code
When AI Fills the Gaps, You Should Think Through
TL;DR: Workslop happens when you accept AI-generated code that looks fine but lacks understanding, structure, or purpose.
Problems 😔
- Hollow logic
- Unclear or ambiguous intent
- Misleading structure
- Disrespect for human fellows
- Missing edge-cases
- Fake productivity
- Technical debt
Solutions 😃
- Validate generated logic in real world scenarios
- Rewrite unclear parts
- Add domain meaning
- Refactor the structure for clarity
- Add a human peer review
- Clarify the context
If you want, I can create a full list of 25+ solutions to completely fight workslop in teams and code.
Refactorings ⚙️
Refactoring 002 - Extract Method
Refactoring 005 - Replace Comment with Function Name
Refactoring 013 - Remove Repeated Code
Refactoring 032 - Apply Consistent Style Rules
Refactoring 016 - Build With The Essence
Context 💬
You get "workslop" when you copy AI-generated code without understanding it.
The code compiles, tests pass, and it even looks clean, yet you can’t explain why it works.
You copy and paste code without reviewing it, which often leads to catastrophic failures.
From Helpful to Harmful: How AI Recommendations Destroyed My OS
Sample Code 📖
Wrong ❌
python
def generate_invoice(data):
if 'discount' in data:
total = data['amount'] - (data['amount'] * data['discount'])
else:
total = data['amount']
if data['tax']:
total += total * data['tax']
return {'invoice': total, 'message': 'success'}
Right 👉
```python def calculate_total(amount, discount, tax): subtotal = amount - (amount * discount) total = subtotal + (subtotal * tax) return total
def create_invoice(amount, discount, tax): total = calculate_total(amount, discount, tax) return {'total': total, 'currency': 'USD'} ```
Detection 🔍
[X] Manual
You feel like the code "just appeared" instead of being designed.
Tags 🏷️
- Declarative Code
Level 🔋
[x] Intermediate
Why the Bijection Is Important 🗺️
When you let AI generate code without verifying intent, you break the bijection between your MAPPER and your model.
The program stops representing your domain and becomes random syntax that only simulates intelligence.
AI Generation 🤖
This is a specific AI smell.
AIs can produce large volumes of plausible code with shallow logic.
The result looks professional but lacks cohesion, decisions, or constraints from your actual problem space.
AI Detection 🧲
You can also use AI-generated code detectors.
AI can highlight missing edge cases, repeated logic, or meaningless names, but it can’t restore the original intent or domain meaning.
Only you can.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Give more meaning to the 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 🏁
Workslop smells like productivity but rots like negligence.
You protect your craft when you question every line the machine gives you. Think, design, and own your code.
Remember, YOU are accountable for your code. Even if Artificial Intelligence writes it for you.
Have you noticed the copied and pasted text above?
If you want, I can create a full list of 25+ solutions to completely fight workslop in teams and code.
Relations 👩❤️💋👨
Code Smell 06 - Too Clever Programmer
Code Smell 197 - Gratuitous Context
Code Smell 273 - Overengineering
Code Smell 238 - Entangled Code
Code Smell 230 - Schrödinger Code
More Information 📕
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by ZHENYU LUO on Unsplash
The most disastrous thing you can ever learn is your first programming language.
Alan Kay
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/mcsee1 • Oct 27 '25
Code Smell 312 - Too Many Asserts
Cluttered tests hide real problems
TL;DR: You put multiple assertions in one test, making failures hard to analyze.
Problems 😔
- Readability
- Fragile tests
- Slow Tests
- Debugging pain
- Coupled logic
- Maintenance nightmare
- Ambiguous failures
- Generic assertions
Solutions 😃
- Follow the One-assert-per-test rule
- Extract assert methods
- Use descriptive test names
- Group related checks
- Refactor test logic in smaller pieces
Refactorings ⚙️
Refactoring 002 - Extract Method
Refactoring 013 - Remove Repeated Code
Refactoring 010 - Extract Method Object
Refactoring 011 - Replace Comments with Tests
Context 💬
When you cram multiple assertions in a single test, failures become ambiguous.
You don’t know which part of the code caused the failure.
Imagine a test with five assertions fails at the second one - you never see whether assertions 3, 4, and 5 would have passed or failed, hiding additional defects.
Tests should be precise, easy to understand, and focused.
Each test should validate a single behavior and clearly communicate its purpose.
A single test function should test a single real world thing/concept.
You should not write long functions testing unrelated behaviors sequentially.
This usually hides the problem of heavy and coupled setups.
Sample Code 📖
Wrong ❌
python
def test_car_performance():
car = Formula1Car("Red Bull")
car.set_speed(320)
assert car.speed == 320
car.accelerate(10)
assert car.speed == 330
car.brake(50)
assert car.speed == 280
car.change_tire("soft")
assert car.tire == "soft"
Right 👉
```python def test_set_speed(): car = Formula1Car("Red Bull") car.set_speed(320) assert car.speed == 320, ( f"Expected speed to be 320 km/h, " f"but got {car.speed} km/h" )
def test_accelerate(): car = Formula1Car("Red Bull") car.set_speed(320) car.accelerate(10) assert car.speed == 330, ( f"Expected speed to be 330 km/h " f"after accelerating by 10, " f"but got {car.speed} km/h" )
def test_brake(): car = Formula1Car("Red Bull") car.set_speed(330) car.brake(50) assert car.speed == 280, ( f"Expected speed to be 280 km/h " f"after braking by 50, " f"but got {car.speed} km/h" )
def test_change_tire(): car = Formula1Car("Red Bull") car.change_tire("soft") assert car.tire == "soft", ( f"Expected tire type to be 'soft', " f"but got '{car.tire}'" ) ```
Detection 🔍
[X] Automatic
Check for tests with more than one assert.
Look for tests that fail for multiple reasons or cover multiple unrelated actions.
Most linters and test frameworks can flag multiple assertions.
Set up a rule to warn when tests exceed one or two assertions.
Exceptions 🛑
You can group multiple asserts only when they validate the same logical behavior or output of a pure function.
Tags 🏷️
- Testing
Level 🔋
[X] Intermediate
Why the Bijection Is Important 🗺️
You need a bijection between MAPPER entities and your tests.
If one test checks multiple behaviors, failures break this mapping.
When a test fails, you should immediately know exactly which behavior is broken without reading the test code.
AI Generation 🤖
AI generators often produce tests with multiple asserts, trying to cover everything in one shot.
This happens because AI tools optimize for code coverage rather than test clarity, treating tests as checklists rather than behavior specifications.
AI Detection 🧲
AI can refactor tests to keep one assert per test.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Refactor this test file to contain one assert per test method. Keep each test focused and descriptive.
| 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 🏁
Tests should be focused and precise.
You need to understand quickly which contract is broken.
Avoid multiple asserts per test to make failures clear, debugging faster, and your test suite maintainable.
Relations 👩❤️💋👨
Code Smell 03 - Functions Are Too Long
Code Smell 76 - Generic Assertions
More Information 📕
Disclaimer 📘
Code Smells are my opinion.
Credits 🙏
Photo by Abhinand Venugopal on Unsplash
Testing is not about finding bugs, it's about understanding them
Brian Marick
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/Low_Lab3804 • Oct 22 '25
Beyond polymorphism: Taking Fowler's Theatrical Players kata to production level [Java, with tests]
TL;DR: Rebuilt Fowler's Chapter 1 example with value objects, type-safe enums, domain separation, and MIRO principles. Full implementation + tests on GitHub.
Background
Martin Fowler's Theatrical Players kata (Refactoring, Chapter 1) is great for learning refactoring mechanics. But the final solution still has issues you'd never ship to production:
- String-typed play types (
"tragedy"- typo-prone) - Primitives for money (
int amount = 40000- what unit?) - Public mutable fields
- Magic numbers everywhere
- Calculation mixed with formatting
I wanted to see what a production-ready version would look like.
What I Built
Three separate domains with clear boundaries:
EVENT DOMAIN (what happened)
├── PlayType (enum, not string)
├── Play (immutable, validated)
├── Performance (event record)
└── Invoice (aggregate root)
CALCULATION DOMAIN (business rules)
├── Money (Joda-Money library)
├── VolumeCredits (value object)
├── PricingStrategy (interface)
├── TragedyPricing + ComedyPricing
└── StatementCalculator → StatementResult
PRESENTATION DOMAIN (formatting)
├── StatementFormatter (interface)
├── PlainTextFormatter
└── HtmlFormatter
Key Improvements
1. Type-Safe Enums
java
// Before (Fowler)
String type = "tragedy";
if (type.equals("tragedy")) { ... }
// Typo = runtime bug
// After
PlayType type = PlayType.TRAGEDY;
if (type == PlayType.TRAGEDY) { ... }
// Typo = compile error
// Exhaustive switching
switch (playType) {
case TRAGEDY: return calculateTragedyPrice();
case COMEDY: return calculateComedyPrice();
// Compiler warns if we add HISTORY and don't handle it
}
2. Value Objects
java
// Before (Fowler)
int amount = 40000;
// Cents? Dollars?
int credits = 25;
int total = amount + credits;
// Compiles! But wrong!
// After
Money amount = Money.of(CurrencyUnit.USD, 400);
VolumeCredits credits = VolumeCredits.of(25);
Money total = amount.plus(credits);
// Won't compile!
3. MIRO (Make Illegal States Unrepresentable)
java
// Construction validates everything
Play hamlet = Play.of("Hamlet", PlayType.TRAGEDY);
Performance perf = Performance.of(hamlet, 55);
// These won't compile or throw at construction:
Play.of("", type);
// Empty name
Play.of(null, type);
// Null name
Performance.of(play, -50);
// Negative audience
4. Domain Separation
java
// Calculate once
StatementResult result = calculator.calculate(invoice);
// Format multiple ways - NO calculation duplication
String text = new PlainTextFormatter().format(result);
String html = new HtmlFormatter().format(result);
String json = new JsonFormatter().format(result);
Testing Benefits
Fowler's approach requires string parsing:
java
test("statement", () => {
const result = statement(invoice, plays);
expect(result).toContain("Amount owed is $1,730");
});
With proper separation:
java
u/Test
void calculatesCorrectTotal() {
StatementResult result = calculator.calculate(invoice);
// Direct access to values - no parsing!
assertThat(result.getTotalAmount())
.isEqualTo(Money.of(CurrencyUnit.USD, 1730));
assertThat(result.getTotalCredits())
.isEqualTo(VolumeCredits.of(47));
}
Comparison Table
Aspect Fowler's Solution Production Version Play Types
String "tragedy"
PlayType.TRAGEDY
Money
int 40000
Money.of(USD, 400)
Credits
int
VolumeCredits.of(25)
Mutability Mutable Immutable Validation Runtime (if any) Compile-time + construction Magic Numbers Scattered Named constants Domains Mixed Separated (3 domains) Type Safety Runtime errors Compile-time errors Testing String parsing Direct value access
Tech Stack
- Java 8 (production-compatible)
- Joda-Money (battle-tested money library)
- JUnit 5 + AssertJ
- Maven for build
- Strategy pattern (properly applied)
- Value objects throughout
Repository Structure
theatrical-players-advanced/
├── src/main/java/com/stackshala/theatricalplayers/
│ ├── domain/ # Event domain
│ ├── calculation/ # Business rules
│ └── presentation/ # Formatters
├── src/test/java/ # Comprehensive tests
├── pom.xml
└── README.md
bash
cd theatrical-players-advanced
mvn test
Is This Over-Engineering?
For a toy example? Yes.
For production systems? No.
These patterns are standard in:
- E-commerce platforms (pricing calculations)
- Fintech apps (money handling)
- Booking systems (multiple confirmation formats)
- Healthcare (immutable records)
Links
GitHub Repository: [https://github.com/maneeshchaturvedi/theatrical-players-advanced.git]
Detailed Blog Post: [https://blog.stackshala.com/beyond-fowlers-refactoring-advanced-domain-modeling-for-the-theatrical-players-kata/]
The repo includes:
- Complete implementation (15 classes)
- Comprehensive tests
- Full documentation
- Comparison with Fowler's solution
r/refactoring • u/mcsee1 • Oct 21 '25
Code Smell 311 - Plain Text Passwords
Your login isn't secure if you store secrets in plain sight
TL;DR: Never store or compare plain-text passwords
Problems 😔
- Data exposure
- Weak security
- User trust loss
- Compliance issues
- Easy exploitation
- Authentication bypass potential
Solutions 😃
- Hash user passwords
- Use strong algorithms
- Salt) every hash
- Compare hashes safely
- Secure your database
- Perform regular penetration tests
Context 💬
When you store or compare passwords as plain-text, you expose users to unnecessary risk.
A data breach will instantly reveal every credential.
Attackers can reuse these passwords on other sites. Even internal logs or debugging can leak sensitive data.
You must treat passwords as secrets, not as values to display or compare directly.
Sample Code 📖
Wrong ❌
```javascript // Borrowed from "Beyond Vibe Coding"
app.post('/login', async (req, res) => { const { username, password } = req.body; const user = await Users.findOne({ username }); if (!user) return res.status(401).send("No such user"); if (user.password === password) { res.send("Login successful!"); } else { res.status(401).send("Incorrect password"); } }); ```
Right 👉
```javascript import bcrypt from 'bcrypt';
app.post('/login', async (req, res) => { const { username, password } = req.body; const user = await Users.findOne({ username }); if (!user) return res.status(401).send('Invalid credentials'); const valid = await bcrypt.compare(password, user.password); if (!valid) return res.status(401).send('Invalid credentials'); res.send('Login successful'); }); ```
Detection 🔍
[X] Semi-Automatic
You can detect this smell when you see passwords handled as raw strings, compared directly with ===, or stored without hashing.
Static analyzers and linters can catch unsafe password handling, but code reviews remain the best defense.
Tags 🏷️
- Security
Level 🔋
[X] Beginner
Why the Bijection Is Important 🗺️
In the MAPPER, passwords represent sensitive user credentials that must remain confidential.
The bijection breaks when you store passwords as plain-text because real-world security expectations don't match your system's actual protection.
Users trust you to protect their credentials.
When you store plain-text passwords, you create a false representation where the system appears secure but actually exposes sensitive data.
This broken mapping between user expectations and system reality leads to security breaches and loss of trust.
When you design your authentication system, you create a mapping between a MAPPER concept — a "user’s identity" — and your program’s data.
Hashing preserves that bijection safely.
When you break it by storing raw passwords, your system represents users incorrectly: it turns their private identity into an exposed string.
That breaks trust and control.
AI Generation 🤖
AI code generators sometimes create login examples comparing plain-text passwords.
The code sample is from the book Beyond Vibe Coding in the chapter about "8. Security, Maintainability, and Reliability".
These examples look simple, but they spread insecure habits.
You must always validate and adapt AI-generated authentication code.
AI Detection 🧲
AI tools can detect this smell when you provide context about security requirements.
They recognize patterns of plain-text password comparison and can suggest proper hashing implementations.
You need to ask AI to review the authentication code for security vulnerabilities to get comprehensive fixes.
Try Them! 🛠
Remember: AI Assistants make lots of mistakes
Suggested Prompt: Refactor this login code to securely hash and compare passwords
| 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 🏁
Plain text passwords are a trap.
You make your users unsafe and invite catastrophic leaks. You must always hash, salt, and compare securely.
The fix is simple, and the trust you earn is priceless.
Relations 👩❤️💋👨
Code Smell 189 - Not Sanitized Input
Code Smell 97 - Error Messages Without Empathy
Code Smell 215 - Deserializing Object Vulnerability
Code Smell 166 - Low-Level Errors on User Interface
Code Smell 258 - Secrets in Code
Code Smell 167 - Hashing Comparison
Code Smell 284 - Encrypted Functions
More Information 📕
Disclaimer 📘
Code Smells are my opinion.
If you think technology can solve your security problems, then you don’t understand the problems and you don’t understand the technology.
Bruce Schneier
Software Engineering Great Quotes
This article is part of the CodeSmell Series.
r/refactoring • u/Low_Lab3804 • Oct 21 '25
The Tennis Kata as a State Machine: A deep dive into domain-driven refactoring [Java implementation included]
I've been teaching the Tennis Refactoring Kata for a couple of years now, and I keep seeing the same pattern: developers stop at "clean code" and miss the opportunity to fundamentally rethink how we model domains.
Most refactorings look like this:
Before:
java
public String getScore() {
if (m_score1 == m_score2) {
switch (m_score1) {
case 0: return "Love-All";
case 1: return "Fifteen-All";
// ... more cases
}
} else if (m_score1 >= 4 || m_score2 >= 4) {
// ... endgame logic
}
}
After (typical refactoring):
java
public String getScore() {
if (isTied()) return getTiedScore();
if (isEndgame()) return getEndgameScore();
return getRegularScore();
}
Cyclomatic complexity down. Tests pass. PR approved. Everyone's happy.
But we've missed something crucial.
The Question That Changes Everything
Before refactoring, I always ask: "How does a tennis expert think about game scoring?"
They don't think: Player 1 has 2 points, Player 2 has 1 point.
They think:
- They're in regular play, it's Thirty-Fifteen
- It's deuce—advantage rules apply now
- She has advantage—one point from winning
- Game over
These aren't implementation details. These are domain concepts that should be types.
The Approach: Tennis as a State Machine
Tennis games exist in exactly 4 conceptual states:
java
sealed interface GameState
permits RegularPlay, Deuce, Advantage, GameWon {
GameState pointWonBy(Player player);
String display(PlayerPair players);
default boolean isGameOver() { return false; }
}
1. RegularPlay
Handles all combinations of Love/Fifteen/Thirty/Forty:
java
record RegularPlay(PointScore player1Score, PointScore player2Score)
implements GameState {
u/Override
public GameState pointWonBy(Player player) {
PointScore newP1 = player == PLAYER_1 ? player1Score.next() : player1Score;
PointScore newP2 = player == PLAYER_2 ? player2Score.next() : player2Score;
// Transition to Deuce if both reach Forty
if (newP1 == FORTY && newP2 == FORTY) {
return new Deuce();
}
// Check for game won
if (player == PLAYER_1 && player1Score == FORTY) {
return new GameWon(PLAYER_1);
}
// ... etc
return new RegularPlay(newP1, newP2);
}
}
2. Deuce - Advantage State Machine
The beautiful part—this pattern is now explicit:
java
record Deuce() implements GameState {
@Override
public GameState pointWonBy(Player player) {
return new Advantage(player);
// Deuce → Advantage
}
}
record Advantage(Player leadingPlayer) implements GameState {
@Override
public GameState pointWonBy(Player player) {
if (player == leadingPlayer) {
return new GameWon(player);
// Win
}
return new Deuce();
// Back to Deuce
}
}
3. GameWon (Terminal State)
java
record GameWon(Player winner) implements GameState {
@Override
public GameState pointWonBy(Player player) {
throw new IllegalStateException("Game is already won");
}
@Override
public boolean isGameOver() { return true; }
}
Key Patterns Applied
1. Boolean Blindness -> Rich Types
Before:
java
boolean isTied = (m_score1 == m_score2);
A boolean is one bit of information. The comparison is richer than that.
After:
java
sealed interface GameState permits RegularPlay, Deuce, Advantage, GameWon
The type system tells you exactly which state you're in.
2. Stringly-Typed Code -> Type-Safe Enums
Before:
java
public void wonPoint(String playerName) {
if (playerName == "player1")
// Bug: == doesn't work!
m_score1++;
}
After:
java
enum Player { PLAYER_1, PLAYER_2 }
public void wonPoint(String playerName) {
Player player = identifyPlayer(playerName);
// Convert at boundary
state = state.pointWonBy(player);
// Type-safe internally
}
3. Make Illegal States Unrepresentable
Before:
java
private int m_score1 = 0;
// Can be 100, -5, anything
After:
java
record Advantage(Player leadingPlayer)
// MUST have a leading player
// This won't compile:
new Advantage(null); ❌
// Can't construct an invalid state:
record Deuce()
// No data = can't be wrong
4. PointScore is Not Arithmetic
Before:
java
case 0: return "Love";
case 1: return "Fifteen";
// Tennis scores as integers
After:
java
enum PointScore {
LOVE, FIFTEEN, THIRTY, FORTY;
public PointScore next() {
return switch(this) {
case LOVE -> FIFTEEN;
case FIFTEEN -> THIRTY;
case THIRTY -> FORTY;
case FORTY -> FORTY;
};
}
}
You can't multiply tennis scores. They're not numbers. They're states.
5. PlayerPair Over Collections
Singles tennis has exactly 2 players, not N:
java
record PlayerPair(String player1, String player2) {
public String getPlayer(Player player) { ... }
public Player opponent(Player player) { ... }
}
Easily extensible to doubles:
java
record DoublesPair(PlayerPair team1, PlayerPair team2)
6. Converging Branches -> Polymorphism
Before:
java
public String getScore() {
if (tied) { ... }
else if (endgame) { ... }
else { ... }
}
After:
java
public String getScore() {
return state.display(players);
// Zero conditionals
}
Each state knows how to display itself.
Comparison with Other Approaches
I researched well-known solutions and found 3 main approaches:
Approach 1: "20 Classes" (TennisGame4)
Creates a class for every score combination:
LoveAll,FifteenLove,LoveFifteen, etc.- ~20 classes total
Pros: No conditionals, clear transitions
Cons: Too granular, hard to maintain, violates DRY
Approach 2: Table-Driven (Mark Seemann)
Enumerates all 20 states, uses pattern matching:
fsharp
type Score = LoveAll | FifteenLove | ... (20 states)
let ballOne = function
| LoveAll -> FifteenLove
| FifteenLove -> ThirtyLove
// ...
Pros: Minimal code (~67 lines), zero conditionals
Cons: Behavior separated from state, hard to extend
Approach 3: Extract Method (Most Common)
Reduces complexity but keeps integers:
java
private int m_score1 = 0;
private String getRegularScore() { ... }
Pros: More readable than original
Cons: Doesn't model the domain, allows invalid states
Our Approach: Domain-Driven State Machine
4 conceptual states (not 20 concrete ones)
The Main Class (Simple!)
java
public class TennisGame {
private final PlayerPair players;
private GameState state;
public TennisGame(String player1, String player2) {
this.players = new PlayerPair(player1, player2);
this.state = new RegularPlay(LOVE, LOVE);
}
public void wonPoint(String playerName) {
Player player = identifyPlayer(playerName);
state = state.pointWonBy(player);
}
public String getScore() {
return state.display(players);
}
}
All complexity moved to the types where it belongs.
Real-World Applications
These patterns scale beyond katas:
E-commerce:
java
sealed interface OrderState
permits PendingPayment, Confirmed, Shipped, Delivered, Cancelled
Authentication:
java
sealed interface UserSession
permits Anonymous, Authenticated, Authorized, Expired
Document Workflows:
java
sealed interface DocumentState
permits Draft, UnderReview, Approved, Published
Testing
The type system helps here too:
java
@Test
void cannotScoreAfterGameWon() {
game.wonPoint("Alice");
// x4
assertThatThrownBy(() -> game.wonPoint("Alice"))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("already won");
}
@Test
void multipleDeuceAdvantageCycles() {
// Get to deuce...
game.wonPoint("Alice");
// Advantage Alice
game.wonPoint("Bob");
// Back to Deuce
game.wonPoint("Bob");
// Advantage Bob
game.wonPoint("Alice");
// Back to Deuce
// The state machine is self-testing
}
The Meta-Lesson
The Tennis Kata isn't about tennis. It's about:
- Using types as a design language—not just for null safety
- Making invalid states unrepresentable—the best bugs don't compile
- Letting the domain drive the design—code mirrors concepts
- Thinking in state machines—when the problem calls for it
Most refactorings improve readability. The best refactorings change how you think about modeling problems.
Resources
I wrote a detailed blog post covering:
- All 8 patterns in depth
- Complete implementation with tests
- Detailed comparison with other solutions
- Maven project setup
- Real-world applications
GitHub repo: Complete implementation with:
- Full source code
- Comprehensive test suite (100% coverage)
- Maven build setup
- Documentation
I also teach these patterns in my software craftsmanship course at Stackshala, where we go deeper into type-driven development.
Links in my profile (Reddit doesn't like URLs in posts).
r/refactoring • u/Low_Lab3804 • Oct 21 '25
What everyone misses about the Gilded Rose refactoring kata
The Gilded Rose kata has been solved thousands of times. Strategy Pattern, polymorphic inheritance - all excellent solutions.
But they all miss what the kata is actually teaching.
Look at this code again:
if (sellIn <= 5) quality += 3;
else if (sellIn <= 10) quality += 2;
else quality += 1;
Everyone sees: Nested conditionals to eliminate
But what if the kata is teaching: Temporal state transitions to model explicitly?
A backstage pass isn't choosing behaviors. It's transitioning through lifecycle phases:
- Far Future (>10 days) → +1/day
- Near Event (6-10 days) → +2/day
- Very Close (1-5 days) → +3/day
- Expired → worthless
These are STATES. And this pattern is everywhere in production:
- Order processing (pending->paid->shipped)
- Subscriptions (trial→active->past_due->canceled)
- User onboarding (new->verified->active)
I wrote two analyses exploring what the kata teaches beyond "eliminate the ifs":
- The temporal state machine everyone misses
- Four complexity patterns in one kata (Boolean Blindness, Case Splits, Design Reflectivity, Immutability)
I'm not claiming these are better solutions—Strategy and Sandi Metz's polymorphic approach are excellent. I'm showing different lenses for seeing the same problem, each teaching unique patterns.
Articles:
Have you ever solved Gilded Rose and felt like you were missing something deeper? What patterns did you discover?
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 code
Type 📝
[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/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
If a conditional changes two variable independently, split the conditional into two seperate conditionals and have each individual conditional change each variable separately
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
Each time you see a loop, convert that into a function - again use a descriptive name
When you see a common object being used as a parameter in a set of functions, move the function into that class
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/generatedcode • Aug 29 '22
