> But these traits are in some ways at odds with each other. The most simple code is probably not the most testable. All those interfaces and injected dependencies make for convenient testing, but have a cost in terms of simplicity. Exactly The same code snippet might be good in one context and an annoyance in another context Here's where "generic rules" fail. Like python's avoidance of lambdas and favouring just hav…
20 short functions definitely sound as though they should be explicit. Named, documented, testable. 1 or 2 you could get away with being implicit. 20 requires a lot of understanding as to what's going on!
books
.join(authors, book => book.author, author => author.id)
.filter(([book, author]) => author.lastName === searchText)
.map((book, author) => `The Book ${book.title}, by ${author.fullName()}, has ${book.chapters.count()} chapters, totaling ${book.chapters.sum(chapter => chapter.pages.count())} pages.`);
There are 5 lambda functions in there. Can you tell what the code is doing? Is it correct? Yes, and yes. This is the kind of code that, in my opinion, doesn't need tests at all, nor comments. You can look at it and understand what it's doing and know that it is doing it correctly, as long as it compiles. If it's mission critical, you should test that the join really should be on book.author and author.id, but you need to know the correct answer to write the test, so why not just look at the code and verify it's correct? If your answer is "because another change might break it": no, it won't! Given the preconditions, that code is correct, and no other code can effectively break (and still have your code compile) it without lying to the Type-checker. If someone breaks that code, it's because they're intentionally changing it to do something else, so they'd redline any tests you wrote for it anyway.It sounds like you're suggesting this code should be more like:
///
/// Gets the author of a book
/// The author of the given book
///
/// The book to get the author of
///
///
function getAuthorOfBook(book: Book) {
return book.author;
}
@testMethod()
function canGetAuthorOfBook() {
const mockBook: Book = {
author: 'Test Author',
title: 'Verbosity',
...
};
Assert.areEqual('Test Author', getAuthorOfBook(mockBook);
}
... ///
/// Takes a last name and returns a function that returns true if the given (Book, Author) tuple contains an author whose last name matches the given string of the outer function.
/// ...
function getBookAuthorPairPredicateFromAuthorLastName(lastName: string) {
return function(pair: [Book, Author])
return pair[1].lastName === lastName;
}
}
And on and on and on, still needing the original code, but just a lot more obfuscated: books
.join(authors, getAuthorOfBook, author => getIdOfAuthor)
.filter(getBookAuthorPairPredicateFromAuthorLastName (searchText))
.map(describeBookAuthorPair)
Now I have no idea what the hell this code is doing and whether it's correct or not. Note that to avoid lambdas in the filter (filtering by a value in the closure) we need to write a function that returns the predicate we want to test on, instead of just sticking the right thing in the right place to start with. All to avoid a single "=>".What circle of hell are we in!?