Four ways to manage code smell
By Jay Jones
Code in any language can misbehave, read poorly, and smell funky. In this post, we cover four ways to manage code smell.
What is code smell?
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 misbehave, 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 various 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 more substantial.
- Please 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?”
- 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?”
- 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 with the sales team to find opportunities for change. Ask yourself and your team, “How well does the community support this library?”
- Refactor often with the goals of eliminating functional issues, reducing tech debt, and improving code smell.
- Think collaboratively to plan around team changes. 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.
- 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 also also be for others.
- Focus on reusability to enforce DRY (Don’t Repeat Yourself): recognize patterns and ask yourself, “Where could this piece be used elsewhere?”
- Don’t abstract too much: balance your 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?”
- 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 reusable modules. Ask yourself, “If this black box that, by design, I don’t need to understand becomes deprecated, how will we manage it?”
- 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 duties. Ask yourself, “Is this file getting long and hard to read? Should I abstract some of it into a new component?”
- Create a “style guide” abstraction to interface with design stakeholders. Set expectations with client designers by working with them on a standard palette; enforce a UI aesthetic (and, sometimes, 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.
- 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?”
- Name components explicitly, but do not exclude them from being reusable. Ask yourself, “Am I spending too much time thinking about this name?”
- 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?”
- 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?”
- Try to avoid ‘any’ types (or blanket pointers to objects of arbitrary type); use strong types to help pass 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?”
- 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). Using an optional type should be by design and for a good reason. (Example good reason: optional boolean may not be loaded yet, so it could be undefined.)
04. Strategize Version Control
- Version all code and project artifacts so that changes can be tracked.
- 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?”
- Consider how your changes may destabilize the code or introduce regressions.
- Reference items in this guide (or add things 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 approach to combating code smell, ensuring we can repeatedly deliver quality solutions to our clients. If our approach to code smell resonated with you, we’d love to hear from you. We’re hiring!