I'll start out by saying I have some pretty strong positions opposing what it sounds to me like yours are.

>I would say some junior devs can get too fixated on hierarchies and patterns and potential areas of code re-use, though;

Agreed.

> instead, they should try to write code that addresses core problems,

Still with you, agreed.

> and worry about creating more correct abstractions later.

I don't quite agree. I agree you shouldn't spend too much time, but I think "don't worry about it" gets you a hodgepodge of spaghetti code and half-baked ideas that no one can maintain in the future.

One of the most important things someone can do when adding a feature for instance is understanding the surrounding code, it's intentions, and how any abstractions it may have work, and then add their feature in a way that complements that and doesn't break backwards compatibility.

I'd go as far as arguing that only ever MVP'ing every story without a thought to design or abstraction is one of the major problems in industry alongside cargo-culting code maintenance rather than ever trying to form deep understanding of any meaningful part of the software.

> Just like premature optimization is the "root of all evil", the same goes for premature refactoring. This is the rule of YAGNI: You Ain't Gonna Need It. Don't write code for problems you think you might have at some indeterminate point in the future.

YAGNI is far too prescriptive and misses the point of programming that literate programming gets right:

Programming (like writing) is about communicating intent to other human beings in an understandable way and shuffling complexity around in the way it makes the most sense for:

- The typical reader skimming to understand something else (Needs to know: what does it do?)
- The feature adder (needs to know how it works, so they need a high level, then easy way to understand the low level as needed)
- The deep reader (needs to be able to take in all of the code to deeply understand it, needs straightforward path to get there)

What you describe sounds like no abstraction and just throwing all of the complexity in front of everyones faces all at once. I can appreciate the habitability advantage of that, but I think that discarding context of acquired domain knowledge as you work on issues is too great of a cost.

In case it's not obvious, the words came to me for the point I'm trying to make: Domain knowledge you acquire while working on something should be encoded in sensible abstractions that others can uncover later on, peeling away more and more complex layers as needed.

> When it comes to testing, TDD adherents will disagree, but if you ask me it's overkill to test small private subroutines (or even going so far as to test individual lines of code). For example, if I have a hashing class, I'm just going to feed the hash's test vector into the class and call it done. I'm not going to split some bit of bit-shift rotation code off into a separate method and test only that; if there's a bug in that part, I'll find it fast enough without needing to give it its own unit test. That's what debuggers are for. All the unit test should tell me is whether I can be confident that the hashing part of my code is working and won't be the cause of any bugs up the line.

This view sounds like it may be a direct result of a YAGNI/avoid abstraction style to me actually. If you avoid abstracting or code-reuse quite a lot (or even don't spend enough energy on it), you lose one of the largest benefits of TDD:

regression testing

If nearly all of your functions are single use or don't cross module boundaries... then the value add of TDD's regression testing never really has a chance to multiply.

For OOP I feel like this would be reflected in terms of testing base objects the most or static methods. For functional code, it would just be shared functions.

> Obviously I'm not in the "tests first" camp; instead I write tests once a class is complete enough to have a clearly defined responsibility and I can test that those responsibilities are being fulfilled correctly

I'd argue you are testing in your head anyway. Visualizing, conceptualizing, and trying to shape the essence of the problem into something that makes sense.

The problem is sometimes our mental compilers/interpreters aren't perfect and the mistakes are reflected as kludges or tech debt in our code.