Join today
Write your awesome label here.

Writing Clean Code: 20 Code Smells and How to Get Rid of Them

Learn to identify and eliminate 20 critical code smells that plague software projects. Master the design principles and refactoring techniques that separate maintainable code from fragile, hard-to-change systems. Transform your development skills with hands-on training in writing code that scales.
Write your awesome label here.

73 Lectures

Comprehensive Knowledge

6 Hours

Video Duration

20+ Labs

Focus on Practice

Course Certificate

Validate Your Learning
What you are going to learn

Build Code That Developers Actually Want to Maintain

This course teaches you to recognize and fix the patterns that make codebases hard to maintain, understand, and extend. Starting from core design principles and the SOLID framework, you will build the vocabulary and mental models needed to evaluate code quality objectively. Every concept is grounded in concrete before-and-after examples that show not just what to change, but why the change matters.

By the end of this course, you will be able to identify 20 of the most common code smells, apply targeted refactoring techniques to eliminate them, and reason clearly about design trade-offs. Whether you are writing new code or improving an existing codebase, you will have the skills to make deliberate, defensible decisions that your team will appreciate for years.

By completing this course, you will be able to:

  • Recognize and eliminate loops that hide complexity and obscure intent
  • Write meaningful comments that add value rather than duplicate information
  • Eliminate shotgun surgery by properly encapsulating internal structures
  • Consolidate duplicated knowledge into shared, well-designed modules
  • Create consistent abstractions across similar classes through common interfaces
  • Choose descriptive names that make code self-documenting
  • Replace primitive types with custom domain objects that enforce invariants
  • Split multitasking functions into focused, single-purpose components
  • Identify divergent change and restructure code to reduce reasons for modification
  • Simplify interfaces to hide internal complexity rather than expose it
  • Reduce long parameter lists through grouping and polymorphism
  • Avoid side effects by returning new data rather than mutating inputs
  • Eliminate middleman classes that add unnecessary indirection
  • Group related data into meaningful objects that encapsulate behavior
  • Replace repeated switch statements with polymorphic designs

Course Contents

01

Introduction

This section provides an introduction to the course on writing clean code and demonstrates how to navigate the code examples contained in the repository. It covers the foundational importance of understanding code smells and introduces the resources and tools available for exploring different code examples and their solutions.
Introduction
01:35
Navigating the Code Base
04:16
Note on Code Smells
00:31
Course Resources
00:41
Let's Stay Connected!
00:28

02

Fundamentals of Software Design and SOLID Principles

This section covers fundamental design principles that form the foundation for writing clean code, including information hiding, encapsulation, abstraction, polymorphism, and deep modules. It then introduces the five SOLID principles which are essential guidelines for creating maintainable and extensible software.
Fundamentals of Good Software Design
06:59
Designing Deep Modules
03:46
SOLID Principles: Single Responsibility Principle
03:47
SOLID Principles: Open/Close Principle
04:03
SOLID Principles: Liskov Substitution Principle
03:48
SOLID Principles: Interface Segregation Principle
03:42
SOLID Principles: Dependency Inversion Principle
04:12

03

Code Smell: Repeated Switches

This section discusses the problem of identical switch statements or conditional chains appearing in multiple locations throughout a codebase. The focus is on understanding why repeated conditional logic creates maintenance nightmares and how polymorphism and encapsulation can eliminate this duplication.
Repeated Switches - Overview
02:51
Repeated Switches - Code Example
03:09
Repeated Switches - Solution Walkthrough
27:50

04

Code Smell: Primitive Obsession

This section addresses the code smell of primitive obsession, which occurs when developers rely exclusively on primitive types instead of creating custom types to represent domain concepts. It demonstrates how custom types enable encapsulation and reduce knowledge duplication.
Primitive Obsession - Overview
03:08
Primitive Obsession - Code Example
01:59
Primitive Obsession - Solution Walkthrough
11:09

05

Code Smell: Loops

This section addresses the code smell of using explicit loops in code, which adds unnecessary complexity and obscures logic. It demonstrates how pipeline operations and functional approaches can abstract away iteration details and make code more concise and maintainable.
Loops - Overview
02:43
Loops - Code Example
06:07
Loops - Solution Walkthrough
09:15

06

Code Smell: Long Parameter List

This section explores the problems created when functions require many parameters, particularly when including flag parameters that merely determine conditional execution paths. The discussion covers maintenance challenges, type safety issues, and refactoring strategies including data object parameters and polymorphism to eliminate conditional branches.
Long Parameter List - Overview
02:49
Long Parameter List - Code Example
02:21
Long Parameter List - Solution Walkthrough
15:06

07

Code Smell: Knowledge Duplication

This section addresses the code smell of knowledge duplication, which occurs when the same logic is repeated across multiple places in the codebase. It demonstrates how extracting duplicated knowledge into shared functions or modules improves maintainability while avoiding over-extraction of unrelated code.
Knowledge Duplication - Overview
03:35
Knowledge Duplication - Code Example
03:32
Knowledge Duplication - Solution Walkthrough
10:48

08

Code Smell: Uninformative Comments

This section discusses the code smell of uninformative comments, which are comments that state the obvious rather than explaining the reasoning or intent behind code. It emphasizes that good comments should capture domain knowledge and usage intent while avoiding redundancy with the code itself.
Uninformative Comments - Overview
03:49
Uninformative Comments - Code Example
03:11
Uninformative Comments - Solution Walkthrough
08:33

09

Code Smell: Shotgun Surgery

This section addresses the code smell of shotgun surgery, which occurs when changes to one component require modifications in multiple unrelated places. It demonstrates how high coupling to internal data structures and semantic knowledge creates this problem and how encapsulation provides the solution.
Shotgun Surgery - Overview
02:26
Shotgun Surgery - Code Example
08:08
Shotgun Surgery - Solution Walkthrough
15:08

10

Code Smell: Alternative Classes with Different Interfaces

This section addresses the code smell of alternative classes with different interfaces, which occurs when similar classes have different methods for performing the same functionality. It demonstrates how establishing common abstractions through interfaces enables substitutability and reduces code duplication.
Alternative Classes with Different Interfaces - Overview
02:05
Alternative Classes with Different Interfaces - Code Example
01:52
Alternative Classes with Different Interfaces - Solution Walkthrough
03:14

11

Code Smell: Refused Bequest

This section examines the problem of subclasses inheriting methods and fields from parent classes that they don't actually need or use. The discussion covers how this represents poor inheritance hierarchy design and how composition often provides better alternatives than forcing unrelated types into the same inheritance structure.
Refused Bequest - Overview
04:03
Refused Bequest - Code Example
04:47
Refused Bequest - Solution Walkthrough
09:53

12

Code Smell: Data Clumps

This section addresses the problem of related data that is always used together but not encapsulated into a single object. The focus is on recognizing when multiple parameters or fields logically belong together and how to group them into proper abstractions that encapsulate behavior and reduce duplication.
Data Clumps - Overview
01:39
Data Clumps - Code Example
01:26
Data Clumps - Solution Walkthrough
07:29

13

Code Smell: Middle-Man

This section identifies the problem of middleman classes that merely delegate calls to dependencies without adding meaningful logic or behavior. The discussion covers how such classes increase coupling and complexity without providing real value, and explains when it's appropriate to remove intermediaries or consolidate functionality.
Middle-Man - Overview
03:02
Middle-Man - Code Example
02:09
Middle-Man - Solution Walkthrough
03:31

14

Code Smell: Mutable Data

This section addresses the problematic practice of modifying data structures in place rather than returning new instances with changes. The focus is on understanding why mutation creates difficult-to-track bugs, hidden dependencies between code parts, and how to adopt immutable or copy-based approaches for safer code.
Mutable Data - Overview
03:56
Mutable Data - Code Example
02:55
Mutable Data - Solution Walkthrough
04:45

15

Code Smell: Feature Envy

This section identifies the problem of functions that rely too heavily on other modules to complete their work, indicating low cohesion and misplaced responsibilities. The discussion covers how to recognize when functionality belongs in different locations and how to reorganize modules to reduce coupling and improve maintainability.
Feature Envy - Overview
02:28
Feature Envy - Code Example
03:53
Feature Envy - Solution Walkthrough
14:05

16

Code Smell: Divergent Change

This section addresses the code smell of divergent change, which occurs when a single entity needs to be modified for multiple unrelated reasons. It demonstrates how this violates the single responsibility principle and shows how splitting entities by responsibility reduces the reasons for change.
Divergent Change - Overview
01:44
Divergent Change - Code Example
01:55
Divergent Change - Solution Walkthrough
03:56

17

Code Smell: Large Interfaces

This section discusses the problematic pattern where modules expose too many public methods, leading to information leakage, increased coupling between components, and difficulty maintaining code quality. The focus is on identifying when interfaces become unnecessarily large and strategies for breaking them down into properly bounded, more focused abstractions that serve clear domain responsibilities.
Large Interfaces - Overview
04:17
Large Interfaces - Code Example
04:24
Large Interfaces - Solution Walkthrough
08:24

18

Code Smell: Multitaskers

This section addresses the code smell of multitaskers or functions that do multiple things, which causes them to be harder to understand and less reusable. It clarifies that the problem is not length but rather handling multiple unrelated responsibilities within a single entity.
Multitaskers - Overview
03:15
Multitaskers - Code Example
01:18
Multitaskers - Solution Walkthrough
05:40

19

Code Smell: Unclear / Confusing Names

This section addresses the code smell of unclear names, which occurs when variable and method names do not clearly convey their purpose or content. It emphasizes that meaningful names reduce cognitive load and eliminate the need to constantly refer back to declarations and usage contexts.
Unclear Names - Overview
02:41
Unclear Names - Code Example
03:03
Unclear Names - Solution Walkthrough
04:22

20

Code Smell: Speculative Generality

This section addresses the problem of adding functionality that isn't needed right now but is anticipated for future use. The discussion emphasizes how speculative features add unnecessary complexity and waste resources, while advocating for simple designs that remain flexible and easy to extend when actual requirements emerge.
Speculative Generality - Overview
02:04
Speculative Generality - Code Example
06:47
Speculative Generality - Solution Walkthrough
13:41

21

Code Smell: Unnecessary Exceptions

This section discusses the problem of throwing exceptions when reasonable default behaviors or values could be used instead. The focus is on understanding how unnecessary exceptions interrupt program flow, complicate code, and create boilerplate handling, and how sensible defaults can make code simpler and more resilient.
Unnecessary Exceptions - Overview
05:12
Unnecessary Exceptions - Code Example
02:38
Unnecessary Exceptions - Solution Walkthrough
04:06

22

Code Smell: Shallow Modules

This section addresses the problem of modules with large interfaces that provide simple or shallow functionality, resulting in complexity without corresponding benefit. The focus is on creating deep modules where simple interfaces hide meaningful complexity, and on eliminating shallow modules that add little value.
Shallow Modules - Overview
02:51
Shallow Modules - Code Example
05:14
Shallow Modules - Solution Walkthrough
09:30

23

Conclusion

This section wraps up the course on writing quality software by reviewing the core design principles covered throughout the material. It emphasizes the importance of recognizing common warning signs of poor design and understanding not just how to fix them but why they occur.
Congratulations and thank you!
00:39
Certificate of Completion

Frequently asked questions

Who is this course designed for?

This course is designed for multiple technical roles and career stages.

Software Engineers and Developers who want to write better code and advance their careers will learn to recognize the subtle design issues that make code hard to change and maintain. You will gain practical techniques to refactor existing code and make better design decisions in new code from day one.

Technical Leads and Senior Engineers responsible for code quality will discover systematic approaches to mentor junior developers, establish code standards, and recognize when architectural decisions create unnecessary complexity. You will understand not just what good design looks like but how to communicate why poor design matters to your team.

Software Architects and Technical Stakeholders evaluating code quality and maintainability will gain the vocabulary and frameworks to assess codebases, identify structural problems, and guide refactoring efforts. You will understand how design decisions early in a project compound over time.

Developers Transitioning to New Roles who want to succeed in positions with higher technical expectations will build the foundational knowledge required to work effectively in teams with strong engineering standards.

What prior knowledge do I need before taking this course?

You should have practical experience writing code in any object-oriented or functional programming language. Basic familiarity with concepts like classes, functions, interfaces, and inheritance will help you follow the examples.

No prior knowledge of design patterns, SOLID principles, or software architecture is required. The course builds your knowledge from the ground up, starting with foundational concepts and progressing through increasingly sophisticated design challenges.

How are the code examples structured so I can follow along?

Each code smell is presented in three parts. First, you see the problematic code and understand the issues it creates. Second, you learn the specific refactoring techniques to fix the problem. Third, you see the improved code and understand why it is better.

The course uses Git branches to organize examples. You can check out different branches to see before-and-after versions of code, making it easy to compare changes and understand the transformation. The repository includes clear instructions on navigating the examples using standard Git commands and IDE features.

Will I be able to refactor existing code with these techniques?

Yes, absolutely. The refactoring techniques covered in this course are designed to be practical and applicable to existing systems. You will learn how to improve code incrementally without requiring a complete rewrite.

Each section includes concrete refactoring steps that show exactly how to move from problematic code to improved code. You will understand how to identify the scope of changes needed and how to refactor safely in a way that maintains functionality while improving design. These are the same techniques used by professional development teams to gradually improve their codebases while continuing to deliver features.

We use cookies to provide you with an optimal experience and relevant communication. Learn more or accept individual cookies.

Necessary

Necessary cookies (First Party Cookies) are sometimes called "strictly necessary" as without them we cannot provide the functionality that you need to use this website. For example, essential cookies help remember your preferences as you navigate through the online school.

Functional

Functional cookies enable this website to provide enhanced functionality and personalization, by remembering information you have entered and choices you make. These preferences are remembered through the use of persistent cookies, so that you will not have to set them again the next time you visit the website.

Analytics

Analytics cookies track information about visits on our website so that we can measure and improve its performance, as well as optimize our course content. These cookies help us analyze user behavior by tracking the number of visits, how visitors use the website, which site or page they come from and how long they are staying for.

Marketing

Marketing cookies are used to deliver advertising material relevant to you and your interests. They are also used to limit the number of times you see an advertisement, resulting to more targeted advertising, as well as help us measure the effectiveness of our campaigns. They are usually placed by advertising networks we collaborate with, with our permission.