Multiple Conditionals Clean Way

 |  Niceties

Make conditionals of any size readable and manageable.

For more about conditionals code smells and refactorings refer to Venables R., Refactoring Workbook (2003), ch. 7, Fowler M.,Refactoring (1999, 2018), ch. 10, Martin R., Clean Code (2008), G28,29,33, Kerievsky J., Refactoring to Patterns (2004), ch. 4, Conditional Complexity.

Contents

Problem

Note the context we are talking.

There are always many conditionals asking to be implemented in code. But beyond the simplest cases (noted below) conditionals quickly become unreadable and unmanageable. Here is the starting point demonstrating how to transform these into the effective constructs.

Desired Behavior: Detect if the provided given argument is among the allowed values; Inputs: given: a string or an array of strings to check, allowed: the value(s) to check against; Outputs: boolean, true;

Goal: treat multiple allowed argument values cleanly (no ifs.), hide conditions complexity;

Solution

From consumer standpoint the solution encapsulates 1 the conditional behavior behind a meaningfully named method. From the implementation details standpoint the conditionals are simplified 2 into a short flat structure.

/**
 * Check if a `given` value is allowed.
 *
 * @param {string | string[]} given The value(s) to check.
 * @param {string | string[] | | Record<string, unknown>} allowed The allowed value(s) to compare against.
 * Can be a TypeScript enum
 *
 * @returns {boolean} - Returns true if all `given` are among the allowed values, otherwise false.
 */
public static isAllowed(given: string | string[], allowed: string | string[] | Record<string, unknown>): boolean {

  /**
   * Normalize `given` to array.
   * Could use here and further isString from Lodash if already used in a project.
   */
  const givens = Array.isArray(given) ? given : [given];

  /**
   * Avoiding conditionals to normalize `allowed` argument string, string array or enum to array of strings input.
   */
  const allowedArgValues = [
    typeof allowed === 'string' ? [allowed] : null,
    typeof allowed === 'object' ? Object.values(allowed as Record<string, unknown>) : null
  ];

  // Finally get the `allowed` values.
  const _allowed = allowedArgValues.find((value: string[] | null) => { return value; }) as string[];

  // Replace conditionals.
  return givens.every((given: string) => { return _allowed.includes(given); });
}

Benefits

  • No if's, expressive, scalable, readable, straightforward extensible for more allowed argument values still keeping readability;

  • Extendable by 1) extracting allowed to the object property (e.g. this.allowed) or 2) application-wide constant or enum (e.);

  • Further extendable - the baby version of Strategy pattern 3 - as logic in allowedArgValues items grows, introduce on-demand dedicated strategy methods, then classes;

Implied SWE Principles

  • Polymorphism: given conveniently accepts 2 types, allowed 3 types (arrays and enums - objects are objects in JS).
  • SRP: encapsulate the decision on argument transformation in independent allowedArgValues elements;
  • SoC: encapsulate the final argument value retrieved in .find one-liner function;
  • Open/Closed Principle: easily add more types for allowed argument values with no need for other modifications in the method;
  • Delegation, SRP, encapsulation: _givens helper, that can be extended to further prepare for comparison givens (e.g. normalize to upper case);
  • Declarative Approach: the method name, intermediary variables usage and the ir naming, use of .find, .every, .includes.

Important on ifs

  • Single if is acceptable on the single early return;
  • Two have to be written as ternary;
  • 3+ ifs: be the above solution at start and following towards (not necessarily to) Strategy pattern;

Footnotes

  1. Martin R., Clean Code (2008), p. 301, "G28: Encapsulate Conditionals".

  2. Fowler M., Refactoring (2018), p. 239, "Simplifying Conditionals Logic", Kerievsky J., Refactoring to Patterns (2004), p. 84, "Conditional Complexity".

  3. Kerievsky J., Refactoring to Patterns (2004), p. 169, "Replace Conditional Logic with Strategy".