Что такое функции почему они полезны

Что такое функции почему они полезны thumbnail

  Обновл. 19 Сен 2020  | 

Теперь, когда мы уже знаем, что такое функции и зачем они нужны, давайте более подробно рассмотрим, почему они так полезны.

Зачем использовать функции?

Начинающие программисты часто спрашивают: «А можно ли обходиться без функций и весь код помещать непосредственно в функцию main()?». Если вашего кода всего 10-20 строк, то можно. Если же серьезно, то функции предназначены для упрощения кода, а не для его усложнения. Они имеют ряд преимуществ, которые делают их чрезвычайно полезными в нетривиальных программах.

   Структура. Как только программы увеличиваются в размере/сложности, сохранять весь код внутри main() становится трудно. Функция — это как мини-программа, которую мы можем записать отдельно от головной программы, не заморачиваясь при этом об остальных частях кода. Это позволяет разбивать сложные задачи на более мелкие и простые, что кардинально снижает общую сложность программы.

   Повторное использование. После объявления функции, её можно вызывать много раз. Это позволяет избежать дублирования кода и сводит к минимуму вероятность возникновения ошибок при копировании/вставке кода. Функции также могут использоваться и в других программах, уменьшая объем кода, который нужно писать с нуля каждый раз.

   Тестирование. Поскольку функции убирают лишний код, то и тестировать его становится проще. А так как функция — это самостоятельная единица, то нам достаточно протестировать её один раз, чтобы убедиться в её работоспособности, а затем мы можем её повторно использовать много раз без необходимости проводить тестирование (до тех пор, пока не внесем изменения в эту функцию).

   Модернизация. Когда нужно внести изменения в программу или расширить её функционал, то функции являются отличным вариантом. С их помощью можно внести изменения в одном месте, чтобы они работали везде.

   Абстракция. Для того, чтобы использовать функцию, нам нужно знать её имя, данные ввода, данные вывода и где эта функция находится. Нам не нужно знать, как она работает. Это очень полезно для написания кода, понятного другим (например, Стандартная библиотека С++ и всё, что в ней находится, созданы по этому принципу).

Каждый раз, при вызове std::cin или std::cout для ввода или вывода данных, мы используем функцию из Стандартной библиотеки C++, которая соответствует всем вышеперечисленным концепциям.

Эффективное использование функций

Одной из наиболее распространенных проблем, с которой сталкиваются новички, является понимание того, где, когда и как эффективно использовать функции. Вот несколько основных рекомендаций при написании функций:

   Рекомендация №1: Код, который появляется более одного раза в программе, лучше переписать в виде функции. Например, если мы получаем данные от пользователя несколько раз одним и тем же способом, то это отличный вариант для написания отдельной функции.

   Рекомендация №2: Код, который используется для сортировки чего-либо, лучше записать в виде отдельной функции. Например, если у нас есть список вещей, которые нужно отсортировать — пишем функцию сортировки, куда передаем несортированный список и откуда получаем отсортированный.

   Рекомендация №3: Функция должна выполнять одно (и только одно) задание.

   Рекомендация №4: Когда функция становится слишком большой, сложной или непонятной — её следует разбить на несколько подфункций. Это называется рефакторингом кода.

При изучении языка C++ вам предстоит написать много программ, которые будут включать следующие три подзадания:

   Получение данных от пользователя.

   Обработка данных.

   Вывод результата.

Для простых программ (менее, чем 30 строк кода) частично или все эти три подзадания можно записать в функции main(). Для более сложных программ (или просто для практики) каждое из этих трех подзаданий является хорошим вариантом, чтобы написать отдельные функции.

Новички часто комбинируют обработку ввода и вывод результата в одной функции. Тем не менее, это нарушает правило «одного задания». Функция, которая обрабатывает значение, должна возвращать его в caller, а дальше уже пускай caller сам решает, что ему с ним делать.

Оценить статью:

Загрузка…

Источник

Хороший программист старается делать свои функции чистыми. Если знать, что это такое, можно сойти за своего, а заодно написать читаемый код.

Что такое функция

Функция — это мини-программа внутри вашей основной программы, которая делает какую-то одну понятную вещь. Вы однажды описываете, что это за вещь, а потом ссылаетесь на это описание в тех частях программы, где это нужно.

Например, вы пишете игру. Каждый раз, когда игрок попадает в цель, убивает врага, делает комбо, заканчивает уровень или падает в лаву, вам нужно добавить или убавить ему очков. Это делается двумя действиями: к старым очкам добавляются новые, на экран выводится новая сумма очков. Допустим, эти действия занимают 8 строк кода.

Допустим, в игре есть 100 ситуаций, когда нужно добавить или убавить очки — для каждого типа врага, преграды, уровня и т. д. Чтобы в каждой из ста ситуаций не писать одни и те же восемь строк кода, вы упаковываете эти восемь строк в функцию. И теперь в ста местах вы пишете одну строку: например, changeScore(10) или changeScore(-10). В первом случае прибавится 10 очков, во втором — столько же отнимется, но функция используется одна.

Если теперь изменить, что происходит в функции changeScore(), то изменения отразятся как бы во всех ста местах, где эта функция вызывается.

Зачем нужны функции

Функции нужны, чтобы заметно упрощать и сокращать код, адаптировать его для разных платформ, делать более отказоустойчивым, легко отлаживать. И вообще порядок в функциях — порядок в голове.

Возьмём тот же пример с подсчётом очков. Что если при добавлении очков нужно не только выводить их на экран, но и записывать в файл? Просто добавляете в определении функции дополнительные команды, связанные с файлами, и они теперь будут исполняться каждый раз, когда функцию снова вызовут в основной программе. Не нужно ползать по всему коду, искать места с добавлением очков и дописывать там про файлы. Меньше ручного труда, меньше опечаток, меньше незакрытых скобок.

А что если нужно не только писать очки в файл, но и следить за рекордом? Пишем новую функцию getHighScore(), которая достаёт откуда-то рекорд по игре, и две другие — setHighScore() и celebrateHighScore() — одна будет перезаписывать рекорд, если мы его побили, а вторая — как-то поздравлять пользователя с рекордом.

Теперь при каждом срабатывании changeScore() будет вызывать все остальные функции. И сколько бы раз мы ни вызвали в коде changeScore(), она потянет за собой всё хозяйство автоматически.

Читайте также:  Чем полезен лук репчатый для мужчины

Сила ещё в том, что при разборе этой функции нам неважно, как реализованы getHighScore(), setHighScore() и celebrateHighScore(). Они задаются где-то в другом месте кода и в данный момент нас не волнуют. Они могут брать данные с жёсткого диска, писать их в базу данных, издавать звуки, взламывать Пентагон — для нас сейчас это неважно.

Без функций трудно повесить действия на какие-либо кнопки в интерфейсе. Например, у вас на сайте есть форма, и при клике на кнопку «Отправить» вы хотите проверять, что данные в форме правильно введены. Вы спокойно описываете где-то в коде функцию validateForm() и вешаете её на нажатие кнопки. Кнопку нажали — функция вызвалась. Не нужно вписывать в кнопку полный текст программы.

А без функции пришлось бы писать огромную программу-валидатор прямо внутри кнопки. Это исполнимо, но код выглядел бы страшно громоздким. Что если у вас на странице три формы, и каждую нужно валидировать?

Хорошо написанные функции резко повышают читаемость кода. Мы можем читать чужую программу, увидеть там функцию getExamScore(username) и знать, что последняя каким-то образом выясняет результаты экзамена по такому-то юзернейму. Как она там устроена внутри, куда обращается и что использует — вам неважно. Для нас это как бы одна простая понятная команда.

На разработку малозначительных функций можно посадить джуниоров, пока главный мастер-программист будет создавать основную логику программы. Если вы главный мастер, вы просто говорите в коде doNormalno(), а как это будет реализовано — уже не ваша забота. Пусть потеют джуниоры.

Можно написать кучу вспомогательных функций, держать их в отдельном файле и подключать к проекту как библиотеку. Например, вы написали один раз все функции для обработки логики и физики игры и потом подключаете эти функции во все свои игры. В одной — роботы, в другой — пираты, но в обеих один и тот же подсчёт очков, физика, движения, главное меню и управление.

Можно подключить чужие функции — например, найти в интернете библиотеку, которая умеет работать с гугл-документами, подключить её, вызвать таблицу функцией loadGoogleTable() и не думать, как там внутри реализован этот вызов.

В общем, функции — это бесконечная радость. На этом наш экскурс в функции закончен, переходим к чистоте.

Что такое чистые функции

Есть понятие чистых функций. Это значит, что если функции два раза дать на обработку одно и то же значение, она всегда выдаст один и тот же результат и в программе не изменит ничего, что не относится непосредственно к этой функции. То есть у чистой функции предсказуемый результат и нет побочных эффектов.

Вот примеры.

Один и тот же результат

Допустим, мы придумали функцию, которая считает площадь круга по его радиусу: 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’) — вернёт новый массив с приоритетными задачами, не изменив другие массивы. В памяти был один массив, а теперь появится ещё и второй.

Такие изменения называют мутациями: я вызвал функцию в одном месте, а мутировало что-то в другом.

Программисты настороженно относятся к мутациям, потому что за ними сложно следить. Что если из-за какой-то ошибки функции выполнятся в неправильном порядке и уничтожат важные для программы данные? Или функция выполнится непредсказуемо много раз? Или она застрянет в цикле и из-за мутаций разорвёт память? Или мутация произойдёт не с тем куском программы, который мы изначально хотели?

Читайте также:  Чем полезен крыжовник полезные свойства и витамины

Вот типичная ошибка, связанная с мутацией. Мы пишем игру, нужно поменять сумму игровых очков. За это отвечает функция changeScore(), которая записывает результат в глобальную переменную playerScore — то есть мутирует эту переменную. Мы случайно, по невнимательности, вызвали эту функцию в двух местах вместо одного, и баллы увеличиваются вдвое. Это баг.

Другая типичная ошибка. Программист написал функцию, которая удаляет из таблицы последнюю строку, потому что был почему-то уверен: строка будет пустой и никому не нужной. Случайно эта функция вызывается в бесконечном цикле и стирает все строки, от последней к первой. Данные уничтожаются. А вот если бы функция не удаляла строку из таблицы, а делала новую таблицу без последней строки, данные бы не пострадали.

Без мутирующих функций, конечно, мы не обойдёмся — нужно и выводить на экран, и писать в файл, и работать с глобальными переменными. Сложно представить программу, в которой вообще не будет мутирующих функций. Но программисты предпочитают выделять такие функции отдельно, тестировать их особо тщательно, и внимательно следить за тем, как они работают. Грубо говоря, если функция вносит изменения в большой важный файл, она должна как минимум проверить корректность входящих данных и сохранить резервную копию этого файла.

Как этим пользоваться

Когда будете писать свою следующую функцию, задайтесь вопросами:

  • Нет ли тут каких-то зависимостей, которые могут повести себя непредсказуемо? Не беру ли я данные неизвестно откуда? Что если все эти данные у меня не возьмутся или окажутся не тем, что мне надо? Как защитить программу на случай, если этих данных там не окажется?
  • Влияет ли эта функция на данные за её пределами?

И если логика программы позволяет, постарайтесь сделать так, чтобы функция ни от чего не зависела и ни на что за своими пределами не влияла. Тогда код будет более читаемым, а коллеги-программисты сразу увидят, что перед ними вдумчивый разработчик.

Подписывайтесь на наш канал, и да пребудут с вами логика и структура!

Источник

Хоро­ший про­грам­мист ста­ра­ет­ся делать свои функ­ции чисты­ми. Если знать, что это такое, мож­но сой­ти за сво­е­го, а заод­но напи­сать чита­е­мый код.

Если вы не совсем пони­ма­е­те, что такое функ­ция и зачем она нуж­на — доб­ро пожа­ло­вать в наш кат:

Что такое функция

Функ­ция — это мини-программа внут­ри вашей основ­ной про­грам­мы, кото­рая дела­ет какую-то одну понят­ную вещь. Вы одна­жды опи­сы­ва­е­те, что это за вещь, а потом ссы­ла­е­тесь на это описание.

Напри­мер, вы пише­те игру. Каж­дый раз, когда игрок попа­да­ет в цель, уби­ва­ет вра­га, дела­ет ком­бо, закан­чи­ва­ет уро­вень или пада­ет в лаву, вам нуж­но доба­вить или уба­вить ему очков. Это дела­ет­ся дву­мя дей­стви­я­ми: к ста­рым очкам добав­ля­ют­ся новые, на экран выво­дит­ся новая сум­ма очков. Допу­стим, эти дей­ствия зани­ма­ют 8 строк кода.

Допу­стим, в игре есть 100 ситу­а­ций, когда нуж­но доба­вить или уба­вить очки — для каж­до­го типа вра­га, пре­гра­ды, уров­ня и т. д. Что­бы в каж­дой из ста ситу­а­ций не писать одни и те же восемь строк кода, вы упа­ко­вы­ва­е­те эти восемь строк в функ­цию. И теперь в ста местах вы пише­те одну стро­ку: напри­мер, changeScore(10) — чис­ло очков повы­сит­ся на 10.

Если теперь изме­нить, что про­ис­хо­дит в функ­ции changeScore(), то изме­не­ния отра­зят­ся как бы во всех ста местах, где эта функ­ция вызывается.

Зачем нужны функции

Функ­ции нуж­ны, что­бы замет­но упро­щать и сокра­щать код, адап­ти­ро­вать его для раз­ных плат­форм, делать более отка­зо­устой­чи­вым, лег­ко отла­жи­вать. И вооб­ще поря­док в функ­ци­ях — поря­док в голове.

Возь­мём тот же при­мер с под­счё­том очков. Что если при добав­ле­нии очков нуж­но не толь­ко выво­дить их на экран, но и запи­сы­вать в файл? Про­сто добав­ля­е­те в опре­де­ле­нии функ­ции допол­ни­тель­ные коман­ды, свя­зан­ные с фай­ла­ми, и они теперь будут испол­нять­ся каж­дый раз, когда функ­цию сно­ва вызо­вут в основ­ной про­грам­ме. Не нуж­но пол­зать по все­му коду, искать места с добав­ле­ни­ем очков и допи­сы­вать там про фай­лы. Мень­ше руч­но­го тру­да, мень­ше опе­ча­ток, мень­ше неза­кры­тых скобок.

А что если нуж­но не толь­ко писать очки в файл, но и сле­дить за рекор­дом? Пишем новую функ­цию getHighScore(), кото­рая доста­ёт откуда-то рекорд по игре, и две дру­гие — setHighScore() и celebrateHighScore() — одна будет пере­за­пи­сы­вать рекорд, если мы его поби­ли, а вто­рая — как-то поздрав­лять поль­зо­ва­те­ля с рекордом.

// Объявляем новую функцию. Она будет называться changeScore и принимать один аргумент, который мы для этого фрагмента назовём howMuch. Дальше мы просто будем подавать в функцию число.
function changeScore(howMuch) {
// Прибавим к старым очкам новые
playerScore = playerScore + howMuch;
// Выведем новые очки на экран
$(‘#scoretext’).text(playerScore)
// Узнаем, какой у нас рекорд. Для этого объявим новую переменную highScore, вызовем функцию getHighScore(), запишем её результат в эту переменную
var highScore = getHighScore();
// А теперь сравним, больше ли наши очки, чем рекорд по игре
if (playerScore > highScore) {
//Рекорд побит, значит, надо его записать
setHighScore(playerScore, playerName);
// Делаем тут что-то, что обычно делают, когда ты побил рекорд игры. Фейерверки? Музыка? Мигание рекордных очков на экране? Мы не знаем пока, что именно будет делать эта функция, и нам это сейчас неважно
celebrateHighScore();
}
}

Теперь при каж­дом сра­ба­ты­ва­нии changeScore() будет вызы­вать все осталь­ные функ­ции. И сколь­ко бы раз мы ни вызва­ли в коде changeScore(), она потя­нет за собой всё хозяй­ство автоматически.

Сила ещё в том, что при раз­бо­ре этой функ­ции нам неваж­но, как реа­ли­зо­ва­ны getHighScore(), setHighScore() и celebrateHighScore(). Они зада­ют­ся где-то в дру­гом месте кода и в дан­ный момент нас не вол­ну­ют. Они могут брать дан­ные с жёст­ко­го дис­ка, писать их в базу дан­ных, изда­вать зву­ки и взла­мы­вать Пен­та­гон — это будет рас­пи­са­но внут­ри самих функ­ций в дру­гих местах текста.

Без функ­ций труд­но пове­сить дей­ствия на какие-либо кноп­ки в интер­фей­се. Напри­мер, у вас на сай­те есть фор­ма, и при кли­ке на кноп­ку «Отпра­вить» вы хоти­те про­ве­рять, что дан­ные в фор­ме пра­виль­но вве­де­ны. Вы спо­кой­но опи­сы­ва­е­те где-то в коде функ­цию validateForm() и веша­е­те её на нажа­тие кноп­ки. Кноп­ку нажа­ли — функ­ция вызва­лась. Не нуж­но впи­сы­вать в кноп­ку пол­ный текст программы.

А без функ­ции при­шлось бы писать огром­ную программу-валидатор пря­мо внут­ри кноп­ки. Это испол­ни­мо, но код выгля­дел бы страш­но гро­мозд­ким. Что если у вас на стра­ни­це три фор­мы, и каж­дую нуж­но валидировать?

Хоро­шо напи­сан­ные функ­ции рез­ко повы­ша­ют чита­е­мость кода. Мы можем читать чужую про­грам­му, уви­деть там функ­цию getExamScore(username) и знать, что послед­няя каким-то обра­зом выяс­ня­ет резуль­та­ты экза­ме­на по такому-то юзер­ней­му. Как она там устро­е­на внут­ри, куда обра­ща­ет­ся и что исполь­зу­ет — вам неваж­но. Для нас это как бы одна про­стая понят­ная команда.

Мож­но напи­сать кучу вспо­мо­га­тель­ных функ­ций, дер­жать их в отдель­ном фай­ле и под­клю­чать к про­ек­ту как биб­лио­те­ку. Напри­мер, вы напи­са­ли один раз все функ­ции для обра­бот­ки физи­ки игры и потом под­клю­ча­е­те эти функ­ции во все свои игры. В одной — робо­ты, в дру­гой — пира­ты, но в обе­их одна и та же физика.

Читайте также:  Салаты из свежих помидор и огурцов очень полезны ошибка

Функ­ции — это бес­ко­неч­ная радость. На этом наш экс­курс в функ­ции закон­чен, пере­хо­дим к чистоте.

Что такое чистые функции

Есть поня­тие чистых функ­ций. Это зна­чит, что если функ­ции два раза дать на обра­бот­ку одно и то же зна­че­ние, она все­гда выдаст один и тот же резуль­тат и в про­грам­ме не изме­нит ниче­го, что не отно­сит­ся непо­сред­ствен­но к этой функ­ции. То есть у чистой функ­ции пред­ска­зу­е­мый резуль­тат и нет побоч­ных эффектов.

Вот при­ме­ры.

Один и тот же результат

Допу­стим, мы при­ду­ма­ли функ­цию, кото­рая счи­та­ет пло­щадь кру­га по его ради­у­су: getCircleArea(). Для наших целей мы берём чис­ло пи, рав­ное 3,1415, и впи­сы­ва­ем в функцию:

function getCircleArea(radius) {
// Задаём наше местное определение числа пи
var localPi = 3.1415;
// Считаем площадь: пи на радиус в квадрате. Это то же самое, что пи умножить на радиус и ещё раз умножить на радиус
var circleArea = localPi * radius * radius;
// Говорим функции вернуть то, что мы сейчас рассчитали
return circleArea;
}

Теперь этой функ­ции надо скор­мить чис­ло, и она выдаст пло­щадь круга:

  1. getCircleArea(2) все­гда выдаст резуль­тат 12,6060
  2. 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 — то есть мути­ру­ет эту пере­мен­ную. Мы слу­чай­но, по невни­ма­тель­но­сти, вызва­ли эту функ­цию в двух местах вме­сто одно­го, и бал­лы уве­ли­чи­ва­ют­ся вдвое. Это баг.

Дру­гая типич­ная ошиб­ка. Про­грам­мист напи­сал функ­цию, кото­рая уда­ля­ет из таб­ли­цы послед­нюю стро­ку, пото­му что был почему-то уве­рен: стро­ка будет пустой и нико­му не нуж­ной. Слу­чай­но эта функ­ция вызы­ва­ет­ся в бес­ко­неч­ном цик­ле и сти­ра­ет все стро­ки, от послед­ней к пер­вой. Дан­ные уни­что­жа­ют­ся. А вот если бы функ­ция не уда­ля­ла стро­ку из таб­ли­цы, а дела­ла новую таб­ли­цу без послед­ней стро­ки, дан­ные бы не пострадали.

Без мути­ру­ю­щих функ­ций, конеч­но, мы не обой­дём­ся — нуж­но и выво­дить на экран, и писать в файл, и рабо­тать с гло­баль­ны­ми пере­мен­ны­ми. Слож­но пред­ста­вить про­грам­му, в кото­рой вооб­ще не будет мути­ру­ю­щих функ­ций. Но про­грам­ми­сты пред­по­чи­та­ют выде­лять такие функ­ции отдель­но, тести­ро­вать их осо­бо тща­тель­но, и вни­ма­тель­но сле­дить за тем, как они рабо­та­ют. Гру­бо гово­ря, если функ­ция вно­сит изме­не­ния в боль­шой важ­ный файл, она долж­на как мини­мум про­ве­рить кор­рект­ность вхо­дя­щих дан­ных и сохра­нить резерв­ную копию это­го файла.

Как этим пользоваться

Когда буде­те писать свою сле­ду­ю­щую функ­цию, задай­тесь вопросами:

  1. Нет ли тут каких-то зави­си­мо­стей, кото­рые могут пове­сти себя непред­ска­зу­е­мо? Не беру ли я дан­ные неиз­вест­но отку­да? Что если все эти дан­ные у меня не возь­мут­ся или ока­жут­ся не тем, что мне надо? Как защи­тить про­грам­му на слу­чай, если этих дан­ных там не окажется?
  2. Вли­я­ет ли эта функ­ция на дан­ные за её пределами?

И если логи­ка про­грам­мы поз­во­ля­ет, поста­рай­тесь сде­лать так, что­бы функ­ция ни от чего не зави­се­ла и ни на что за сво­и­ми пре­де­ла­ми не вли­я­ла. Тогда код будет более чита­е­мым, а коллеги-программисты сра­зу уви­дят, что перед ними вдум­чи­вый разработчик. 

Один мальчик подписался на рассылку Кода и постепенно стал
программистом

Основы программирования без заумия и снобизма. Просто и понятно о том,
как получить новую профессию

Некорректный формат почты

Спасибо!
Проверьте почту

Источник