Skip to main content

Command Palette

Search for a command to run...

Developing as a Developer: Introduction

Updated
14 min readView as Markdown
Developing as a Developer: 
Introduction

Overview: Motivation for writing this

Writing clean, maintainable code is crucial in developing software that stands the test of time. In today's landscape where there is a massive push for AI-generated code, I suspect the art of producing readable code will become even more valuable as entire codebases are built and refactored using large language models. Furthermore, as this code and other AI-generated content pollutes the internet, it will becomes increasingly difficult for new engineers to identify "good" code. As such, I'm committed to understanding what is constitutes truly robust code. Moreover, the introduction of AI-assisted coding has minimized the need for the junior engineer, since senior engineers can now delegate more trivial tasks and code generation to an LLM. The danger, which likely won't be realized for several years, is that there is no longer a built-in path for new engineers to receive guidance and mentorship to give them the skills required to serve more senior roles. I suspect the consequences will become apparent over the next 10-20 years when senior and staff engineers retire and there are no suitable replacements. However, that won't stop the oversaturation of the current job market for entry-level software engineers. While companies may realize this and take efforts to alleviate the problem by pushing for more junior headcount, I would rather do everything in my power to write code like a senior engineer than wait around for companies to take action.

Hence, the introduction of my "Developing as a Developer" series. In each part, I plan to explore and apply a concept that will teach me (and anyone who reads it) to produce better code. While I'm going to do my best to keep each part narrowly scoped, the nature of software development is that several topics are inherently linked. For example, originally I wanted to only discuss code smells in this article, but quickly realized that the accompanying refactoring techniques are crucial in building a mental model of what constitutes clean code. Then I realized that I should probably investigate SOLID principles before getting into code smells, and now my original two part series has quickly become who-knows-how-many parts.

With that said, one constant in life and computer science is change. Before delving into the specific topics, I think it's important to understand the history of the ideas and how they have changed or evolved over time. Tracing the origins of the ideas serves two main purposes:

  1. It exercises our critical thinking muscles. We can speculate on reasons for change and improve our intuition of what will work/not work. We get to learn how subject matter experts adopt and adapt theories. This is important because blindly following principles will not make you a better programmer. In Martin's words: "principles have to be applied with judgement. If they are applied by rote it is just as bad as if they are not applied at all" (Martin, 2009). Furthermore, historizicing ideas gives us context into how theories are applied by different practitioners. Through investigating the origins of modern principles we develop a mutual understanding of their foundational underpinnings and the shared language to communicate and critique them. Without this, we would end up realizing Martin's concerns.

  2. Proper attribution of content and ideas. A huge issue I've noticed (which has been exacerbated by the rise of LLM-generated content) is failing to properly cite sources. People may copy and paste or paraphrase a response from generative AI without ever knowing (or caring) where that information came from. That's fucked up. With time these types of posts flood the internet and it will grow increasingly difficult to trace the origin of knowledge.

History of Code Smells

Refactoring describes "change[s] made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior" (Fowler & Beck, 2018, p.45). While they knew that refactoring was valuable for writing maintainable and scalable software, both authors recognized there was a gap in communicating about refactoring beyond the vague notion of programming aesthetics. Thus, when writing the first edition of their book in the 90's, Martin Fowler and Kent Beck sought to clarify how to identify when code needs to be refactored. The conception of "smells" (attributed to Kent Beck, perhaps inspired by his newborn daughter) refers to an indication (not definitive proof) that some refactoring may be needed (Fowler & Beck, 2018, p. 71). Fowler and Beck outline 24 distinct code smells in the 2nd edition of their book, which represent common patterns and structures that likely benefit from refactoring. Not all instances of code that needs refactoring will fit cleanly into one of the named categories, but familiarity with the smells will point you in the right direction.

In their 2003 paper titled "A taxonomy and initial empirical study of bad smells in code," Finnish researchers at Helsinki University of Technology proposed grouping the original 22 code smells into 7 different classifications to make them more understandable and clarify relationships between the smells (Mäntylä et al., 2003):

Bloaters: Something in the code that has grown out of control and can no longer be maintained.

Object-Orientation Abusers: Cases where the solution fails to leverage object-oriented design effectively.

Change Preventers: Code structures that when changed result in a cascade of other changes on dependent structures.

Dispensables: Redundant or otherwise unnecessary code.

Encapsulators: Issues pertaining to the chain of delegation in how objects hide or pass along data.

Couplers: Code structures are too tightly coupled.

Other Smells: Smells that don't fit into any of the other categories.

Later in 2006, Mäntylä refined the taxonomy down to five groups in, collapsing two of the categories related to coupling (Encapsulators was absorbed by Couplers).

Category Code Smell
Bloaters Long Method, Large Class, Primitive Obsession, Long Parameter List, Data Clumps
Object-Orientation Abusers Switch Statements, Temporary Field, Refused Bequest, Alternative Classes with Different Interfaces
Change Preventers Divergent Change, Shotgun Surgery, Parallel Inheritance Hierarchies
Dispensables Lazy Class, Data Class, Duplicate Code, Dead Code, Speculative Generality
Couplers Feature Envy, Inappropriate Intimacy, Message Chains, Middle Man
Other Smells Comments, Incomplete Library Class

Since the taxonomy was published before the 2nd edition of Refactoring, there does not exist a perfect mapping to pull from the literature. Of note, a few code smells were renamed from the 1st to the 2nd edition:

  • Long Method ➡️ Long Function

  • Switch Statements ➡️ Repeated Switches

  • Lazy Class ➡️ Lazy Element

  • Inappropriate Intimacy ➡️ Insider Trading (hmm, I wonder why).

Four new smells related to newer concerns (such as code comprehension, shared state, immutability, and opting for first-class functions over loops) were also added in the 2nd edition: Mysterious Name, Global Data, Mutable Data, and Loops. For the sake of completeness and consistency, I will consider these part of a grouping I'll call "New Smells".

The following table provides a brief description and the category for each of the 24 smells that I will be investigating in more depth throughout this series.

Code Smell Description Category
Long Function A function that has grown to unmanageable size over time. Bloaters
Long Parameter List Excessively large list of parameters. Bloaters
Data Clumps A set of entities that always appear together. Bloaters
Primitive Obsession No small classes for small entities, so functionality is added to another class. Bloaters
Large Class A class that is trying to do too many things (often presents as having too many fields). Bloaters
Repeated Switches Using conditional statements when a subclass could be used instead. Object-Orientation Abuser
Temporary Field Variable is declared in the wrong scope (e.g., in class scope when it should be in the method scope). Object-Orientation Abuser
Alternative Classes with Different Interfaces Closely related classes are lacking a common interface. Object-Orientation Abuser
Refused Bequest A subclass only uses some methods and properties inherited from its parents. Object-Orientation Abuser
Divergent Change A class is changed in different ways for different reasons. Change Preventers
Shotgun Surgery A single change to the system requires modifying many classes. Change Preventers
Lazy Element An element that just doesn't do much of anything for the application. Dispensables
Speculative Generality Code that exists to handle potential changes to requirements or functionality. Dispensables
Data Class Exists to solely to hold data, containing only getting and setting methods. Dispensables
Comments Misuse of comments, such as explaining overcomplicated code rather than writing simpler code. Dispensables
Duplicated Code Code that is redundant or can be consolidated with abstraction. Dispensables
Feature Envy One method is tightly coupled to other classes (outside of its own). Couplers
Message Chains Entities depend on a chain of multiple other entities to get the data it needs. Any change to the intermediates requires the client to change as well. Couplers
Middle Man A class only exists to delegate work to another class Couplers
Insider Trading Two classes are coupled tightly together Couplers
Global Data Data that can be modified from anywhere in the code base. New Smells
Mutable Data Data that can be altered in-place after initialization. New Smells
Loops ... Just loops (e.g., "for" and "while"). New Smells
Mysterious Name Names that do not clearly indicate what things are or what functions do. New Smells

Link to Code Smell – Bloaters article

History of SOLID Design Principles

SOLID represents "five principles intended to improve the make source code more understandable, flexible, and maintainable" (SOLID, Wikipedia). These principles were first articulated across the late-1990s Engineering Notebook columns for the C++ Report and later consolidated in an abbreviated form in Robert C. Martin's 2000 paper Design Principles and Design Patterns, in which he discusses the concept of software rot: the process of a once-beautiful architecture degrading into an unmanageable mess. Interestingly, it was not Martin himself who formed the mnemonic device– that is attributed to Michael Feathers, who noted the opportunity to rearrange the letters via an email to Martin around 2004 (Martin, 2018, p.58).

Of note, two principles predate Martin:

Open-Closed Principle

The Open-Closed Principle originates from Bertrand Meyer's (1988) book Object-Oriented Software Construction, where he states that "modules should be both open and closed" (Meyer, 1997) (I can't seem to find a copy of the 1st edition, but he has published the 1997 2nd edition for free on his website, so I will reference that instead). A module is considered open if it's still available for extension (e.g., adding fields to the data structures or expanding its functionality). A module is closed if it is available for use by other modules and has been given a well-defined, stable description (Meyer, 1997). Meyer (1997) resolved the open-closed tension through inheritance: a concrete base class stays closed to modification while new functionality is added by subclassing it (pp.58-60).

Martin's version of the Open-Closed Principle maintains the slogan of "open for extension but closed for modification" but differs in the mechanism. Martin leverages abstraction– client code depends on a fixed abstract type, and the system is extended by deriving new implementations of that type, protecting existing code from future edits. This shift was likely due to issues related to implementation inheritance, whereby a subclass inherits the actual implementation (including code and state), resulting in tight coupling between child and the internal workings of the parent, thus violating encapsulation (Snyder, 1986; Gamma et al., 1994). Furthermore, Mikhajlov and Sekerinski (1998) highlight the fragility of the paradigm by demonstrating how revising a base class can result in breaking subclasses. Inheriting from an abstraction solves these issues by decoupling the implementation. The subtype only inherits the method signatures (name, arguments, return type) and behavioral promise (conditions for calling the method, guarantees once it returns, rules about state) and is responsible for supplying its own behavior.

Liskov Substitution Principle

The Liskov Substitution Principle was introduced by MIT's Barbara Liskov in her 1987 OOPSLA Keynote, titled Data Abstraction and Hierarchy, where she discusses the importance of data abstraction in developing programs that are highly maintainable and easy to modify when requirements change (Liskov, 1987, p.33). When discussing a type hierarchy composed of subtypes and supertypes, she proposes the following substitution property:

If for each object o1, of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1, is substituted for o2, then S is a subtype of T.

(Liskov, 1987, p.25).

In other words, an object of a superclass can be replaced by an object of a subclass without breaking the program.

The concept was formally introduced into academia by Liskov and Wing's 1994 paper A Behavioral Notion of Subtyping, in which they argued that "the objects of the subtype ought to behave the same as those of the supertype as far as anyone or any program using supertype objects can tell" (Liskov & Wing, 1988).

SOLID Principles: Current State

Martin describes the principles more definitively in his later books Agile Software Development: Principles, Patterns and Practices, and Agile Principles, Patterns, and Practices in C#. Since then, SOLID principles have become a standard practice in the field for creating scalable and maintainable software, with several academic publications supporting their usage (Ali, 2002; Cabral et al., 2024; Yanakiev et al., 2025).

Below lists the SOLID principles from Martin (2002) that will be explored at length in a future article:

Single Responsibility Principle (SRP) (p.95)

A class should have only one reason to change"

Open-Closed Principle (OCP) (p.99)

"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"

Liskov Substitution Principle (LSP) (p.111)

"Subtypes must be substitutable for their base types"

Interface Segregation Principle (ISP) (p.138)

"Clients should not be forced to depend on methods that they do not use"

Dependency Inversion Principle (DIP) (p.127)

"a. High-level modules should not depend on low-level modules. Both should depend on abstractions.

b. Abstractions should not depend on details. Details should depend on abstractions."

References:

  1. Fowler, M. (2018). Refactoring: Improving the design of existing code (2nd ed.). Addison-Wesley Professional

  2. Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.

  3. Mäntylä, Mika & Vanhanen, Jari. (2003). Bad Smells in Software – a Taxonomy and an Empirical Study.

  4. Mäntylä, M.V., Lassenius, C. Subjective evaluation of software evolvability using code smells: An empirical study. Empir Software Eng 11, 395–431 (2006). https://doi.org/10.1007/s10664-006-9002-8

  5. Martin, Robert C, (2000). Design Principles and Design Patterns

  6. Raphael Cabral, Marcos Kalinowski, Maria Teresa Baldassarre, Hugo Villamizar, Tatiana Escovedo, and Hélio Lopes. 2024. Investigating the Impact of SOLID Design Principles on Machine Learning Code Understanding. https://doi.org/10.1145/3644815.3644957

  7. Mohamed Ali, Azrajabeen. (2022). The Impact of SOLID Principles on Code Quality and Software Lifecycle. 7. 10.5281/zenodo.15062293

  8. Yanakiev, I., Lazar, B. M., & Capiluppi, A. (2025). Applying SOLID principles for the refactoring of legacy code: An experience report. Journal of Systems and Software, 220, 112254

  9. Martin, Robert (2018). Clean Architecture: A Craftsman's Guide to Software Structure and Design. Pearson. p. 58. ISBN 978-0-13-449416-6

  10. Meyer, B. (1997). Object-Oriented Software Construction (2nd ed.) Prentice Hall.

  11. Liskov, B. (1987). "Data Abstraction and Hierarchy." OOPSLA '87 (pub. SIGPLAN Notices 23(5), 1988).

  12. Liskov, B. H., & Wing, J. M. (1994). A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems (TOPLAS), 16(6), 1811-1841.

  13. Cook, W. R., Hill, W. L., & Canning, P. S. (1990). Inheritance is not subtyping. In Proceedings of the 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (pp. 125–135)

  14. Mikhajlov, Leonid; Sekerinski, Emil (1998). A study of the fragile base class problem

  15. Snyder, A. (1986). "Encapsulation and Inheritance in Object-Oriented Programming Languages." OOPSLA '86, pp. 38–45.