Функции нужны для того, чтоб в коде не возникали ошибки и хаос. Если весь код состоит всего из нескольких переменных и операций, это понять сложно. Но как только их становятся десятки, сотни и тысячи, без разделения кода на изолированные логические кусочки (функции) уже не обойтись.
Функции позволяют писать программы, постепенно углубляясь в отдельные аспекты и не теряя при этом "общую картину". Например, в программе нужно получить данные с сервера, распарсить их, что-то посчитать и вывести результаты. Вместо того, чтоб писать простыню, в которой тут же запутаешься, можно разбить код на функции getData() - parseData() - performCalculation() - displayResult(), и постепенно писать их логику, концентрируясь на решении конкретной задачи отдельной функции. Разделение сложной задачи на более простые - это, вообще, основной принцип борьбы со сложностью. Если это делать разумно (т.е. разделять на осмысленные кусочки), то функции, написанные один раз, можно использовать в разных местах программы (повторное использование кода).
Наконец, они нужны для сокращения возможных ошибок, т.к. позволяют изолировать переменные, объявляемые внутри функций от случайного их изменения в других частях кода (принцип инкапсуляции в ООП). В случае т.н. глобальных переменных (объявленных в областях видимости, доступных в разных, иногда вообще не связанных друг с дугом логически местах) очень велик шанс объявить переменную для чего-то одного, а потом забыть об этом, и в другом месте использовать ее для чего-то другого.
И еще они позволяют вносить изменения в логику программы, не перекраивая ее для этого целиком (полиморфизм). Если в примере выше вдруг понадобится изменить алгоритм рассчета, достаточно изменить performCalculation() (или даже написать другую функцию performNewSpecialCalculation() и просто изменить вызов).
На уровне "внутреннего устройства языка" функции также позволяют эффективно управлять памятью, и обеспечивать взаимодействие программ, написанных разными людьми (например, библиотек), но это уже - совсем другая история.