Review this. A small online shop has one helpers file with 40 functions - price formatting, SMS sending, tax calculation, CSV parsing, refund amounts, retry wrappers, slug generation. Three developers want to split it by type into strings, dates and money. What would you change, and what would you leave alone?
Show the full answer Hide the answer
What is being tested
Whether you split code by what it looks like or by why it changes. Cohesion means things that change for the same reason live together, and the proposed split by data type groups functions that have nothing to do with each other while separating ones that do.
The reasoning
Count the reasons these 40 functions would change. Tax calculation changes when a tax rule changes. Refund amounts change when the refund policy changes. Those two usually move together, because a refund has to reverse the tax that was charged. Under a "money" file they sit together by luck, but the SMS sender and the CSV parser would land under "strings" with price formatting, and a change to the SMS provider would then touch the same file as a change to how prices are displayed.
The check that settles it is version history, not opinion. Run git log --name-only over the last 90 days and count how often pairs of functions change in the same commit. If calculate_tax and refund_amount appear together in 9 of 11 commits, they belong in one billing module. If slugify has not changed in two years and nothing co-changes with it, it can stay in a small shared utilities file.
What I would change
- Move tax, refund and price rounding into a billing module that owns the money rules.
- Move SMS and email sending into a notifications module so a provider switch touches one place.
- Keep the truly generic leftovers (slug generation, retry wrapper) in a small utilities file of perhaps ten functions.
What I would leave alone
The CSV parser if only one import job uses it: put it next to that job rather than inventing a module. Do not rename things for taste. Failure to watch for: if a rule change in tax still needs edits in two files after the split, the boundary is wrong; re-run the co-change count after a month. Rule: move a function only when the history shows it co-changes with its new neighbours. Grounding: this is the usual state of a young shop codebase, where a utils file collects whatever had no obvious home.
When this is the wrong answer
For a 600-line file that one person maintains, splitting costs more than it saves: every move creates imports to fix and a review to read. Refactor when two people collide in the same file or when a change in one area keeps breaking another, not because the file is long.
Common weak answers
- "Split by type, it is the standard layout." It groups by appearance, so one business rule change still touches several files.
- "Leave it, it works." At 40 functions and three developers, merge conflicts and accidental coupling are already likely.
What a strong answer adds
Suggest making the first split mechanical and reversible: move functions without changing behaviour, in a pull request with no logic edits, so a reviewer can verify it in minutes.