TDD vs Defensive Programming
Asked Answered
M

3

5

Uncle Bob says:

"Defensive programming, in non-public APIs, is a smell, and a symptom, of teams that don't do TDD."

I am wondering how TDD can avoid an (internal) function to be used in an unintended way? I think TDD can´t avoid it. It merely shows that the function is used correctly because a calling function is covered by it´s passing unit tests.

When developing a new feature using the (undefensive) function this feature is also developed with TDD. So unintended use of the function will fail the new features tests.

So using TDD to drive new features will force you to correcty use (internal) functions.

Do you think that is what is meant by Uncle Bob´s tweet?

Mcelroy answered 21/8, 2017 at 13:3 Comment(1)
you could/should trust your teammates to follow interal specs/docs. #24845514Ransom
O
3

So using TDD to drive new features will force you to correctly use (internal) functions.

Exactly. But keep in mind the subtle "gap" here: you should use TDD to write (unit) tests that test the contract of your public methods. You do not care about the implementation of these methods - that is all internal implementation detail.

Therefore: if your "new" code uses an existing method in an unintended way you are "told" because an exception is thrown or you receive an unexpected result.

That is what I mean by "gap": you see, the above describes a black box testing approach. You have a public method X, and you verify its public contract. Compare that to white box testing where you write tests to cover all paths taken within X. When doing that, you could notice: "ok to test that one condition in my internal method, I would have to drive this special data".

But as said - I think you should go for black box testing - white box tests might break easily when refactoring internal methods.

And there is an additional dimension here: keep in mind that ideally you change code in order to implement new features. This means that adding new features only takes place by writing new classes and methods. This means that your new code has no chance using private internal methods. Because you are within a new class. In other words: when you regularly happen to run into situations where your internal methods are used in many different ways - then you are probably doing something wrong.

The ideal path is: you implement a new requirement by creating a set of new classes. Later on, you have to add other requirements - by writing more classes.

In that ideal path - there is no need for defensive programming within internal methods. Because you exactly understand each use case for such internal methods!

Thus, the conclusion is: avoid defensive programming in internal methods. Make sure that your public APIs check all pre-conditions, so they fail (as fast as possible) if there is a problem. Try to avoid these internal consistency checks - as they tend to bloat your code - and rest assured: in 5 weeks or 5 months you will not remember if you really needed that check, or if it is just "defensive".

Orvieto answered 22/8, 2017 at 9:8 Comment(0)
C
2

One way to answer this is to look at what else Uncle Bob has had to say on the topic. For example:

In a system with meager code coverage, few tests, and lots of tangled legacy code, defensive programming should be the rule.

In a system born of TDD, with 90+% coverage and highly reliable, well-maintained unit tests, defensive programming should be the exception.

From this, we can infer his main argument -- if the defensive checks are actually providing a benefit, then that is a hint that we are missing some constraints. If we are missing some constraints, and all the tests are passing, then we must also be missing some tests.

Or, to express the same idea in a slightly different way -- the constraints implied by the defensive patterns in your implementation belong closer to the boundary (ie, in the public API).

If there are constraints, for example, to limit what data is allowed to pass through the boundary, then there should be tests to ensure that the boundary actually implements the constraints.

Calliopsis answered 22/8, 2017 at 13:40 Comment(0)
W
1

When you use TDD properly, you cover all the possible cases and assert that your public functions that call the private ones do respond properly as expected not only for the happy scenario, but for all different possible scenarios. When you use defending programing in your private methods, you are actually getting yourself ready for these (different possible) scenarios mentioned above.

I, personally, do not think defending programing is bad even if it is in private methods, however, based on my description above I see it is a double effort that is unnecessary and also, it eliminates the importance of the TTD because you are handling these special cases in your application by complicating the code, instead of writing it a way that is proof.

Willyt answered 21/8, 2017 at 13:13 Comment(3)
tdd relies primary on the public interface, protected and private methods are implicitly covered by testsRansom
@pce: so the public interface tests really make sure, that the private methods are used correctly. meaning no null value is passed for example. so no need for defensive programming here. but: the private methods could still fail if they were used unintentionally. but this would mainly result in a failing interface method and would therefore be identified quickly. so when doing tdd there is no need for defensive coding internally because you cover defensively at the interface and never call the private method or identify unintended use with a failing test during development. correct?Mcelroy
@Mcelroy yes. I have a private or protected method under test (over reflection) and if the implementation changes (even without changes to the public API), one would have to drop or adjust the test.Ransom

© 2022 - 2024 — McMap. All rights reserved.