Code Smells

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:
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:
Often passing and using together the same variables. Should be replaced by an object.

Solutions:
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
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:
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:
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

Obfuscators

It's a comment that tells a lie. Inconsistencies should be removed,

Solutions:
Sometimes method name lies outright because the code has changed. Method name should be true to what is happening inside.

Solutions:
Sometimes method name is missing something or uses ambiguous words (data, information). Method name should represent the entirety of what is happening inside.

Solutions:
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:
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 forEach loops and lambdas instead of indexed loop.
  • Replace explicit iterator loop with build in methods like contains, includes, or in syntax 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:
Regex expressions are hard to read and understand. They should hide in methods with easy-to-understand names.

Solutions:
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:
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:
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:
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:
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:
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:
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:
Long list of chained callbacks. The general solution to this is to replace it with async functions or promises.

Solutions:
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:
"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:
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:
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)