Smart-contracts have become synonymous with Solidity scripts on Ethereum and they can be used to create many things: multi-signature wallets, fungible and non-fungible tokens, payments with the escrow and so on. In most cases, you don’t need to decentralize all user interactions, so smart-contracts should do as less as possible. Some people have found out about that the hard way when their smart-contracts cannot be executed anymore because of block gas limit and unbounded loops.
There are endless ways how the same functionality can be written differently in Solidity, which means that these smart-contracts are usually not readable for regular users (even after the source code has been verified) and each implementation of the same functionality can have different bugs because a developer might have forgotten to restrict access to something that should have been limited by default (Parity hack). It’s good to see standardization of basic functionalities (ERC20, ERC721, SafeMath) on Solidity code, but it’s usually too late if your broken code is already up there and you didn’t think about how to replace the broken parts of you smart-contract. Also, ERC20 only defines required functions for the token, but every developer can still write their own implementation — so not all ERC20 tokens are the same. The possibility to implement functions to replace some parts of your smart-contract could also mean that the same ERC20 standard token can tomorrow have different spending rules or even paused by the developer.