Продолжим обсуждать какими же должны быть функции. Второе важное свойство функции - это понятность этих функций в месте вызова. Давайте для примера возьмем задачу hotel manager, про систему бронирования отелей, в которой, собственно, был класс hotel manager, и там был метод book, который позволял забронировать отель в конкретный момент времени, в отеле с конкретным названием, для конкретного клиента, с конкретным количеством комнат. И вот был такой метод, мы его прекрасно использовали, а теперь представьте, что мы хотим вызвать этот метод с какими-то конкретными значениями с целочисленных полей, и у нас получается такая вот строка, вида manager.Book(0, потом название отеля и еще 1 и 2). Понятно ли чем здесь отличается 1 и 2 и 0, какие-то 3 числа. Я могу совершенно спокойно перепутать. Вот я допустим здесь хотел забронировать две комнаты для клиента номер один, я увидел здесь, что у меня сначала client_id, потом room_count, а потом, пока листал этот код, почему-то решил, что сначала надо указать количество комнат и вот у меня получилось здесь 2, а здесь 1. 2 и 1. Я могу совершенно спокойно перепутать 2 целочисленных аргумента, и код все равно продолжит компилироваться. Мне здесь могла бы помочь среда разработки, если наведу на метод book, я увижу, что у меня сначала client_id, потом room_count. Но ничто не мешает ошибиться при написании этой функции не посмотрев подсказку, или просто не понять при чтении этого кода, что же здесь происходит. Итак, будьте осторожны с функциями, которые принимают несколько целочисленных аргументов, ну и проблема на самом деле шире, мы рассмотрим позже в этом блоке. Следующая проблема, это удобство тестирования функций. Вернемся к нашему замечательному примеру с функцией процесс в экспрессах. Это функция не принимала ничего, не возвращала ничего. Как написать на нее unit-тест - непонятно, потому что входные данные для нее подаются в потоке cin и через глобальную переменную, а ее выход осуществляется в стандартный поток вывода. Непонятно, что вообще мне с ней делать, как мне для нее переопределить входные данные. Если бы она хотя бы принимала поток по ссылке, я мог бы его переопределить, то еще куда ни шло, но сейчас глобальная переменная потоки cin и out не позволяют мне написать нормальный unit-тест на эту функцию. Следующее свойство - это поддерживаемость функции. Давайте я покажу пример не поддерживаемой функции, даже не поддерживаемой архитектуры класса, на примере системы бронирования отелей. Давайте представим, что внутри всех этих классов я решил отказаться от структуры booking. Была такая структура бронирования, я напомню, в которой мы хранили для конкретного отеля все параметры одного бронирования. Там было время, id клиента и количество комнат. Давайте я почему-то решу, что мне структура не нужна, и от нее везде избавлюсь, и у меня получается довольно много полей в классе конкретного отеля. У меня теперь 3 отдельные очереди вместо одной, у меня есть очередь времен, очередь client_id и очередь количеств комнат, и мне везде нужно писать три раза push, потом, когда я удаляю надо написать три раза pop и наверное такой может показаться нормальным, но как только я буду добавлять какое-то новое свойство бронирования, мне нужно добавить еще одну очередь вот здесь, мне нужно добавить push вот здесь, и самое важное то место, где очень легко ошибиться, это забыть добавить pop, например, то есть, структура нам здесь помогала сделать код более поддерживаемым, более устойчивым к ошибкам, которые могут возникнуть при расширении функциональности. Собственно, этот например, очень похож на тот пример, который я приводил в первом курсе, когда рассказывал про структуры. Итак, это был пример не поддерживаемой архитектуры, и наконец про сфокусированность. Опять же хороший пример, не сфокусированная функция, это функция процесс, потому что она делает сразу несколько вещей. Во-первых, она считывает данные, во-вторых, она выводит данные, ну и наконец она взаимодействует с переменной routes. Получается, что я, смотря на какой-то произвольный кусок функций, ну давайте представим, что она очень большая, я не могу заранее угадать, а не считает ли она здесь еще какие-то данные. Она может быть даже уже что-то считала и вывела, и потом еще раз что-то считала и вывела, то есть несколько задач одной функции в ней как-то могут перемещаться, и вот это очень неприятно и очень мешает понимать функции, особенно если они довольно большие. Так что эта функция решает несколько задач, и это неприятно. Такие же на самом деле функции можно считать хорошими. Давайте я покажу несколько примеров хороших функций, чтобы вы понимали на что вам ориентироваться. Пример вообще идеальной функции, это функция remainder, который вычисляет остаток отделения для двух вещественных чисел. Это функция принимает два вещественных числа и возвращает одно вещественное число. Что-то вообще непонятно, делимое, делитель и остаток в результате. Прекрасная функция. Чуть посложнее, метод вектора, метод size, тоже, абсолютно понятно, метод константный, то есть вектор не меняет и возвращает одно число. Совершенно очевидно, что он возвращает размер этого вектора. Тоже все прекрасно. Еще одна отличная функция - функция to_string, которая принимает число, ну или что-то еще, что может преобразовать в строку, и возвращает строчку. Приняла один объект, вернула один объект. Понятная, предсказуемая функция, тестировать одно удовольствие. Или чуть более сложная функция, тем не менее все еще понятная, алгоритм count, который подсчитывает сколько в данном диапазоне элементов встречается конкретных объектов. Он принимает два итератора, которые олицетворяют собой набор объектов по которым надо пройтись, в которых надо поискать, и третьим аргументом по константной ссылке сам объект, который надо найти. Итого три аргумента на входе и одно число на выходе. Собственно, количество вхождения этого объекта в диапазон. Тоже очень понятная, прозрачная функция без побочных эффектов, без использования глобальных переменных и тому подобного. Ориентируйтесь на эти функции. Итак, мы обсудили важные свойства функций и уже почти готовы перейти к обсуждению деталей.