Four ways to manage code smell

Image of a Skunk on a teal background

What is code smell? According to the all-knowing Wikipedia, "In computer programming, a code smell is any characteristic in the source code of a program that possibly indicates a deeper problem."

Code in any language can behave badly, read poorly, and smell funky. We have great people and tools to help improve debugging and readability, but fixing code smell requires agreement and training on some best practices.

Assemble has deeply unique processes, capabilities, and relationships; that's our special sauce. For example, if "the standard" is to start with test-driven design, but we can't work that into our budgets, then it's not for us. Instead, we codify our standard for engineering in a way that does work for our process.

The following guidelines demonstrate smell in a variety of languages, but the core concepts remain the same. So without further ado, here are some tools and tricks we use to combat code smell: 

01. Plan for change 

Stakeholders, scope, requirements, design, architecture, schedules, budgets, teams, tools, technologies, and code all change over time, starting on day one. Anticipating and planning for change makes our code, projects, and company stronger as a whole.

  1. Stick to the scope, but openly and frequently reevaluate/reinforce stakeholder expectations regarding the stake they hold. Try to understand the subtext. Ask, "what are they really asking for?"
  2. Think holistically and anticipate the ripple effect of changes defined at any level. Ask yourself and your team, "How might this affect other pieces of the project?"
  3. Be on the lookout for changes to third party libraries, frameworks, platforms, and other shared systems over time.  Collaborate with the development team and stakeholders to mitigate risk and the sales team to find opportunities for change. Ask yourself and your team, "How well does the community support this library?"
  4. Refactor often with the goals of eliminating functional issues, reducing tech debt, and improving code smell.
  5. Think collaboratively to plan around teams changing. Stop and ask yourself, "What and how can the semantics of my work be effectively communicated to team members now and in the future?"

02. Explore abstraction

Projects exhibit abstraction at every conceptual level.

  1. Whiteboard it first: try to conceptualize and visualize a solution to see if the boxes and arrows make sense. If it's a messy picture for you, it will be for others, as well.
  2. Focus on reusability to enforce DRY (Don't Repeat Yourself): recognize patterns and ask yourself, "Where could this piece be used elsewhere?"
  3. Don't abstract too much: try to balance your own internal conventions with well-accepted standards/practices. Ask yourself, "How will I explain and defend the functionality and value of this abstract module to others?"
  4. Don't lean on libraries too much: beware of "magic" libraries that take on opaque responsibilities. Evaluate libraries for support and documentation; consider writing Assemble's own reusable modules. Ask yourself, "If this black box that, by design, I don't need to understand becomes deprecated, how will we manage?"
  5. Define and reinforce concise, disparate component responsibilities: don't try to give any one component too much responsibility. Don't let two components share the same responsibilities. Ask yourself, "Is this file getting long and hard to read? Should I abstract some of it into a new component?"
  6. Create a "style guide" abstraction to interface with design stakeholders. Set expectations with client designers by working with them on a common palette; enforce a UI aesthetic (and in some cases UX) through a parameterized theme. Ask yourself, "If the designer decides all buttons should be blue, how many lines of code will I need to change?"

03. Name and type everything with intention

Ideally, a project is self-documenting because names and types express semantics and responsibilities.

  1. Pick intentional, conventional, and conversational names for things. Ask yourself, "Does this name make sense? Does it clearly express the semantics and responsibilities of the thing I'm naming? If a new team member hopped onto this project tomorrow, would they understand what this means and what it does?"
  2. Name components explicitly but not to exclude them from being reusable. Ask yourself, "Am I spending too much time thinking about this name?"
  3. Define explicit types to describe inputs, outputs, and behaviors of components. Ask yourself, "Is it clear what this component expects as input? Will (and how will) this component break if the wrong types are used?"
  4. Lean on types to avoid string duplication. Ask yourself, "If the human-readable description of this type needs to change, how many lines of code need to change? Should we use a constant, enum, or typedef instead?"
  5. Try to avoid 'any' types (or blanket pointers to objects of arbitrary type); use strong types to help passing around state objects. Use struct/class/interface for objects and enums for shared type/mode/status/state constants wherever possible so the compiler can catch your bug instead of the runtime app. Ask yourself, "What type do I expect? How might/should my component break if I don't get the type I expect?"
  6. Try to avoid (optional) types. Use required types in interfaces to convey semantics and avoid ambiguities (e.g. optional booleans are true, false, or undefined). If using an optional type, it should be by design and for good reason. (Example good reason: optional boolean may not be loaded yet, so it could be undefined.)

04. Strategize Version Control 

  1. Version all code and project artifacts so that changes can be tracked.
  2. Only commit what you need to; try to do small, iterative, focused commits. Ask yourself, "What does the diff look like to the team member(s) reviewing my PR?"
  3. Consider how your changes may destabilize the code or introduce regressions.
  4. Reference items in this guide (or add items as needed) to this guide when reviewing/commenting on pull requests.

Join our team

At Assemble, one of our core values is to make others successful. This includes our clients. That's why we developed our own approach to combating code smell, ensuring we can deliver quality solutions to our clients over and over again. If our approach to code smell resonated with you, we'd love to hear from you. We're hiring