> ## Content Index
> Fetch the complete content index at: https://bitstales.eu/llms.txt
> Use this file to discover other available public pages before exploring further.

# One more if, please: a function's life in seven meetings
- URL: https://bitstales.eu/en/if-proliferation/
- Published: 2025-09-06T10:23:00.000Z
- Updated: 2025-09-06T10:23:00.000Z
- Description: The business keeps changing its mind, a super-manager with a degree in Potato Chip Science has a vision, and finalPrice() goes from one line to a maze. Autopsy and treatment of if proliferation.
- Author: Vittorio
- Tags: Software Engineering, Programming, #en

This is the true story, or at least a plausible one, of a function. It was called `finalPrice()`, it was born healthy and it had one line. Then it met **Dr Brambilla**, Chief Vision Officer, with a degree in Potato Chip Science, a master's in Cross-Functional Synergies and one unshakeable belief: *"It's just an if, how long can it take?"*.

The characters are made up. The `if`s, sadly, are real: you'll find them in any repository more than three years old. Let's follow the function meeting by meeting, then do the autopsy, and finally see how it could have been saved.

## Meeting 1: "Let's give a 10% discount"

Monday morning. Dr Brambilla has read an article about customer loyalty. Simple request, simple code:

```javascript
function finalPrice(order) {
  return order.total * 0.9;
}
```

One line, one test, no doubts. Enjoy it, because it won't last.

## Meeting 2: "Only for premium customers. And not on Saturdays"

"The shop is already full on Saturdays," explains Brambilla, with the look of someone who once saw a chart.

```javascript
function finalPrice(order) {
  if (order.customer.premium && new Date().getDay() !== 6) {
    return order.total * 0.9;
  }
  return order.total;
}
```

Note the first warning sign: the function now depends on the **system clock**. The test that passed on Friday will fail on Saturday. The server runs on UTC and the shop doesn't: in summer, between midnight and 2 a.m. on Saturday (Italian time), the server still thinks it's Friday and applies the discount. Nobody will notice for eight months.

## Meeting 3: "Customers in Bergamo get 15%, but only by bank transfer"

A commercial agreement has been made. Nobody knows with whom, but it has been made.

```javascript
  if (order.customer.province === "BG" && order.payment === "bank_transfer") {
    return order.total * 0.85;
  }
```

A question nobody asked: does a premium customer in Bergamo paying by bank transfer get 10% or 15%? Or 25%? It depends on which `if` comes first in the code. So the company's commercial policy is being decided by the **order of the lines**.

## Meeting 4: "Black Friday counts double, except on items already on sale"

```javascript
  if (isBlackFriday() && !order.lines.some(l => l.product.alreadyDiscounted)) {
    discount *= 2;
  }
```

"Already on sale" compared to what? The list price, yesterday's price or a competitor's price? Three interpretations, three developers, three different behaviours in three different places in the code.

## Meeting 5: "Customer Rossi has a special deal"

```javascript
  if (order.customer.id === 4812) {   // Rossi, 2025 deal (ask Brambilla)
    return order.total * 0.7;
  }
```

A customer ID hard-coded in the source is a commercial contract stored in the wrong place. When Rossi changes VAT number, a duplicate account will appear. And when Brambilla changes company, nobody will know what that comment means any more.

## Meeting 6: "Remove the Saturday discount. Actually no, put it back, but only in March"

By now the function has 60 lines and a new global variable, `SATURDAY_DISCOUNT_ENABLED`, read from a config file that only exists in production. The "only in March" code lives in another module, because "it was quicker that way".

## Meeting 7: "I saw a demo: we want AI dynamic pricing"

September 2025: no company meeting goes by without the word "agentic". Brambilla has seen a presentation where a model picks the optimal price in real time. The request is to build it "by the end of the sprint, without touching the existing rules".

```javascript
function finalPrice(order) {
  if (FEATURE_AI_PRICING) {
    const p = await suggestPriceAI(order);            // async in a sync function. Yes.
    if (p && p > 0 && p < order.total * 2) return p;  // "guardrail"
  }
  if (order.customer.id === 4812) return order.total * 0.7;
  let discount = 0;
  if (order.customer.premium) {
    if (shopDay() !== 6 || (SATURDAY_DISCOUNT_ENABLED && currentMonth() === 3)) {
      discount = 0.10;
    }
  }
  if (order.customer.province === "BG") {
    if (order.payment === "bank_transfer") {
      if (discount < 0.15) discount = 0.15;   // or do they add up? (see ticket #1123, closed without an answer)
    }
  }
  if (isBlackFriday() && !order.lines.some(l => l.product.alreadyDiscounted)) discount *= 2;
  // TODO: remove after the spring 2023 campaign
  if (SPRING_CAMPAIGN) discount += 0.05;
  return order.total * (1 - discount);
}
```

End of the story. The function is still in production. Nobody touches it. The file has a comment at the top: `// DO NOT MODIFY WITHOUT TALKING TO BRAMBILLA`. Brambilla, meanwhile, has been promoted.

## Autopsy: why ifs multiply

**1\. Complexity grows exponentially, not linearly.** In 1976 Thomas McCabe defined *cyclomatic complexity*: the number of independent paths through a function, which in practice is the number of decisions plus one. McCabe suggested staying under 10\. Our function is above 15\. But the scary number is another one: with *n* independent conditions there are **2n** possible combinations. Seven conditions make 128 scenarios. How many has anyone tested? And how many has the business actually *defined*?

**2\. Every if is a business rule in disguise.** "Bergamo gets 15%" is not program logic: it's a commercial policy, with an owner, a start date and (hopefully) an end date. Written as an `if`, it loses all of that: no expiry, no author, no priority relative to the other rules. It just stays there forever.

**3\. Dead ifs never die.** The most famous case is **Knight Capital**, on 1 August 2012\. A new deployment reused an old flag that, years earlier, had switched on a retired trading feature called "Power Peg". On one of the eight servers the new code was not installed. The flag woke up the dead code, which started buying and selling shares with no control. In 45 minutes the firm lost about $440 million, and a few months later it no longer existed as an independent company. The `// TODO: remove after the spring 2023 campaign` in our function is a close relative of that flag.

**4\. Code mirrors the org chart.** *Conway's law* (1967) says that systems copy the communication structure of the organisations that build them. You can see it clearly in `finalPrice()`: every branch is a meeting, every exception is a manager, every "ask so-and-so" comment is a person who has since changed jobs.

**5\. The business doesn't know what it wants, and that's fine.** This is the least popular part among programmers, but it is true. Fred Brooks wrote it in 1986 in *No Silver Bullet*: *"The hardest single part of building a software system is deciding precisely what to build."* Changing requirements are not a business defect: they are how you discover what is needed. The defect is turning every exploratory idea into a permanent `if`, with no model, no expiry date and nobody deciding what happens when two rules overlap.

## Treatment: how finalPrice() could have been saved

**Flatten with guard clauses.** The first step is cosmetic but important: return early instead of nesting. The "arrow" of `if`s inside `if`s becomes a readable list.

**Turn rules into data.** Commercial rules have attributes: condition, discount, priority, validity, owner. Put them in a data structure (or a table) and write *one* engine that applies them:

```javascript
const RULES = [
  { id: "rossi-deal",       priority: 100, when: o => o.customer.contract === "ROSSI-2025",
    discount: 0.30, stackable: false, valid: ["2025-01-01", "2025-12-31"], owner: "sales" },
  { id: "bergamo-transfer", priority: 50,  when: o => o.customer.province === "BG" && o.payment === "bank_transfer",
    discount: 0.15, stackable: false, valid: ["2025-03-01", "2025-12-31"], owner: "north sales" },
  { id: "premium",          priority: 10,  when: (o, ctx) => o.customer.premium && ctx.shopDay !== 6,
    discount: 0.10, stackable: true,  valid: ["2025-01-01", null], owner: "marketing" },
];

function finalPrice(order, ctx) {           // ctx: the shop's date and time, passed in from outside
  const active = RULES
    .filter(r => inValidity(r.valid, ctx.date) && r.when(order, ctx))
    .sort((a, b) => b.priority - a.priority);
  const top = active[0];
  const discount = !top ? 0
    : top.stackable ? active.filter(r => r.stackable).reduce((s, r) => s + r.discount, 0)
    : top.discount;
  return order.total * (1 - Math.min(discount, 0.5));   // explicit cap
}
```

It isn't magic. But "only in March" becomes a date instead of an `if`, "who wins between Bergamo and premium" becomes a number the business has to *choose*, and the date and time come from outside, so tests no longer depend on the day they run. On top of that, every rule has an owner you can ask questions.

**Use polymorphism when the *model* changes, not the number.** If business customers have a different price list, auctions use another algorithm and subscriptions are billed separately, those are not three `if`s: they are three pricing strategies (see the design patterns entry). It's the refactoring Martin Fowler calls *Replace Conditional with Polymorphism*.

**Give feature flags an owner and an expiry date.** Every flag is born with a removal date and a ticket. A CI check that fails when a flag has expired is worth more than a hundred reminders. Knight Capital teaches that a recycled flag is a loaded gun.

**Ask for examples, not sentences.** "Discount for premium customers except on Saturdays" is a sentence. A table of examples agreed with the business (*Specification by Example*, Gojko Adzic, 2011) is a specification, and it immediately becomes a test:

| Customer | Province | Payment       | Day                               | Expected discount |
| -------- | -------- | ------------- | --------------------------------- | ----------------- |
| premium  | MI       | card          | Tuesday                           | 10%               |
| premium  | BG       | bank transfer | Tuesday                           | **?**             |
| premium  | BG       | bank transfer | a Saturday in March, Black Friday | **??**            |

Those question marks are the most valuable output of an engineer's work: they are business decisions *nobody has made yet*. Without the table, the order of the lines of code would have made them.

**Beware of the "universal rules engine".** The opposite extreme exists and is just as dangerous. When rules become a home-made programming language, in XML or YAML, you have built a second language with no debugger, no tests and no types. It's called the *inner-platform effect*. The `if`s haven't gone away; they've just moved to a file where nobody can see them.

## And the programmer? Not just a victim

It would be convenient to blame everything on Dr Brambilla, and it's fun too. But every `if` written without asking questions is an `if` we signed ourselves. An engineer's job is not translating sentences into conditions: it's turning vague wishes into **models** with explicit rules, priorities and expiry dates. Sometimes the most useful thing to say in a meeting is also the simplest:

> "Sure. And if a premium customer in Bergamo pays by bank transfer on a Saturday in March during Black Friday, how much do they pay?"

The silence that follows is worth more than any refactoring. That is where the business finds out what it really wants. And since Brambilla's silence usually lasts until the next change of strategy, the answer had better end up in a table and not in an `if`.

See you at the next bit's tale. No `else`. 🧾