1. 程式人生 > >How TDD Can Prevent Over-Engineering

How TDD Can Prevent Over-Engineering

Once you copy the calculation for loan amounts above $2000, the test passes:

Here's how the code looks like once you remove all Magic Numbers:

The code that shows the result after you apply the same steps of the previous post, including all the duplication.

Right now that seems like a mess. The code has a lot of duplication and hard-coded values everywhere. However, this is the kind of messy code that was driven

by tests. Therefore, it contains many patterns that can lead to insightful discoveries.

To uncover those patterns, you need to refactor. You need to apply small changes to the code without altering its behavior. The way you see that you're not altering the behavior of the program is when you apply the changes for a module/class/function — like saving, — and the behavior of the program doesn't change.

That’s the reason why it’s so critical to start with Tests-First. If you don’t write Tests-First, it's harder to ensure that you're testing the right things and that the behavior of the system won’t change when you refactor. In the same way, without practicing Test-Driven, it's hard to understand if you’re increasing or decreasing the level of transformation according to the

Transformation Priority Premise.

You know you're refactoring when you change the code and the following remains true: the level of transformation of the code doesn't decrease, the tests stay green, and future tests which follow the same pattern would also stay green.

At this point, the code has duplication for each conditional. An effective way to remove that duplication is to create a function with arguments for the values that change.

However, it's hard to know how that new function should look like beforehand. If you want to increase the chances for the tests to stay green all the time and keep the changes small, you can start with pure functions that are very specific to their purpose. You can modify them to be more generic later.

That said, create a new function for the calculation of interest rates when the loan amount is higher than $2000. It's a good idea to keep the function closer to the code you're extracting so that you can see in which position the arguments should be.

After that, it's a good idea to lift the function that calculates the interest to a scope outside the primary function "interest to pay for." Although this violates the Strictness Principle, which states you should keep variables only in the scope that's using them, it also allows you to verify that the function doesn't access any external variables, the "side-effects." If the function you create has access to external variables, it's hard to change it. If the tests don't break after moving it, that means the function has no side-effects.

After you make sure that the tests pass, lift the function outside the scope and replace the logic everywhere else.

When you refactor code to a new function, verify if it doesn’t have side-effects.

If you do the same thing for each one of the other calculations, you'll end up with a code that looks like this:

The code for the conditions after you create one function for each calculation.

You still have duplication, but it looks better than before. There’s one function to handle $2000, one function to handle $5000 and another function to handle $10000.

The code that shows the implementation of the functions to calculate each range.

When you refactor, and there's duplication, it's essential to keep the functions as similar as you can to each other. As humans, we are pattern recognition creatures. If you have code that looks the same, it's much easier to understand the problem and discover meaningful patterns.

Notice that the first function to calculate loan amounts above $2000 is missing one argument to have the same number of arguments as the other functions. You can fix that.

Also, the internal variables and arguments for all the functions have different names. Let's make them the same.

You can see now that all the functions accept the same things:

  • The loan amount.
  • The amount that represents the "end of the range."
  • The amount that represents the "interest per dollar."
  • The amount that represents the "previous interest per dollar."

The “loan amount” is a fixed value. It’s the input that only changes in the context of the primary function “interest to pay for.” The value for the "loan amount" won't change throughout the execution of each calculation.

The other arguments are different:

  1. The code calls the functions with a different value for the arguments “end of the range,” “interest per dollar” and “previous interest per dollar” depending on which calculation is running.
  2. The functions to calculate the interest for each range uses Connascence of Position for its arguments, instead of Connascence of Name. That's a Bad Code Smell.

To fix the Bad Code Smell and discover why the code calls the functions with the different arguments, you can apply the DRY approach. Create one Object Literal representing the arguments that change, then reuse them. You can start with the range of calculations for loan amounts above $2000:

Then, as a second step, uplift the Object Literal outside the function. Given this is an interface breaking change, you need to update all the external function calls inside the other conditionals for the tests to remain green.

If you do the same thing for the other ranges and delete the duplication completely you'll end up with a piece of code that exposes a new pattern:

The code after you delete all the functions to calculate interest and replace with a generic one.

You can see the commits which lead to that result.

Now that you refactored the code, you can see that there's only one return, which is the "interest amount," but the code duplicates it inside every condition.

Let's remove that duplication:

Now look carefully at the code:

The code that shows the first two conditions. There's a duplication in the lines 3 and 6.

You can see the condition for a loan amount greater than $5000 repeats the calculation for a loan amount greater than $2000. The reason it repeats is that the first condition only runs if the loan amount is less than $5001.

You can dump the right-hand side conditional of the first condition. If you do, you fix the duplication:

You can do the same thing for the rest of the code:

Here's the result:

The code that shows each condition handling one calculation.

The code above clearly shows how the algorithm calculates the interest if a “loan amount” is greater than $2000, $5000, or $10000.

Now here's the mind-blowing moment:

When you refactor the code to make each component similar to each other and remove Bad Code Smells, not just the code becomes painless to maintain, but you also understand better the patterns of the problem you are trying to solve. This way, you know you are generalizing in the right direction without speculation or over-engineering.

In Test-Driven Development, you only write the code you need. Nothing else.

Another interesting thing you can see is that there's a decoupling between the ranges and the code that runs the calculation on them. You can extract the ranges into a JSON configuration file. If you do that, Jack can modify the behavior of the code by modifying the config file. He doesn't need to pay a developer every time he wants to modify behavior that follows the same pattern.

If you don't want to extract the ranges into a configuration file, you can still refactor the code to emphasize the decoupling. When you emphasize decoupling, you also help to increase the legibility of the code, regardless if you move the data to a configuration file or not.

Test-Driven Development allows you to understand the problem and create more value.

That's it! Here's the final code:

The final code after you apply all the refactoring from this post.

If you've been reading this since the first post, now you should understand every detail of "Jack, The Moneylender" problem. That means you can continue refactoring the code as much as you want and be confident you never introduce bugs unintentionally.

Test-Driven Development and refactoring may sound like a tedious process. However, as with any skill, you get better over time. With practice, your velocity increases. Next time you get a similar problem, you may discover the patterns earlier and finish all this in a fraction of the time.

Many say Test-Driven Development doesn't work. It's too slow, and there's no value in doing it. Those words usually come from people who are either writing code for an "obvious" domain or don't know they're writing more code than what they need.

Jack didn't merely choose anybody to solve his problem.

He chose a professional programmer.

相關推薦

How TDD Can Prevent Over-Engineering

Once you copy the calculation for loan amounts above $2000, the test passes:Here's how the code looks like once you remove all Magic Numbers:The code that

HOW TO BECOMING A FREELANCE ENGINEERING CONSULTANT.

Finding best freelance engineering consultant jobs If you happen to be a freelance engineering consultant looking for a new / better-paid job, you s

10種軟件開發中 over-engineering 的錯誤套路

ica couchdb 吃飯 del 開源庫 gis 演進 做出 20px 別把「不要過度使用 Generic」誤解成「不用 Generic」。也別把「不要寫一些

Bend Radius—How It Can Impact Your Cable Performance?

Why should fiber optic cable not be tightly bent? Are fiber optic cable fragile? These issues are what users care about when deploying fib

How introducing guilds made our engineering team a better one

How introducing guilds made our engineering team a better oneAs THRON co-founder I have been waiting for this day for the longest time, so I’m especially p

How AI Can Amplify Human Competencies

Advanced systems will continue to help people do their jobs better instead of replacing them. This article is part of an MIT SMR initiative exploring how t

How Chatbots Can Help A Startup Grow

For anyone starting up a business, there is never enough time and always too much to do. The same is true of information flow within a startup, with often

How businesses can build fairness into their machine learning models

Enterprise-wide deployments of AI are constrained by the requirements of scaling any new system or technology: transparency, security, and the application'

How AI can to help traders make better decisions?

Dark pools are electronic trading platforms that have emerged in the past decade in advanced markets. They allow traders to buy or sell large blocks of sha

Ask HN: How one can remove search history in gmail?

Hey,Recently I've cleaned up my Gmail account and removed a lot of messages, however, when I search in Gmail it suggests already removed messages and attac

Ask HN: How much can you multi

I always wanted to do multiple things at the same time, to be more efficient in life. Simple examples include: listening to podcasts while driving, cooking

How ML Can Help With Your BI Insights

Companies in all industries must stay up to date with the latest tech to survive in this digital world. This is especially true in the case of machine lear

How Water Can Identify Murder Victims and Fake Scotch

This story is for Medium members.Continue with FacebookContinue with GoogleMedium curates expert stories from leading publishers exclusively for members (w

Ask HN: How do you foster strong engineering culture in your organization?

* Instill a learning culture. An easy way to start is to do brown bag lunches/lunch and learn once a month. Ask engineers to volunteer to talk about things

How Blockchain can be used in the application of AI

Blockchain and artificial intelligence are highly converging technologies in the today's tech world. The combined use of these two technologies is offering

How Enterprises Can Help Build Ethical AI Strategies

As 2018 continues on its downward slope toward a new year, artificial intelligence technologies are becoming more and more useful in every aspect of our li

The Fear that KRACK Built…and How You Can Protect Your WiFi Security

You may have heard of KRACK. Not crack. KRACK.Last year, Belgian security researcher, Mathy Vanhoef, published a paper that ignited fear in WiFi users rely

Everyone is saying membership is the future of journalism. Here’s how you can put it into practice

Everyone is saying membership is the future of journalism. Here’s how you can put it into practiceEight years ago I began what at first glance is a rather

Bitcoin Liquidity: How It Can Hurt You, and How It Can Help You

Imagine you’re watching the 15-minute chart of Bitcoin on your favorite exchange. All the signals in your trading strategy are indicating the price of Bitc

How blockchains can be used in authenticating video and countering deepfakes

Take, for example, police departments (where officers wear body cams) and the numerous stakeholders that exist when a shooting occurs. These could include