First off what you are talking about doesn't sound quite like TDD. TDD implies a test first approach which is about driving the design of your system by following the pattern of
Test->Code->Refactor. So perhaps your first problem is the purpose of your tests, are you writing them as you code? If so I would expect that pretty much all of the logic within your test relates back to some unit test. High code coverage is therefore an indirect result of applying TDD.
When you are doing TDD you write enough test code to motivate the code you want to write. You also ensure the test fails first. Basically ask yourself what is something this method needs to do. Then you code it, but just enough to make the test pass, if it isn't what you are looking for then you write additional tests and then refactor the method.
Code coverage after the fact is not an effective method of measure your adherence to TDD, though you will typically find very high code coverage in code written using TDD do to the fact that all of the code should have been motivated by some test.
TDD tests serve to both drive the design and document and explain the design to others in plain language (so how you name your tests is very important).
However none of this rambling really answers your question directly so I'll just say, you should aim for pretty high code coverage of service (non-UI) code, especially whereever there is non-trival logic, and even better if the tests are written first ;-). The fact is (though some may disagree) that more tests are generally better. Many high quality open source projects have far more test code than running code.
Additionally, tests should be written whenever:
You are writing new code, tests should drive and document your design and explain your assumptions about what the code should do. The should be written before you code.
You found a bug, a failing test should demonstrate the bug. When the bug is fixed the test should pass.
You change code in a way that changes the nature of what a method or class does (though if a lot of tests fail when one area of the code changes this could indicate brittle tests). This keeps the tests documenting the code correctly.
Personally, I have found that learning TDD is an interesting challenge, and it takes time to develop a good "gut feeling" for it. Practice, practice, practice has been the best way to learn for me. That and reading test code from open source projects and now also contributing to them while writing new tests with my changes.
How do you parse
if (a > b && foo(param)) {
doSomething();
} else {
doSomethingElse();
}
The parse tree probably looks something like
if:
condition:
and:
lt:
left: a
right: b
function:
name: foo
param: param
true-block:
function:
name: doSomething
false-block:
function:
name: doSomethingElse
hmm... let's serialize this tree into a list, prefix notation
if(and(<(a, b), function(foo, param)), function(doSomething), function(doSomethingElse))
This parse tree format is pretty easy to manipulate, but I have one problem. I hate separators. I like terminators. At the same time, I like sprinkling in whitespace.
if( and (<(a b) function(foo param)) function (doSomething) function ( doSomethingElse))
hmm... the additional whitespace makes certain things harder to parse... Maybe I could just make a rule that the tree is represented as (root leaf leaf leaf).
(if (and (< a b) (function foo param)) (function doSomething) (function doSomethineElse)
Now my serialization of a parse tree is lisp (rename function to apply, and this probably runs). If I want programs that write programs, it's kind of nice to just manipulate parse trees.
This isn't entirely how s-expressions came about, but it was identified early, and it is one feature that lisp programmers use. Our programs are pre-parsed in some sense, and writing programs to manipulate programs is fairly easy because of the format. That's why the lack of syntax is sometimes considered a strength.
But as David said, use an s-expression aware editor. You are more likely to lose track of a closing brace in an s-expression than a closing brace in xml (</foo>
only closes <foo>
, but right paren closes ANY s-expression). In racket, the use of square brackets for some expressions, coupled with good indenting style, fixes most problems.
The lisp version:
(if (and (< a b) (foo param))
(doSomething)
(doSomethingElse))
Not too bad.
Best Answer
When you format a complex Linq query well enough (don't let it run on too many lines) it shouldn't be more difficult to read than a complex condition in a
while
loop orif
statement.The follow reads just fine to me:
So does the version using an extracted
equals
Func<T, bool>
.However, usually when I see a
Func
object like that, I make the assumption that it may not always be the same logic. I use aFunc
when the action being performed needs to be passed in externally, or determined by some condition. In this case, yourFunc
is right next to the Linq expression itself, so it's easy to see that it's not the case, but it may be confusing if they were separated further.Let's pretend this logic was being used in an
if
condition. If the logic is complex enough that you would consider splitting it out into its own named Boolean variable, it's probably justifiable to do something similar and make aFunc
. If you would write the condition directly inside the body of yourif
, it's probably simple enough to understand inside the body of yourWhere
function.