In order for a program to get superuser privileges it must already have superuser privileges. The alternative is to break apps up into two parts -- the frontend and the secured backend -- but that is neither practical for many projects nor actually a complete solution. If _any_ part of the program runs setuid then there is an opportunity for exploit. If _no_ part of it runs as setuid, then there essentially is no opportunity.
That is what capabilities are for. Give the program only what it needs and nothing else. You can take it a step further and split the program into frontend and backend pieces and give only the backend piece the capability necessary, too.