Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Accelerate quality software

How to manage conditionals in code effectively

Conditionals in code can be as invasive as Asian carp. Learn how to manage them effectively with these practical strategies to keep your code clean and maintainable.
Sam Jones
|
December 12, 2017
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Growing up near the Great Lakes, I’m well aware of the threat that Asian carp are posing on the polluted lakes we endearingly call the “North Coast.” It seems that these carp can’t be stopped. They’re breeding like crazy in an environment that isn’t equipped to keep their population in check. It’s so bad that underwater electric barriers have become viable solutions to the problem.

I can’t help but draw parallels between these carp and conditionals in code I’ve seen over the years.

They’re always introduced for a good reason, whether they’re controlling parasites or controlling application flow. Once they start to spread, though, they tend to take over.

Conditionals spread

Are conditionals really as evil as an invading species of bottom feeders? To answer that question we need to look at how software changes over time. That requires us to think about how people change software over time.

People usually make the most obvious and convenient change. When there’s delivery pressure, we do what’s easiest. This is especially true when we aren’t confident that our change is isolated. When we’re worried that we may break other code, we do what’s most convenient and seems least “dangerous”.

Unfortunately, this means that conditionals tend to grow. What’s the easiest change when the complexity of a function is already through the roof? We add one more branch to the conditional and cross our fingers.

Conditionals are confusing

Conditionals mix multiple levels of abstraction in one neat little package. Here’s a list of things that I have to keep in my head while I’m reading a conditional:

  1. The shape of the conditional to understand the available paths through the application.
  2. The scope of multiple variables as they’re used within the conditional.
  3. The values that are used to determine which path to follow. (* This often means understanding the structure of a deeply nested object.)
  4. The actual code being executed within each path.
  5. How each of these specific actions affect the larger system since none of them have names.

Just thinking about all the things I need to think about leaves me exhausted. This is another reason why conditionals spread. After getting a grasp on all that information, I just don’t have any more room to plan a refactor!

Abstractions to keep the population at bay

Just like those carp, conditionals should be carefully culled. Unfortunately, Slack doesn’t have integrations for electric barriers or poison pills. There are a few patterns that I regularly use instead of conditionals. Before blindly adopting any of these techniques, ask if the code can be written without a conditional.

These 2 techniques build on All the Little Things. They are ways to design components that have a singular focus, rather than a list of responsibilities like we see above.

Each technique has a specific problem it solves. Each proposed alternative has a few important things in common.

  • The new abstraction has a single purpose. Choose and execute the appropriate path(s) through the application. That’s it. Fake paths can be provided for testing the abstraction so details about a specific path aren’t complicating the test.
  • Each path has the same 2 methods. One to test whether it should be executed, and one to execute the action. This allows each piece to be defined and tested in isolation. Also we gain valuable names describing each path, helping other developers understand its purpose.

Alternative to if / else if / else

The problem: We only want to execute one path through the application.

export default function walkDog (dog) {
  if (dog.hasEatenRecently()) {
    dog.walk(length: LONG_WALK)
  } else if ((time.readyForBed() && dog.hasWalkedRecently()) || time.aboutToLeave()) {
    dog.walk(length: JUST_POP_OUTSIDE)
  } else {
    dog.walk(length: AROUND_THE_BLOCK)
  }
}

The solution: Find the first matching path, then execute it.

import * as pooWalk from './poo-walk'
import * as beforeBed from './before-bed'
import * as aboutToLeave from './about-to-leave'
import * as normalWalk from './normal-walk'

const walkingPatterns = [ pooWalk, beforeBed, aboutToLeave, normalWalk ]

export default function walkDog (dog, walkingPatterns = walkingPatterns) {
  walkingPatterns
    .find(pattern => pattern.matches(dog))
    .execute(dog)
}

// ------------- ./normal-walk.js ------------- //
export function matches (shoppingCart) { ... }
export function execute (shoppingCart) { ... }

Alternative to multiple ifs

The problem: We only want to execute each appropriate path through the application.

export default function applyDiscounts (shoppingCart) {
  const discountedTotal = shoppingCart.total;

  if (shoppingCart.promotions && shoppingCart.promotions.length >= 0) {
    discountedTotal -= shoppingCart.promotions.each(promo => promo.apply(shoppingCart))
  }

  if (shoppingCart.user.credit) {
    discountedTotal -= shoppingCart.user.credit
  }

  return discountedTotal
}

The solution: Find all matching paths, then execute them.

import * as cartPromotions from  './cart-promotions'
import * as userCredits from  './user-credits'

const discountCalculators = [ cartPromotions, userCredits ]

export default function applyDiscounts (shoppingCart, discountCalculators = discountCalculators) {
  return discountCalculators
    .filter(calculator => calculator.canBeApplied(shoppingCart))
    .reduce((discountedTotal, calculator) => discountedTotal - calculator.apply(shoppingCart), shoppingCart.total)
}

// ------------- ./cart-promotions.js ------------- //
export function canBeApplied (shoppingCart) { ... }
export function apply (shoppingCart) { ... }

Related Insights

🔗
A better way to sort Ruby objects: How our Put gem makes sorting by multiple conditions easy

Explore our insights

See all insights
Leadership
Leadership
Leadership
The business of AI: Solve real problems for real people

After participating in the Perplexity AI Business Fellowship, one thing became clear: the AI hype cycle is missing the business fundamentals. Here are 3 evidence-based insights from practitioners actually building or investing in AI solutions that solve real problems.

by
Cathy Colliver
Leadership
Leadership
Leadership
Pragmatic approaches to agentic coding for engineering leaders

Discover essential practices for AI agentic coding to enhance your team’s AI development learning and adoption, while avoiding common pitfalls of vibe coding.

by
A.J. Hekman
by
Aaron Gough
by
Alex Martin
by
Dave Mosher
by
David Lewis
Developers
Developers
Developers
16 things software developers believe, per a Justin Searls survey

Ruby on Rails developer Justin Searls made a personality quiz, and more than 7,000 software developers filled it out. Here's what it revealed.

by
Justin Searls
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.