Ну приведу простой пример. У вас есть функция которая должна открывать соединение с базой данных/читать из файла/подключаться к сокету... словом любая функция возвращающая некий ресурс, при помощи которого в дальнейшем будет осуществляться доступ к чему-то там.
Функция должна вернуть какой-то результат, причем определенного типа. Что вы будете делать в случае ошибки? А как уведомить разработчика что именно пошло не так?
Для этого ввели механизм исключений. Если в функции что-то идет не так, можно сформировать исключение со всей имеющейся по ошибке информации и выбросить его.
Если вы используете конструкцию try/catch в использующем эту функцию коде, то вместо крэша у вас вызовется один из обработчиков catch (вы можете спускаться по иерархии классов, задавая различное поведение для разного рода ошибок), возможно произведете какие-то уточнения в данных, переспросите пользователя и программа продолжит работу совершенно нормально.