Building a CiviCRM extension is the *recommended* way to customize CiviCRM, but others strategies exist too. This page summarizes some of the advantages and disadvantages of each strategy.
When developing an enhancement for CiviCRM, one writes code based on Civi's programming interfaces, such as APIv3, `CRM_Core_Page`, or `CRM_Report_Form`. You need to put this code in some kind of *file* or *package* that can be *installed* on a CiviCRM system.
The best-supported approach is to develop the package as a native *CiviCRM extension* -- this package format works in all CiviCRM environments. However, there are other packaging formats (such as Drupal modules and WordPress plugins). How do they differ? Well, from Civi's perspective, code is code regardless of the packaging -- but the tutorials, glue-code, tools, and culture may differ. For example, you can write a subclass of `CRM_Report_Form` and put it in either a CiviCRM extension or a Drupal module, but a few lines of glue-code would be different, and the code-generator (`civix generate:report`) is only tuned to output native CiviCRM extensions.