When building any software, it's important to use the simplest solution appropriate to the task. Even on teams that follow this guidance, it's easy to end up with massively bloated, complex solutions. Sometimes problems just require complexity, and that complexity breeds further complexity.
This is especially important in systems with high resource costs. For example, embedded hardware on tiny implanted medical devices. You just don't have as much CPU, RAM, etc. as on today's modern computers. Blockchain also has a high resource cost, but for very different reasons. Your code has to run on every computer in the network, and that comes with a monetary (gas on Ethereum) or time (mana on Koinos) cost.
On Blockchain, the simplest solution is often to do nothing. You could issue a governance token, perform an ICO, and manage the upgrading of your contracts through governance proposals and tight community management. Or, you could just make your contracts non-upgradeable.
Non-upgrading contracts are simple. If there's a need to upgrade, you just upload a new version to a different address. Your users don't have to use the new version, but you'll point your website at the new contract so it's the default choice.
The main problem for this approach is bugs. If your code is broken, you have to get users away from the bad version. For a lot of contracts, just sharing information about the bug with the community and providing a new version is enough. However, if your contract has user tokens inside of it, those tokens are at risk. Bugs can sometimes be exploited to steal staked tokens.
For this possibility, I recommend including an escape hatch in your contract. This is an entry point that can only be called via multisig, and it returns all the tokens in the contract to their owners. If you or someone in your community notices a theft in progress, you have a way to react to the situation.
P.S. Upgradeable contracts don't need escape hatches, but they do require more protection for users. The contract owner shouldn't be able to withdraw tokens or make selfish upgrades. Community owned escape hatches are still a good option in the event of a buggy or malicious proposal being voted in.