Bloaters
A method that is longer than a couple of lines is usually too long. Generally, a method is small enough if You can't meaningfully extract any other method out of it.
Solutions:
- Compose Method using Extract Method several times, extract or split loops and other chunks of code that can be grouped together.
- Try splitting method on if statements using Decompose Conditional, replace it with Command, Strategy or use Replace Conditional with Polymorphism
- If local variables are an obstacle use Replace Temp with Query or Preserve Whole Object.
- If You need to accumulate result over few methods try using Introduce Parameter Object and treat it like Visitor or Collecting Paramenter.
- You can also move a method to a separate object using Replace Method with Method Object if other solutions are not enough.
A method shouldn't have more than a few arguments. PyLint default configuration suggests 7 as a maximum. I recommend 3-4 at most. A lot of arguments mean that some can be replaced with data transfer objects or a method does too much.
Solutions:
- If there are any flags in parameters use Replace Parameter with Explicit Methods
- Try Replace Parameter with Query, Preserve the Whole Object or Introduce Parameter Object
- Perthaps method deserve its own class with parameters as attributes – use Extract Class
Often passing and using together the same variables. Should be replaced by an object.
Solutions:
- Replace it with an object using Preserve the Whole Object or Introduce Parameter Object
- If clump deserves its own class just Extract Class
Using primitives as more abstract concepts like currencies, special strings (phone numbers), and ranges. Using constants for coding information or field names in arrays. It's a good idea to replace those variables with objects of a class that will take care of special behavior.
Solutions: Try choose something fitting your use case
- Replace Data Value with Object
- Extract Class
- Introduce Parameter Object
- Replace Array with Object
- Replace Type Code with Class
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
- Replace State-Altering Conditionals with State
- Replace Conditional Logic with Strategy
- Encapsulate Composite with Builder
- Move Embellishment to Decorator
- Replace Implicit Language with Interpreter
- Replace Implicit Tree with Composite
Even with a few variables combination of their possible values multiply leading to a lot of code that does almost the same. Introducing new functionality requires multiple places to be modified. Instead, the code should be open to extension.
Solutions:
- Default solution is to Replace Implicit Language with Interpreter
- If code uses inheritance You can Replace Inheritance with Delegation
There are two places in code where the same problem is solved by in two different ways. There should be one way of dealing with the same problem in a project.
Solutions:
- Remove worse solution and Unify Interfaces with Adapter
After or before using some piece of code some other code needs to be run. Setup or Teardown should be coupled and run automatically.
Solutions:
- If setup code is required Replace Constructor with Factory Method
- If teardown is a problem Introduce Parameter Object. In Python try using
withstatement.
Class with a lot of attributes, methods, and more than a hundred lines of code is too big. Default PyLint config suggests that a class shouldn't have more than 7 attributes and 20 public methods.
Solutions: We want to split the class into smaller pieces using the following techniques
- Extract Class
- Extract Subclass
- Extract Interface
- Replace Data Value with Object
- Replace Conditional Dispatcher with Command
- Replace Implicit Language with Interpreter
- Replace State-Altering Conditionals with State
Obfuscators
It's a comment that tells a lie. Inconsistencies should be removed,
Solutions:
- Remove Inconsistency by removing comment entirely.
- If it holds useful information use Extract Method, Rename Method or Introduce Assertion
Sometimes method name lies outright because the code has changed. Method name should be true to what is happening inside.
Solutions:
- To remove contradictions Rename Method
Sometimes method name is missing something or uses ambiguous words (data, information). Method name should represent the entirety of what is happening inside.
Solutions:
- To make names communicative Rename Method or variable
Method or variable is named differently in one place than another, even if they relate to the same thing. Sometimes the order of words in names is different. The same concepts should have the same name in the project.
Solutions:
- To remove inconsistencies Rename Method or variable
Numbers themselves need explanation. Everything in an expression should be obvious. To fix this replace them with constants with proper names.
Solutions:
Solving already solved solution. Reinventing the wheel instead of using a well-know solution. Make sure code uses standard solutions Maybe do a quick research about the problem.
Solutions:
- Replace with Built-In solution or Library
Loops can be hard to read at times even if they are fundamental blocks of programming. Often you can replace them with something simpler.
Solutions:
- Try using
forEachloops andlambdasinstead of indexed loop. - Replace explicit iterator loop with build in methods like
contains,includes, orinsyntax in Python.
Boolean expressions can be unclear what True or False means. If that is the case try using custom type.
Solutions:
- Introduce New Type based on Boolean with straightforward names
Boolean expressions are hard to read and understand. They should hide in methods with easy-to-understand names.
Solutions:
- Extract Method or variable to explain expression
- Replace Nested Conditional with Guard Clauses
- Simplify or Decompose Conditional
Regex expressions are hard to read and understand. They should hide in methods with easy-to-understand names.
Solutions:
- Extract Method and build expression with well-named variables
Status variables are initialized before an operation to store some information about the process. Later they are used in conditional. Status variables are coupled with the operation and are confusing. Get rid of them.
Solutions:
- Replace with Built-In
- Extract Method
Grrouping code into "paragraphs" or regions by type (variables, properties, public, private methods). Group code by functionality. Keep related things as close to each other as possible (helper functions under the main function).
Solutions:
- Remove separation and put related things close to each other.
- Extract Method if function is divided into chunks. Often those chunks are good candidates for methods
Code is formatted differently in the same project. Not everyone cares about formatting, and those that do, do it differently. The best solution is to use linters or even better code formatters. For Python, I recommend
black and mypy (type checking). You can find more in Python Environment.
Solutions:- Agree on maximum line length
- Use linters and code formatters
Object-Orientation Abusers
Two or more classes have the same functionality but different implementations. The obvious solution is to extract superclass and put shared code there.
Solutions:
- Extract Superclass
- Move Method
- Change Method Declaration
Refused Bequest is when a subclass inherits from a parent but only uses a subset of the implemented parent methods. The solution is reorganizing the code so that child classes support all parent behavior.
Solutions:
If the same switch statement repeats across the system it should be dealt with by using polymorphism.
Solutions:
- Move Accumulation to Visitor
- Replace Conditional Dispatcher with Command
- Replace Conditional with Polymorphism
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
- Replace Parameter with Explicit Methods
- Introduce Null Object
Fields that are needed only on initialization or are always converted to something else. These variables should be used directly.
Solutions:
Very complicated and/or long conditional are hard to understand and most likely are the cause of code duplication. They should be simplified.
Solutions:
- Use a Guard Clause
- Decompose Conditional
- Replace Conditional with Polymorphism
- Use Strategy
- Introduce Null Object
Base class shouldn't depend on the subclass. Fix it by moving dependent code into the parent class.
Solutions:
Static methods that have no reason for Polymorphism should be moved to appropriate classes.
Solutions:
Change Preventers
When the class does a lot of things and can change for a number of different reasons. The class should be split into specialized classes.
Solutions:
Flag arguments make code unclear. Methods with flag arguments can be split into two without flag arguments but with descriptive names.
Solutions:
- Remove Flag Argument
- Extract Method
When change needs to be done a lot of classes. There are most likely more classes than they should be. Solution is to combine into specialized classes.
Solutions:
- Combine Functions into Class/Function
- Extract Method
- Move Method
- Move Field
- Inline Method
- Inline Class
When an inheritance tree depends on another inheritance tree by composition, and to create a subclass for a class, one finds that he has to make a subclass for another class. It can be solved, like Shotgun Surgery by combining .
Solutions:
- Move Method
- Move Field
- Fold Hierarchy into one
Long list of chained callbacks. The general solution to this is to replace it with async functions or promises.
Solutions:
- Extract Method
- Use Asynchronous Functions
- Use Promises
Class that operates on a too broad level of abstractions. Code should descend only one level of abstraction.
Solutions:
Method has a special case that has to be checked and handled before the regular case. Clean code up by hiding special cases.
Solutions:
Dispensables
I wrote about comments in Refactoring Recipes. "What" comments are smelly. There is a place for "Why" comments cause they don't degrade as much. The best solution is to move information from comments to code.
Solutions:
Blocks of code that are identical or almost identical should be extracted into reusable methods and classes.
Solutions:
Part of the code is not executed. It's quite easy to spot with modern IDE. The solution is to remove it.
Solutions:
- Remove It
Class or method that does not do enough. Maybe it's too simple or used once or twice that is not worth it to have separated. The solution is to inline it or collapse the hierarchy depending on the case.
Solutions:
When generalization is unnecessary and was put there for a future that never came. It should be stripped back to make the code simple.
Solutions:
Couplers
When a method inside a class manipulates more fields or methods of another class more than from its own. The solution is to move methods and or fields to proper classes.
Solutions:
When one class has to do unnecessary calls to the third class when interacting with another class. Class should be able to do its own manipulation.
Solutions:
The class that only performs delegation work to other classes. The simplest solution is to remove middle man.
Solutions:
When classes access too much of private fields or methods of other classes. They should be separated to make them independent and have fewer reasons to change.
Solutions:
Unnecessarily exposing internal details should be fixed by hiding them behind the abstract class, interface, or method.
Solutions:
Additional if checks for return status or null checks. The solution would be to raise Exceptions without delaying dealing with the problem.
Solutions:
Classes that have fields, getting and setting methods for the fields, and
nothing else. For this purpose, You should use data class library or move fields to more appropriate places.
Solutions:
- Encapsulate Field
- Move Method
- Extract Method
- Encapsulate Collection
- Freeze Variables
"And", and "Or" in the name implies that the method does more than one thing. It should be split into two or more methods.
Solutions:
If the name of the object contains its type. Instead, use data class and type in your code.
Solutions:
- Extract Class
- Rename Method
- Rename Variable
Data that is accessible from the entire project causes hidden dependencies. Except for constants, all datashould be limited in scope.
Solutions:
A piece of data that is passed everywhere over multiple calls. The functionality should be as close to the data it uses as possible.
Solutions:
Data that is widely used and mutable can lead to unexpected problems. Generally, data should be immutable if it's passed in a project. Objects have mutable data, but it should be fully encapsulated in that object.
Solutions:
- Choose Proper Access Control
- Remove Setting Method
- Separate Query from Modifier
- Change Reference to Value
- Replace Derived Variable with Query
- Extract Method
- Encapsulate Field
- Extract Class
When methods are silently resolving dependencies, hiding that behavior from the caller. The essence of these objects should be passed on to the creation.
Solutions:
- Inject Dependencies
When a method does more than one thing and part of its functionality is hidden. The solution is to extract additional functionality.
Solutions:
If it's not possible to reuse code that has been already written and reimplementing it from scratch is massive duplication of work. The solution is to just add missing functionality.
Solutions:
Sources:
Refactorings to Patterns (Joshua Kerievsky)
Refactoring Improving the Design of Existing Code (Martin Fowler)
Code Smells Catalog (Marcel Jerzyk)
Code Smells (Refactoring Guru)