In a nutshell there is one key notion in this debate which is your appetite for understanding memory management for collections of objects and the associated trade offs
You can go with "static, fixed size" or "dynamic variable size" memory allocation.
Strings are not the only buffer you’ll need (think about binary protocols or just collections of values in an array, vectors, matrix, graphs, and any other type of data set), so for those of us with enough programming experience and willingness to understand what’s under the hood the answer is clear : you need to know how to handle the static fixed size case as it’s a pattern you’ll see a lot in C or C++.
Once you master that pattern you’ll see there are many functions to handle the fixed size constraint and you can just code with those or decide you are tired of working at that level and then you write your own set of helper functions or classes to encapsulate the frequent operations. You do this by building on the knowledge you acquired and carefully address the potential edge cases and overflow issues. This is what also led to more classes in C++ like the containers.
This encapsulation comes at a cost (a few more bytes, a few more computing cycles, possible non universal API so non portable or not optimized knowledge).
When you work on small microcontrollers sometimes every byte and every cycle matters and if you are a diligent and careful programmer then you can save a bit here and there to fit into the hardware constraints.
In some cases understanding what’s happening in memory is a choice between life and death. It’s no joke. So for those of us who have been exposed to such responsibility or just care about not crashing, you can understand there is a strong position on staying in control with proven functions.
But the crux is there: The native functions or any library built on top of static usage won’t handle edge cases for you. The good ones just give you a way to catch the issue. How you handle that is for the programmer to solve and lazy / bad / tired coders just don’t code for it, thinking "it will never happen in a normal use case and too bad for you if you did not follow what’s expected". That’s not counting on hackers looking for this...
Amongst the edge cases, buffer overflow is one of the most common issue developers face and need to code for. With price of memory going down and more capable hardware around, some thought that the best way to solve for this edge case was by not having the programmer worry about: if the buffer is too small, just make it bigger.
And that works until you hit memory limits…
On modern 64 bits computers with an OS, Gigs of RAM and virtual memory this limit is quite far and you only get a speed impact if you have to swap a lot.
On small microcontrollers with no OS and where you play with actual RAM and no virtualization it’s another story.
One question then is whether the library you use catches the exception that there is no memory available for buffer expansion and how this is presented to the developer to handle. (try/catch)
One challenge you have with the Arduino String class is that this is left uncaught, the operation fails silently and can either crash or just not do anything leading the programmer to believe all went fine.
(The other challenge is that your code could actually have been fine with a larger static buffer but rules for dynamic allocations lead to a situation where there was no block of memory large enough to accommodate the expansion - that’s the challenge of poking holes into the heap and having no garbage collector)
➜ if you use the String class you can’t catch the memory limit issue and thus your code is at risk, you are no longer in control.
It all boils down to Is it a risk you can live with?