Live data from Hacker News

The Start-Up Trap

blog.8thlight.com

131–140 of 145 posts

Re: The Start-Up Trap

#131
post #80
post #58

Earlier quoted context omitted.

FWIW, Robert C. Martin is a talented and well-respected guy who has been writing code for more than 30 years. He has shipped plenty of working, reliable products. https://github.com/unclebob -- critique his code. I find it to be pretty dang clean. He means something very specific when he talks about TDD, and while he's certainly emphatic about it, I dont think he's wrong. He might not be right either, but he also isn…

It's when he ventures into the territory of "startups" that I start to question his experience. He's definitely talented and experienced, but it seems to me that vast majority of his experience is in consulting -- which is a very different beast than working in a startup. I had an exchange on Twitter with him today in which I asked what startups he was involved in, and he said that he consulted for several, and then…

This is certainly the dominant perception coming out of SV these days. But since the vast majority of startups fail and have no more time (or rather, will) to collect development metrics than they do to implement TDD, I doubt we'll ever have any kind of real validation of this perception.

I suspect it comes from a combination of a) young coders more likely to find themselves at a startup, who are also more likely not to have developed TDD habits and 2) VCs, angels and the like who, while well-intentioned, likely push this notion that TDD is for "mature" companies and/or, "we'll pay that off later, just get the code out the door." Young CEOs, CTOs, founders and the like who need to validate all of their startups' initiatives and practices probably are swimming upstream if they want to go TDD from the ground up.

For some instances of prototyping and lightweight apps (like Obie mentioned), this is probably fine. In my experience though, the short term effects of playing "shipping" against "craft/testing" settle in within a matter of days and weeks, not months and years, and the mid-term effects of technical debt when a startup moves into "mature" phase (again, a much shorter time span than anyone realizes) are such that the idea of "debt" and "what are we going to do about it" becomes a/the dominant conversation on the team. I've experienced both sides.

My perspective: the cost of TDD is mainly to young coders who need to learn it and find the curve daunting. Teams built around these type of otherwise talented devs really would incur significant up-front cost to booting up a CI/Test server and getting past the curve to the point where the team is moving at a comfortable pace, comparable to where they were before implementing TDD. In this case, TDD is a very hard sell. I don't know how to solve that problem, other than changing the SV/startup culture to the point where those holding the purse strings see the benefits of craft/testing as being as attainable in the mid-to-short term as they are in the long term (and in my experience, they absolutely are).

If you have buy-in to TDD/craft (I can't really separate the two in my mind) from the top-down, and you have experienced coders that can facilitate implementing, in my opinion it's absolutely worth pursuing. The second-order effects of a culture built on these principles kick in with a vengeance right when a startup needs it: at crucial pivot points.

When you have to change your business assumptions quickly -- and this usually happens weeks or months into the starup lifecycle -- would you rather do it on a codebase where, say, your authentication/authorization and user model was tightly coupled to other concerns or not? Would you rather know with a relative degree of confidence that the changes you're making are not breaking core concerns? Wouldn't you rather do this quickly, rather than thrash around in unclear code that you'd rather just rewrite (you know, like it felt when we started!)?

And if you're pivoting, are you not more likely to be less cash-rich, under more pressure and in need of some really good success right now, since pivoting probably implies that former assumptions did not pan out the way you'd hoped? Isn't that exactly the point where you'd like to both reuse the code you need and know that things are still working around your baseline assumptions that seemed so clear a few short weeks/months ago?

I would think at this point the benefits to a startup in this mode, of a clean, fast-moving and validated codebase, of the kind that good TDD tends to produce, should be patently freaking obvious. Just my opinion.

Re: The Start-Up Trap

#132

Imagine test driven development for visual art. How would one write a test for visual art, beyond viewing and comprehending the partially completed work? If one could write a test in advance, one would have made a significant advance: distilled to a verbal form a description of what good art is. Instead, many artists -- or software authors, or chefs, find a way to repeatedly sample and appraise their work as it devel…

Actually, that is a great metaphor. But you should know, TDD is the way I repeatedly sample and appraise my work as I go. Multiple times per hour, in fact.

Red -> Green is the sampling and Green -> Refactor is the appraising.

Re: The Start-Up Trap

#133

Earlier quoted context omitted.

> By definition, going through TDD is going to be a safe route, ... Why is TDD safe? Most TDD advocates seem to be blind to the fact that testing is a terrible way to prove many important properties about software systems, security properties for example. If you're betting your ever-so-scarce programming resources on TDD, you're probably paying too much, getting a lower return than you could be getting, and leaving s…

It's Test Driven Development - not Proof Of Correctness. The worst thing about TDD having 'test' in the name is that people think it is about preventing bugs, or catching them. It is not. It is about enabling easy refactoring. The other stuff - preventing regressions, catching bugs, etc. is gravy.

Just curious: If you believe that TDD isn't supposed to help you write code that does what it's supposed to do, what do you do in addition to TDD to make sure that your code actually works correctly?

Re: The Start-Up Trap

#134

TDD is often a waste of time. I worked at some place that had taken the TDD philosophy to the extreme: every getter and setter had a test. On the opposite side of the spectrum, I worked on software that processed millions of dollars in daily transactions and there were maybe 5 "tests" in the whole system. This was about 500,000 lines of C and C++ code in the early 2000's. My personal philosophy is to write unit tests…

I won't write tests for getters and setters, but then again, I try not to write getters and setters very much in the first place.

Re: The Start-Up Trap

#135

Earlier quoted context omitted.

It's Test Driven Development - not Proof Of Correctness. The worst thing about TDD having 'test' in the name is that people think it is about preventing bugs, or catching them. It is not. It is about enabling easy refactoring. The other stuff - preventing regressions, catching bugs, etc. is gravy.

Just curious: If you believe that TDD isn't supposed to help you write code that does what it's supposed to do, what do you do in addition to TDD to make sure that your code actually works correctly?

Here's my work flow:

I write a specification (using RSpec) of some behavior. It fails.

Then I write code to make that specification pass.

Now the code is working correctly, as I have defined it.

Then I refactor, using my specs (tests) as a safety net to ensure that everything after the refactor still works as intended.

This is a _VERY_ different approach than coming up with some solution in my head, implementing it (most likely with bugs), and then using tests to find and eliminate as many bugs as possible (but usually not all of them).

Any errors that make it through to a commit, when I am doing TDD, are errors in how I have specified (or failed to specify) the behavior. Any errors in the design or implementation of the solution are caught by building to a spec in very small steps.

That's the key difference between properly done BDD/TDD and other testing. Writing the tests prevents bugs instead of catching them, and it ensures that behavior does not change after refactoring.

It may be a subtle distinction, but in practice it makes a huge impact.

Re: The Start-Up Trap

#136

Earlier quoted context omitted.

Just curious: If you believe that TDD isn't supposed to help you write code that does what it's supposed to do, what do you do in addition to TDD to make sure that your code actually works correctly?

Here's my work flow: I write a specification (using RSpec) of some behavior. It fails. Then I write code to make that specification pass. Now the code is working correctly, as I have defined it. Then I refactor, using my specs (tests) as a safety net to ensure that everything after the refactor still works as intended. This is a _VERY_ different approach than coming up with some solution in my head, implementing it (…

Two questions:

1. What do you about security? How, for example, do you make sure you don't introduce XSS vulnerabilities into your code? To use your words, how does "writing the tests prevent bugs" when we're talking about bugs that create XSS vectors?

2. Don't you think you're paying a penalty by defining and implementing your system's semantics through the pinhole-sized view of one failing test at a time? That is, why wouldn't you be better off defining the semantics in whatever-sized units make the most sense, not necessarily one spec's worth at a time, and then deriving your tests and implementation from the semantics accordingly?

Re: The Start-Up Trap

#137

Earlier quoted context omitted.

And the road to failure is ignoring best practices because "I'm special" and "I'll do it later because I'll magically have time then". It's a balancing act that pithy aphorisms don't help with. There's a reason people use the metaphor "Design Debt". Debt is a tool with tradeoffs, financial or design, use appropriately.

But what if the debt outweighs the benefits? What if you can't measure the debt?

If the debt outweighs the benefits you don't take it, I have a project right now that I "know" will be fragile and a maintenance headache later. So even though it's urgent I'm writing the appropriate tests and doing all those best practice things that you know will pay off 10x later but often skip because you don't want to take the time now.

You could measure how much debt your team tends to take on, by measuring how much time is spent on new features and how much on the type of bugs and the refactoring that pays off design debt. This still misses what I think is the most important part of design debt, how much longer it takes to implement new ideas because you are paying the "the pieces this depends on were rushed and don't work/integrate/extend well" tax (I guess I should call this "interest payments" to not mix metaphors) .

You can't measure debt as you take it on though, the best you can do is estimate how much work it will be to fix it later (and that's only if it needs to be fixed later, maybe you get lucky and your hacked up code just stays good enough). We all know more than enough about the pitfalls of estimating software projects and this adds in more uncertainty about future need.

This is all part of the craft side of software development, experience helps, but it's not something easily measured. Too many people take that as an excuse to just do the quick and easy thing and say "move fast and break things!" or fall back on over designing and never get any work done. HN talks about the latter more often and pretty much ignores the former. I find this strange, I've read plenty of accounts of failure where the reasons boiled down to "we got to a point where we couldn't adapt our codebase to changes needed to face a new competitor, change in the landscape, business model pivot, etc. because it was too crufty". Enough design debt means some smaller and more agile competitor will eat your lunch.

Well that's definitely a long enough answer to two simple short questions.

Re: The Start-Up Trap

#138

Earlier quoted context omitted.

The safety of TDD comes from having a test suite that you trust. Given that suite, you can safely refactor the code. If you can refactor safely, you can improve the design safely. If you can improve the design, you can stop the inevitable slowdown that comes from making a mess. What is the risk of _not_ doing TDD? The risk is that slowdown. We've all experienced it. What is the cost of TDD? You'd like to say that it…

The benefits that you attribute to TDD are not exclusive to TDD. They are the benefits of having well-tested code, and TDD is only one of the ways to get there. The problem with the TDD way of getting there, however, is that it's expensive: It makes programmers see their code through the pinhole of one failing test at a time, blinding them to larger concerns, which are important. As a result, a lot of avoidably crapp…

The idea that TDD involves some kind of blind faith that the tests will generate grand designs and beautiful code is both silly and wrong. You are right about that. Good design and good code require skill, thought, and knowledge irrespective of whether you are using TDD. So as I practice the discipline, I am thinking all the time about larger scale issues, and I am _not_ being blinded to those concerns.

However, the act of writing tests first has a powerful benefit: the code you write _must_ be testable. It is hard to understate this benefit. If forces a level of decoupling that most programmers, even very experience programmers, would not otherwise engage in.

It also has a psychological impact on the programmer. If every line of production code you write is in response to a failing test, you will _trust_ your test suite. And when you trust your test suite, you can make fearless changes to the code on a whim. You can _clean_ it and improve the design without trepidation.

Gaining these benefits without writing tests first is possible, but much less reliable. And yet the cost of writing the tests first is no greater than writing the tests second.

Re: The Start-Up Trap

#139

Earlier quoted context omitted.

The benefits that you attribute to TDD are not exclusive to TDD. They are the benefits of having well-tested code, and TDD is only one of the ways to get there. The problem with the TDD way of getting there, however, is that it's expensive: It makes programmers see their code through the pinhole of one failing test at a time, blinding them to larger concerns, which are important. As a result, a lot of avoidably crapp…

The idea that TDD involves some kind of blind faith that the tests will generate grand designs and beautiful code is both silly and wrong. You are right about that. Good design and good code require skill, thought, and knowledge irrespective of whether you are using TDD. So as I practice the discipline, I am thinking all the time about larger scale issues, and I am _not_ being blinded to those concerns. However, the…

> However, the act of writing tests first has a powerful benefit: the code you write _must_ be testable.

No, the act of writing well-tested code at all has that benefit. Whether you write the tests before or after the code, one at a time or in module-sized groups, writing code that's hard to test has immediate and obvious penalties when you test it (e.g., tedious rework), and you'll quickly learn to avoid those penalties. So just having the discipline to write well-tested code at all forces you to write code that's not only testable but easily testable. This benefit is not unique to TDD.

> It also has a psychological impact on the programmer. If every line of production code you write is in response to a failing test, you will _trust_ your test suite.

It's not enough to trust that your tests actually test your code. You also need to trust that your tests express your desired semantics. And that's harder to do when the semantics is not designed in whatever form and grouping is most natural to its representation but rather is extruded, one test at a time, through the pinhole view that TDD imposes upon programmers.

> And yet the cost of writing the tests first is no greater than writing the tests second.

What you seem to be overlooking is that TDD not only forces you to write tests first but also in tiny baby-steps that cause programmers to focus only on satisfying one test at a time. As a result, the initial code that is written satisfies only a small portion of the system's overall semantics (the portion that's been expressed as tests so far), and a lot of that code ends up having to be reworked when later tests finally uncover other requirements that affect it. This leads to rework that would have been avoidable had the programmers not been blinded to those requirements earlier on.

The problem with TDD isn't so much that it's test first but that it promotes a pinhole view of subjects that are not narrow.

Re: The Start-Up Trap

#140

Earlier quoted context omitted.

> Not doing TDD often leads to tightly-coupled brittle software, which can be very fast to implement but also difficult to change down the road. It certainly doesn't have to, but in reality that's what happens 90% of the time. This is flatly wrong. It may be a tool for productive programmers to keep their code on the right track, but assuming that loosely-coupled code isn't written without it is a huge overreach. Cer…

I can only speak from my experience (20 years) of developing web apps, but I've never seen a cohesive, loosely coupled web app, that was both over 2 years old AND not using some form of TDD.

Phrased that way, I agree completely. An established codebase needs to be verified in some ways. But writing code and running it against tests is not exactly equivalent to TDD.

TDD as I understand it is characterized by writing a failing test, followed by implementation code, followed by test-fixing, etc.

However, I can write plenty of good code that does what it's supposed to and works and is stable. THEN I'll refactor as needed, write tests, and since I anticipated my needs, making those tests good and the code testable will be relatively straightforward. That's not TDD, though. It's a pragmatic approach that doesn't prioritize setting requirements (or solidifying an API) over starting simple and iterating quickly.

Post reply on HN