There are a few Drupal modules that are essential do doing anything remotely "custom" and the most important of these I would consider to be CCK, Views and Panels. Many of the better modules integrate with Views. The top 20% of Drupal modules are decent, the remainder are to varying degrees quite amateurish.
If you find yourself writing a lot of PHP on a Drupal project, you're doing it wrong. It's not just that the learning curve is steep, it's that the internals are a steaming pile. The code is straight out of the last decade: large PHP functions sprinkled with SQL queries built up from concatenated strings. It's no surprise that SQL injection vulnerabilities are still being found on almost a weekly basis in one module or another. There is absolutely no transaction management; the assumption is that a Drupal site will be modified by a few and viewed by many.
The functionality of Drupal is powerful. But stick to using modules and maybe a little bit of PHP helper code. If you are implementing a lot of custom logic in PHP, you're better off using a more conventional (modern) web app framework.
1. I'm aware that Drupal is a CMS.
2. I don't find myself writing a lot of custom code on my Drupal projects and that's the entire point (for me anyway) of using it as a dev framework.
3. You have a few thousand developers of various skill levels submitting modules and the quality is absolutely going to vary from module to module, this is unavoidable. Learning to vet your modules carefully and taking a less is more approach with module installation is part of the learning curve.
4. Drupal's internals are a steaming pile compared to what, exactly? Ever taken a look at Wordpress's core code? All code sucks.
Yes, and I'm sure Joomla's code is terrible as well. On the other hand, I can most often peer into Django's internals comfortably and see exactly how it is working, and extend that as needed.
Wordpress's core code has some frustrating choices in it, but even I as primarily a non-developer have been able to find what pieces of code in the core are causing something to happen, and how to change them if needed.