The Quest Begins (The "Why")
I still remember the first time I opened a legacy codebase that looked like a tangled ball of yarn after a cat got loose. Functions stretched over a hundred lines, nested conditionals that felt like Inception layers, and variable names that were cryptic enough to make a spy blush. I spent three hours tracking down a bug that turned out to be a stray == in a loop that should have been a >=. When I finally fixed it, I felt less like a victorious hero and more like someone who’d just survived a raid boss in Dark Souls without ever leveling up—exhausted, bruised, and wondering if there was a better way.
That experience forced me to ask: What single habit could keep me from getting lost in the jungle every time I opened a file? The answer turned out to be deceptively simple: write small, focused functions.
The Revelation (The Insight)
A function should do one thing, and do it well. When you keep a function under, say, 20‑30 lines (or even less), you give yourself a mental model that fits comfortably in your working memory. You can read it top‑to‑bottom without scrolling, reason about its inputs and outputs, and test it in isolation.
The payoff is immediate:
- Readability – Anyone (including future you) can grasp the intent at a glance.
- Debugging – When something goes wrong, the culprit is usually in a tiny, isolated piece rather than a monolithic beast.
- Reuse – Small functions are like Lego bricks; they snap together in new ways you hadn’t imagined.
- Testing – Unit tests become straightforward because there’s less setup and fewer paths to cover.
I realized that the “one thing” rule isn’t about dogma; it’s about giving your brain a break. Think of it as the Jedi’s lightsaber: elegant, precise, and deadly effective when wielded with purpose.
Wielding the Power (Code & Examples)
The Trap: The “God” Function
Here’s a typical before‑snippet I’ve seen (and written) more times than I care to admit. It’s a JavaScript function that validates a user registration form, hashes the password, and sends a welcome email—all in one go.
// ❌ BEFORE: A function that does too much
function registerUser(formData) {
// 1️⃣ Validate fields
if (!formData.email || !formData.email.includes('@')) {
throw new Error('Invalid email');
}
if (!formData.password || formData.password.length < 8) {
throw new Error('Weak password');
}
if (!formData.name || formData.name.trim() === '') {
throw new Error('Name required');
}
// 2️⃣ Hash the password (bcrypt‑style pseudo‑code)
const salt = generateSalt();
const hashedPassword = hash(formData.password, salt);
// 3️⃣ Persist user
const user = {
id: generateId(),
email: formData.email,
name: formData.name.trim(),
passwordHash: hashedPassword,
createdAt: new Date(),
};
db.save('users', user);
// 4️⃣ Send welcome email
const emailBody = `Hey ${user.name}, welcome aboard!`;
sendEmail(user.email, 'Welcome', emailBody);
return user;
}
Why this hurts:
- The function mixes validation, cryptography, persistence, and communication.
- Changing the email template means touching a function that also handles security—risky and nerve‑wracking.
- Unit testing requires mocking the DB, the mailer, and the bcrypt library all at once.
- A single typo in the validation block can hide a bug in the hashing step, making debugging a nightmare.
The Power: Small, Single‑Purpose Functions
Now let’s refactor using the “one thing” principle. Each concern gets its own tiny helper, and the main orchestrator reads like a story.
// ✅ AFTER: Small, focused functions
function validateEmail(email) {
if (!email || !email.includes('@')) {
throw new Error('Invalid email');
}
}
function validatePassword(password) {
if (!password || password.length < 8) {
throw new Error('Weak password');
}
}
function validateName(name) {
if (!name || name.trim() === '') {
throw new Error('Name required');
}
}
function hashPassword(password) {
const salt = generateSalt();
return hash(password, salt);
}
function createUserRecord(email, name, passwordHash) {
return {
id: generateId(),
email,
name: name.trim(),
passwordHash,
createdAt: new Date(),
};
}
function persistUser(user) {
db.save('users', user);
}
function sendWelcomeEmail(user) {
const emailBody = `Hey ${user.name}, welcome aboard!`;
sendEmail(user.email, 'Welcome', emailBody);
}
// Orchestrator – clear, easy to follow
function registerUser(formData) {
validateEmail(formData.email);
validatePassword(formData.password);
validateName(formData.name);
const passwordHash = hashPassword(formData.password);
const user = createUserRecord(
formData.email,
formData.name,
passwordHash
);
persistUser(user);
sendWelcomeEmail(user);
return user;
}
What changed?
- Each helper does exactly one thing and is trivial to test in isolation.
- If the email template changes, you only touch
sendWelcomeEmail. - Swapping bcrypt for Argon2? Just edit
hashPassword. - The main
registerUserfunction now reads like a checklist: validate → hash → store → notify.
Common Pitfalls to Avoid
-
Over‑extracting – Don’t create a function for a single line that adds no clarity (e.g.,
const tmp = a + b;). - Leaking state – Keep helpers pure when possible; side effects should be explicit and limited to the orchestrator.
-
Naming vagueness – Use verbs that reveal intent:
validateEmail,hashPassword, notdoStuff1.
Why This New Power Matters
Adopting small functions feels like unlocking a new skill tree in an RPG. Suddenly, you can:
- Refactor fearlessly – Change one piece without worrying about a cascade of hidden bugs.
- Onboard teammates faster – New hires can grasp a function’s purpose in seconds, not minutes.
- Increase test coverage – With less setup, you’ll actually write those unit tests instead of skipping them.
- Spot duplication – When two tiny functions look alike, you notice the opportunity to extract a shared utility.
The codebase stops feeling like a dungeon you’re forced to crawl through blindly and starts resembling a well‑lit workshop where every tool has its place.
Your Turn
Pick a function you’ve written recently that’s longer than 30 lines. Spend ten minutes breaking it into the smallest logical pieces you can identify. Write a quick test for each new piece. Notice how the mental load drops.
Challenge: Share your before/after snippets in the comments (or a gist) and tell me which part felt like leveling up. Let’s keep our codebases as sharp as a Jedi’s lightsaber—one small, focused swing at a time. 🚀
Top comments (0)