os-sdlc lint rule: mine reek Feature Envy (conservative) into a custom cop #114

Closed
opened 2026-07-23 18:43:38 +00:00 by jared · 1 comment
Owner

Mine reek's Feature Envy detector into a custom Sdlc/Structural/* cop, with a conservative threshold.

Rationale: POODR Ch.4 -- behavior living in the wrong object, reaching into another object's data instead of sending it a message. Highest false-positive rate of the endorsed reek subset (fluent APIs), so mine with a deliberately conservative detection threshold. Fourth-priority mining target per ADR-0061.

Note: "sandi-analysis.md" cited in the original body does not exist; rationale stands on ADR-0061.

Problem

def total_with_tax(order)
  order.subtotal + (order.subtotal * order.region.tax_rate)
end

Three of the method's four calls target order/order.region; the method itself never touches self. This behavior belongs on Order.

Detection

  • Inputs: each def node's body; local variables/params bound to a single external receiver.
  • Algorithm (conservative): 1) count message sends per distinct external receiver (a param or local, not self); 2) count message sends to self/no receiver; 3) flag only if one external receiver receives >=3 calls AND self receives 0 calls AND the receiver is not a plain attr-chain on a single value (avoids fluent-API false positives, e.g. Rails .where(...).order(...)) -- limit to distinct method names, not chained calls on one expression.
  • Message: "total_with_tax sends 3 messages to 'order' and none to self (Reek: Feature Envy) -- this calculation likely belongs on Order; move it there or extract."

Correction

- def total_with_tax(order)
-   order.subtotal + (order.subtotal * order.region.tax_rate)
- end
+ # on Order
+ def total_with_tax
+   subtotal + (subtotal * region.tax_rate)
+ end

Pass/fail examples

  • Must fail: method with 3+ distinct-method calls to one external receiver, 0 calls to self.
  • Must pass: method mixing calls to self and to a collaborator, or a single fluent chain (x.where(a).order(b)) that only ever calls 2 distinct messages on one chained expression.

Provenance

ADR-0061, reek Feature Envy detector (conservative threshold), filed 2026-07-23.

Mine reek's Feature Envy detector into a custom Sdlc/Structural/* cop, with a conservative threshold. Rationale: POODR Ch.4 -- behavior living in the wrong object, reaching into another object's data instead of sending it a message. Highest false-positive rate of the endorsed reek subset (fluent APIs), so mine with a deliberately conservative detection threshold. Fourth-priority mining target per ADR-0061. Note: "sandi-analysis.md" cited in the original body does not exist; rationale stands on ADR-0061. ### Problem ```ruby def total_with_tax(order) order.subtotal + (order.subtotal * order.region.tax_rate) end ``` Three of the method's four calls target `order`/`order.region`; the method itself never touches `self`. This behavior belongs on `Order`. ### Detection - **Inputs:** each `def` node's body; local variables/params bound to a single external receiver. - **Algorithm (conservative):** 1) count message sends per distinct external receiver (a param or local, not `self`); 2) count message sends to `self`/no receiver; 3) flag only if one external receiver receives >=3 calls AND self receives 0 calls AND the receiver is not a plain attr-chain on a single value (avoids fluent-API false positives, e.g. Rails `.where(...).order(...)`) -- limit to distinct method names, not chained calls on one expression. - **Message:** `"total_with_tax sends 3 messages to 'order' and none to self (Reek: Feature Envy) -- this calculation likely belongs on Order; move it there or extract."` ### Correction ```diff - def total_with_tax(order) - order.subtotal + (order.subtotal * order.region.tax_rate) - end + # on Order + def total_with_tax + subtotal + (subtotal * region.tax_rate) + end ``` ### Pass/fail examples - **Must fail:** method with 3+ distinct-method calls to one external receiver, 0 calls to self. - **Must pass:** method mixing calls to self and to a collaborator, or a single fluent chain (`x.where(a).order(b)`) that only ever calls 2 distinct messages on one chained expression. ### Provenance ADR-0061, reek Feature Envy detector (conservative threshold), filed 2026-07-23.
Author
Owner

This was generated by AI during triage.

Implemented via os-sdlc pipeline (test-writer -> red -> programmer -> green -> ac-lint -> lint -> reviewer APPROVE, 2 rounds). Merged to main in 3fbb9b8.

> *This was generated by AI during triage.* Implemented via os-sdlc pipeline (test-writer -> red -> programmer -> green -> ac-lint -> lint -> reviewer APPROVE, 2 rounds). Merged to main in 3fbb9b8.
jared 2026-08-01 19:40:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#114
No description provided.