- Функции — Основы программирования
- Полезное
- Способы записи функций
- Формальные и Фактические параметры функции
- Return
- Остались вопросы? Задайте их в разделе «Обсуждение»
- Функции. Зачем они нужны и как их писать, чтобы вас уважали программисты
- Что такое функция
- Зачем нужны функции
- Что такое чистые функции
- Один и тот же результат
- Побочные эффекты
- Как этим пользоваться
- Искусство написания простых и коротких функций
- 1. Функции должны быть маленькими. Совсем маленькими.
- Шаг 0: Первичная большая функция
- Шаг 1: Извлекаем вес по типу и ликвидируем магические числа
- Шаг 2: Продолжаем разделение и делаем функции расширяемыми
- Шаг 3: Никогда не прекращайте улучшения
- 2. Функции должны быть простыми
- 3. Используйте компактные названия функций
- 4. Вывод
Функции — Основы программирования
Полезное
Способы записи функций
Кроме указанного в видео определения функции:
Существует несколько других. Например, если у вас функция-однострочник, то можно использовать сокращенный синтаксис:
В коде выше мы опустили фигурные скобки и слово return , а также скобки вокруг аргумента (это можно делать только если у функции один аргумент).
Так же можно определять функции, используя ключевое слово function :
В наших курсах мы будем придерживаться () => <> такой формы записи по многим причинам. Во-первых, она лаконичнее, во-вторых, обладает одним важным свойством, которое будет изучено позже, ну а в-третьих, такой способ записи визуально стирает грань между функциями и данными, что очень пригодится нам в будущем.
Формальные и Фактические параметры функции
Немного терминологии. Формальными параметрами функции называются имена переменных в определении функции, например у функции const f = (a, b) => a — b; формальные параметры это a и b . А фактические параметры — это то, что было передано в функцию в момент вызова, например если предыдущую функцию вызвать так f(5, z) , где const z = 8 , то фактическими параметрами являются 5 и z . Результатом этого вызова будет число -3 , а внутри функции, на момент конкретного вызова, параметр a становится равным 5 и b становится равным 8 .
Как видите, нет никакой связи между именами формальных и фактических параметров, более того, у фактических параметров вообще может не быть имен, как в примере выше, где мы сразу передали число 5 в функцию. Что имеет значение, так это позиция. Во время вызова функции параметры должны передаваться в правильном порядке, и только тогда функция отработает, как предполагается.
Return
Вызов оператора return приводит к изменению течения программы. Последующие инструкции никогда не будут выполнены:
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты.
Источник
Функции. Зачем они нужны и как их писать, чтобы вас уважали программисты
Сложная важная статья для тех, кто хочет стать крутым программистом.
Хороший программист старается делать свои функции чистыми. Если знать, что это такое, можно сойти за своего, а заодно написать читаемый код.
Если вы не совсем понимаете, что такое функция и зачем она нужна — добро пожаловать в наш кат:
Что такое функция
Функция — это мини-программа внутри вашей основной программы, которая делает какую-то одну понятную вещь. Вы однажды описываете, что это за вещь, а потом ссылаетесь на это описание.
Например, вы пишете игру. Каждый раз, когда игрок попадает в цель, убивает врага, делает комбо, заканчивает уровень или падает в лаву, вам нужно добавить или убавить ему очков. Это делается двумя действиями: к старым очкам добавляются новые, на экран выводится новая сумма очков. Допустим, эти действия занимают 8 строк кода.
Допустим, в игре есть 100 ситуаций, когда нужно добавить или убавить очки — для каждого типа врага, преграды, уровня и т. д. Чтобы в каждой из ста ситуаций не писать одни и те же восемь строк кода, вы упаковываете эти восемь строк в функцию. И теперь в ста местах вы пишете одну строку: например, changeScore(10) — число очков повысится на 10.
Если теперь изменить, что происходит в функции changeScore(), то изменения отразятся как бы во всех ста местах, где эта функция вызывается.
Зачем нужны функции
Функции нужны, чтобы заметно упрощать и сокращать код, адаптировать его для разных платформ, делать более отказоустойчивым, легко отлаживать. И вообще порядок в функциях — порядок в голове.
Возьмём тот же пример с подсчётом очков. Что если при добавлении очков нужно не только выводить их на экран, но и записывать в файл? Просто добавляете в определении функции дополнительные команды, связанные с файлами, и они теперь будут исполняться каждый раз, когда функцию снова вызовут в основной программе. Не нужно ползать по всему коду, искать места с добавлением очков и дописывать там про файлы. Меньше ручного труда, меньше опечаток, меньше незакрытых скобок.
А что если нужно не только писать очки в файл, но и следить за рекордом? Пишем новую функцию getHighScore(), которая достаёт откуда-то рекорд по игре, и две другие — setHighScore() и celebrateHighScore() — одна будет перезаписывать рекорд, если мы его побили, а вторая — как-то поздравлять пользователя с рекордом.
Теперь при каждом срабатывании changeScore() будет вызывать все остальные функции. И сколько бы раз мы ни вызвали в коде changeScore(), она потянет за собой всё хозяйство автоматически.
Сила ещё в том, что при разборе этой функции нам неважно, как реализованы getHighScore(), setHighScore() и celebrateHighScore(). Они задаются где-то в другом месте кода и в данный момент нас не волнуют. Они могут брать данные с жёсткого диска, писать их в базу данных, издавать звуки и взламывать Пентагон — это будет расписано внутри самих функций в других местах текста.
Без функций трудно повесить действия на какие-либо кнопки в интерфейсе. Например, у вас на сайте есть форма, и при клике на кнопку «Отправить» вы хотите проверять, что данные в форме правильно введены. Вы спокойно описываете где-то в коде функцию validateForm() и вешаете её на нажатие кнопки. Кнопку нажали — функция вызвалась. Не нужно вписывать в кнопку полный текст программы.
А без функции пришлось бы писать огромную программу-валидатор прямо внутри кнопки. Это исполнимо, но код выглядел бы страшно громоздким. Что если у вас на странице три формы, и каждую нужно валидировать?
Хорошо написанные функции резко повышают читаемость кода. Мы можем читать чужую программу, увидеть там функцию getExamScore(username) и знать, что последняя каким-то образом выясняет результаты экзамена по такому-то юзернейму. Как она там устроена внутри, куда обращается и что использует — вам неважно. Для нас это как бы одна простая понятная команда.
Можно написать кучу вспомогательных функций, держать их в отдельном файле и подключать к проекту как библиотеку. Например, вы написали один раз все функции для обработки физики игры и потом подключаете эти функции во все свои игры. В одной — роботы, в другой — пираты, но в обеих одна и та же физика.
Функции — это бесконечная радость. На этом наш экскурс в функции закончен, переходим к чистоте.
Что такое чистые функции
Есть понятие чистых функций. Это значит, что если функции два раза дать на обработку одно и то же значение, она всегда выдаст один и тот же результат и в программе не изменит ничего, что не относится непосредственно к этой функции. То есть у чистой функции предсказуемый результат и нет побочных эффектов.
Один и тот же результат
Допустим, мы придумали функцию, которая считает площадь круга по его радиусу: getCircleArea(). Для наших целей мы берём число пи, равное 3,1415, и вписываем в функцию:
Теперь этой функции надо скормить число, и она выдаст площадь круга:
- getCircleArea(2) всегда выдаст результат 12,6060
- getCircleArea(4) всегда выдаст 50,2640
Разработчик может быть уверен, что эта функция всегда выдаст нужную для его задачи площадь круга и не будет зависеть от каких-либо других вещей в его программе. Эта функция с предсказуемым результатом.
Другой пример. Мы пишем программу-таймер, которая должна издать звук, например, за 10 секунд до конца отведённого ей времени. Чтобы узнать, сколько осталось секунд, нам нужна функция: она выясняет количество секунд между двумя отметками времени. Мы даём ей два времени в каком-то формате, а функция сама неким образом высчитывает, сколько между ними секунд. Как именно она это считает, сейчас неважно. Важно, что она это делает одинаково. Это тоже функция с предсказуемым результатом:
- getInterval(’09:00:00′, ’09:00:12′) всегда выдаст 12
- getInterval(’09:00:00′, ’21:00:00′) всегда выдаст 43 200
А теперь пример похожей функции: она определяет время от текущего до какого-то другого времени. При исполнении эта функция запрашивает текущее время в компьютере, сравнивает с целевым и делает нужные вычисления. При запуске одной и той же функции с разницей в несколько секунд она даст разные результаты:
- getSecondsTo(’23:59:59′) в один момент даст 43 293 секунды,
- а спустя 2 минуты эта же функция getSecondsTo(’23:59:59′) даст 43 173 секунды.
Это функция с непредсказуемым результатом. У неё есть непредсказуемая зависимость, которая может повлиять на работу программы — зависимость от текущего времени на компьютере. Что если во время исполнения у пользователя обнулились часы? Или он сменил часовой пояс? Или при запросе текущего времени происходит ошибка? Или его компьютер не поддерживает отдачу времени?
С точки зрения чистых функций, правильнее будет сначала отдельными функциями получить все внешние зависимости, проверить их и убедиться, что они подходят для нашей работы. И потом уже вызвать функцию с подсчётом интервалов. Что-то вроде такого:
- var now = getCurrentTime();
- var interval = getInterval(now, ’23:59:59′);
Тогда в функции getCurrentTime() можно будет прописать всё хозяйство, связанное с получением нужного времени и его проверкой, а в getInterval() оставить только алгоритм, который считает разницу во времени.
Побочные эффекты
Современные языки программирования позволяют функциям работать не только внутри себя, но и влиять на окружение. Например, функция может вывести что-то на экран, записать на диск, изменить какую-то глобальную переменную. Взломать Пентагон, опять же. Всё это называется побочными эффектами. Хорошие программисты смотрят на них крайне настороженно.
Мы пишем таск-менеджер. В памяти программы хранятся задачи, у каждой из которых есть приоритет: высокий, средний и низкий. Все задачи свалены в кучу в памяти, а нам надо вывести только те, что с высоким приоритетом.
Можно написать функцию, которая считывает все задачи из памяти, находит нужные и возвращает. При этом на задачи в памяти это не влияет: они как были свалены в кучу, так и остались. Это функция без побочных эффектов.
- getTasksByPriority(‘high’) — вернёт новый массив с приоритетными задачами, не изменив другие массивы. В памяти был один массив, а теперь появится ещё и второй.
А можно написать функцию, которая считывает задачи, находит нужные, стирает их из исходного места и записывает в какое-то новое — например, в отдельный массив приоритетных задач. Получается, будто она физически вытянула нужные задачи из исходного массива. Побочный эффект этой функции — изменение исходного массива задач в памяти.
- pullTasksByPriority(‘high’) — физически вытащит задачи из исходного массива и переместит их в какой-то новый. В старом массиве уменьшится число задач.
- Такие изменения называют мутациями: я вызвал функцию в одном месте, а мутировало что-то в другом.
Программисты настороженно относятся к мутациям, потому что за ними сложно следить. Что если из-за какой-то ошибки функции выполнятся в неправильном порядке и уничтожат важные для программы данные? Или функция выполнится непредсказуемо много раз? Или она застрянет в цикле и из-за мутаций разорвёт память? Или мутация произойдёт не с тем куском программы, который мы изначально хотели?
Вот типичная ошибка, связанная с мутацией. Мы пишем игру, нужно поменять сумму игровых очков. За это отвечает функция changeScore(), которая записывает результат в глобальную переменную playerScore — то есть мутирует эту переменную. Мы случайно, по невнимательности, вызвали эту функцию в двух местах вместо одного, и баллы увеличиваются вдвое. Это баг.
Другая типичная ошибка. Программист написал функцию, которая удаляет из таблицы последнюю строку, потому что был почему-то уверен: строка будет пустой и никому не нужной. Случайно эта функция вызывается в бесконечном цикле и стирает все строки, от последней к первой. Данные уничтожаются. А вот если бы функция не удаляла строку из таблицы, а делала новую таблицу без последней строки, данные бы не пострадали.
Без мутирующих функций, конечно, мы не обойдёмся — нужно и выводить на экран, и писать в файл, и работать с глобальными переменными. Сложно представить программу, в которой вообще не будет мутирующих функций. Но программисты предпочитают выделять такие функции отдельно, тестировать их особо тщательно, и внимательно следить за тем, как они работают. Грубо говоря, если функция вносит изменения в большой важный файл, она должна как минимум проверить корректность входящих данных и сохранить резервную копию этого файла.
Как этим пользоваться
Когда будете писать свою следующую функцию, задайтесь вопросами:
- Нет ли тут каких-то зависимостей, которые могут повести себя непредсказуемо? Не беру ли я данные неизвестно откуда? Что если все эти данные у меня не возьмутся или окажутся не тем, что мне надо? Как защитить программу на случай, если этих данных там не окажется?
- Влияет ли эта функция на данные за её пределами?
И если логика программы позволяет, постарайтесь сделать так, чтобы функция ни от чего не зависела и ни на что за своими пределами не влияла. Тогда код будет более читаемым, а коллеги-программисты сразу увидят, что перед ними вдумчивый разработчик.
Источник
Искусство написания простых и коротких функций
Софт постоянно усложняется. Стабильность и простота расширения приложения напрямую зависят от качества кода.
К сожалению, почти каждый разработчик, и я в том числе, в своей работе сталкивается с кодом плохого качества. И это — болото. У такого кода есть токсичные признаки:
- Функции слишком длинные, и на них слишком много задач
- Часто у функций есть побочные эффекты, которые сложно определить, а иногда даже сложно отлаживать
- Непонятные имена у функций и переменных
- Хрупкий код: небольшая модификация неожиданно ломает другие компоненты приложения
- Плохое покрытие кода тестами или вообще его отсутствие
Всем знакомы высказывания «я не понимаю, как работает этот код», «бредовый код», «этот код сложно изменить» и другие.
Однажды мой коллега уволился, потому что пытался справиться с REST API на Ruby, который было трудно поддерживать. Он получил этот проект от предыдущей команды разработчиков.
Исправление текущих ошибок создавало новые, добавление новых функций рождало новую серию ошибок, и так далее (хрупкий код). Клиент не хотел перестраивать приложение, делать ему удобную структуру, и разработчик принял правильное решение — уволиться.
Такие ситуации случаются часто, и это печально. Но что делать?
Во-первых, помнить: создать работающее приложение и позаботиться о качестве кода — разные задачи.
С одной стороны, вы реализуете требования приложения. Но с другой, вы должны тратить время и проверять, не висит ли слишком много задач на какой-нибудь функции, давать содержательные названия переменным и функциям, избегать функций с побочными эффектами и так далее.
Функции (в том числе методы объекта) — это маленькие шестерёнки, которые заставляют приложение работать. В начале вы должны сосредоточиться на их структуре и составе. Статья охватывает лучшие подходы, как писать простые, понятные и легко тестируемые функции.
1. Функции должны быть маленькими. Совсем маленькими.
Избегайте раздутых функций, у которых очень много задач, лучше делать несколько мелких функций. Раздутые функции со скрытым смыслом трудно понять, модифицировать и, особенно, тестировать.
Представьте ситуацию, когда функция должна возвращать сумму элементов массива, map’а или простого объекта JavaScript. Сумма рассчитывается складыванием значений свойств:
- 1 балл за null или undefined
- 2 балла за примитивный тип
- 4 балла за объект или функцию
Например, сумма массива [null, ‘Hello World’, <>] вычисляется так: 1 (за null ) + 2 (за строку, примитивный тип) + 4 (за объект) = 7 .
Шаг 0: Первичная большая функция
Давайте начнем с худшего метода. Идея — писать код одной большой функцией getCollectionWeight() :
Проблема хорошо видна. Функция getCollectionWeight() слишком раздутая и выглядит как черный ящик, полный сюрпризов.
Вам, скорее всего, с первого взгляда сложно понять, какая у неё задача. А представьте набор таких функций в приложении.
Когда вы работаете с таким кодом, вы растрачиваете время и усилия. А качественный код не вызовет у вас дискомфорта. Качественный код с короткими и не требующими объяснения функциями приятно читать и легко поддерживать.
Шаг 1: Извлекаем вес по типу и ликвидируем магические числа
Теперь цель — разбить длинную функцию на мелкие, независимые и переиспользуемые. Первый шаг — извлечь код, который определяет сумму значения по его типу. Эта новая функция будет называться getWeight() .
Также обратите внимание на магические цифры этой суммы: 1 , 2 и 4 . Просто чтение этих цифр, без понимания всей истории, не даёт полезной информации. К счастью, ES2015 позволяет объявить const как read-only, так что можно легко создавать константы со значимыми именами и ликвидировать магические числа.
Давайте создадим небольшую функцию getWeightByType() и одновременно усовершенствуем getCollectionWeight() :
Правда, выглядит лучше?
Функция getWeightByType() — независимый компонент, который просто определяет сумму по типу. И она переиспользуемая, потому что может выполняться в пределах любой другой функции.
getCollectionWeight() становится чуть более облегчённой
WEIGHT_NULL_UNDEFINED , WEIGHT_PRIMITIVE и WEIGHT_OBJECT_FUNCTION — не требующие объяснения константы, которые описывают типы сумм. Вам не нужно догадываться, что означают цифры 1 , 2 и 4 .
Шаг 2: Продолжаем разделение и делаем функции расширяемыми
Обновленная версия по-прежнему обладает недостатками.
Представьте себе, что у вас есть план реализовать сравнение значений Set или вообще другой произвольной коллекции. getCollectionWeight() будет быстро увеличиваться в размерах, так как её логика — собирать значения.
Давайте извлечём код, который собирает значения из map getMapValues() и простых JavaScript-объектов getPlainObjectValues() в отдельные функции. Посмотрите на улучшенную версию:
Сейчас читая getCollectionWeight() вам намного проще понять, что делает эта функция. Выглядит, как интересная история.
Каждая функция очевидна и доходчива. Вы не тратите время, пытаясь понять, что делает такой код. Вот насколько чистым он должен быть.
Шаг 3: Никогда не прекращайте улучшения
Даже на этом этапе у вас есть много возможностей для повышения качества!
Вы можете создать отдельную getCollectionValues() , которая содержит операторы if/else и дифференцирует типы коллекций:
Тогда getCollectionWeight() станет действительно простой, потому что единственное, что нужно сделать, это получить значения коллекции getCollectionValues() и применить к нему sum reducer.
Можно также создать отдельную функцию сокращения:
Потому что в идеале getCollectionWeight() не должна определять функции.
В конце концов начальная большая функция превращается в маленькие:
Это искусство создания небольших и простых функций!
После всех оптимизаций качества кода появляется горсть недурных преимуществ:
- Читаемость getCollectionWeight() упростилась благодаря не требующему объяснения коду
- Размер getCollectionWeight() значительно уменьшился
- Функция getCollectionWeight() теперь защищена от быстрого разрастания, если вы захотите реализовать работу с другими типами коллекций
- Извлеченные функции теперь — это разгруппированные и переиспользуемые компоненты. Ваш коллега может попросить вас импортировать эти приятные функции в другой проект, и вы сможете это легко сделать.
- Если случайно функция сгенерирует ошибку, стек вызовов будет более точным, поскольку содержит имена функций. Почти сразу можно обнаружить функцию, которая создает проблемы.
- Разделённые функции намного проще тестировать и достигать высокого уровня покрытия кода тестами. Вместо того, чтобы тестировать одну раздутую функцию всеми возможными сценариями, вы можете структурировать тесты и проверять каждую маленькую функцию отдельно.
- Можно использовать формат модулей CommonJS или ES2015. Создавать отдельные модули из извлеченных функций. Это сделает файлы вашего проекта легкими и структурированными.
Эти преимущества помогут вам выжить в сложной структуре приложений.
Общее правило — функции не должны быть больше 20 строк кода. Чем меньше, тем лучше.
Я думаю, теперь у вас появится справедливый вопрос: «Я не хочу создавать по функции для каждой строки кода. Есть какие-то критерии, когда нужно остановиться?» Это тема следующей главы.
2. Функции должны быть простыми
Давайте немного отвлечёмся и подумаем, что такое приложение?
Каждое приложение реализует набор требований. Задача разработчика — разделить эти требования на небольшие исполняемые компоненты (области видимости, классы, функции, блоки кода), которые выполняют чётко определенные операции.
Компонент состоит из других более мелких компонентов. Если вы хотите написать код для компонента, его нужно создавать из компонентов только предыдущего уровня абстракции.
Другими словами, нужно разложить функцию на более мелкие шаги, но все они должны находится на одном, предыдущем, уровне абстракции. Важно это потому, что функция становится простой и подразумевает «выполнение одной задачи, и выполнение это — качественное».
В чём необходимость? Простые функции — очевидны. Очевидность означает лёгкое чтение и модификацию.
Попробуем последовать примеру. Предположим, вы хотите реализовать функцию, которая сохраняет только простые числа (2, 3, 5, 7, 11, и т.д.) массива и удаляет остальные (1, 4, 6, 8, и т.д.). Функция вызывается так:
Какие шаги предыдущего уровня абстракции нужны для реализации функции getOnlyPrime() ? Давайте сформулируем так:
Для реализации getOnlyPrime() отфильтруйте массив чисел с помощью функции IsPrime() .
Просто примените функцию-фильтр IsPrime() к массиву.
Есть необходимость на этом уровне реализовать детали IsPrime() ? Нет, потому что тогда у функции getOnlyPrime() появятся шаги из другого уровня абстракций. Функция примет на себя слишком много задач.
Не забывая эту простую идею, давайте реализуем тело функции getOnlyPrime() :
Как видите, getOnlyPrime() — элементарная функция. Она содержит шаги из одного уровня абстракции: метод .filter() массива и IsPrime() .
Теперь пришло время перейти на предыдущий уровень абстракции.
Метод массива .filter() входит в JavaScript и используется как есть. Конечно, стандарт описывает именно то, что выполняет метод.
Теперь можно конкретизировать то, как будет реализована IsPrime() :
Чтобы реализовать функцию IsPrime() , которая проверяет, является ли число n простым, нужно проверить, делится ли n на любое число от 2 до Math.sqrt(n) без остатка.
Давайте напишем код для функции IsPrime() , пользуясь этим алгоритмом (он еще не эффективный, я использовал его для простоты):
getOnlyPrime() — маленькая и элементарная. В ней только строго необходимые шаги предыдущего уровня абстракции.
Чтение сложных функций может быть значительно упрощено, если следовать правилу делать их очевидными. Если код каждого уровня абстракции написан педантично, это предотвратит порождение крупных кусков неудобного кода.
3. Используйте компактные названия функций
Имена функций должны быть компактными: не больше и не меньше. В идеале название должно чётко указывать, что делает функция, без необходимости рыться в деталях реализации.
Для имен функций используйте формат camel case, который начинается с маленькой буквы: addItem() , saveToStore() или getFirstName() .
Поскольку функция — это действие, её имя должно содержать, как минимум, один глагол. Например deletePage() , verifyCredentials() . Чтобы получить или установить свойство, используйте префиксы set и get: getLastName() или setLastName() .
В production избегайте запутывающие имена, вроде Foo() , bar() , а() , fun() и подобные. Такие имена не имеют смысла.
Если функции маленькие и простые, а имена компактные: код читается как хорошая книга.
4. Вывод
Конечно, приведенные примеры незамысловаты. Приложения, существующие в реальности, более сложные. Можно жаловаться, что писать простые функции предыдущего уровня абстракции — нудное занятие. Но оно не настолько трудоёмкое если делать это с самого начала проекта.
Если в приложении уже есть слишком раздутые функции, перестроить код, скорее всего, будет сложно. И во многих случаях невозможно в разумных временных промежутках. Начните, хотя бы с малого: извлеките то, что сможете.
Конечно, правильное решение — грамотно реализовать приложение с самого начала. И вложить время не только в реализацию, но и в правильную структуру функций: сделать их маленькими и простыми.
Семь раз отмерь, один раз отрежь.
В ES2015 реализована хорошая модульная система, которая четко показывает, что небольшие функции — это хорошая практика.
Просто помните, что чистый и организованный код всегда требует вложений времени. Вам может быть сложно. Вам, возможно, потребуется долго практиковаться. Вы можете возвращаться и менять функции по нескольку раз.
Нет ничего хуже грязного кода.
Какие методы используете вы, чтобы сделать код организованным?
Источник