When working with a larger code base, there will always be parts that you don't remember writing and you'll inevitably have to read the code to understand it. That's just part of the job/task, regardless of the style it's written in.
In shared code particularly with a culture of refactoring, there's no guarantee that the function call you see is doing what you remember it doing a year ago.
When I was coming up I got gifted a bunch of modules at several jobs because the original writer couldn't be arsed to keep up with the many incremental changes I'd been making. They had a mentality that code was meant to be memorized instead of explored, and I was just beginning to understand code exploration from a writer's perspective. So they were both on the wrong side of history and the wrong side of me. Fuck it, if you want it so much, kid, it's yours now. Good luck.
"Make the change easy, then make the easy change" hadn't even been coined as a phrase yet when I discovered the utility of that behavior. When I read 'Refactoring (Fowler)' it was more like psychotherapy than a roadmap to better software. "So that's why I am like this."
When we get unstuck on a problem it's usually due to finding a new perspective. Sometimes those come as epiphanies, but while miracles do happen, planning on them leads to disappointment. Sometimes you just have to do the work. Finding new perspectives 'the hard way' involves looking at the problem from different angles, and if explaining it to someone else doesn't work, then often enough just organizing a block of code will help you stumble on that new perspective. And if that also fails, at least the code is in better shape now.
Not long after I figured out how to articulate that, my writer friend figured out the same thing about creative writing, so I took it as a sign I was on the right track.
I do know that the first time I was doing that, it was for performance reasons. I was on a project that was so slow you could see the pixels painting. My first month on that project I was doing optimizations by saying "1 Mississippi" out loud. The second month I used a timer app. I was three months in before I even needed to print(end - start).
> there's no guarantee that the function call you see is doing what you remember it doing a year ago.
TDD provides those guarantees. If someone changes the behaviour of the function you will soon know about it.
That's significant because Robert 'Clean' Martin sells clean code as a solution to some of the problems that TDD creates. If you reject TDD, clean code has no relevance to your codebase. As Casey does not seem to practice TDD, it is not clear why he though clean code would apply to his work?
It doesn't. TDD is about writing new code. It doesn't say anything about existing tests being sacrosanct, or pinning tests sticking around forever. I can extract code from a function and write tests for it. I probably know that there's still code that checks for user names but I can't guarantee that this code is being called from function X anymore, or whether it's before or after calling function Y. Those are the sorts of things people try to memorize about code. "What are the knock-on effects of this function" doesn't always survive refactoring. Particularly when the refactoring is because we have a new customer who doesn't want Y to happen at all. So now X->Y is no longer an invariant of the system.
TDD is about documenting behaviour. Which is why it was later given the name Behaviour Driven Development (BDD), to dispel the myths that it is about testing. It is true that you need to document behaviour before writing code, else how would you know what to write? Even outside of TDD you need to document the behaviour some way before you can know what needs to be written.
A function's behaviour should have no reason to change after its behaviour is documented. You can change the implementation beneath to your hearts content, but the behaviour should be static. If someone attempts to change the behaviour, you are going to know about it. If you are not alerted to this, your infrastructure needs improvement.
> A function's behaviour should have no reason to change after its behaviour is documented.
That's only true with spherical cows. That something happens is a requirement. When it happens is often only as specific as 'before' or 'after' but tests often dictate that they happen 'between', which is not an actual requirement, it's an accident of implementation. It was 'easy' to put it here.
Nowhere is it written that behavior in a system is strictly additive.
Systems are full of XY problems. When you recognize that, and start addressing that problem, you sprout a lot of tests for the Y solution and block delete tests for the X solution. That behavior doesn't exist in the system anymore because it's answering the wrong question. Functional parity tests can be copied, or written in parallel. But the old tests disappear with the old code (when the feature toggle goes away).
Leaving the code for X around is at best a footgun for new devs, and at worse a sign of hoarding behavior of an intensity that requires therapy.
You're espousing a process whereby you've nailed one foot to the deck, preferring form over function. Whether you believe what you're saying or not I can't say, but it's restrictive and harmful.
> Nowhere is it written that behavior in a system is strictly additive.
For a unit of the same identity to suddenly start doing something different is plain nonsensical, never mind the technical challenges that come with breaking behaviour that should scare anyone away from trying. Logically, a unit is additive until the unit are no longer used, at which point it can be eliminated.
> But the old tests disappear with the old code (when the feature toggle goes away).
Absolutely, but static analysis can easily determine that the tests being removed correspond with units being removed. If (TDD) tests are removed and the unit code isn't, something has gone wrong and your infrastructure should make this known.
Refactors compose. In three months you can completely rearchitect a module without breaking it at any point in the process. That’s the promise of refactoring.
Functions don’t have an identity. There is no such thing. I don’t know who taught you that but they have broken you in the process. Renaming things is a refactoring. We don’t check the entire commit history to make sure that function name has never existed. Only that it hasn’t existed recently. There’s no identity.
One of the reasons to refactor is that the function has been lying about its responsibilities. So you extract steps out of it, create a new call path that fixes the discrepancy, migrate the call sites, delete the incorrect function, and then, if the function name was really good, you might wait a while and rename the new function to the old name. Each step makes sense if you’ve followed the entire process. If you haven’t been following along at all then you have absolutely no idea how things got here until you read the git history thoroughly, which some people can’t do, and others won’t do if they expect the code to be static.
Also, to clarify, I'm talking about cumulative changes. If I'm working with someone on a feature then we both see all of the changes as they occur. If I'm off dealing with some long initiative, I may not look at that code for 3 months and so I miss all of the intermediate states that made perfect sense at the time.
Like visiting a friend who did their own house remodel. Their spouse saw all the steps, all you saw was before and after, and so the fact that the bathroom door is missing is confusing. The bathroom still exists, but now it's the master bath.
You seem to ignore that when the unit changes, the tests do too. If you come back a year later, foo.bar.baz(quux) might have been refactored and lazily so. The tests were also updated and still pass. You may jump into the code only to realize that someone no-op'd everything and never removed call sites. TDD is primarily a design tool, not a lock-into-implementation tool.
Of course any code requires some refresher at time, but the difficulty and time required to figure it out again is a spectrum that goes all the way down to the seventh circle of hell.