One place for hosting & domains

      функций

      Выполнение бессерверных функций при помощи OpenFaas на DigitalOcean Kubernetes


      Автор выбрал фонд Free and Open Source Fund для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Обычно для хостинга программного приложения в сети Интернет требуется наличие возможностей по управлению инфраструктурой, планированию и мониторингу для создания надежной системы. В отличие от такого традиционного подхода, бессерверная архитектура (также известная как function as a service или FaaS) разбивает ваше приложение на функции. Эти функции без сохранения состояния полностью независимы и срабатывают при событии. Контролируемое вами выполнение функций осуществляется через API, а не через явно выделенную инфраструктуру и базовые аппаратные средства. Функции обладают масштабируемой структурой, они портативны, их быстрее настроить и проверить по сравнению с обычными приложениями. Для работы бессерверной архитектуры в основном требуется независимый от платформы способ упаковки и контроля функций.

      OpenFaas – это система с открытым исходным кодом для внедрения бессерверной архитектуры на Kubernetes с использованием контейнеров Docker для хранения и запуска функций. Она позволяет упаковать любую программу в контейнер и обеспечить ее работу в виде функции с помощью командной строки или интегрированного веб-интерфейса пользователя. OpenFaaS отлично поддерживает метрику и автоматически масштабирует функции при увеличении нагрузки.

      В этом руководстве вы развернете OpenFaaS в кластере DigitalOcean Kubernetes на вашем домене и защитите ее при помощи сертификатов TLS от Let’s Encrypt. Вы также познакомитесь с веб-интерфейсом пользователя и развернете существующие и новые функции при помощи официального инструмента командной строки faas-cli. В результате вы получите гибкую систему для развертывания бессерверных функций.

      Предварительные требования

      • Кластер DigitalOcean Kubernetes с вашим подключением, настроенным с помощью kubectl по умолчанию. Кластер должен иметь минимум 8 Гб ОЗУ и 4 ядра процессора для работы OpenFaas (или более при увеличенной нагрузке). Инструкции по настройке kubectl описаны в шаге Подключение кластера, где вы создадите ваш кластер. Процесс создания кластера Kubernetes в DigitalOcean, см. Быстрое начало работы с Kubernetes.
      • Docker, установленный на вашем компьютере. В дополнение к шагам 1 и 2 см. Установка Docker.
      • В этом руководстве вы создадите учетную запись Docker Hub для хранения образов Docker.
      • faas-cli,официальный инструмент командной строки для управления OpenFaaS, установленный на вашем компьютере. Инструкции для различных платформ содержатся в официальной документации.
      • Диспетчер пакетов Helm, установленный на вашем компьютере. Для этого выполните шаг 1 и добавьте репозиторий stable из шага 2 из руководства Установка программного обеспечения в кластерах Kubernetes с помощью диспетчера пакетов Helm 3.
      • Nginx Ingress-контроллер и Cert-Manager, установленные на вашем кластере с помощью Helm, для опубликования OpenFaaS с использованием ресурсов Ingress. Инструкции содержатся в Настройка Nginx Ingress на DigitalOcean Kubernetes с помощью Helm.
      • Зарегистрированное полное доменное имя для хостинга OpenFaaS, указывающее на средство балансировки нагрузки, используемое в Nginx Ingress. В этом руководстве мы будем использовать имя openfaas.your_domain. Вы можете купить доменное имя на Namecheap, получить его бесплатно на Freenom или воспользоваться услугами любого предпочитаемого регистратора доменных имен.

      Примечание. Доменное имя, которое вы будете использовать в этом руководстве, должно отличаться от имени из предварительного руководства “Настройка Nginx Ingress на DigitalOcean Kubernetes с помощью Helm”.

      Шаг 1 — Установка OpenFaaS с помощью Helm

      На этом шаге вы установите OpenFaaS для кластера Kubernetes с помощью Helm и опубликуете ее на вашем домене.

      В предварительном руководстве по контроллеру Nginx Ingress вы создали образцы Services и Ingress. Они не нужны в этом руководстве, удалите их, выполнив следующие команды:

      • kubectl delete -f hello-kubernetes-first.yaml
      • kubectl delete -f hello-kubernetes-second.yaml
      • kubectl delete -f hello-kubernetes-ingress.yaml

      Поскольку вы развертываете функции в виде объектов Kubernetes, сохраняйте их и OpenFaaS в отдельных пространствах имен в вашем кластере. Пространство имен для OpenFaaS будет называться openfaas, а пространство имен для функций openfaas-fn. Создайте пространства в кластере, выполнив следующую команду:

      • kubectl apply -f https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml

      Вывод должен выглядеть так:

      Output

      namespace/openfaas created namespace/openfaas-fn created

      Далее вам необходимо добавить репозиторий OpenFaaS в Helm для хостинга чарта OpenFaaS. Для этого запустите следующую команду:

      • helm repo add openfaas https://openfaas.github.io/faas-netes/

      В результате в Helm появится следующее:

      Output

      "openfaas" has been added to your repositories

      Обновите кэш чарта в Helm:

      Вывод должен выглядеть так:

      Output

      Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "openfaas" chart repository ...Successfully got an update from the "jetstack" chart repository ...Successfully got an update from the "stable" chart repository Update Complete. ⎈ Happy Helming!⎈

      Перед установкой OpenFaaS вам необходимо настроить некоторые параметры чарта. Параметры хранятся в файле values.yaml на вашем компьютере. Создайте и откройте этот файл в текстовом редакторе:

      Добавьте следующие строки:

      values.yaml

      functionNamespace: openfaas-fn
      generateBasicAuth: true
      
      ingress:
        enabled: true
        annotations:
          kubernetes.io/ingress.class: "nginx"
        hosts:
          - host: openfaas.your_domain
            serviceName: gateway
            servicePort: 8080
            path: /
      

      Сначала укажите пространство имен для сохранения функций, присвоив значение openfaas-fn​​​​​​ переменной functionNamespace. Установка значения true для generateBasicAuth приведет к тому, что Helm будет запрашивать обязательную аутентификацию при доступе к веб-интерфейсу пользователя OpenFaaS и создаст имя администратора и пароль для доступа.

      Далее активируйте создание Ingress и продолжите настройку конфигурации для использования контроллера Nginx Ingress и обслуживания шлюза OpenFaaS на вашем домене.

      Замените openfaas.your_domain на предпочтительный для вас домен из предварительного руководства. После внесения изменений сохраните и закройте файл.

      Установите OpenFaaS в пространство имен openfaas с заданными значениями:

      • helm upgrade openfaas --install openfaas/openfaas --namespace openfaas -f values.yaml

      Результат будет выглядеть следующим образом:

      Output

      Release "openfaas" does not exist. Installing it now. NAME: openfaas LAST DEPLOYED: ... NAMESPACE: openfaas STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: To verify that openfaas has started, run: kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas" To retrieve the admin password, run: echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)

      В результате вам сообщат, что установка выполнена успешно. Выполните следующую команду для получения пароля для учетной записи admin.

      • echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode) | tee openfaas-password.txt

      В результате вы получите декодированный пароль, который будет одновременно записан в файл openfaas-password.txt при помощи tee. Запомните пароль для учетной записи admin в OpenFaas.

      Для отображения контейнеров OpenFaaS выполните следующую команду:

      • kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas"

      Когда все перечисленные развертывания получат статус ready, нажмите CTRL + C для выхода.

      Далее перейдите к домену через ваш веб-браузер. При запросе введите имя пользователя admin и соответствующий пароль. Вы увидите веб-интерфейс пользователя OpenFaaS:

      OpenFaaS — пустая панель управления

      Вы успешно установили OpenFaaS и опубликовали ее панель управления в вашем домене. Защитите ее при помощи сертификатов TLS от Let’s Encrypt.

      Шаг 2 — Активация TLS для вашего домена

      На этом шаге вы защитите ваш опубликованный домен при помощи сертификатов Let’s Encrypt от Cert-Manager.

      Измените конфигурацию Ingress в values.yaml. Откройте его для редактирования:

      Добавьте выделенные строки:

      values.yaml

      generateBasicAuth: true
      
      ingress:
        enabled: true
        annotations:
          kubernetes.io/ingress.class: "nginx"
          cert-manager.io/cluster-issuer: letsencrypt-prod
        tls:
          - hosts:
              - openfaas.your_domain
            secretName: openfaas-crt
        hosts:
          - host: openfaas.your_domain
            serviceName: gateway
            servicePort: 8080
            path: /
      

      В блоке tls определяется Secret, где сертификаты для ваших сайтов (перечислены под hosts) будут сохранять сертификаты, выпущенные letsencrypt-prod ClusterIssuer. В общем, каждый Ingress в вашем кластере должен иметь свой Secret.

      Замените openfaas.your_domain на желаемый домен, а затем сохраните и закройте файл.

      Примените изменения, внесенные в ваш кластер, с помощью следующей команды:

      • helm upgrade openfaas --install openfaas/openfaas --namespace openfaas -f values.yaml

      Вывод должен выглядеть так:

      Output

      Release "openfaas" has been upgraded. Happy Helming! NAME: openfaas LAST DEPLOYED: ... NAMESPACE: openfaas STATUS: deployed REVISION: 2 TEST SUITE: None NOTES: To verify that openfaas has started, run: kubectl -n openfaas get deployments -l "release=openfaas, app=openfaas" To retrieve the admin password, run: echo $(kubectl -n openfaas get secret basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode)

      Дождитесь, пока серверы Let’s Encrypt выпустят сертификат для вашего домена. Обычно это занимает несколько минут. Вы можете отслеживать прогресс выполнения задачи при помощи следующей команды:

      • kubectl describe certificate openfaas-crt -n openfaas

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

      Output

      Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal GeneratedKey 24m cert-manager Generated a new private key Normal Requested 16m cert-manager Created new CertificateRequest resource "openfaas-crt-1017759607" Normal Issued 16m cert-manager Certificate issued successfully

      Когда в последней строке полученного результата отобразится Certificate issued successfully​​​, выполните выход, нажав CTRL + C. Обновите домен в браузере для тестирования. Слева от адресной строки появится замок, это будет обозначать, что ваше соединение защищено.

      Вы защитили ваш домен OpenFaaS при помощи бесплатных сертификатов TLS от Let’s Encrypt. Далее вы сможете использовать веб-интерфейс пользователя для управления функциями.

      Шаг 3 — Развертывание функций через веб-интерфейс пользователя

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

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

      Для развертывания новой функции нажмите кнопку Deploy New Function под логотипом OpenFaaS вверху слева. На экране появится диалоговое окно, где вас попросят выбрать функцию:

      OpenFaaS — диалоговое окно для развертывания новой функции

      На вкладке FROM STORE находятся готовые функции из официального магазина функций OpenFaaS. Вы можете использовать их. Каждая функция имеет короткое описание, также можно увидеть исходный код, нажав на значок ссылки справа от нужной функции. Для развертывания функции из списка в магазине, выберите ее, а затем нажмите кнопку DEPLOY.

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

      OpenFaaS - Развертывание пользовательской функции

      Здесь необходимо указать образ Docker вашей функции, специально настроенный для OpenFaaS и доступный в реестре Docker (например в Docker Hub). На этом шаге вы развернете готовую функцию из магазина OpenFaaS, далее вы научитесь создавать и разворачивать пользовательские функции в Docker Hub.

      Вы развернете функцию NodeInfo, которая возвращает информацию о компьютере, где она развернута. Информация может включать, например, данные об архитектуре ЦП, количестве ядер, общем объеме ОЗУ и времени работы (в секундах).

      Выберите NodeInfo из списка готовых функций в магазине и нажмите DEPLOY. На экране появится список развернутых функций.

      OpenFaaS - Развернутая функция NodeInfo

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

      OpenFaaS - Информация о развернутой функции

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

      После появления Ready развернутая функция станет доступна по указанному URL-адресу. Для проверки перейдите по URL в вашем браузере, или выполните вызов функции с панели Invoke function под информацией о функции.

      OpenFaaS — вызов развернутой функции

      Вы можете выбирать между Text, JSON и Download, чтобы указать тип ожидаемого ответа. Если необходим запрос POST вместо GET, введите данные по запросу в поле Request body.

      Для вызова функции nodeinfo нажмите кнопку INVOKE. OpenFaaS создаст и выполнит запрос HTTP согласно выбранным опциям, а также заполнит поля ответа полученными данными.

      OpenFaaS - Ответ функции nodeinfo

      Ответ HTTP 200 OK означает, что запрос был выполнен успешно. Тело ответа будет содержать системную информацию, собранную функцией NodeInfo. Это будет означать, что функция доступна и работает правильно.

      Для удаления функции выберите ее из списка и нажмите на значок корзины в правом верхнем углу страницы. Нажмите OK при запросе для подтверждения действия. После чего статус функции изменится на Not Ready​​​ (это будет означать, что функция была удалена из кластера).В скором времени функция пропадет из пользовательского интерфейса.

      На этом шаге вы использовали веб-интерфейс пользователя OpenFaaS для развертывания и управления функциями. Далее вы научитесь развертывать и управлять функциями OpenFaaS при помощи командной строки.

      Шаг 4 — Управление функциями с помощью faas-cli

      В этом разделе вы настроите faas-cli для работы с вашим кластером. Далее вы научитесь развертывать и управлять функциями с помощью командной строки.

      Сохраните домен OpenFaas в переменной среды OPENFAAS_URL, чтобы не указывать его при каждом использовании faas-cli. Значение домена будет автоматически браться из переменной среды и использоваться во время выполнения функции.

      Откройте .bash_profile в вашей домашней директории для редактирования:

      Добавьте следующую строку:

      ~/.bash_profile

      . . .
      export OPENFAAS_URL=https://openfaas.your_domain
      

      Замените openfaas.your_domain на желаемый домен, а затем сохраните и закройте файл.

      Чтобы не входить в систему еще раз, самостоятельно оцените файл:

      Убедитесь, что faas-cli установлен на вашем компьютере. Если он не установлен, следуйте инструкциям из официальной документации.

      Настройте ваши учетные данные для входа, выполнив следующую команду:

      • cat ~/openfaas-password.txt | faas-cli login --username admin --password-stdin

      Вывод будет выглядеть следующим образом:

      Output

      Calling the OpenFaaS server to validate the credentials... credentials saved for admin https://openfaas.your_domain

      Для развертывания функции из магазина, выполните следующую команду:

      • faas store deploy function_name

      Разверните nodeinfo, выполнив:

      • faas store deploy nodeinfo

      Результат будет выглядеть примерно следующим образом:

      Output

      Deployed. 202 Accepted. URL: https://openfaas.your_domain/function/nodeinfo

      Для получения списка развернутых функций выполните faas list​​​:

      На экране появятся установленные функции:

      Output

      Function Invocations Replicas nodeinfo 0 1

      Для подробной информации по развернутой функции выполните faas describe:

      Результат будет выглядеть примерно следующим образом:

      Name:                nodeinfo
      Status:              Ready
      Replicas:            1
      Available replicas:  1
      Invocations:         0
      Image:               functions/nodeinfo-http:latest
      Function process:
      URL:                 https://openfaas.your_domain/function/nodeinfo
      Async URL:           https://openfaas.your_domain/async-function/nodeinfo
      Labels:              faas_function : nodeinfo
                           uid : 514253614
      Annotations:         prometheus.io.scrape : false
      

      Для вызова функции выполните faas invoke​​​:

      На экране появится следующее сообщение:

      Output

      Reading from STDIN - hit (Control + D) to stop.

      Далее можно сделать запрос. Для запроса используйте POST вместо GET. После ввода данных или в случае, если вы захотите сделать запрос GET, нажмите CTRL + D. Интерфейс faas-cli выполнит необходимый запрос и отобразит ответ наподобие ответа веб-интерфейса пользователя.

      Для удаления функции выполните faas remove:

      На экране появится следующее сообщение:

      Output

      Deleting: nodeinfo. Removing old function.

      Выполните faas list​​​ еще раз, чтобы убедится в удалении nodeinfo:

      Output

      Function Invocations Replicas

      На этом шаге вы научились развертывать, выводить в виде списка, вызывать и удалять функции в кластере при помощи командной строки faas-cli. На следующем шаге вы создадите свою функцию и развернете ее в кластере.

      Шаг 5 — Создание и развертывание новой функции

      Сейчас вы создадите образец функции Node.JS при помощи командной строки faas-cli и развернете его в кластере.

      Созданная функция будет упакована в виде контейнера Docker и опубликована в Docker Hub. Для публикования контейнеров войдите в систему, выполнив следующую команду:

      Для завершения процесса входа введите ваше имя пользователя и пароль Docker Hub.

      Образец функции Node.JS сохраняется в папке sample-js-function​​​. Создайте файл с помощью следующей команды:

      Перейдите в директорию:

      Поместите шаблон JS функции в директорию, выполнив следующую команду:

      • faas new sample-js --lang node

      Итоговый результат будет выглядеть следующим образом:

      Output

      2020/03/24 17:06:08 No templates found in current directory. 2020/03/24 17:06:08 Attempting to expand templates from https://github.com/openfaas/templates.git 2020/03/24 17:06:10 Fetched 19 template(s) : [csharp csharp-armhf dockerfile go go-armhf java11 java11-vert -x java8 node node-arm64 node-armhf node12 php7 python python-armhf python3 python3-armhf python3-debian ru by] from https://github.com/openfaas/templates.git Folder: sample-js created. ___ _____ ____ / _ _ __ ___ _ __ | ___|_ _ __ _/ ___| | | | | '_ / _ '_ | |_ / _` |/ _` ___ | |_| | |_) | __/ | | | _| (_| | (_| |___) | ___/| .__/ ___|_| |_|_| __,_|__,_|____/ |_| Function created in folder: sample-js Stack file written: sample-js.yml ...

      Код самой функции находится в папке sample-js​​​, а конфигурация OpenFaas для нее в файле sample-js.yaml​​​. В директории sample-js (которая напоминает обычный проект Node.JS) находятся два файла — handler.js и package.json.

      handler.js​​​ содержит фактический код JS для возврата ответа при вызове функции. Содержимое handler выглядит следующим образом:

      sample-js-function/sample-js/handler.js

      "use strict"
      
      module.exports = async (context, callback) => {
          return {status: "done"}
      }
      

      Он экспортирует lambda function с двумя параметрами, context с данными запроса и callback, которые вы можете использовать для обратной передачи данных запроса вместо выполнения обычного возврата.

      Откройте этот файл для редактирования:

      • nano sample-js/handler.js

      Измените выделенную строку следующим образом:

      sample-js-function/sample-js/handler.js

      "use strict"
      
      module.exports = async (context, callback) => {
          return {status: "<h1>Hello Sammy!</h1>"}
      }
      

      После внесения изменений сохраните и закройте файл. При вызове этой функции OpenFaaS на экране появится сообщение Hello Sammy!​​​ в ответ на запрос.

      Далее откройте файл конфигурации для редактирования:

      Он будет выглядеть следующим образом:

      sample-js-function/sample-js.yml

      version: 1.0
      provider:
        name: openfaas
        gateway: https://openfaas.your_domain
      functions:
        sample-js:
          lang: node
          handler: ./sample-js
          image: sample-js:latest
      

      Он определяет openfaas и шлюз по умолчанию для provider. Затем определяет функцию sample-js, ее язык (node), handler и имя образа Docker, которые необходимо изменить для включения вашего имени пользователя учетной записи Docker Hub, например:

      sample-js-function/sample-js.yml

      version: 1.0
      provider:
        name: openfaas
        gateway: http://127.0.0.1:8080
      functions:
        sample-js:
          lang: node
          handler: ./sample-js
          image: your_docker_hub_username/sample-js:latest
      

      Сохраните и закройте файл.

      Создайте образ Docker, переместите его в Docker Hub и разверните в вашем кластере. Выполните все действия одновременно, используя следующую команду:

      На экране появится большой объем данных (в основном из Docker), которые будут завершаться примерно следующим:

      Output

      . . . [0] < Pushing sample-js [your_docker_hub_username/sample-js:latest] done. [0] Worker done. Deploying: sample-js. Deployed. 202 Accepted. URL: https://openfaas.your_domain/function/sample-js

      Вызовите вашу новую развернутую функцию, чтобы проверить, что она работает:

      Нажмите CTRL + D. На экране появится следующее:

      Output

      <h1>Hello Sammy!</h1>

      Это означает, что функция упакована и развернута правильно.

      Вы можете удалить функцию, выполнив команду:

      Вы успешно создали и развернули пользовательскую функцию Node.JS в OpenFaaS в вашем кластере.

      Заключение

      Вы развернули OpenFaaS в вашем кластере DigitalOcean Kubernetes и готовы развернуть и начать пользоваться как готовыми, так и пользовательскими функциями. Вы научились внедрять архитектуру Function as a Service, которая поможет вам оптимизировать использование ресурсов и увеличит производительность ваших приложений.

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



      Source link

      Определение и вызов функций в Go


      Введение

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

      Go поставляется с мощной стандартной библиотекой, где имеются самые разные предопределенные функции. С некоторыми из них вы уже знакомы, например с функциями пакета fmt:

      • fmt.Println(), которая будет выводить объекты в стандартный вывод (скорее всего, это будет ваш терминал).
      • fmt.Printf(), которая позволяет форматировать отображаемый результат.

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

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

      Определение функции

      Давайте начнем с превращения классического примера “Hello, World!” из программы в функцию.

      Мы создадим новый текстовый файл в текстовом редакторе по выбору и вызовем программу hello.go. Затем мы определим функцию.

      Функция определяется с помощью ключевого слова func. За ним следует имя функции и набор скобок, которые хранят любые параметры, принимаемые функцией (скобки могут быть пустыми). Строки кода функции помещаются в фигурные скобки {}.

      В данном случае мы определим функцию с именем hello():

      hello.go

      func hello() {}
      

      Это первоначальное объявление для создания функции.

      Теперь мы можем добавить вторую строку для определения того, что будет делать наша функция. В данном случае мы будем выполнять вывод Hello, World! в консоль:

      hello.go

      func hello() {
          fmt.Println("Hello, World!")
      }
      

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

      Давайте внутри нашего блока функции main() вызовем функцию с именем hello():

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          hello()
      }
      
      func hello() {
          fmt.Println("Hello, World!")
      }
      

      Теперь мы запустим программу:

      Вывод должен выглядеть следующим образом:

      Output

      Hello, World!

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

      main.go

      package main
      
      import "fmt"
      
      func main() {
          fmt.Println("this is the main section of the program")
      }
      

      Функции могут быть более сложными, чем функция hello(), которую мы определили. Мы можем использовать циклы for, условные операторы и многое другое внутри нашей функции.

      Например, следующая функция использует условный оператор для проверки того, содержит ли переменная name гласные звуки, а затем использует цикл for для прохождения по буквам строки name.

      names.go

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          names()
      }
      
      func names() {
          fmt.Println("Enter your name:")
      
          var name string
          fmt.Scanln(&name)
          // Check whether name has a vowel
          for _, v := range strings.ToLower(name) {
              if v == 'a' || v == 'e' || v == 'i' || v == 'o' || v == 'u' {
                  fmt.Println("Your name contains a vowel.")
                  return
              }
          }
          fmt.Println("Your name does not contain a vowel.")
      }
      

      Функция names(), которую мы определили здесь, устанавливает значение переменной name, а затем задает условный оператор внутри цикла for. Здесь показано, как можно организовывать код внутри функции. Однако в зависимости от того, что мы намерены делать в нашей программе и как мы хотим организовать наш код, мы можем использовать условный оператор и цикл for в качестве двух отдельных функций.

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

      Работа с параметрами

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

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

      Давайте создадим программу, которая повторяет слово определенное количество раз. Она будет получать параметр string с именем word и параметр int с именем reps для количества повторений этого слова.

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          repeat("Sammy", 5)
      }
      
      func repeat(word string, reps int) {
          for i := 0; i < reps; i++ {
              fmt.Print(word)
          }
      }
      

      Мы передали значение Sammy для параметра word и 5 для параметра reps. Эти значения соответствуют каждому параметру в заданном нами порядке. Функция repeat имеет цикл for, который будет выполняться количество раз, определенное значением параметра reps. Для каждой итерации значение параметра word выводится на экран.

      Здесь вы увидите вывод программы:

      Output

      SammySammySammySammySammy

      Если у вас есть набор параметров, которые имеют одинаковое значение, вы можете каждый раз не указывать тип значения. Давайте создадим небольшую программу, которая получает параметры x, y и z, имеющие тип int. Мы создадим функцию, которая складывает значения параметров в разных конфигурациях. Получаемые суммы будут выводиться функцией. Затем мы будем вызывать функцию и передавать числа в эту функцию.

      add_numbers.go

      package main
      
      import "fmt"
      
      func main() {
          addNumbers(1, 2, 3)
      }
      
      func addNumbers(x, y, z int) {
          a := x + y
          b := x + z
          c := y + z
          fmt.Println(a, b, c)
      }
      

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

      Мы передали число 1 в параметр x, 2 в параметр y и 3 в параметр z. Эти значения соответствуют каждому параметру в заданном нами порядке.

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

      a = 1 + 2
      b = 1 + 3
      c = 2 + 3
      

      Функция также выводит a, b и c, и на основе этих математических операций мы ожидаем, что a будет равна 3, b4 и c5. Давайте запустим программу:

      Output

      3 4 5

      Когда мы передадим 1, 2 и 3 в качестве параметров функции addNumbers(), мы получаем ожидаемый результат.

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

      Возврат значения

      Вы можете передать значение параметра в функцию, а функция может также генерировать значение.

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

      До настоящего момента мы использовали fmt.Println() вместо оператора return в наших функциях. Давайте создадим программу, которая вместо вывода на экран будет возвращать переменную.

      В новом текстовом файле с именем double.go мы создадим программу, которая удваивает параметр x и возвращает переменную y. Мы осуществляем вызов для вывода переменной result, которая создается при запуске функции double() с переданным ей значением 3:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          return y
      }
      
      

      Мы можем запустить программу и увидеть следующий вывод:

      Output

      6

      Целое число 6 возвращается как результат, что является ожидаемым значением при умножении 3 на 2.

      Если для функции определено возвращаемое значение, вы должны указать в коде оператор return. В обратном случае вы получите ошибку компилятора.

      Мы можем продемонстрировать это, закомментировав строку с оператором return:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          // return y
      }
      
      

      Теперь мы снова запустим программу:

      Output

      ./double.go:13:1: missing return at end of function

      Без оператора return программу не удастся скомпилировать.

      Выполнение функции прекращается немедленно при достижении оператора return, даже если он находится не в конце функции:

      return_loop.go

      package main
      
      import "fmt"
      
      func main() {
          loopFive()
      }
      
      func loopFive() {
          for i := 0; i < 25; i++ {
              fmt.Print(i)
              if i == 5 {
                  // Stop function at i == 5
                  return
              }
          }
          fmt.Println("This line will not execute.")
      }
      

      Здесь мы используем цикл for и выполняем 25 итераций данного цикла. Однако внутри цикла for у нас есть условный оператор if, который проверяет, имеет ли i значение 5. Если условие выполняется, выполняется оператор return. Поскольку мы находимся внутри функции loopFive, срабатывание оператора return внутри этой функции приводит к прекращению ее работы. В результате мы никогда не доберемся до последней строки, которая выводит строку This line will not execute.​​

      Использование оператора return внутри цикла for позволяет завершить работу функции, так что строка, находящаяся вне цикла, не будет выполняться. Если же, напротив, мы бы использовали оператор break, прекращалась только работа цикла, а последняя строка fmt.Println() все равно бы выполнялась.

      Оператор return прекращает работу функции и может возвращать значение, если оно указано в сигнатуре функции.

      Возврат нескольких значений

      Для функции может быть указано более одного возвращаемого значения. Давайте посмотрим на программу repeat.go и сделаем так, чтобы она возвращала два значения. Первым значением будет повторяющееся значение, а второе будет ошибкой, которая возникает, если параметр reps имеет значение меньше 0:

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          val, err := repeat("Sammy", -1)
          if err != nil {
              fmt.Println(err)
              return
          }
          fmt.Println(val)
      }
      
      func repeat(word string, reps int) (string, error) {
          if reps <= 0 {
              return "", fmt.Errorf("invalid value of %d provided for reps. value must be greater than 0.", reps)
          }
          var value string
          for i := 0; i < reps; i++ {
              value = value + word
          }
          return value, nil
      }
      

      Первое, что делает функция repeat, — это проверка действительности значения аргумента reps. Любое значение меньше 0 будет вызывать ошибку. После того как мы передали значение -1, эта часть кода будет выполнена. Обратите внимание, что при выполнении возврата из функции, мы должны предоставить возвращаемые значения типа string и error. Поскольку предоставленные аргументы привели к ошибке, мы передадим пустую строку для первого возвращаемого значения и ошибку для второго возвращаемого значения.

      В функции main() мы можем получить оба возвращаемых значения, объявив две новые переменные, value и err. Поскольку в возвращаемом значении может быть ошибка, нам нужно проверить, получаем ли мы ошибку, прежде чем продолжить работу с нашей программой. В данном примере мы получили ошибку. Мы выводим ошибку и с помощью return выходим из функции main() для выхода из программы.

      Если ошибки не было, мы выводим возвращаемое значение функции.

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

      При запуске программы вы получите следующий вывод:

      Output

      invalid value of -1 provided for reps. value must be greater than 0.

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

      Заключение

      Функции — это блоки кода с инструкциями, которые выполняются внутри программы и помогают сделать код модульным и доступным для многоразового использования.

      Чтобы узнать больше о том, как сделать ваш код более модульным, вы можете ознакомиться с нашим руководством Написание пакетов в Go.



      Source link

      Использование функций с переменным количеством аргументов в Go


      Введение

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

      Функции с переменным количеством аргументов встречаются чаще, чем кажется. Наиболее распространенная из них — функция Println из пакета fmt.

      func Println(a ...interface{}) (n int, err error)
      

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

      Создадим программу, которая использует функцию fmt.Println и передает ноль, одно или несколько значений:

      print.go

      package main
      
      import "fmt"
      
      func main() {
          fmt.Println()
          fmt.Println("one")
          fmt.Println("one", "two")
          fmt.Println("one", "two", "three")
      }
      

      При первом вызове fmt.Println мы не передаем никаких аргументов. При втором вызове fmt.Println мы передаем только один аргументо со значением one. Затем мы передаем значения one и two, и в заключение one, two и three.

      Запустим программу с помощью следующей команды:

      Результат должен выглядеть так:

      Output

      one one two one two three

      Первая выводимая строка будет пустой. Это связано с тем, что мы не передали никаких аргументов при первом вызове fmt.Println. При втором вызове будет выведено значение one. Затем будут выведены значения one и two, а в заключение — one, two и three.

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

      Определение функции с переменным количеством аргументов

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

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          sayHello()
          sayHello("Sammy")
          sayHello("Sammy", "Jessica", "Drew", "Jamie")
      }
      
      func sayHello(names ...string) {
          for _, n := range names {
              fmt.Printf("Hello %sn", n)
          }
      }
      

      Мы создали функцию sayHello, которая принимает только один параметр с именем names. Это параметр с переменным количеством аргументов, поскольку мы поставили многоточие (...) перед типом данных: ...string. Это указывает Go, что функция может принимать ноль, один или много аргументов.

      Функция sayHello получает параметр names в качестве среза. Поскольку используется тип данных string, параметр names можно рассматривать как срез строк ([]string) в теле функции. Мы можем создать цикл с оператором range и выполнять итерацию в слайсе строк.

      Если мы запустим программу, результат будет выглядеть так:

      Output

      Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

      Обратите внимание, что при первом вызове sayHello ничего не выводится. Это связано с тем, что значением параметра с переменным количеством аргументов были пустой срез или пустая строка. Поскольку мы выполняем цикл в срезе, объектов для итерации нет, и функция fmt.Printf не вызывается.

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

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          sayHello()
          sayHello("Sammy")
          sayHello("Sammy", "Jessica", "Drew", "Jamie")
      }
      
      func sayHello(names ...string) {
          if len(names) == 0 {
              fmt.Println("nobody to greet")
              return
          }
          for _, n := range names {
              fmt.Printf("Hello %sn", n)
          }
      }
      

      Теперь, если при использовании выражения if не передаются никакие значения, длина names будет равна 0, и мы не будем приветствовать никого:

      Output

      nobody to greet Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

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

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"})
          fmt.Println(line)
      
          line = join(",", []string{"Sammy", "Jessica"})
          fmt.Println(line)
      
          line = join(",", []string{"Sammy"})
          fmt.Println(line)
      }
      
      func join(del string, values []string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

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

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

      Поскольку функция принимает срез строки в качестве параметра values, нам нужно было заключать все слова в срез при вызове функции join. Это усложняет чтение кода.

      Теперь напишем ту же функцию, но как функцию с переменным количеством аргументов:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      Если мы запустим эту программу, результат будет выглядеть как предыдущая программа:

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

      Хотя обе версии функции join выполняют одно и то же с программной точки зрения, версия функции с переменным количеством аргументов намного проще читается при вызове.

      Порядок при переменном количестве аргументов

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

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      }
      
      func join(values ...string, del string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      В этот раз мы поместим параметр values первым в функции join. В результате возникнет следующая ошибка компиляции:

      Output

      ./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

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

      Раскрывающиеся аргументы

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

      Возьмем функцию join из последнего раздела и посмотрим, что получится:

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          names := []string{"Sammy", "Jessica", "Drew", "Jamie"}
      
          line = join(",", names)
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      Если мы запустим эту программу, то получим ошибку компиляции:

      Output

      ./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

      Хотя функция с переменным количеством аргументов конвертирует параметр values ...string в срез строк []string, мы не можем передать срез строк в качестве аргумента. Это связано с тем, что компилятор ожидает получить дискретные аргументы строк.

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

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          names := []string{"Sammy", "Jessica", "Drew", "Jamie"}
      
          line = join(",", names...)
          fmt.Println(line)
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      Теперь при вызове функции join мы раскрываем срез names посредством добавления многоточия (...).

      Так программа работает ожидаемым образом:

      Output

      Sammy,Jessica,Drew,Jamie

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

      join.go

      package main
      
      import "fmt"
      
      func main() {
          var line string
      
          line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...)
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
          fmt.Println(line)
      
          line = join(",", "Sammy", "Jessica")
          fmt.Println(line)
      
          line = join(",", "Sammy")
          fmt.Println(line)
      
      }
      
      func join(del string, values ...string) string {
          var line string
          for i, v := range values {
              line = line + v
              if i != len(values)-1 {
                  line = line + del
              }
          }
          return line
      }
      

      Output

      Sammy,Jessica,Drew,Jamie Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

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

      Заключение

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

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

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



      Source link