There are two problems. First, when writing a contract every line you write might be a potential avenue for an attack. Second, the language (Solidity) and the parsing of the bytecode (EVM) don't help.
If you look at the kind of attacks that happen I feel like two major problems came from the fact that addresses can be real account addresses or contracts (it could have been good to segregate them) and errors/exceptions don't propagate well.
You're assuming it's possible to "use well". It's delusional to believe it's possible to understand the subtle interactions within and between "smart contracts". Even if we ignore the ambiguity of the real world, malicious actors, and other complexity multipliers, we already know it isn't possible to know if a "smart contract" will halt.
Code will always have bugs, and you cannot truly understand how something written in a Turing complete language will behave without running it.
> very painful tool if mishandled
Part of being a skilled craftsman is choosing the right tools, which includes understanding and respecting their limits. The language for writing contracts makes all state mutable by default, has ambiguous operators that change behavior depending on storage location or if the operand was a literal, and doesn't defined the order of evaluation for expressions, to name just a few of it's design problems[1]. This isn't a "useful tool:", it's a strong indicator of a another fractal of bad design[2].
Isn't that something that can be helped by having a good standard library though (e.g. STL)? OpenZeppelin [1] is one example that comes to mind.
There are also other contract languages aimed to solve some of the limitations you mention, like Tezos/Michelson [2][3], which facilitate formal verification.
The issues you point out are certainly valid, but I believe people in the space are cognizant of them and are working on solutions.
While a good standard library would help, the fundamental problem (undecidability) still exists. You could build a system that is understandable if you only use calls into a (presumably proven/tested to be safe) standard library. At that point it would de facto be a declarative language, which is decidable iff the grammar is not Turing complete[1].
It's limiting, but you can still have many of the fancier features by baking standard versions of them into the language/stdlib itself.
> I believe people in the space are cognizant of them and are working on solutions.
The problem with that is that this isn't a bug or engineering problem that we can solve with impossibly talented devs and a sufficiently large r&d budget. Questions about any non-trivial semantic behavior of a program (such as, "will the program halt"[2]) are known[3] too be undecidable[4].
[1] according to LangSec, the grammar needs to be deterministic context-free (or simpler). Anything more complex is undecidable.