The main reason is typically that an assignment rule can only have 300 entries, and you can only have one default rule. Even worse, there's a bug in the platform that allows you to save more than 300 entries, but it will only evaluate the first 300 before going to the default owner.
As an alternative, all of the API-based features could specify the
AssignmentRuleHeader in order to get around the 300 rules/1 active rule problem. However, if any specific rule needs more than 300 entries, then you definitely need to go with Apex, nothing else will be suitable in those cases.
As a second reason, not every possible criteria can be specified. It'd be, at minimum, challenging to assign a case to a specific queue, but only on Tuesdays, otherwise it has to go to a separate queue. Or if you need to figure out "after hours" in order to handle the assignment differently. There's so many limitations that might cause a developer to say "yep, it's time to get some Apex."
Generally speaking, up until the limits are reached/complexity is reasonable, assignment rules are preferred, but when you need to go beyond those limits, Apex is easily the best way to handle large or complicated assignment situations.