4 ms·
> 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 dependenc
by raverbashing 5y ago
> 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 having functions. Well, but, depending on the context, they make a lot of sense.
"Oh but lambdas are less readable" not if you need 20 different short functions and you need to combine them. Too implicit and it's hard to read, too explicit it gets boring and also hard to read.
- robertlagrant 5y ago20 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!
- fdgsdfogijq 5y agoWait until you work on a 100k line code base where someone enforced this kind of thing
- DarylZero 5y agopoint-free master race
- robertlagrant 5y agoFair enough - I haven't done that. I'd be surprised if having loads of anonymous, untested functions would make such a thing more manageable, though.
- valenterry 5y agoDo you test every line of code you write separately? Probably not. You test a function that has 5 lines of code. Same for anonymous functions. You test the functions that use them and that's usually enough. If not, then that is a good indicator of separating them out into named functions.
- robertlagrant 5y agoThis is the wrong way to think about it, though. A function encapsulates some behaviour, regardless of how short or long it is. You don't test each line (directly); you test each function's mode of operation. So a function with one if statement in it potentially needs two (happy path) tests.
- valenterry 5y agoI don't think we disagree. It's just that a lambda doesn't change this. If the lambda itself also contains a branch, then yeah, you should probably test the outer function with at least 2 inputs.
- robertlagrant 5y agoI agree, except I think that a lamdba is an arbitrary line to draw for that as well. Why not stop at the main function and give it a load of inputs? A five line lamdba looks a lot like a named function, just harder to test, reuse, and debug in a stack trace.
- valenterry 5y agoI was basing my comment on this: > 20 short functions definitely sound as though they should be explicit. And you are right that it's pretty arbitrary when/if a lambda should specifically be tested or not. But the number of lambdas in a project isn't really a good factor to make that decision - it's individual for each function that contains a lambda.
- robertlagrant 5y agoWell, I more meant that if they're "short" as opposed to "20 character one-liners" then it sounds as though they should be tested, whether or not they're defined using lambda syntax.
- ajuc 5y agoTask: write code that does genetic programming by applying combination of 20 specific 5-line functions in random combinations and comparing results. My solution: array of lambdas Your solution: array of references to functions named f1 .. f20. Which is cleaner?
- fendy3002 5y agots/js: I see no problem? * if anyone not get it, ts/js can declare a key-value lambda like: let myFunc = {f1: () => , f2: () => } and so on. It can be called by string key like myFunc["f1"](), and all it's props can be get by Object.keys(myFunc) so it can be randomized easily.
- thom 5y agoAre we allowed an array of references to functions with meaningful names?
- ajuc 5y agoSure but the point was - sometimes there's no meaningful names, and the best description of a function is its definition. That's why you write functions like this: def testXIsYAfterSettingAToBAndCToD(): A = B; C = D; assert(X == Y); So much meaning provided by the name ;)
- cryvate1284 5y agoNot that I am taking a stance here, but you didn't really engage with the GP saying that having functions instead of lambdas allows one to name, test and document them: in fact, they might not be called f1 to f20, in fact they might have docstrings (I guess one could comment lambdas, but see it rarely) and you couldn't test lambdas unless you assign them, i.e. give them a name i.e. turn them into functions in all but name.
- mypalmike 5y agoThe one with the names.
- feoren 5y agoSo here's some TypeScript code I just made up, with a lot of lambdas. It's somewhat typical of code I write all the time. 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: /// <verboseDocs purpose="Verbosity"> /// <purpose> Gets the author of a book /// <returns> The author of the given book /// <inputs> /// <input param="book">The book to get the author of /// </inputs> /// </verboseDocs> function getAuthorOfBook(book: Book) { return book.author; } @testMethod() function canGetAuthorOfBook() { const mockBook: Book = { author: 'Test Author', title: 'Verbosity', ... }; Assert.areEqual('Test Author', getAuthorOfBook(mockBook); } ... /// <verboseDocs> /// <purpose> 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!?