One of the easiest ways to keep things clean is to separate your functions by their actual job. Most code fits into four basic buckets: actions, transformations, validations, and orchestrators.
Actions are the parts of your code that actually make things happen. They write to a database, trigger an API call, send an email, or append a line to a log file.
Because they interact with the outside world, their main job is to cause side effects. They rarely return data and usually just a success status or nothing at all. These functions are also called commands, handlers, or services.
Transformations take raw data, reshape it, and hand back something new. They do not touch the database, hit the network, or modify any global state. They just map input to output.
These are your calculators and utility helpers. Think of a function that takes a messy, user submitted email string, strips the whitespace, converts it to lowercase, and splits it into a username and domain. Because they do not have side effects, these are the easiest functions in your codebase to write tests for.
Validation functions are the gatekeepers. They take some data, check it against a set of rules, and return true or false.
They should never modify the data or trigger any external actions. Their only job is to protect your system from bad input.
Orchestrators do not do any of the heavy lifting. Instead, they act like a conductor, calling other functions in a specific order to run a workflow.
You won't find raw calculation logic or database queries inside an orchestrator. Instead, you'll see conditional logic that decides what to do next based on what the other functions return. Having a dedicated orchestrator means you can look at one file and understand the entire business process without getting bogged down in the low level details.
In a real project, these roles work together in a chain managed by the orchestrator. The orchestrator gets the input, hands it to a validator, passes it to a transformer to clean it up, and finally triggers an action to save it. Keeping these boundaries clear is usually the difference between a codebase that is easy to maintain and one where every minor change breaks something completely unrelated.