> the more concepts and abstractions you apply to your code the better programmer you are!
These contradict each other. XP very explicitly opposes introducing (unnecessary) abstractions: YAGNI, DTSTTCPW, etc. And TDD is a good tool for enforcing that, as you only get to write code that you have a failing test case for.
TDD encourages the use of mocks and unit testing to increase code coverage. And unit testing is specially dangerous. You write a test, then program, so the test is helping you (the programmer). Selling the idea that the higher the test code coverage is the better and safer your code is. Not true at all. If your code doesn't have integration tests for example, you will never know how it actually runs. If you mock everything, you are not really "testing" anything but your internal logic. Unit testing and code coverage just checks that a code path has been run. But there are other tools like fuzzy testing or mutation testing... Do you randomize the memory at every test run? Do you make sure that the CPU cache is cold or hot depending on the test? Good testing is hard.
Most unit tests are written to ease the development. After finishing the development, they are safe to delete. Because they don't add any real value as I understand it. I understand that a test is a business contract of something that MUST work in a certain way. Unless the contract changes, the test must never be removed or changed. TDD and exhaustive unit testing make the maintenance process harder because you don't know if a test is useful or not.
If you follow TDD, most unit tests are re-written all the time. Because they were not written to test a business or critical contract, they were originally written to help some programmer write some internal logic.
No, it encourages reasonable decoupling, i.e. good design.
If you see yourself introducing mocks (I think you mean stubs, mocks are something more specific) everywhere, you are feeling the pressure, but avoiding the good design.
https://blog.metaobject.com/2014/05/why-i-don-mock.html
> Most unit tests are written to ease the development.
Yes, unit tests help significantly in development.
> After finishing the development, they are safe to delete.
Noooooooooooooooooooooooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
They are your guardrails against regressions.
> If you follow TDD, most unit tests are re-written all the time.
Nope.
And of course that's important because it enables you to be courageous and refactor mercilessly. Which again is important because it enables you to Do the Simplest Thing That Could Possibly Work, and stick to YAGNI, because you know you can change your mind later.
That's the purpose of unit tests. They do not exclude the need to perform other kinds of test. Integration tests, contract tests, stress tests - all those will focus on different facets of a system.
> Most unit tests are written to ease the development. After finishing the development, they are safe to delete.
This is especially bad advice, unless no one will never touch that codebase ever again.
I saw old unit tests highlight bugs that would have been introduced by new code many times over the years.
> TDD and exhaustive unit testing make the maintenance process harder because you don't know if a test is useful or not.
Then, as a developer, remove unit tests that became useless.
Code coverage is a measurement. If you turn it into a goal, it will become useless. If you have "useless" unit tests, it tells me that some unit tests were written as padding to move code coverage up.
TDD was later given the name BDD (Behaviour Driven Development) to emphasize that you are not testing, but actually documenting behaviour. What kind of behaviour are you documenting that isn't useful and why did you find it necessary to document in the first place?
> If you follow TDD, most unit tests are re-written all the time.
What for? If changing requirements see that your behaviour has changed to the extent that that your unit does something completely different, it's something brand new and should be treated as such. Barring exceptional circumstances, public interfaces should be considered stable for their entire lifetime and, at most, deprecated if they no longer serve a purpose.
The implementation beneath the interface may change over time, but TDD is explicit that you should not test implementation – it is not about testing – only that you should document the expected behaviour of any implementation that may carry out your desired behaviour.
I don't think someone that believes this has a good understanding of unit testing. You can easily get 100% coverage without testing anything at all!
Coverage is a great metric if it's predicated on high quality tests. Even then 100% coverage doesn't equal "safe". It means that a lot of effort has been put into understanding and testing internal behavior.
You still need higher order tests, arguably even more.
Well written unit tests serve to provide regression testing to prevent bugs reoccurring and to keep existing functionality (e.g. support for reading existing data) working.
TDD is used as a way to help write tests for the API surface and usage of your classes, functions, etc.. You should generally avoid testing internal state as that can change.
For example, if you are writing a set class, the logical place to start is with an empty set -- that's because it is easy to define the empty logic, defining accessor functions/properties like isEmpty, size, and contains. The next logical step is adding elements (two tests: add a single element, add multiple elements). Etc.
Later on, you can change the internal logic of the set from e.g. an array to a hash map. You will keep your existing tests as they document and test your API contract and external semantics. Likewise, if your hash set uses another class like an array, or a custom structure like a red-black tree, you shouldn't mock that class.
A code base that can't be understood and maintained by the whole team, will degrade quickly.
What Casey is doing is showing how bad a hammer is at removing screws. I mean duh, you're removing screws with a hammer.
My comment was a jest (as suggested by the smiley).
What I take from Casey's post is that a simple non-pessimistic representation allows for efficient code. That is, using a table instead of a class hierarchy gives massive performance boost. Compared to a "clever" loop unrolling doesn't give that much of a boost.
So we need simpler representation. IMHO the table implementation is not less readable nor less flexible than the class hierarchy. But it is less common in the code I am used to, in other words, it's not a widely used pattern).
That's the insight, I think. "Clean Code" tells you to use maximally pessimistic representation for everything, because everything could be extended in some way in every direction. Meanwhile, in the real world, you likely have a good idea what directions of evolution are possible, and which of them are even useful.
Casey's example shows you that, if you design your code to make use of those assumptions, you'll get absurd performance benefits for little to none loss in readability (and perhaps even a gain!).
Some may ask, "what if you're wrong with your assumptions?". Well, you pay a price then. Worst case, you may need to rip out a module, rethink the theory behind it, and rewrite it from scratch - likely forgoing some of the performance benefits, too. Usually, the price will be much smaller. Either way, it's still better than being maximally pessimistic from the start, and writing software that never had a chance of ever becoming good or fast.
Techniques like manual code unrolling/inlining, writing branchless code, compressing data to fit into a pointer, etc.
As an extreme example, people choose not to write low level code for a good reason, even if it might be fast.
High level / interpreted code will be more understandable, but will automatically have an overhead in most cases.
You can have fast and clean code, just not the Uncle Bob style of "clean code". Uncle Bob hijacked the meaning of cleanliness. It doesn't mean that code written like that is actually clean, in fact it's usually the opposite: Uncle Bob's clean code is NOT clean.
I had an Android programmer, who was eager to write clean code following GOF patterns, OP and the rest of the fancy things senior developers usually do. Ended up Android team with 3 devs required 3x time to develop same feature compared to single iOS engineer.
You said you're targeting Android, that implies you were using Java, which has its reputation for both a rigidly inflexible language-design team and its ecosystem having more design-patterns than a set of fabrics swatches - that's not a coincidence.
But for iOS, they'd be using Swift, right? Swift's designers clearly decided they didn't want to be like Java: take the best bits of C# and other well-designed languages and don't be afraid to iterate on the design, even if it means introducing breaking-changes - but the result is a highly-expressive language that, as you've demonstrated, allows just 1 Swift person to do the equivalent of 3 Java people. Swift is an actual pleasure to use, but using Java today makes me weary.
(To be clear: Java was a fantastic language when it was introduced, but it simply hasn't kept-up with the times to its own detriment, it feels like its falling behind more-and-more at time goes on - but that's going to be the fate of every programming language eventually, imo).
And perhaps I was lucky, but typically readability and maintainability are orthogonal to performance and efficiency. Sometimes readable will have optimal performance, sometimes not. Then it becomes a matter of tradeoffs.
The first entry even stated that they were guidelines and if you have a valid reason to deviate: discuss it and you'll get an exception.
The discussion thing was mainly for new coders. We had a library part of the code that was used by many programs. Optimizing parts of it for their use case, could make it unusable for the others who used it.
Communication is key. If you discuss, before implementing it, why you're making certain design decisions then everything goes a lot smoother. If there are objections, keep in mind that the worst case isn't throwing away your design and starting over. The worst case is implementing it and screwing over your fellow coders.
That was the case during the 90s and the first decade of the 2000. Just wait for Moore's Law to kick in and in 18 months your code will get faster by an order of magnitude for free.
Single-threaded performance has long-since effectively plateaued: it's 2023 now and a desktop computer built 10 years ago (2013) can run Windows 11 just fine (ignoring the TPM thing) - but compare that to using a computer from 2003 in 2013 (where it'd run, but poorly), or a computer from 1993 in 2003 (which simply wouldn't work at all).
This is not to say that there won't be any significant performance gains to come, such as with rethinks in hardware (e.g. adding actual RAM into a desktop CPU package, non-volatile memory, etc) but I struggle to see how typical x86 MOV,CMP,JMP instructions could be executed sequentially any faster than they are right now.
https://stoneridgetechnology.com/company/blog/the-exponentia...
The rest of the article is concerned with how software today is still written for those "serial" processors in-mind and fails to take advantage of parallel computing hardware - but this is hardly a new nor controversial statement.
My argument was simple: 20 years ago nobody cared about performance because of the strong correlation between Moore Law and MFLOPS and performance in general.
Nowadays with multicore, individual CPU cores are becoming slower, not faster. Then we can't just wait 18 months for individual cores to get faster.
Today we need to write better software.
No, that's not what I said: I'm saying that single-threaded performance is no-longer directly (let alone linearly) correlated with transistor count, and hasn't been for decades - but it's single-threaded performance that matters for most end-user applications on peoples' computers/smartphones/etc.
And Moore's Law makes no claims about performance increases either, only transistor density. It's literally the first paragraph of the Wikipedia article:
> Moore's law is the observation that the number of transistors in a dense integrated circuit (IC) doubles about every two years. Moore's law is an observation and projection of a historical trend.
The link to performance (any kind of performance: parallel or serial) was made incorrectly by another Intel executive, David House, but that assumption simply isn't true.
To summarize: *yes*: bleeding-edge IC transistor density generally doubles every 18-24 months, but this does not translate into any kind of performance-doubling in end-user applications. In fact, on the contrary, perceived "performance" (however you define it) outside of parallelizable programs, has demonstrably stagnated.
I vaguely smell the language of choice has something to do with that, and C++ would benefit more from data-oriented design than from literate OOP or functional programming patterns took from very very different programming ethoses (the programming language is an interface to something else)
Which is all well and good, until you need to hire all the network engineers, systems administrators, devops people, security staff, datacenter operations managers, database sharding engineers, etc to manage the 10x more hardware and network surface area you have to throw at your slow codebase.
Re-usability, OOP concepts or pure functional style, design patterns, TDD or XP methodologies are the only things that matter... And if you use them you will write "clean code". Even worse, the more concepts and abstractions you apply to your code the better programmer you are!
If you look at the history of programming and classic texts like "the art of programming", "sicp", "the elements of programming"... The concept of "beautiful code" appears a lot. This is an idea that has always existed in our culture. The main difference with the "clean code" cult is that "beautiful code" also used to mean fast and efficient code, efficient algorithms, low memory footprint... On top of the "clean code" concepts of easy to test and re-usable code, modularity... etc