
A Tale of Software Rot
/ 52 min read
Table of Contents
A fictional story about learning DRY principles the hard way
Before We Start
This story is fictional, but the problems it describes are real. Every software organization faces the challenge of knowledge duplication as they grow and it can be around other areas than code. The specific technical solutions may vary, but the principles remain the same:
- Recognize that duplication is an organizational issue, not just a technical one.
- Invest time in understanding the full scope before attempting to fix individual cases. Don’t delay solving the overall problem; don’t rush to find a quick fix.
- Develop well-documented, shared solutions that are easy to implement.
- Change the culture to promote sharing instead of copying.
- Measure success by a reduction in complexity.
DRY1 —Don’t Repeat Yourself—is a philosophy for building sustainable software systems (and organizations) that can grow without collapsing under their own complexity.
If you prefer, you may jump to the code examples section to see the issues and how they were fixed. However, it might miss the point, as DRY is more about the why than it is about the how.
Chapter 1: The New Job
The email notification chimed at 6:47 AM on my first Monday at TechFlow Solutions. I was still in the kitchen, barely through my first coffee, when my phone lit up with a Slack message from someone named Marcus Chen, the Engineering Manager.
“Sarah, urgent issue with the user authentication system. Can you look at this when you get in? Three different validation errors happening across our services.”
I frowned. It was my first day, and already there was a fire to put out? I’d joined TechFlow as a Senior Full-Stack Developer, excited to work on their AI-powered customer service platform. The company was growing fast—from 20 to 80 employees in the past year—and they needed experienced developers to help scale their systems.
When I arrived at the office, I found Marcus pacing near the dev team’s area, a coffee-stained notepad in one hand and his phone in the other.
“Sarah! Perfect timing. Welcome to TechFlow, and sorry about the chaos.” Marcus was mid-thirties, with the slightly frazzled look of someone who’d been putting out fires all weekend. “I know it’s your first day, but we’ve got customers calling about login issues. Three different error messages for what should be the same validation problem.”
“No worries,” I said, setting down my laptop bag. “Walk me through what’s happening.”
Marcus led me to a desk next to a developer hunched over multiple monitors. “This is Jake Rodriguez, one of our frontend leads. Jake’s been trying to track down this authentication bug since Friday.”
Jake looked up with tired eyes. “Hey Sarah. So here’s the weird thing—users are getting different error messages when they enter invalid emails, depending on how they access our app. Desktop web says ‘Invalid email format,’ mobile web says ‘Email validation failed,’ and our mobile app says ‘Please enter a valid email address.’”
“Same validation logic?” I asked.
Jake laughed bitterly. “That’s what we thought. Turns out we have email validation in three different places, all slightly different.”
Marcus rubbed his temples. “Show her the code.”
Jake pulled up his IDE, switching between files rapidly. “So we’ve got the main web app using this validation…” He showed me a JavaScript function with a complex regex. “Then the mobile web version uses this…” Another file, different regex pattern. “And the mobile app team wrote their own version in TypeScript that’s… well, different again.”
I studied the code. Three different implementations of email validation, each with slightly different rules about what constituted a valid email. The web app allowed plus signs, the mobile web didn’t. The mobile app had stricter rules about domain names. It was a mess.
“How did this happen?” I asked.
“Each team built their validation when they needed it,” Jake explained. “The mobile team didn’t even know the web team had already written email validation. We don’t have great documentation about shared utilities.”
Marcus jumped in. “And now we’ve got customers who can log in on web but not mobile, or vice versa. Our support team is drowning in tickets, and we have no idea how many users are affected.”
I was starting to see the scope of the problem. “This isn’t just about email validation, is it?”
“Bingo,” said a voice behind me. I turned to see a woman approaching with two cups of coffee, offering me one. “I’m Elena Vasquez, Staff Engineer. And no, this isn’t just about email validation. We’ve got duplicated logic everywhere—password rules, phone number formatting, address validation, even business logic for discount calculations.”
“Elena’s been pushing for us to clean this up for months,” Marcus said. “But we’ve been too busy building new features to fix the foundation.”
Elena nodded grimly. “Last month, our discount logic got out of sync between the checkout service and the order service. Some customers got charged full price while others got double discounts. We had to manually reconcile 2,000 orders.”
My coffee suddenly felt very necessary. “How many engineers are working on this platform?”
“Twelve developers across four teams,” Marcus replied. “Frontend web, mobile, backend services, and AI/ML. We’ve been growing fast, and honestly, coordination hasn’t been our strong suit.”
Jake groaned. “And now we’ve got this email validation bug that’s affecting all three user interfaces, but fixing it means coordinating changes across three different codebases with three different deployment schedules.”
I sipped my coffee, thinking. This was exactly the kind of problem I’d seen before—rapid growth leading to duplicated effort and inconsistent implementations. The technical term was “software rot 2,” but Marcus probably wouldn’t appreciate me dropping academic terminology on my first morning.
“Okay,” I said. “Let’s start with the immediate fire. What’s the simplest way to get all three validation functions aligned so users get a consistent experience?”
Marcus looked relieved. “That’s what I was hoping you’d ask.”
“But,” I continued, “we should also talk about the bigger picture. This email validation bug is just a symptom. The real problem is that we don’t have a single source of truth 3 for any of our business logic.”
Elena smiled for the first time since I’d met her. “I like her already.”
Chapter 2: The Discovery
Over the next two days, while Jake and I worked on aligning the email validation logic, I started to understand the true scale of TechFlow’s duplication problem. It was like archaeological excavation—each layer I dug into revealed more duplicated knowledge.
“Check this out,” I said to Elena during our Thursday afternoon coffee break. I’d been documenting what I found, and the list was getting disturbingly long. “We have user validation logic in seven different places.”
Elena leaned over to look at my notes. “Seven? I thought it was just the three authentication services.”
“Nope. There’s also the admin panel, the customer support tool, the data import service, and some kind of legacy migration script that’s still running in production.” I pulled up my IDE. “And they all have different ideas about what makes a valid user account.”
“Show me the worst one,” Elena said.
I navigated to the admin panel code. “This one accepts usernames with spaces, but the main app doesn’t. So customer support can create accounts that users can’t actually use to log in.”
Elena stared at the screen. “You’ve got to be kidding me.”
“Gets better. The customer support tool allows phone numbers in any format—parentheses, dashes, spaces, whatever. But the SMS notification service expects phone numbers in E.164 format4 with no special characters. So we’ve got customers in the system whose phone numbers literally can’t be used to send them SMS codes.”
Elena put her head in her hands. “How did we not notice this before?”
“Because each service kind of works on its own. The problems only show up at the boundaries, when data moves between systems.” I scrolled through more code. “And here’s my personal favorite—we have three different implementations of the same discount calculation algorithm. They all produce the same results for normal cases, but they handle edge cases differently.”
“What kind of edge cases?”
I showed her the code. “Leap year discounts, timezone boundaries for daily deals, and compound discount calculations. The checkout service handles all three correctly, the admin preview only handles two of them, and the reporting service handles none of them.”
“So our reports show different numbers than what customers actually pay?”
“Only for edge cases, but yeah. And accounting has been manually reconciling the differences every month, thinking it’s just rounding errors.”
Elena was quiet for a moment. “Sarah, how long have you been here? Four days?”
“Three and a half.”
“And you’ve found all this just by looking at the code?”
“I’ve been taking notes on every function that looked familiar. Once you start looking for duplication, it’s everywhere.” I pulled up another file. “We even have duplicate configuration. The API timeout value is defined in six different config files, with four different values.”
“Which explains why some of our services are slower than others.”
“Exactly. And when DevOps updated the timeout settings last month to improve performance, they only updated three of the six config files.”
Elena stood up abruptly. “We need to show this to Marcus. And probably David too.”
David Kumar was the VP of Engineering, Marcus’s boss. I’d only met him briefly during my interview, but he seemed like the kind of person who appreciated data-driven arguments.
“Are you sure? I don’t want to seem like I’m criticizing everything on my first week.”
“Sarah, you’re not criticizing. You’re documenting. And this explains so many weird bugs we’ve been dealing with.” Elena was already pulling out her phone. “Marcus? Can you grab David and meet us in Conference Room B? Sarah’s found something important.”
Chapter 3: The Presentation
Twenty minutes later, I found myself in a conference room with Marcus, Elena, David, and Jake, my laptop connected to the big screen showing what I’d started calling my “Duplication Inventory.”
David Kumar was exactly as I remembered—calm, analytical, and the kind of person who asked good questions. He studied my spreadsheet of duplicated logic for a full minute before speaking.
“Walk me through your methodology,” he said. “How did you find all this?”
“I started with the email validation bug Jake was working on,” I explained. “But once I saw we had three different implementations, I got curious about what else might be duplicated. So I started searching for common patterns—validation functions, configuration constants, business logic keywords.”
I clicked to the next slide. “I spent about two hours each day just reading code and taking notes. Nothing fancy, just grep searches and following imports.”
“And you found duplicated logic in…” David counted the rows, “…forty-three different areas?”
“Forty-three that I’ve documented so far. I’m sure there’s more.”
Marcus leaned forward. “David, this explains the user sync bug from last month. And the pricing discrepancy Elena mentioned. And probably a dozen other issues we’ve been treating as isolated incidents.”
David nodded slowly. “Show me the worst ones.”
I clicked through my slides. “User validation—seven different implementations with six different rule sets. Phone number formatting—five implementations, three different output formats. Address validation—four implementations, including one that thinks ‘Missouri’ isn’t a valid state name.”
Jake winced. “That’s why Mrs. Peterson from Kansas City couldn’t update her address.”
“Email validation we already know about. But we also have discount calculation logic in three places, shipping cost calculations in four places, and tax calculations in…” I checked my notes, “…six places, including two that don’t handle sales tax exemptions.”
David was taking notes. “What’s this costing us?”
“I can’t put an exact dollar figure on it, but I can estimate the impact.” I’d prepared for this question. “The customer support team spends about 20% of their time on issues that are actually caused by inconsistent validation logic. That’s roughly one full-time support person’s salary.”
“The accounting team spends two days every month reconciling report discrepancies that are caused by duplicate calculation logic. And last month’s user sync bug cost us about 40 hours of engineering time across three teams.”
Elena added, “And we’re probably losing customers who can’t figure out why they can create an account on one interface but can’t log in on another.”
David was quiet for another minute, studying the data. Finally, he looked up. “What do you recommend?”
I took a breath. “Short term, we need to fix the critical inconsistencies that are causing customer-facing issues. The email validation bug, the phone number formatting problems, and the discount calculation differences.”
“Long term, we need to establish single sources of truth for all our shared business logic. Centralized validation, configuration, and calculation functions that all our services can use.”
Marcus frowned. “That sounds like a lot of refactoring5 work. How do we do that without stopping feature development?”
“We don’t have to do it all at once,” I said. “We can refactor incrementally. Every time we touch code that has duplicated logic, we extract the shared parts into a common library. Over time, the duplicated code gets replaced with calls to the centralized logic.”
David nodded. “What would you need to make this happen?”
“Time to investigate and document the full scope. Maybe a week to create a proper inventory and prioritization plan. Then dedicated time each sprint to work on refactoring, probably 20-30% of one developer’s time.”
“And a way to prevent new duplication,” Elena added. “Code review guidelines, better documentation of existing shared utilities, maybe some automated checks.”
David closed his notebook. “Elena, I want you to lead this effort. Sarah, since you found all this, I’d like you to work with Elena on the analysis and planning phase. Marcus, can you free up some of Sarah’s time for this?”
Marcus nodded. “What about the immediate customer issues?”
“Fix those first,” David said. “Get the email validation aligned across all three interfaces. But as you do that, extract the shared logic into a common function. Start establishing the pattern.”
“And then,” he continued, “I want a full plan in two weeks. Scope, timeline, and resource requirements for fixing this systematically.”
As everyone started packing up, David looked at me. “Sarah, good catch. This is exactly the kind of technical debt that can kill a growing company if you don’t address it early.”
After he left, Marcus turned to me. “You know, when I hired you, I thought I was getting someone to help build new features.”
“And instead you got someone who wants to tear apart your existing code?” I asked.
Marcus laughed. “Instead I got someone who might save us from drowning in our own complexity.”
Chapter 4: The Deep Dive
Elena and I spent the following week doing the most thorough code archaeology I’d ever attempted. We divided up the codebase, systematically going through every service, every utility function, every configuration file.
“This is like a really depressing treasure hunt,” Elena said on Wednesday morning. We’d set up in Conference Room C with two laptops, a whiteboard covered in flowcharts, and an alarming amount of coffee.
“Look at this,” I said, pulling up two files side by side. “The user registration flow and the admin user creation flow. They do exactly the same validation, but they’re implemented completely differently.”
Elena leaned over to look. “And they have different error messages for the same validation failures.”
“Which explains why our customer support team has two different scripts for helping users with account creation problems.”
We’d discovered that the duplication problem went beyond just code. The customer support team had duplicated knowledge too—different procedures for the same underlying problems, because the error messages were inconsistent between services.
“Check this out,” Elena said, showing me her screen. “I found the holy grail of duplication. We have a function called calculateShippingCost
in four different services.”
I looked at her code. “They all do the same calculation?”
“Sort of. They all use the same basic formula, but they have different handling for edge cases. The checkout service accounts for oversized packages, the admin preview doesn’t. The reporting service uses different tax rates. And the mobile API has hardcoded rates that were supposed to be temporary.”
“So customers see different shipping estimates depending on where they check?”
“Exactly. And when we updated our shipping rates last month, we only updated two of the four implementations.”
I was starting to understand why Marcus had looked so frazzled when I started. “Elena, how long has this been going on?”
“The duplication started small, maybe eighteen months ago when we split the original monolith into services. Each team needed similar functionality, so they just implemented their own versions.” Elena pulled up a Git history view. “Look at this commit message from Jake: ‘Quick fix: copy validation logic from user service since their API is down.’”
“And the ‘quick fix’ never got replaced with a proper shared solution.”
“Exactly. And then other developers saw that validation logic existed in the new service, so they copied it again instead of using the original.”
I looked at the Git blame data Elena was showing me. “So we have a family tree of copied code, where each copy drifted a little further from the original.”
“Like the world’s worst game of telephone.”
By Thursday, our whiteboard looked like a crime scene investigation. Red string connecting different services, sticky notes marking areas of duplication, and a growing list of “WTF moments” where we’d found particularly egregious examples.
“Okay,” I said, stepping back to look at our map. “I think we have enough data to prioritize this properly.”
Elena nodded. “What’s your criteria for prioritization?”
I pointed to different areas of the whiteboard. “Customer impact first. Email validation affects user login, so that’s critical. Phone number formatting affects SMS notifications, also critical.”
“Then business impact. The discount calculation differences are costing us money in two directions—some customers get overcharged, others get undercharged.”
“And finally, developer productivity. The more time developers spend hunting down bugs caused by inconsistent logic, the less time they spend building valuable features.”
Elena was taking notes. “So critical, high, medium, low priority categories?”
“Something like that. But we also need to consider dependencies. Some of this duplicated code is interconnected. If we fix user validation without fixing phone number formatting, we might create new edge cases.”
I pointed to a cluster of connected sticky notes. “Look at this—user validation connects to phone formatting, which connects to SMS notifications, which connects to two-factor authentication. We probably need to tackle that whole cluster together.”
“That’s actually good news,” Elena said. “It means we can fix multiple problems with one coordinated effort.”
By Friday afternoon, we had a plan. Not just a technical plan, but a communication plan and a rollout strategy.
“The key insight,” I said to Elena as we prepared for our presentation to David and Marcus, “is that this isn’t just a technical problem. It’s an organizational problem.”
“How so?”
“The duplication happened because teams didn’t know what shared utilities existed, or they couldn’t easily use the ones that did exist.” I pulled up our dependency chart. “Look—the user validation logic exists in the auth service, but three other teams reimplemented it because calling the auth service would have created a circular dependency.”
“So we need better architecture patterns, not just better code.”
“Exactly. Shared utility libraries, clear ownership of business logic, and design patterns that make it easy to reuse code instead of copying it.”
Elena looked at our week’s work spread across the whiteboard. “You know what the crazy thing is? This is probably the most thorough documentation of our business logic that’s ever existed.”
“Because we had to understand the duplicated code to consolidate it.”
“Right. We accidentally created a specification for how TechFlow actually works, instead of how we think it works.”
I laughed. “We should probably put that in the presentation. ‘Side benefit: we now actually know how our own system works.’”
Chapter 5: The Plan
Monday morning found Elena and me back in the conference room with David, Marcus, and this time Jake and three other team leads—Sofia Chen from mobile, Raj Patel from backend services, and Amy Liu from the AI/ML team.
“Before we start,” David said, “I want everyone to know that this isn’t about blame. This duplication happened for understandable reasons—we were growing fast, teams needed to move quickly, and we didn’t have the infrastructure to make sharing easy.”
“But,” he continued, “we’re at a point where the cost of not addressing this is higher than the cost of fixing it. Sarah and Elena, show us what you found.”
I started with the summary slide. “We documented 43 areas of significant duplication across our codebase. That represents approximately 8,000 lines of duplicated logic, spread across 12 services and 4 client applications.”
Sofia raised her hand. “When you say ‘significant duplication,’ what’s your threshold?”
“Logic that implements the same business rule or validates the same data type,” Elena answered. “We didn’t count things like similar utility functions or common patterns. This is specifically business knowledge that exists in multiple places.”
I clicked to the next slide. “We categorized the impact into four areas: customer experience, data integrity, development velocity, and operational overhead.”
Jake leaned forward. “Show us customer experience first.”
“Right now, users can have different experiences depending on how they interact with our platform.” I pulled up the demo Elena and I had prepared. “Watch what happens when I try to create an account with the same email address on web versus mobile.”
I showed the two different flows side by side. The web version accepted the email format that the mobile version rejected. The mobile version required a phone number that the web version didn’t. The error messages were different for the same validation failures.
“This is why our support team gets confused when users say ‘the app won’t let me sign up,’” said Amy. “They have to ask which app, which device, what browser…”
“Exactly. And it’s why we have users who can log in on one interface but not another.”
Raj was looking at his laptop. “I just checked our logs. We’ve got about 200 users in the last month who created accounts on web but can’t log in through the mobile app.”
David frowned. “They created accounts we can’t authenticate?”
“Their accounts exist, but the mobile app’s validation logic is stricter than the web signup validation. So the mobile app rejects their email addresses even though they’re already in our database.”
I moved to the next slide. “Data integrity issues are similar. We have customers in our system with phone numbers that can’t receive SMS, addresses that can’t calculate shipping costs, and discount codes that work differently depending on where they’re applied.”
Sofia pulled up something on her tablet. “Is this why some customers complain that their discount ‘disappeared’ when they switched from the mobile web to the native app?”
“Probably,” Elena said. “The discount calculation logic is different between those two interfaces. Same discount code, different rules.”
David was taking notes. “What about development velocity?”
I clicked to our developer impact slide. “We surveyed the last three months of bug reports and support tickets. About 30% of bugs were caused by inconsistencies between duplicated logic.”
“That means 30% of our debugging time was spent on problems that wouldn’t exist if we had single sources of truth,” Elena added.
Marcus looked up from his notes. “And developer onboarding?”
“Right now, when a new developer joins a team, they have to learn not just how our system works, but how their specific piece of the system works differently from all the other pieces,” I said. “Jake, how long did it take you to understand all the different validation rules when you started?”
Jake laughed ruefully. “I’m still learning them. Every time I think I understand how user validation works, I find another service that does it differently.”
Amy nodded. “Same with the AI team. We’ve been building features that depend on user data, but we kept getting inconsistent data formats from different services. We ended up building our own validation layer just to normalize everything.”
“Which created more duplication,” Elena pointed out.
“Which created more duplication,” Amy agreed.
David looked around the room. “Okay, I’m convinced there’s a problem. What’s the solution?”
Elena took over the presentation. “We’re proposing a three-phase approach. Phase one is immediate fixes for critical customer-facing issues. Phase two is consolidation of duplicated business logic. Phase three is prevention mechanisms to stop new duplication.”
“Phase one should take about two weeks,” I said. “We fix the email validation, phone number formatting, and discount calculation inconsistencies. The work is straightforward—pick one implementation as the canonical version and update the others to match.”
Raj raised his hand. “What about backwards compatibility? Some of these changes might break existing integrations.”
“Good question. We’re proposing to phase in the changes with feature flags6. Old behavior for existing users, new behavior for new signups, then gradually migrate existing users.”
Elena pulled up our architecture diagrams. “Phase two is the big effort. We extract all duplicated business logic into shared libraries. Common validation functions, calculation algorithms, formatting utilities.”
Sofia looked at the diagrams. “How do we avoid circular dependencies? Our services are already pretty interconnected.”
“We create new shared libraries that don’t depend on any existing services,” Elena explained. “Think of them as business logic microservices. They’re pure functions—given the same input, they always return the same output.”
“And we update our existing services to use these shared libraries instead of their local implementations,” I added. “But we do it incrementally. Every time we touch code that has duplicated logic, we refactor it to use the shared version.”
David studied the timeline. “Phase two is estimated at six months?”
“That’s conservative,” Elena said. “We could probably do it faster if we dedicated more resources, but we want to maintain feature development velocity.”
“Phase three is ongoing,” I continued. “Code review guidelines, documentation standards, automated checks to detect new duplication before it gets committed.”
Amy looked thoughtful. “What about tooling? Can we automate any of this detection?”
“Absolutely,” Elena said. “We can write linting rules that flag common duplication patterns. And we can set up monitoring to alert us when the same business logic starts appearing in multiple places.”
Marcus leaned back in his chair. “This is a big effort. What’s the ROI?”
I pulled up our final slide. “Based on our analysis, the current duplication is costing us about one full-time developer’s worth of effort per month in debugging and support. Plus the opportunity cost of features not built because developers are fixing inconsistency bugs.”
“The six-month investment to fix this should pay for itself within a year, just in reduced debugging time. But the bigger benefit is development velocity—we’ll be able to build new features faster when we’re not constantly working around inconsistent data and conflicting business logic.”
David closed his laptop. “Questions?”
Raj raised his hand. “Who owns the shared libraries? If we extract common logic, someone needs to maintain it.”
“Elena and I would start by owning the shared business logic library,” I said. “Long term, we’d probably want to rotate ownership or establish a platform team.”
Sofia asked, “What about testing? Shared libraries need really good test coverage.”
“Absolutely,” Elena said. “Part of the extraction process is writing comprehensive tests for the shared logic. Actually, that’s one of the benefits—right now, the same logic is tested differently in different places. Centralizing it means we can test it once, thoroughly.”
Jake had a question. “What if teams need slightly different behavior? Not everything can be completely generic.”
“Good point,” I said. “The shared libraries will have configuration options for common variations. And if a team needs truly unique logic, they can still implement their own—but they need to justify why their case is special.”
David looked around the room. “Any objections to moving forward with this plan?”
Silence.
“Okay. Elena, you’re the technical lead. Sarah, you’re the implementation lead. Marcus, free up whatever resources they need for phase one. I want the critical customer issues fixed in two weeks.”
“And I want a detailed project plan for phases two and three by the end of next week.”
As everyone packed up, Sofia approached me. “Sarah, I just wanted to say thanks for finding all this. The mobile team has been struggling with inconsistent data for months, but we thought it was just growing pains.”
Raj nodded. “Same with backend. We’ve been treating these bugs as one-offs, but seeing them all mapped out like this… it’s clearly a systemic issue.”
Elena and I were the last ones in the room. “You ready for this?” she asked.
I looked at our whiteboard, still covered in red string and sticky notes mapping out TechFlow’s duplicated knowledge. “You know what? Yeah, I am. This is exactly the kind of problem I love solving.”
“Plus,” I added, grinning, “it’s going to be really satisfying to delete all that duplicated code.”
Chapter 6: The Implementation
Two weeks later, Jake and I were sitting in the same conference room where this had all started, but the atmosphere was completely different. Instead of frazzled debugging, we were doing methodical code review.
“Okay,” Jake said, looking at his screen, “I’ve updated the mobile web validation to use the new shared EmailValidator class. Same behavior as before, but now it’s calling the centralized logic.”
I checked the test results on my laptop. “All green. And the error messages are now consistent across all three interfaces?”
“Yep. Same text, same format, same internationalization keys.”
It had taken exactly 13 days to fix the critical customer-facing issues. Not because the technical work was hard, but because coordinating changes across three different deployment pipelines took careful planning.
“The best part,” Jake continued, “is that we found two edge cases in the old mobile validation that were never tested. The shared validator caught them immediately.”
Elena walked into the room with her laptop and a satisfied expression. “Customer support just confirmed—zero email validation tickets today. First time in weeks.”
“How are the phone number changes going?” I asked.
“Deployed to staging yesterday, production tomorrow. Raj found an interesting side effect though—fixing the phone number formatting automatically fixed the SMS delivery issues that the support team thought were carrier problems.”
I laughed. “Of course. We were sending SMS to phone numbers that looked like ‘(555) 123-4567’ instead of ‘+15551234567’.”
“Marcus is going to love this,” Jake said. “We solved a customer issue that we didn’t even know was related to the duplication problem.”
Elena sat down and opened her laptop. “Speaking of Marcus, he wants a demo for David on Friday. Show off the before and after user experience.”
“What about the discount calculation fixes?” I asked.
“Those are trickier. Sofia’s team found an issue where the mobile app was doing currency conversion twice for international customers.”
Jake winced. “Double conversion? So customers were being charged the wrong amount?”
“Only for about 200 transactions over the past month, but yeah. The good news is that we caught it during testing, before we deployed the unified discount logic.”
This was exactly why we’d decided to fix everything systematically instead of just patching individual bugs. Each fix revealed other problems that we wouldn’t have found otherwise.
Elena pulled up a spreadsheet. “I’ve been tracking our progress. We’ve eliminated 12 instances of duplicated validation logic, 8 instances of duplicated calculation logic, and 6 instances of duplicated formatting logic.”
“How much code did we delete?” Jake asked.
“About 1,200 lines of duplicated business logic, replaced with calls to shared functions.” Elena scrolled down her spreadsheet. “But more importantly, we now have test coverage for logic that was never properly tested before.”
I opened my own notes. “And we documented business rules that existed only in code comments before. The email validation rules, for example—we found four different interpretations of what ‘valid email’ meant.”
“What did you decide on?” Jake asked.
“We went with the most permissive rules that still prevent obvious errors. If someone manages to create an account with an edge-case email address, we’ll support it rather than locking them out.”
Elena nodded. “Better to be inclusive than to have customers who can’t access their accounts.”
My phone buzzed with a Slack notification from Amy Liu: “Sarah, the AI team wants to use your new phone number formatter for our customer data preprocessing. Can we import the shared library?”
I showed the message to Elena and Jake. “See? This is exactly what we wanted. Teams are starting to use the shared logic instead of building their own.”
“That’s the first time another team has proactively asked to use shared utilities instead of just copying code,” Elena said.
Jake pulled up his IDE. “While we’re on a roll, should we tackle the address validation duplication next?”
I checked our priority list. “Address validation is phase two. We’re supposed to finish the critical fixes first.”
“But honestly,” Elena said, “the address validation is pretty similar to what we just did with email and phone numbers. And Amy’s team is going to need consistent address formatting for their AI features.”
I thought about it. “You know what? Let’s at least look at it. If it’s straightforward, maybe we can knock it out while we’re in the flow.”
Elena and Jake spent the next hour mapping out the address validation logic across our services while I worked on documentation. We’d learned that having good documentation was crucial—not just for the shared libraries themselves, but for helping teams understand when and how to use them.
“Okay,” Elena said finally, “address validation is weird.”
“Weird how?” I asked.
Jake turned his screen toward me. “We have five different implementations, and they’re all trying to solve different problems. The shipping service validates that addresses exist for delivery. The billing service validates that addresses match credit card records. The user profile service just validates that addresses are properly formatted.”
“So they need different validation rules,” I said.
“Right, but they’re all called ‘validateAddress’ and they’re all mostly the same code with different edge case handling.”
Elena pulled up a side-by-side comparison. “Look at this. Same zip code validation, same state abbreviation logic, same international address handling. But different error messages and different behavior for ambiguous addresses.”
“This is a perfect example of why we need configurable shared utilities,” I said. “One AddressValidator class that can be configured for different use cases.”
Jake was already sketching out the API. “So AddressValidator.forShipping() would validate deliverability, AddressValidator.forBilling() would validate payment compliance, and AddressValidator.forProfile() would just check formatting?”
“Exactly. Same core logic, different configuration options.”
Elena looked at our timeline. “If we can design this right, we eliminate five duplicated implementations and make address validation easier for future features.”
“Plus,” I added, “Amy’s AI team gets consistent address data without having to figure out which validation service to call for which use case.”
By the end of the week, we had eliminated address validation duplication too. But more importantly, we’d established a pattern for how to design configurable shared utilities that could serve multiple teams’ needs without forcing everyone into identical requirements.
“You know what I realized?” Jake said as we were wrapping up Friday afternoon. “This whole process has made me a better developer.”
“How so?” Elena asked.
“Before, when I needed some logic that I thought might exist somewhere else, I’d spend maybe 10 minutes looking for it. If I didn’t find it quickly, I’d just implement my own version.”
“And now?”
“Now I know that 10 minutes of searching can save hours of debugging later. And I know that if I can’t find shared logic, I should create it, not just implement my own version.”
Elena nodded. “We’ve changed the default behavior from ‘implement’ to ‘search, then share.’”
I looked at our updated whiteboard, where red string had been replaced with green checkmarks. “Ready for the demo with David?”
“More than ready,” Elena said. “I’m actually excited to show him what we’ve accomplished.”
Chapter 7: The Results
Three months after my first day at TechFlow, I found myself in the same conference room where we’d first presented the duplication problem to David. But this time, we were showing results instead of problems.
“Before we start,” David said, settling into his chair, “I want to acknowledge that this project exceeded my expectations. Not just in technical outcomes, but in how it changed how our teams work together.”
Marcus nodded. “The collaboration between teams has been remarkable. Elena and Sarah basically turned into internal consultants, helping every team improve their code quality.”
Elena and I had prepared a comprehensive demo, but David wanted to start with the numbers.
“Customer impact first,” I said, pulling up our metrics dashboard. “Email validation issues: down from 15-20 tickets per week to zero. Phone number formatting issues: down from 8-10 tickets per week to 1-2, and those are usually carrier problems, not our validation.”
“Discount calculation discrepancies: eliminated completely. We haven’t had a single customer complaint about inconsistent pricing in six weeks.”
David leaned forward. “What about the data integrity issues?”
Elena took over. “We ran a full audit of our customer database. Before the fixes, we had about 1,200 user records with phone numbers that couldn’t receive SMS, 800 user records with email formats that couldn’t log in on all interfaces, and 300 customer records with addresses that couldn’t calculate shipping.”
“All of those are fixed now?”
“All of them. We wrote migration scripts that used the new shared validation logic to clean up the existing data.”
Jake, who had joined us for the demo, added, “And the new validation prevents any of those issues from happening again.”
“Show me the developer experience improvements,” David said.
I clicked to our development metrics. “Bug reports related to data inconsistencies: down 70%. Time to onboard new developers: reduced from about two weeks to about one week, because they only need to learn one set of business rules instead of multiple conflicting versions.”
“Code review cycle time: improved by about 30%, because reviewers don’t have to check for inconsistencies with other implementations.”
Elena pulled up her laptop. “But the biggest improvement is in feature development velocity. Show him the AI team example.”
Amy Liu had asked to join the demo remotely to talk about how the shared libraries had affected her team’s work. Her face appeared on the conference room screen.
“Before the DRY initiative,” Amy said, “the AI team spent about 40% of our time on data preprocessing—cleaning up inconsistent formats, handling validation edge cases, normalizing phone numbers and addresses.”
“Now, we call Elena and Sarah’s shared validation functions, and we get clean, consistent data. That 40% of our time is now spent on actual AI features.”
David looked impressed. “Concrete example?”
“We shipped a new feature last week that analyzes customer support tickets to predict escalation risk. Before the shared libraries, building that feature would have taken six weeks—four weeks of data preprocessing and two weeks of machine learning.”
“With the shared libraries, it took two weeks total. The data was already clean and consistent.”
After Amy signed off, I showed David our code metrics. “We’ve eliminated about 8,000 lines of duplicated business logic. But more importantly, we’ve centralized business knowledge that was previously scattered across dozens of files.”
“What’s that mean practically?” David asked.
Elena answered. “When the business team decides to change a validation rule or update a calculation algorithm, we change it in one place instead of hunting through multiple services to find all the implementations.”
“Last month, our product manager wanted to update the email validation rules to support a new class of email addresses. Before the DRY initiative, that would have been a multi-week project coordinating changes across four teams.”
“How long did it take with the shared library?” David asked.
“Forty-five minutes,” Jake said. “Update one function, run the tests, deploy to all services.”
Marcus looked up from his notes. “David, this has also improved our deployment reliability. We’re seeing fewer hotfixes and rollbacks because there are fewer places for business logic to be inconsistent.”
David was quiet for a moment, processing everything we’d shown him. Finally, he looked around the room. “What did you learn that surprised you?”
I thought about it. “The biggest surprise was that the duplication problem was worse than we initially thought, but also easier to fix than we expected.”
“Worse how?”
“We found duplication in places we didn’t expect—configuration files, database schemas, even customer support scripts. The software rot had spread beyond just code.”
“But easier to fix?”
Elena answered. “Once we established the pattern of shared libraries and configurable validation, each new consolidation got faster. Teams started requesting shared utilities instead of building their own.”
“And,” I added, “teams started catching potential duplication during code review. The culture shifted from ‘implement quickly’ to ‘implement once, reuse everywhere.’”
David nodded. “What about the things that didn’t work?”
“Our first approach to shared libraries was too generic,” Elena said. “We tried to build one validation function that handled every possible use case. It ended up being complicated and hard to use.”
“We learned that it’s better to have multiple simple, focused functions than one complex, configurable function.”
Jake added, “And we learned that documentation is just as important as the code. Teams won’t use shared utilities if they don’t know they exist or don’t understand how to use them.”
David closed his notebook. “Looking forward, what’s next?”
Elena and I had prepared for this question. “We want to establish a formal platform team,” Elena said. “Not just for maintaining the shared libraries, but for preventing duplication in future development.”
“And we want to expand beyond just business logic,” I added. “We’re seeing duplication in infrastructure code, deployment scripts, and monitoring configurations. The same principles apply.”
Marcus leaned forward. “What would a platform team look like?”
“Two or three developers who are responsible for shared utilities, development tools, and helping other teams avoid reinventing wheels,” Elena explained. “Think of them as internal consultants for code reuse and system design.”
David was quiet again, thinking. Finally, he looked at me. “Sarah, when I hired you, I thought I was getting a developer to help build features.”
I held my breath, not sure where this was going.
“Instead, I got someone who identified and fixed a systemic problem that was slowing down our entire engineering organization.” He smiled. “That’s exactly the kind of thinking we need as we continue to grow.”
“Elena, I want you to lead the platform team. Sarah, I’d like you to be the technical lead. Marcus, let’s budget for one more developer to join them.”
As the meeting wrapped up, David had one more question. “If you had to summarize the most important lesson from this project, what would it be?”
Elena and I looked at each other. “Don’t Repeat Yourself isn’t just about avoiding duplicate code,” I said. “It’s about recognizing that knowledge is your most valuable asset, and protecting that knowledge from decay.”
“Every time business knowledge exists in multiple places, it creates opportunities for inconsistency. And inconsistency is the enemy of reliable software.”
David stood up. “Well said. Thank you all for excellent work.”
After he left, Marcus turned to Elena and me. “You know, when this started, I thought it was going to be a huge disruption to feature development.”
“And instead?”
“Instead, it’s made feature development faster and more reliable. Teams are building features in weeks that would have taken months before, because they’re not reinventing basic functionality.”
Jake was packing up his laptop. “Plus, debugging is so much easier when you know there’s only one implementation of each business rule.”
Elena looked at our now-empty whiteboard. “What should we tackle next with the platform team?”
I thought about it. “Infrastructure duplication. We’ve got deployment scripts that are copied and modified across services, monitoring configurations that are mostly identical but slightly different, and database schemas that implement the same relationships in different ways.”
“Same principles, different domain,” Elena said.
“Exactly. Don’t Repeat Yourself applies to everything we build, not just application code.”
As we walked out of the conference room, I reflected on how much had changed in three months. TechFlow still had the same growing pains that any fast-scaling company faces, but we now had systems in place to prevent software rot before it became a crisis.
More importantly, we’d changed the culture. Teams now thought about reusability and shared ownership by default. New developers learned to search for existing solutions before building their own. Code reviews included questions about whether logic could be shared.
We’d learned that DRY isn’t just a coding principle—it’s an organizational principle. And when an organization commits to eliminating knowledge duplication, everything gets better: code quality, development velocity, bug rates, and developer satisfaction.
Not bad for my first three months on the job.
Epilogue: Six Months Later
I was cleaning out my desk drawer when I found the notebook from my first week at TechFlow. Flipping through the pages, I found my original “Duplication Inventory”—43 areas of duplicated logic that had seemed so overwhelming when I first discovered them.
Every single item had a green checkmark next to it.
Elena walked over with two cups of coffee. “Finding buried treasure?”
“Just looking at where we started,” I said, showing her the notebook. “Remember when we thought this was going to take six months?”
“And we finished in four months,” Elena said. “Turns out that once you establish good patterns, teams adopt them faster than you expect.”
The TechFlow engineering team had grown to 20 developers, but our codebase was actually simpler and more maintainable than it had been with 12 developers. New team members consistently commented on how easy it was to understand our system architecture.
“You know what the best part is?” Elena said.
“What?”
“We haven’t had a single instance of accidental duplication in the past two months. Teams automatically check for existing shared utilities before implementing new logic.”
“And when they can’t find what they need, they build it as a shared utility from the start.”
My phone buzzed with a Slack message from Jake: “Sarah, Elena—David wants to present our DRY initiative as a case study at the engineering leadership conference next month. You two up for giving the talk?”
Elena and I looked at each other and grinned.
“I think that’s a yes,” I said.
~ The End ~
Appendix A: Code Examples from the Story
The following code samples illustrate the specific duplication problems and solutions mentioned throughout the story.
Email Validation: The Problem That Started It All
Before: Duplicated Across Three Services
// web-app/src/validators/user.ts - Web Applicationexport function validateEmail(email: string): boolean { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; return emailRegex.test(email);}
// mobile-web/src/utils/validation.js - Mobile Webfunction isValidEmail(email) { // Stricter validation - no plus signs allowed const regex = /^[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; return regex.test(email) && email.length <= 50;}
// mobile-app/src/auth/validator.ts - Mobile Appclass EmailValidator { static validate(email: string): { valid: boolean; error?: string } { if (!email || email.trim().length === 0) { return { valid: false, error: 'Email is required' }; }
if (email.length > 100) { return { valid: false, error: 'Email too long' }; }
// Different regex pattern again const pattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
if (!pattern.test(email)) { return { valid: false, error: 'Please enter a valid email address' }; }
return { valid: true }; }}
After: Shared Validation Library
export class EmailValidator { private static readonly EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; private static readonly MAX_LENGTH = 254; // RFC 5321 limit
static validate(email: string): ValidationResult { if (!email?.trim()) { return { valid: false, error: 'Email is required', code: 'EMAIL_REQUIRED' }; }
if (email.length > this.MAX_LENGTH) { return { valid: false, error: `Email too long. Maximum ${this.MAX_LENGTH} characters allowed`, code: 'EMAIL_TOO_LONG' }; }
if (!this.EMAIL_PATTERN.test(email)) { return { valid: false, error: 'Please enter a valid email address', code: 'EMAIL_INVALID_FORMAT' }; }
return { valid: true }; }}
export interface ValidationResult { valid: boolean; error?: string; code?: string;}
// web-app/src/validators/user.ts - Updated Web Appimport { EmailValidator } from '@techflow/validation';
export function validateEmail(email: string): boolean { return EmailValidator.validate(email).valid;}
// mobile-web/src/utils/validation.js - Updated Mobile Webimport { EmailValidator } from '@techflow/validation';
function isValidEmail(email) { return EmailValidator.validate(email).valid;}
// mobile-app/src/auth/validator.ts - Updated Mobile Appimport { EmailValidator } from '@techflow/validation';
class AuthValidator { static validateEmail(email: string) { return EmailValidator.validate(email); }}
Phone Number Formatting: The SMS Problem
Before: Inconsistent Formatting
export class User { constructor(public phone: string) { // Store phone as entered by user this.phone = phone; }
getFormattedPhone(): string { // Basic formatting for display return this.phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); }}
// sms-service/src/sms-sender.tsexport class SMSSender { async sendSMS(phoneNumber: string, message: string) { // SMS service expects E.164 format const cleanPhone = phoneNumber.replace(/\D/g, ''); const e164Phone = cleanPhone.startsWith('1') ? `+${cleanPhone}` : `+1${cleanPhone}`;
return this.twilioClient.send(e164Phone, message); }}
// admin-panel/src/user-management.tsexport class UserManager { formatPhoneForDisplay(phone: string): string { // Different formatting logic const digits = phone.replace(/[^\d]/g, ''); if (digits.length === 10) { return `${digits.slice(0,3)}-${digits.slice(3,6)}-${digits.slice(6)}`; } return phone; // Return as-is if not 10 digits }}
After: Centralized Phone Utilities
export class PhoneValidator { private static readonly US_PHONE_PATTERN = /^(\+1)?[\s\-\.]?\(?([0-9]{3})\)?[\s\-\.]?([0-9]{3})[\s\-\.]?([0-9]{4})$/;
static validate(phone: string): ValidationResult { if (!phone?.trim()) { return { valid: false, error: 'Phone number is required', code: 'PHONE_REQUIRED' }; }
const normalized = this.normalize(phone); if (!this.US_PHONE_PATTERN.test(phone)) { return { valid: false, error: 'Please enter a valid 10-digit phone number', code: 'PHONE_INVALID_FORMAT' }; }
return { valid: true, normalized }; }
static normalize(phone: string): string { // Convert to E.164 format for SMS const digits = phone.replace(/\D/g, ''); if (digits.length === 10) { return `+1${digits}`; } if (digits.length === 11 && digits.startsWith('1')) { return `+${digits}`; } throw new Error('Invalid phone number format'); }
static formatForDisplay(phone: string): string { const digits = phone.replace(/\D/g, ''); if (digits.length === 10) { return `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`; } if (digits.length === 11 && digits.startsWith('1')) { const tenDigits = digits.slice(1); return `(${tenDigits.slice(0,3)}) ${tenDigits.slice(3,6)}-${tenDigits.slice(6)}`; } return phone; }}
// Updated services using shared utilitiesimport { PhoneValidator } from '@techflow/validation';
export class User { constructor(phone: string) { const validation = PhoneValidator.validate(phone); if (!validation.valid) { throw new Error(validation.error); } this.phone = validation.normalized; // Store in E.164 format }
getFormattedPhone(): string { return PhoneValidator.formatForDisplay(this.phone); }}
// sms-service/src/sms-sender.tsimport { PhoneValidator } from '@techflow/validation';
export class SMSSender { async sendSMS(phoneNumber: string, message: string) { // Phone is already in E.164 format return this.twilioClient.send(phoneNumber, message); }}
Discount Calculations: The Business Logic Nightmare
Before: Three Different Implementations
export class PricingEngine { calculateDiscount(order: Order, discountCode: string): number { const baseDiscount = this.getBaseDiscount(discountCode); let finalDiscount = baseDiscount;
// Handle compound discounts if (order.total > 100 && baseDiscount > 0) { finalDiscount = Math.min(baseDiscount + 0.05, 0.5); // Max 50% discount }
// Handle leap year promotion (2024 special) const now = new Date(); if (now.getFullYear() === 2024 && now.getMonth() === 1 && now.getDate() === 29) { finalDiscount += 0.29; // 29% extra on leap day }
return Math.min(finalDiscount, 0.5); }}
// admin-panel/src/order-preview.tsexport class OrderPreview { calculateEstimatedDiscount(items: Item[], promoCode: string): number { const subtotal = items.reduce((sum, item) => sum + item.price, 0); const baseDiscount = this.lookupPromoCode(promoCode);
// Simplified compound discount - doesn't match checkout exactly if (subtotal > 100) { return Math.min(baseDiscount * 1.1, 0.45); // Different max discount! }
return baseDiscount; }}
// reporting-service/src/analytics.tsexport class SalesReports { calculateDiscountImpact(orders: Order[]): DiscountReport { return orders.map(order => { // Even simpler calculation for reports const discount = order.discountCode ? 0.1 : 0; // Just 10% flat rate return { orderId: order.id, originalTotal: order.total, discountAmount: order.total * discount }; }); }}
After: Shared Business Logic
export interface DiscountContext { orderTotal: number; orderDate: Date; customerTier?: 'standard' | 'premium' | 'vip'; isFirstOrder?: boolean;}
export interface DiscountRule { code: string; baseRate: number; minOrderAmount?: number; maxDiscount?: number; validFrom: Date; validTo: Date; compoundable?: boolean;}
export class DiscountEngine { private static readonly ABSOLUTE_MAX_DISCOUNT = 0.5; // 50% private static readonly COMPOUND_BONUS = 0.05; // 5% bonus for large orders private static readonly LARGE_ORDER_THRESHOLD = 100;
static calculateDiscount( context: DiscountContext, discountCode: string, rules: DiscountRule[] ): DiscountCalculation { const rule = rules.find(r => r.code === discountCode); if (!rule) { return { discountRate: 0, discountAmount: 0, appliedRules: [] }; }
if (!this.isRuleValid(rule, context)) { return { discountRate: 0, discountAmount: 0, appliedRules: [], error: 'Discount code expired or invalid' }; }
let finalRate = rule.baseRate; const appliedRules = [rule.code];
// Apply compound discount for large orders if (rule.compoundable && context.orderTotal >= this.LARGE_ORDER_THRESHOLD) { finalRate += this.COMPOUND_BONUS; appliedRules.push('LARGE_ORDER_BONUS'); }
// Apply special date-based promotions finalRate += this.getDateBasedBonus(context.orderDate);
// Apply customer tier bonuses finalRate += this.getCustomerTierBonus(context.customerTier);
// Enforce maximum discount limits const maxDiscount = Math.min(rule.maxDiscount || 1, this.ABSOLUTE_MAX_DISCOUNT); finalRate = Math.min(finalRate, maxDiscount);
const discountAmount = context.orderTotal * finalRate;
return { discountRate: finalRate, discountAmount, appliedRules, breakdown: { baseDiscount: rule.baseRate, compoundBonus: rule.compoundable && context.orderTotal >= this.LARGE_ORDER_THRESHOLD ? this.COMPOUND_BONUS : 0, dateBonus: this.getDateBasedBonus(context.orderDate), tierBonus: this.getCustomerTierBonus(context.customerTier) } }; }
private static isRuleValid(rule: DiscountRule, context: DiscountContext): boolean { const now = context.orderDate; return now >= rule.validFrom && now <= rule.validTo && (rule.minOrderAmount ? context.orderTotal >= rule.minOrderAmount : true); }
private static getDateBasedBonus(orderDate: Date): number { // Leap day bonus if (orderDate.getMonth() === 1 && orderDate.getDate() === 29) { return 0.29; // 29% leap day bonus } return 0; }
private static getCustomerTierBonus(tier?: string): number { switch (tier) { case 'premium': return 0.02; case 'vip': return 0.05; default: return 0; } }}
export interface DiscountCalculation { discountRate: number; discountAmount: number; appliedRules: string[]; error?: string; breakdown?: { baseDiscount: number; compoundBonus: number; dateBonus: number; tierBonus: number; };}
// Updated services using shared business logicimport { DiscountEngine, DiscountContext } from '@techflow/business-logic';
export class PricingEngine { calculateDiscount(order: Order, discountCode: string): DiscountCalculation { const context: DiscountContext = { orderTotal: order.total, orderDate: new Date(), customerTier: order.customer.tier, isFirstOrder: order.customer.orderCount === 0 };
return DiscountEngine.calculateDiscount(context, discountCode, this.discountRules); }}
// admin-panel/src/order-preview.tsimport { DiscountEngine } from '@techflow/business-logic';
export class OrderPreview { calculateEstimatedDiscount(items: Item[], promoCode: string): DiscountCalculation { const subtotal = items.reduce((sum, item) => sum + item.price, 0); const context = { orderTotal: subtotal, orderDate: new Date() };
return DiscountEngine.calculateDiscount(context, promoCode, this.discountRules); }}
Address Validation: Configurable Shared Logic
Before: Different Validation for Different Purposes
// shipping-service/src/address-validator.ts - Focused on deliverabilityexport class ShippingAddressValidator { validateAddress(address: Address): boolean { // Must be deliverable return this.validateZipCode(address.zipCode) && this.validateDeliveryAddress(address.street) && this.isDeliverableState(address.state); }
private isDeliverableState(state: string): boolean { // Some states not supported for delivery const unsupportedStates = ['AK', 'HI']; return !unsupportedStates.includes(state); }}
// billing-service/src/address-validator.ts - Focused on payment verificationexport class BillingAddressValidator { validateAddress(address: Address): boolean { // Must match credit card billing address format return this.validateZipCode(address.zipCode) && this.validateStreetAddress(address.street) && this.isValidState(address.state) && address.street.length <= 35; // Credit card processor limit }}
// user-profile-service/src/address-validator.ts - Just formattingexport class ProfileAddressValidator { validateAddress(address: Address): boolean { // Just check that fields are present and formatted correctly return address.street?.trim().length > 0 && address.city?.trim().length > 0 && address.state?.trim().length === 2 && /^\d{5}(-\d{4})?$/.test(address.zipCode); }}
After: Configurable Shared Validator
export enum AddressValidationType { PROFILE = 'profile', SHIPPING = 'shipping', BILLING = 'billing'}
export interface AddressValidationConfig { type: AddressValidationType; requireDeliverability?: boolean; maxStreetLength?: number; allowedStates?: string[]; requireExactZipFormat?: boolean;}
export class AddressValidator { private static readonly DEFAULT_CONFIGS: Record<AddressValidationType, AddressValidationConfig> = { [AddressValidationType.PROFILE]: { type: AddressValidationType.PROFILE, requireDeliverability: false, maxStreetLength: 100, requireExactZipFormat: false }, [AddressValidationType.SHIPPING]: { type: AddressValidationType.SHIPPING, requireDeliverability: true, maxStreetLength: 50, allowedStates: this.DELIVERABLE_STATES, requireExactZipFormat: true }, [AddressValidationType.BILLING]: { type: AddressValidationType.BILLING, requireDeliverability: false, maxStreetLength: 35, // Credit card processor limit requireExactZipFormat: true } };
private static readonly DELIVERABLE_STATES = [ 'AL', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY', 'DC' // Note: AK, HI excluded from shipping ];
static forProfile(): AddressValidator { return new AddressValidator(this.DEFAULT_CONFIGS[AddressValidationType.PROFILE]); }
static forShipping(): AddressValidator { return new AddressValidator(this.DEFAULT_CONFIGS[AddressValidationType.SHIPPING]); }
static forBilling(): AddressValidator { return new AddressValidator(this.DEFAULT_CONFIGS[AddressValidationType.BILLING]); }
constructor(private config: AddressValidationConfig) {}
validate(address: Address): ValidationResult { const errors: string[] = [];
// Basic field validation if (!address.street?.trim()) { errors.push('Street address is required'); } else if (address.street.length > (this.config.maxStreetLength || 100)) { errors.push(`Street address too long (max ${this.config.maxStreetLength} characters)`); }
if (!address.city?.trim()) { errors.push('City is required'); }
if (!address.state?.trim()) { errors.push('State is required'); } else if (address.state.length !== 2) { errors.push('State must be 2-letter abbreviation'); } else if (this.config.allowedStates && !this.config.allowedStates.includes(address.state)) { errors.push(`Delivery not available to ${address.state}`); }
// Zip code validation if (!address.zipCode?.trim()) { errors.push('ZIP code is required'); } else if (this.config.requireExactZipFormat) { if (!/^\d{5}(-\d{4})?$/.test(address.zipCode)) { errors.push('ZIP code must be 5 digits or 5+4 format (12345 or 12345-6789)'); } }
if (errors.length > 0) { return { valid: false, errors, code: 'ADDRESS_VALIDATION_FAILED' }; }
return { valid: true }; }}
export interface Address { street: string; city: string; state: string; zipCode: string;}
// Updated services using configurable validatorimport { AddressValidator } from '@techflow/validation';
export class ShippingService { validateShippingAddress(address: Address): ValidationResult { return AddressValidator.forShipping().validate(address); }}
// billing-service/src/payment-processor.tsimport { AddressValidator } from '@techflow/validation';
export class PaymentProcessor { validateBillingAddress(address: Address): ValidationResult { return AddressValidator.forBilling().validate(address); }}
// user-profile-service/src/profile-manager.tsimport { AddressValidator } from '@techflow/validation';
export class ProfileManager { updateUserAddress(userId: string, address: Address): Promise<void> { const validation = AddressValidator.forProfile().validate(address); if (!validation.valid) { throw new Error(`Invalid address: ${validation.errors?.join(', ')}`); }
return this.userRepository.updateAddress(userId, address); }}
Testing the Shared Libraries
import { EmailValidator } from '../email-validator';
describe('EmailValidator', () => { describe('valid emails', () => { const validEmails = [ 'user@example.com', 'user.name@example.com', 'user+tag@example.com', 'user123@example-domain.com', 'very.long.email.address@very-long-domain-name.com' ];
validEmails.forEach(email => { it(`should accept ${email}`, () => { const result = EmailValidator.validate(email); expect(result.valid).toBe(true); expect(result.error).toBeUndefined(); }); }); });
describe('invalid emails', () => { const invalidCases = [ { email: '', expectedCode: 'EMAIL_REQUIRED' }, { email: ' ', expectedCode: 'EMAIL_REQUIRED' }, { email: 'invalid-email', expectedCode: 'EMAIL_INVALID_FORMAT' }, { email: 'user@', expectedCode: 'EMAIL_INVALID_FORMAT' }, { email: '@example.com', expectedCode: 'EMAIL_INVALID_FORMAT' }, { email: 'a'.repeat(250) + '@example.com', expectedCode: 'EMAIL_TOO_LONG' } ];
invalidCases.forEach(({ email, expectedCode }) => { it(`should reject "${email}" with code ${expectedCode}`, () => { const result = EmailValidator.validate(email); expect(result.valid).toBe(false); expect(result.code).toBe(expectedCode); expect(result.error).toBeDefined(); }); }); });});
// shared-libs/business-logic/src/__tests__/discount-engine.test.tsimport { DiscountEngine, DiscountContext, DiscountRule } from '../discount-engine';
describe('DiscountEngine', () => { const mockRules: DiscountRule[] = [ { code: 'SAVE10', baseRate: 0.1, validFrom: new Date('2024-01-01'), validTo: new Date('2024-12-31'), compoundable: true }, { code: 'BIGORDER', baseRate: 0.15, minOrderAmount: 200, maxDiscount: 0.3, validFrom: new Date('2024-01-01'), validTo: new Date('2024-12-31'), compoundable: false } ];
it('should calculate basic discount correctly', () => { const context: DiscountContext = { orderTotal: 50, orderDate: new Date('2024-06-15') };
const result = DiscountEngine.calculateDiscount(context, 'SAVE10', mockRules);
expect(result.discountRate).toBe(0.1); expect(result.discountAmount).toBe(5); expect(result.appliedRules).toEqual(['SAVE10']); });
it('should apply compound discount for large orders', () => { const context: DiscountContext = { orderTotal: 150, orderDate: new Date('2024-06-15') };
const result = DiscountEngine.calculateDiscount(context, 'SAVE10', mockRules);
expect(result.discountRate).toBe(0.15); // 0.1 base + 0.05 compound bonus expect(result.discountAmount).toBe(22.5); expect(result.appliedRules).toEqual(['SAVE10', 'LARGE_ORDER_BONUS']); });
it('should apply leap day bonus', () => { const context: DiscountContext = { orderTotal: 100, orderDate: new Date('2024-02-29') // Leap day };
const result = DiscountEngine.calculateDiscount(context, 'SAVE10', mockRules);
expect(result.discountRate).toBe(0.44); // 0.1 base + 0.05 compound + 0.29 leap day expect(result.breakdown?.dateBonus).toBe(0.29); });
it('should respect maximum discount limits', () => { const context: DiscountContext = { orderTotal: 1000, orderDate: new Date('2024-02-29'), customerTier: 'vip' };
const result = DiscountEngine.calculateDiscount(context, 'SAVE10', mockRules);
// Would be 0.1 + 0.05 + 0.29 + 0.05 = 0.49, but capped at 0.5 expect(result.discountRate).toBe(0.49); });});
Appendix B: Characters
Sarah - Senior Full-Stack Developer, protagonist. New hire who discovers the duplication problem and leads the effort to solve it. Methodical, analytical, and skilled at seeing patterns across codebases.
Elena Vasquez - Staff Engineer. Experienced developer who had been advocating for fixing duplication issues but lacked the time and support. Becomes Sarah’s partner in the DRY initiative and eventual platform team lead.
Marcus Chen - Engineering Manager. Initially overwhelmed by constant firefighting but supportive of systematic solutions. Balances feature development pressure with technical debt remediation.
David Kumar - VP of Engineering. Data-driven leader who recognizes the strategic importance of addressing technical debt. Provides executive support and resources for the initiative.
Jake Rodriguez - Frontend Lead. Represents the daily frustration developers face with inconsistent systems. Becomes an advocate for shared utilities after experiencing their benefits firsthand.
Amy Liu - AI/ML Team Lead. Demonstrates how duplicated logic affects teams beyond core product development. Her team’s productivity gains illustrate the broader impact of centralized business logic.
Sofia Chen - Mobile Team Lead. Shows how platform fragmentation contributes to duplication problems. Her team’s integration challenges highlight cross-platform consistency issues.
Raj Patel - Backend Services Lead. Represents the infrastructure side of duplication problems. His insights into service dependencies help shape the shared library architecture.
Special thanks to Gene Kim, whose “The Phoenix Project” inspired the narrative structure of this story.
Appendix C: For Fun
I asked the assistant in the Comet Browser (Perplexity’s browser) to critize the story. Below is what it said. Would you agree?
I have read and analyzed the story on the current. Here’s a critical review for “A Tale of software rot”:
1. Timeline Realism
- Sarah, a new hire, uncovers 43 areas of logic duplication and their consequences by her “third and a half” day. This speed is plausible for a skilled engineer in a codebase with searchable patterns, but the sheer depth—across frontend, backend, admin panel, legacy scripts, and DevOps configs (timeout settings in six places)—stretches believability for that timeframe, especially since her regular duties (fixing urgent issues) are ongoing. It’s ambitious to both solve production bugs and perform this deep archaeology so quickly.
2. Team Size and Growth
- The company staff is said to have “grown fast—from 20 to 80 employees in the past year,” but Marcus replies “Twelve developers across four teams.” While not strictly inconsistent, it makes the overall numbers suspect if the rest are non-engineering, or if more engineers exist but aren’t mentioned again.
3. Deployment Schedules
- The text notes “coordinating changes across three different codebases with three different deployment schedules.” Yet, the critical fixes are implemented and rolled to production in “exactly 13 days.” If the changes require refactoring not just code but also deployment and testing for three different platforms, this is surprisingly fast, especially for a company struggling with coordination and knowledge silos.
4. Scope Creep of Duplications
- The story’s duplication discoveries spiral from authentication to discount logic, phone formatting, shipping, tax, config settings, and even customer support scripts. While not logically impossible, the broadness and interconnectedness suggest a catastrophic level of organizational dysfunction that would likely cause more widespread crises than depicted (e.g., regulatory fines, lost revenue, security issues), yet the business is growing.
5. Ease of Consensus
- Despite the large refactor, there is minimal resistance or concern from teams or leadership around risk, technical debt, or possible regressions—real-world scenarios often include more pushback, failed pilots, or negotiation about priorities.
6. Reporting Remediation
- In the final audit, the team claims all customer data integrity issues from past years are cleanly remediated with a migration script. In practice, real-world data usually contains edge cases that are impossible to “fix” without user involvement, such as unreachable phone numbers without user confirmation.
7. Employee Focus
- After only three months at the company, Sarah is offered the technical lead role for a new platform team, with leadership expressing major gratitude for her contribution. While not a contradiction, in larger companies, structural changes and roles are slow; in fast-growth startups, this may happen, but it reads as slightly idealized.
Summary No outright plot holes, but the story compresses timelines, smooths over resistance, and makes refactoring and cross-team remediation more frictionless than would be expected in a real-life organization. The sheer breadth and rapid, clean resolution of problems is slightly at odds with the depth and complexity described regarding “software rot.”