[They're] not bad, they're just drawn that way.
A significant number of perl wizards I respect seem to detest them. I don't, but they have pretty limited use. Usually, perl's loose argument handling is just what you want, and prototypes only get in the way.
It is a source of confusion to Javians and C-ites alike that perl's prototypes are static but not strict regarding type. And, maybe most of all, optional. Perl prototypes confound the expectations of both groups.
There are two particular uses for prototypes which I recommend. An empty prototype, '()', says that the compiler should look for invariant return values and, if found, treat the sub as a constant at compile time. That is the magic behind constant.pm.
The other is in overriding or overloading core functions. Certain core functions like push, pop, splice, chomp, substr, to name a few, modify one of their arguments in-place. That means the argument must be an lvalue. Prototypes can tell the compiler to pass by reference to an lvalue based on position in the arg list.
(Added) The theme is that perl prototypes are compiler directives much like pragmata and attributes. They are not scaffolding for any kind of object system, consistency check, or polymorphism. /Added
Your example needs a code block after each prototype.