While in general the advice on duplication is reasonable I think it tends to get taken too far and the example is a poor one.
Presumably for now Mexico applies a different sales tax % but there are so many unknowns on how Mexican tax rates differ that I'd keep the method separate until I'm certain the abstractions are similar, otherwise you might end up with:
calculateBill(locale, appliesExemption, exemptionRate, regionalTaxRate, nationalTaxRate, clothing Untaxed)
Premature abstractions seem worse to me than some duplication.Besides something like bill calculation should have loads of tests.
Usually I start with reproducing the bug in specs, with at least one or more specs failing due to the bug. Then I go on to fix the bug and see the specs turn green. I think TDD is pretty well suited for the use case.
Just because you have the entire system in your head right now, will you when you revisit it a year later to add a feature or fix a big? How much time will it take to internalize completely? Tests allow you to know less about the system overall and still make assured changes to smaller portions of it because prior you (or someone else) has spent the extra time to make sure any mistakes are caught early and easily.
The go-to counter to this is that you should really know what you're doing and understand the system you're working in, but that's often not only infeasible, but impossible for one person (how well do you know all the libraries you're using?). There are plenty of codebase out there where I think no one person can internalize it all and understand it well enough to always make food choices, and since you'll have to choose what you want to remember, a helpful tool to make sure you don't violate some important facets of how it works is useful.
Now, much of this is about testing in general, but if you think about it, there's a lot similar about writing new code and coming into a large chunk of code that you need to alter but you have limited understanding of or poor recollection of. The same things that keep you from violating how it should function help your design a coherent functionality in the first place.
When one is writing complex logic that is supposed to receive both frequent new features and keep working correctly there is nothing that beats TDD.
Let me attempt to compare this to bowling even though I know nothing about bowling. If you just occasionally like to go out bowling you can use whatever technique you like and you will get better if you just bowl a bit more often. If you want to play bowling on the national level you may at some point have to drop the technique that you made up yourself when you started out. You need to go through the uncomfortable stage of learning a new an better technique and temporarily be worse at it. I think the people not doing TDD are just refusing to go through this because there is not much of an objective standard of quality for developers. We are in a field where code that lacks any sort of quality is standard and where breaking features three times a week is standard. TDD will fix these things.
The reality is most projects start as exploration. What you’re thinking might not even be possible. Can you even approximate the classes you’ll need yet? For these scenarios TDD fits in awkwardly.
I prefer to do a little back and forth. A little dev. When things are looking stable, a little resting. Towards the end a lot of testing.
There can indeed be exploration stages where TDD does not help that much. E.g, if you are going to interface with a piece of hardware you have never seen before and you need to get some sort of working communication going there is much point to what you say. As soon as you know what sequence of commands makes the piece of hardware work one has arrived in the territory where TDD is highly beneficial. You can then write a test where you require that your software indeed produces the correct sequence of commands and after that you can refactor your exploratory code while having to check that it still works together with the hardware much less often then otherwise would be needed.
The remark about TDD and waterfall seems much less true to me. In my original post I wrote 'both frequent new features and keep working correctly'. That does not describe waterfall at all. In fact, and this is where you messages seems to get a bit self-contradictory, your post actually sounds much more waterfally to me than mine. It contains the words 'Towards the end'. It is waterfall projects that have an end that was envisioned right from the start. Whatever 'agile' means, it seems to have the feature that we never stop adding features and that stuff is supposed to be in a working state in between adding these features. So, if one is actually doing that there are dragons in the sentence 'Towards the end a lot of testing' because it actually turns into 'always lots of testing' which costs lots of time. In case of the hardware example it would also require that every developer has a species of that hardware on his desk next to his computer. As I happen to work in a field where we interface with hardware that is three stories high, we should realize that this is not always practical.
One misunderstanding that might be going on is that perhaps you are assuming that in TDD one would test just one class at a time. I generally prefer to test a set of classes, i.e., a subsystem of a whole program. In that case the test are about properties of the program that, in many cases, the user could recognize as beneficial. There should be less churn at that level. I suppose some people would call this behaviour driven development instead but I am not so very married to terminology and will just keep calling it TDD. Testing individual classes can also be helpful but if they contain rather simple logic I am not sure writing tests for that is so very helpful. At some point would just be testing whether the processor really is capable of copying values which is not that interesting.
LeetCode / CS101 problems are almost always like that. In the real world, it's also useful to separate out any tricky "policy" logic into nicely testable pure functions. But you are still going to have a bunch of side-effect munging left over, and unit testing is terrible at that. Tests that only assess their own mocks are a waste of time.
And what do you mean by 'Tests that only asses their own mocks are a waste of time.' Certainly, many side effects can very effecitively be checked by mocks. Now, it could certainly get much more difficult if the side effects were very large and hard to define as you said in the paragraph before that, but I have some difficulty picturing that as a common situation.
It's not really about being close to the hardware[0] - there's many other things that are unrelated to low-level performance tweaks that make TDD or even unit tests a waste of time for experienced game developers.
When someone like Blow or Carmack sees an automated playtest fail, they have a honed intuition for what caused it. This intuition was built up over many years of hard-earned experience. They simply don't need the lower level unit test that says "this matrix multiplication math is wrong" - they'll see a collision fail or a weird scaling thing and _know_ right off the bat the candidates in the codebase where things went wonky and fix it before you can even say "look at the logs" :) Since they get the same results without having to write unit tests everywhere, and especially without writing tests first, it's simply an unnecessary overhead to make them write/maintain it.
I don't think anyone would argue TDD and/or unit tests are valueless, it's just a cost/benefit question. Pretty sure they'd use any technique where the benefit outweighs the cost- and for them, for many reasons in many varied scenarios, it just doesn't.
[0] actually, if anything, I'd bet that the stuff that's really close to the hardware like the core engine stuff (math, memory management, etc.) is the one place they would consider having TDD and/or more unit tests!
If you can’t write code well in the first place, you’ll write terrible tests, and then even more terrible code to make them somehow succeed.
I’d argue TDD has value in the first case, but most teams are more like the second variant.
In my experience, coding is the easy part, and pinning the relevant stakeholders down on what they want coded is the hard part. If you're an employee, the hardest part of your job will be in a.) understanding what'll make your boss happy and b.) communicating how to make that realistic. If you're a B2B business, the hardest part of your job is figuring out a.) who your customers are and b.) what they want. If you're a B2C startup, the hardest part of your job is identifying a market with lots of people who want your product.
If you're a maintenance programmer where these things were hammered out a decade ago, then TDD can be a godsend. If you're working with an excellent business analyst / salesperson / cofounder who can hammer these issues out with enough precision that you can trust their answers, TDD is helpful. Neither of these situations describes the gaming industry, nor a number of other very lucrative industries.
They are functionally the same tool beyond one config file setting defaults and another config file managing access to components of the tool. After playing with the differences, we iced the broken app and updated the setup KB with the config differences.
As we upgrade the core app that the vendor tools work within, we are glad this headache had a quick fix but is being cut as the new version of the application natively supports what the tool did in the old version.
That being said, don't use this as a reason for doing that or at least document that for the future owners of the tool so we don't waste what little time we have troubleshooting functionally identical software.
But there are good and bad principles. Like avoid copy paste. But I would never say, never copy paste code. I do, when I hack something together quickly and if it stays it needs later refactoring. No problem there .. only if the refactoring part gets forgotten bugs sneak in.
Also the advice of less is (usually) more.
Witing code is easy. Writing good code which is working, readable and maintainable is hard. But as little as code also goes against readability.
So like many others here said .. dogmas are stupid. There are different szenarios and just use what works for you.
I don't see your point. You'd still have the same problem if you had a team gold-plating some other code instead of doing their job. In fact, without TDD your problem is far worse because you don't have checks in place to evaluate if some code works as expected.