Язык Go — это простой и легкий в освоении язык программирования.Для инженеров с опытом программирования нетрудно выучить язык Go и написать код, который может работать.Для разработчиков, имеющих опыт работы с другими языками, это на самом деле проблема написать любой язык, подобный языку, который вы выучили.Если вы хотите по-настоящему интегрироваться в экологию и писать элегантный код, вы должны потратить некоторое время и энергию, чтобы понять философию дизайна и лучшие практики, лежащие в основе языка.
Если у вас нет предыдущего опыта разработки на языке Go, но вы изучаете и используете язык Go, я считаю, что эта статья поможет вам быстрее написать элегантный код на языке Go; в этой статье мы не будем давать слишком много. Список описывает, как переменные, методы, и структуры должны быть названы.Эти спецификации кода Go можно найти наGo Code Review CommentsОни очень важны, но не в центре внимания этой статьи. Мы расскажем, как писать элегантный код, исходя из нескольких различных аспектов структуры кода, лучших практик и модульного тестирования. Перейти код языка.
написать впереди
Писать хороший код непросто, это требует от нас постоянно размышлять над существующим кодом — как переписать этот код, чтобы сделать его более элегантным. Элегантность звучит как очень перцептивный и трудно поддающийся количественной оценке результат, но это самое интуитивное чувство, которое может вызвать хороший код, и оно может неявно включать следующие характеристики:
- легко читать и понимать;
- Легко тестировать, поддерживать и расширять;
- Наименование понятное, однозначное, аннотации полные и понятные;
- …
Я считаю, что после прочтения этой статьи мы не сможем сразу написать элегантный код на языке Go, но если мы будем следовать нескольким простым в использовании и практичным методам, представленным здесь, это поможет нам сделать первый шаг Автор написал эту статью со следующими целями:
- Помогите разработчикам Go понять нормы и инструменты экосистемы и написать более элегантный код;
- Обеспечьте правила, консенсус и лучшие практики, которые широко приняты сообществом для управления кодом и проектами;
спецификация кода
Спецификация кода на самом деле является старомодной проблемой, и мы не можем избежать ее или должны кратко представить соответствующий контент.Язык Go относительно распространен, и официально предоставлена широко используемая спецификация кода.Go Code Review Comments, независимо от того, программируете ли вы на Go в краткосрочной или долгосрочной перспективе, вам следуетПрочитайте это официальное руководство по спецификации кода хотя бы один раз полностью., что является не только правилами, которым мы должны следовать при написании кода, но и спецификациями, на которые нужно обращать внимание при просмотре кода.
Изучение спецификаций кода, связанных с языком Go, очень важно, и это также первый шаг к тому, чтобы наши проекты следовали унифицированным спецификациям.Хотя очень важно читать документы, относящиеся к спецификациям кода, мы не можем полагаться на инженеры сознательно в реальной работе.Соответствие часто рассматривается как форма проверки кода, но требует инструментов, помогающих в его выполнении.
Вспомогательный инструмент
Использование автоматизированных инструментов для обеспечения соответствия проекта некоторым основным спецификациям кода очень просто в эксплуатации и эффективно, в то время как способ проверки кода в человеческом теле более подвержен ошибкам, а также будут некоторые особые случаи нарушения правил. Соблюдение спецификаций кода Лучший способ«Постарайтесь автоматизировать все шаги, которые можно автоматизировать, и позвольте инженерам изучить логику и дизайн, которые действительно имеют значение»..
В этом разделе мы представим два очень эффективных способа, которые помогут нам автоматизировать некоторые проверки спецификации кода и статические проверки в проекте для обеспечения качества проекта.
goimports
goimportsЭто официальный инструмент, предоставляемый языком Go.Он может автоматически форматировать для нас код языка Go и управлять всеми импортированными пакетами, включая автоматическое добавление и удаление ссылок на зависимые пакеты, сортировку и классификацию зависимых пакетов в алфавитном порядке. Я считаю, что IDE, используемые многими людьми, будут использовать другой официально предоставленный инструмент.gofmtформатировать код иgoimports
равноgofmt
Плюс управление пакетами зависимостей.
Рекомендуется для использования всеми разработчиками Go при разработкеgoimports
,Несмотря на то чтоgoimports
Иногда вводятся неправильные пакеты, но эти случайные баги допустимы по мнению автора по сравнению с преимуществами; конечно, не хочется использоватьgoimports
Разработчики также должны включить автоматическоеgofmt
(автоматически форматируется при сохранении).
Включить автоматически в проверках IDE и CI
gofmt
илиgoimports
Проверка не является и не должна обсуждаться, это вопрос использования и развития языка Go.долженсписок задач.
golint
Другим часто используемым инструментом статической проверки являетсяgolint
Теперь, как официальный инструмент, он имеет очень плохую поддержку настройки, мы можем запустить его только следующим образом.golint
Посмотрите наши проекты:
$ golint ./pkg/...
pkg/liquidity/liquidity_pool.go:18:2: exported var ErrOrderBookNotFound should have comment or be unexported
pkg/liquidity/liquidity_pool.go:23:6: exported type LiquidityPool should have comment or be unexported
pkg/liquidity/liquidity_pool.go:23:6: type name will be used as liquidity.LiquidityPool by other packages, and that stutters; consider calling this Pool
pkg/liquidity/liquidity_pool.go:31:1: exported function NewLiquidityPool should have comment or be unexported
...
сообщество оgolint
Индивидуальныеобсуждать,golint
разработчиков привели следующие моменты, чтобы объяснить, почемуgolint
Пользовательские функции не поддерживаются:
lint
Цель состоит в том, чтобы поощрять единый и последовательный стиль программирования в языковом сообществе Go.Некоторые разработчики могут не соглашаться с некоторыми спецификациями, но использование единого стиля имеет большое преимущество для языкового сообщества Go и может переключать указанные rules.function приведет кgolint
не уметь эффективно выполнять работу;- Есть некоторые статически проверенные правила, которые вызывают некоторые ложные предупреждения, которые действительно раздражают, но я бы предпочел поддерживать сохранение или удаление этих правил непосредственно в golint, а не предоставлять возможность добавлять или удалять правила произвольно;
- в состоянии пройти
min_confidence
Отфильтруйте некоторые статические правила проверки, но нам нужно выбрать соответствующие значения;
golint
Точка зрения автора таковаissueЯ получил много 👎, но трудно сказать правильно или неправильно; очень полезно обеспечить согласованные стандарты программирования в сообществе, но для многих внутренних сервисов или проектов компании это может быть бизнес-сервисы. более сложные ситуации, когда нет очевидных преимуществ от использования такого чрезмерно сильного ограничения.
Более рекомендуемый способ - использовать в базовой библиотеке или фреймворкеgolint
Выполните статические проверки (или используйте обаgolint
иgolangci-lint), использовать настраиваемый в других проектахgolangci-lint
для статической проверки, потому что наложение строгих ограничений в базовых библиотеках и фреймворках дает больше преимуществ для общего качества кода.
Автор будет использовать его в своем собственном проекте Go.
golint
+golangci-lint
И включите все проверки, чтобы найти все дефекты в коде, включая документацию, как можно раньше.
автоматизация
Будь то для проверки спецификаций и зависимостей кодаgoimports
или инструмент статической проверкиglint
илиgolangci-lint
, пока мы внедряем эти инструменты в проект, мы должны добавить соответствующие автоматические проверки в процесс CI кода:
Мы также должны сделать все возможное, чтобы найти подходящие инструменты на самодельных или других платформах для размещения кода.Современные инструменты для размещения кода должны иметь очень хорошую поддержку CI / CD; нам нужно использовать эти инструменты CI, чтобы изменить автоматическую проверку кода на это. становится предварительным условием для слияния и выпуска PR, уменьшая упущения, которые могут возникнуть при проверке кода инженерами.
Лучшие практики
В предыдущем разделе мы рассказали о некоторых проблемах, которые могут быть обнаружены с помощью автоматизированных инструментов. Передовой опыт, упомянутый в этом разделе, может не гарантироваться автоматическими инструментами. Эти передовые методы больше похожи на внутренний процесс разработки языкового сообщества Go. опыт и консенсус, следование этим передовым методам может помочь нам написать код, который соответствует «аромату» языка Go. В этом разделе мы рассмотрим следующие части:
- Структура каталогов;
- раздельный модуль;
- явный вызов;
- Интерфейс;
Эти четыре части являются относительно распространенными соглашениями в сообществе.Если мы изучим и будем следовать этим соглашениям и будем практиковать эти части в проектах на языке Go, я считаю, что нам будет полезно разрабатывать проекты на языке Go.
Структура каталогов
Структура каталогов — это, по сути, фасад проекта. Во многих случаях по структуре каталогов видно, достаточно ли у разработчика опыта работы с языком, поэтому здесь лучше всего представить, как использовать языковой проект Go. организовать код в службе.
Официальный не дает рекомендуемого способа разделения каталога.Многие проекты также очень небрежно относятся к разделению структуры каталогов, что на самом деле не является проблемой, но в сообществе все же есть некоторые общепринятые соглашения, такие как:golang-standards/project-layoutВ проекте определена относительно стандартная структура каталогов.
├── LICENSE.md
├── Makefile
├── README.md
├── api
├── assets
├── build
├── cmd
├── configs
├── deployments
├── docs
├── examples
├── githooks
├── init
├── internal
├── pkg
├── scripts
├── test
├── third_party
├── tools
├── vendor
├── web
└── website
Здесь мы просто кратко представляем некоторые из наиболее распространенных и важных каталогов и файлов, чтобы помочь нам быстро понять, как использовать структуру каталогов, показанную выше.Если читатели хотят знать причины использования других каталогов, они могут перейти кgolang-standards/project-layoutДля получения более подробной информации см. README в проекте.
/pkg
/pkg
Каталог является очень распространенным каталогом в проектах на языке Go, и мы можем найти его почти во всех известных проектах с открытым исходным кодом (не фреймворк), таких как:
- prometheusБаза данных временных рядов для отчетности и хранения показателей
- istioСервисная сетка 2.0
- kubernetesСистема управления расписанием контейнеров
- grafanaПанели мониторинга, показывающие мониторинг и метрики
В этом каталоге хранится библиотека кода в проекте, которую могут использовать внешние приложения, а другие проекты могут напрямую передаватьimport
импортируйте код сюда, поэтому, когда мы поместим код вpkg
Мы должны быть осторожны, но если мы разрабатываем сервисы интерфейса HTTP или RPC или внутренние сервисы компании, поместите как частный, так и публичный код в/pkg
В этом нет ничего плохого, потому что, будучи проектом верхнего уровня, другие приложения редко напрямую используют его. Конечно, очень полезно строго следовать разделению публичного и приватного кода. код правильно разделен.
приватный код
Приватный код рекомендуется размещать в/internal
каталог, реальный код проекта должен быть написан в/internal/app
В то же время базы кода, от которых зависят эти внутренние приложения, должны быть в/internal/pkg
подкаталоги и/pkg
, на следующем рисунке показано использование/internal
Структура проекта каталога:
Когда мы внедряем включения в другие проектыinternal
, язык Go сообщит об ошибке при компиляции:
An import of a path containing the element “internal” is disallowed
if the importing code is outside the tree rooted at the parent of the
"internal" directory.
Такая ошибка возникает только тогда, когдаinternal
Происходит только в том случае, если пакет не существует в текущем дереве проекта, если проектinternal
В пакете нет этой ошибки.
/src
Структура каталогов, которой не должно быть в проекте на языке Go, на самом деле/src
Да, в некоторых проектах сообщества есть/src
Папка, но перед разработчиками этих проектов у большинства есть опыт программирования Java на Java и других языках, что на самом деле является относительно обычной организацией кода, но как разработчику языка Go мы не должны допускать проект при наличии/src
содержание.
Самая важная причина заключается в том, что проекты языка Go по умолчанию помещаются в$GOPATH/src
Под каталогом этот каталог хранит весь код проекта, который мы разрабатываем и от которого зависим, если мы используем его в нашем собственном проекте./src
каталог, проектPATH
будет дваsrc
:
$GOPATH/src/github.com/draveness/project/src/code.go
Приведенная выше структура каталогов выглядит очень странно, поэтому мы не рекомендуем использовать ее в языке Go./src
Самая важная причина для каталога.
Конечно, даже если мы используем языковой проект Go/src
Каталог не приведет к сбою компиляции или другим проблемам.Если вы настаиваете на таком подходе, это никак не повлияет на удобство использования проекта.Однако, если вы хотите, чтобы мы "выглядели" более профессионально, мы должны следовать установленным правилам в сообществе и сократить количество других языков Go. Разработчики понимают цену, что хорошо для сообщества.
кафельная плитка
Еще один способ организовать код на языке Go — поместить код проекта в корневой каталог проекта.Этот метод очень распространен во многих фреймворках или библиотеках.Если вы хотите ввести использованиеpkg
Когда в рамках структуры каталогов нам часто приходится использоватьgithub.com/draveness/project/pkg/somepkg
, когда код разложен в корневом каталоге проекта, нужно использовать толькоgithub.com/draveness/project
, что значительно сокращает длину инструкции, относящейся к зависимому пакету.
Таким образом, для языковой среды или библиотеки Go нормально размещать код в корневом каталоге, но использование этого метода организации кода в языковой службе Go может быть неуместным.
/cmd
/cmd
Все исполняемые файлы текущего проекта хранятся в каталоге, и каждый подкаталог в этом каталоге должен содержать исполняемые файлы, которые нам нужны.grpc
Сервис, это может быть/cmd/server/main.go
Он содержит код для запуска сервисного процесса и исполняемый файл, сгенерированный после компиляции.server
.
мы не должны быть/cmd
Поместите слишком много кода в каталог, мы должны поместить общедоступный код в/pkg
в и поместите приватный код в/internal
в и в/cmd
Внедрение этих пакетов для обеспеченияmain
Код в функции максимально прост и минимален.
/api
/api
В каталоге хранятся различные типы файлов определения интерфейса API, предоставленные текущим проектом, которые могут содержать аналогичные/api/protobuf-spec
,/api/thrift-spec
или/api/http-spec
Эти каталоги содержат все файлы API, предоставленные и зависящие от текущего проекта:
$ tree ./api
api
└── protobuf-spec
└── oceanbookpb
├── oceanbook.pb.go
└── oceanbook.proto
Основная функция вторичного каталога состоит в том, чтобы избежать потенциальных конфликтов и сделать организацию структуры проекта более четкой, когда проект предоставляет множество различных методов доступа одновременно.
Makefile
последний, чтобы представитьMakefile
Файлы тоже очень заслуживают внимания.В любом проекте будут какие-то скрипты которые надо запускать.Эти скриптовые файлы надо поместить в/scripts
каталог и поMakefile
Запустите, закрепите эти часто выполняемые команды в сценариях, чтобы уменьшить появление «команд предков».
резюме
В общем, каждый проект должен быть реализован в соответствии с установленным организационным методом.Хотя это соглашение не является обязательным, будь то внутри группы, компании или всего языкового сообщества Go, пока есть соглашение, другие инженеры. помогает быстро разобраться и понять проект.
Метод организации языкового проекта Go, представленный в этом разделе, не является обязательным.Это просто метод организации проекта, который часто встречается в языковом сообществе Go.Большой проект также будет тонко настраивать при использовании этой структуры каталогов, но это тип организации действительно более распространен и разумен.
модуль сплит
Теперь, когда мы представили, как организовать структуру проекта с верхнего уровня, мы углубимся в проект и представим некоторые методы разделения модулей в языке Go.
Некоторые проекты верхнего уровня языка Go в конечном итоге приводят к тому, что он сильно отличается от других языков программирования в разделении модулей.Многие веб-фреймворки на других языках используют архитектурный шаблон MVC, например Rails и Spring MVC, и язык Go разделяет модули.Подход полностью отличается от Ruby и Java.
Разделить по слоям
Будь то Java или Ruby, их самые известные фреймворки глубокоАрхитектурный шаблон MVCВлияние Spring MVC, мы можем понять влияние MVC на него из названия Spring MVC, и фреймворк Rails сообщества Ruby также очень тесно связан с MVC, который является наиболее распространенным архитектурным методом веб-фреймворка. Различные компоненты разделены на три уровня: Модель, Представление и Контроллер.
Этот метод разделения модулей на самом деле разделяет по уровням.Код, сгенерированный скаффолдингом Rails по умолчанию, фактически помещает эти три разных исходных файла в соответствующий каталог:models
,views
иcontrollers
, мы проходимrails new example
После создания нового проекта Rails вы можете увидеть в нем структуру каталогов:
$ tree -L 2 app
app
├── controllers
│ ├── application_controller.rb
│ └── concerns
├── models
│ ├── application_record.rb
│ └── concerns
└── views
└── layouts
И многие проекты Spring MVC также будут выглядеть похожими.model
,dao
,view
Есть несколько причин для такого дизайна разделения модулей по слоям:
- Паттерн архитектуры MVC. Сам MVC делает упор на проектирование обязанностей по уровням, поэтому фреймворк, который следует этому шаблону, естественно, имеет тот же образ мышления;
- Плоское пространство имен - будь то весна MVC или рельсы, пространство имен в том же проекте очень плоский. Использование классов или методов, определенных в других папках, в папках, не требует введения новых пакетов, а также не требует классов, определенных в других файлах. Дополнительные Префикс необходимо добавить, а классы, определенные несколькими файлами, являются «объединены» в то же самое пространство имен;
- Сценарии монолитных сервисов. Когда Spring MVC и Rails впервые появились, архитектуры SOA и микросервисов не были так распространены, как сегодня, и в большинстве сценариев не требовалось разделения сервисов;
Вышеперечисленные причины в совокупности определяют появление Spring MVC и Rails.models
,views
иcontrollers
каталог и разделить модули иерархически.
Разделение по обязанностям
Язык Go использует совершенно другую идею при разделении модулей.Хотя архитектурный шаблон MVC нельзя избежать, когда мы пишем веб-сервисы, по сравнению с горизонтальным разделением различных уровней, проекты языка Go часто основаны на Обязанности разбиты на модули:
Для относительно распространенной системы блогов проекты, использующие язык Go, будут вертикально разделены на разные обязанности.post
,user
,comment
Три модуля, каждый модуль обеспечивает соответствующие функции для внешнего мира,post
Модуль содержит соответствующие определения модели и представления, а также контроллер (или службу), используемый для обработки запросов API:
$ tree pkg
pkg
├── comment
├── post
│ ├── handler.go
│ └── post.go
└── user
Каждый файловый каталог в проекте языка Go представляет собойотдельное пространство имен, то есть отдельный пакет, когда мы хотим обратиться к каталогу других папок, нам сначала нужно использоватьimport
Ключевое слово вводится в соответствующий каталог файлов, а затем черезpkg.xxx
В виде ссылок на структуры, функции или константы, определенные другими каталогами, если мы используем язык Gomodel
,view
иcontroller
чтобы разделить иерархию, вы увидите многое в других модуляхmodel.Post
,model.Comment
иview.PostView
.
Этот метод разделения слоев очень избыточен в языке Go, и если управление зависимостями проекта недостаточно тщательно, могут возникнуть циклы ссылок.Основная причина этих проблем на самом деле очень проста:
- Язык Go изолирует пространства имен разных каталогов в одном проекте. Классы и методы, определенные во всем проекте, не находятся в одном и том же пространстве имен, что требует от разработчиков поддерживать зависимости между разными пакетами;
- Очень легко разделить микросервисы в соответствии с вертикальным разделением обязанностей, когда один сервис сталкивается с узким местом Мы можем напрямую разделить один сервис, отвечающий за независимые функции.
package
Выньте его и расширьте емкость этой части точки доступа отдельно;
резюме
Нет абсолютного добра или зла, разделен ли проект на модули в соответствии с иерархией или обязанностями.Дизайн на уровне языка и фреймворка в конечном итоге определяет, как мы должны организовать проект и код.
Такие языки, как Java и Ruby, часто используют горизонтальное разделение для разделения обязанностей на разных уровнях фреймворка, а лучшая практика для языковых проектов Go — разделение модулей по вертикали в соответствии с обязанностями и разделение кода на несколько функциональных групп.package
, дело не в том, что в Go нет горизонтального разделения модулей, простоpackage
В качестве наименьшей детализации управления доступом в языке Go мы должны следовать дизайну верхнего уровня, чтобы таким образом создавать высокосвязные модули.
явный и неявный
От изучения и использования языка Go до участия в некоторых проектах Golang с открытым исходным кодом в сообществе автор обнаружил, что языковое сообщество Go очень важно для языкового сообщества Go.Явная инициализация, вызов метода и обработка ошибокОчень уважаемые, такие фреймворки, как Spring Boot и Rails, широко приняли центральную идею «конвенция важнее конфигурации», упрощая рабочую нагрузку разработчиков и инженеров.
Однако, хотя сообщество языка Go достигло большого консенсуса и соглашений, начиная с дизайна языка и использования инструментов, мы можем обнаружить, что явный вызов методов и обработка ошибок приветствуются.
init
Здесь мы начнем с очень распространенной функцииinit
Например, ввести уважение к явному вызову в языковом сообществе Go; я полагаю, что многие люди в той или иной степениpackage
Я прочитал этот код в:
var grpcClient *grpc.Client
func init() {
var err error
grpcClient, err = grpc.Dial(...)
if err != nil {
panic(err)
}
}
func GetPost(postID int64) (*Post, error) {
post, err := grpcClient.FindPost(context.Background(), &pb.FindPostRequest{PostID: postID})
if err != nil {
return nil, err
}
return post, nil
}
Хотя этот код компилируется и работает правильно, код здесьinit
Функция на самом деле неявно инициализирует ресурсы соединения grpc, если другойpackage
зависит от текущего пакета, инженер, внедривший эту зависимость, может сильно растеряться, столкнувшись с ошибками, потому что вinit
Инициализация таких ресурсов в функции требует очень много времени и чревата проблемами.
Более разумный подход на самом деле таков: сначала мы определяем новыйClient
struct и метод для инициализации структурыNewClient
функция, эта функция получает соединение grpc в качестве входного параметра и возвращаетPost
клиент ресурса,GetPost
становится методом этой структуры всякий раз, когда мы вызываемclient.GetPost
Сохраненное в структуре соединение grpc используется, когда:
// pkg/post/client.go
type Client struct {
grpcClient *grpc.ClientConn
}
func NewClient(grpcClient *grpcClientConn) Client {
return &Client{
grpcClient: grpcClient,
}
}
func (c *Client) GetPost(postID int64) (*Post, error) {
post, err := c.grpcClient.FindPost(context.Background(), &pb.FindPostRequest{PostID: postID})
if err != nil {
return nil, err
}
return post, nil
}
Код для инициализации соединения grpc должен быть помещен вmain
функция илиmain
вызовы функций выполняются в других функциях, если мы находимся вmain
Зависимость явной инициализации в функции очень проста для понимания другими инженерами.Начнем сmain
Начало функции может упорядочить весь процесс запуска программы.
// cmd/grpc/main.go
func main() {
grpcClient, err := grpc.Dial(...)
if err != nil {
panic(err)
}
postClient := post.NewClient(grpcClient)
// ...
}
Каждый модуль будет формировать древовидную структуру и отношения зависимости.Модуль верхнего уровня будет содержать интерфейс или структуру модуля нижнего уровня, и не будет никаких изолированных и не связанных объектов.
Два, которые появляются на картинке выше
Database
по фактуmain
соединения с базой данных, инициализированные в функциях, которые могут представлять одно и то же соединение с базой данных в памяти во время выполнения проекта.
когда мы используемgolangci-lintи включиgochecknoinits
иgochecknoglobals
При статической проверке это на самом деле строго ограничивает нашу способностьinit
Использование функций и глобальных переменных.
Конечно, это не означает, что мы не должны использоватьinit
Функции, как возможность, которую язык Go дает разработчикам, потому что он может неявно выполнять некоторый код при импорте пакета, поэтому мы должны использовать их с осторожностью.
Некоторые рамки будут вinit
чтобы определить, соблюдены ли предварительные условия для использования, но для многих веб-служб или API-сервисов требуется большое количествоinit
Часто означает снижение качества кода и неразумный дизайн.
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath may be overridden by --gopath flag on command line.
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}
Приведенный выше код на самом делеEffective GoПредставляемinit
Использование метода показано в примере кода, что является разумнымinit
пример использования функции, мы не должныinit
В процессе выполняется тяжелая логика инициализации, но делаются некоторые простые и легкие суждения о предварительных условиях.
error
Еще одна вещь, которую следует представить, — это механизм обработки ошибок языка Go.Хотя обработка ошибок Golang уже давно подвергается критике со стороны разработчиков, инженеры пишут каждый день.if err != nil { return nil, err }
Логика обработки ошибок на самом деле заключается в том, чтобы явно обрабатывать ошибки, обращать внимание на все вызовы методов, которые могут вызвать ошибки, и передавать их в модуль верхнего уровня, когда они не могут быть обработаны.
func ListPosts(...) ([]Post, error) {
conn, err := gorm.Open(...)
if err != nil {
return []Post{}, err
}
var posts []Post
if err := conn.Find(&posts).Error; err != nil {
return []Post{}, err
}
return posts, nil
}
Приведенный выше код просто показывает общую логику обработки ошибок в языке Go, мыне следуетВ этом методе инициализируйте подключение к базе данных.
Хотя в Голанге есть похожие Java или Rubytry/catch
ключевое слово, но мало кто будет использовать его в кодеpanic
иrecover
реализовать обработку ошибок и исключений, а такжеinit
Как и функции, язык Go дляpanic
иrecover
также используется с большой осторожностью.
Когда мы имеем дело с логикой, связанной с ошибками в языке Go, наиболее важными на самом деле являются следующие вещи:
- использовать
error
Реализовать обработку ошибок — хоть это и выглядит очень неловко; - Выбросить ошибку на верхний уровень для обработки— должен ли метод возвращать
error
Это также требует от нас тщательного обдумывания Когда ошибка выбрасывается вверх, мы можем пройтиerrors.Wrap
Иметь некоторую дополнительную информацию для облегчения принятия решений на высшем уровне; - обрабатывать все возможные ошибки— Все, что может вернуть ошибку, в конечном итоге вернет ошибку, и всестороннее рассмотрение может помочь нам построить более надежный проект;
резюме
При использовании языка Go автор может глубоко оценить его поддержку явного вызова методов и обработки ошибок, что не только помогает другим разработчикам проекта быстро понять контекст, но также помогает нам создавать более надежную и отказоустойчивую разработку с лучшим надежность и ремонтопригодность.
ориентированный на интерфейс
Интерфейсно-ориентированное программирование — тема избитая.интерфейсЕго функция состоит в том, чтобы обеспечить определенный средний уровень для модулей на разных уровнях.Верхний поток больше не должен полагаться на конкретную реализацию нисходящего потока, а восходящий и нисходящий потоки полностью разделены.
Этот метод программирования рекомендуется не только в языке Go, но и почти во всех языках программирования, мы будем рекомендовать этот метод программирования, он обеспечивает нашим программам очень сильную гибкость, надежные проекты языка Go, совершенно невозможно обойтись без использования интерфейсов.
Если слегка масштабированный проект не показывает никакихtype ... interface
определение , то автор может сделать вывод, что это высокая вероятностьПлохое инженерное качество и малое покрытие юнит-тестамипроект, нам действительно нужно серьезно подумать о том, как провести рефакторинг проекта с использованием интерфейсов.
Модульное тестирование является одним из наиболее эффективных способов обеспечения качества проекта и имеет наивысшую отдачу от инвестиций.Как статический язык Golang, трудно писать модульные тесты с достаточным покрытием (минимальное покрытие основной логики), потому что мы не люблю динамические языкислучайныйИзмените поведение функций и методов, и интерфейс станет нашей спасительной соломинкой. Написание хорошего абстрактного интерфейса и изоляция зависимостей через интерфейс может помочь нам эффективно улучшить качество и тестируемость проекта. Мы подробно представим его в следующий раздел Как писать модульные тесты.
package post
var client *grpc.ClientConn
func init() {
var err error
client, err = grpc.Dial(...)
if err != nil {
panic(err)
}
}
func ListPosts() ([]*Post, error) {
posts, err := client.ListPosts(...)
if err != nil {
return []*Post{}, err
}
return posts, nil
}
Приведенный выше код на самом деле не является хорошо разработанным кодом, он не только вinit
В функции неявно инициализирована глобальная переменная соединения grpc, и нетListPosts
Выставлять через интерфейс, который будет делать зависимостиListPosts
Модули верхнего уровня трудно тестировать.
Мы можем использовать следующий код, чтобы переписать исходную логику, упростив ту же логику для тестирования и обслуживания:
package post
type Service interface {
ListPosts() ([]*Post, error)
}
type service struct {
conn *grpc.ClientConn
}
func NewService(conn *grpc.ClientConn) Service {
return &service{
conn: conn,
}
}
func (s *service) ListPosts() ([]*Post, error) {
posts, err := s.conn.ListPosts(...)
if err != nil {
return []*Post{}, err
}
return posts, nil
}
- через интерфейс
Service
незащищенныйListPosts
метод; - использовать
NewService
инициализация функцииService
Реализация интерфейса и через тело приватного интерфейсаservice
удерживать соединение grpc; -
ListPosts
Больше не полагаются на глобальные переменные, а полагаться на тело интерфейсаservice
соединение установлено;
Когда мы рефакторим код таким образом, мы можемmain
Явно инициализировать подключение и создание grpc в функцииService
Реализация интерфейса и вызовListPosts
метод:
package main
import ...
func main() {
conn, err = grpc.Dial(...)
if err != nil {
panic(err)
}
svc := post.NewService(conn)
posts, err := svc.ListPosts()
if err != nil {
panic(err)
}
fmt.Println(posts)
}
Такой способ организации кода с использованием интерфейсов очень распространен в языке Go, и мы должны максимально использовать эту идею и шаблон в нашем коде для предоставления внешних функций:
- С заглавной буквы
Service
метод внешнего воздействия; - использовать строчные буквы
service
Реализовать методы, определенные в интерфейсе; - пройти через
NewService
инициализация функцииService
интерфейс;
Когда мы используем описанный выше метод для организации кода, мы фактически разделяем зависимости различных модулей, что также следует предложению, часто упоминаемому в разработке программного обеспечения — «полагаться на интерфейс, а не на реализацию», то естьинтерфейсно-ориентированное программирование.
резюме
В этом разделе представлены три «элемента», которые часто имеют дело с языком Go:init
функция,error
И интерфейс, мы в основном должны передать три разных примера, чтобы каждый мог использовать его как можно больше.явный (явный) способНапишите код Go.
модульный тест
Проект с гарантированным качеством и инженерным качеством кода должен иметь относительно разумное покрытие для тестирования единиц. Проект без модульных тестов должен быть неквалифицирован или неважно. Тесты на единиц должны быть кодом, что все проекты должны иметь, и все тесты на единицу представляют собой возможную ситуацию СМодульное тестирование — это бизнес-логика.
Как инженеры-программисты, рефакторинг существующего проекта должен быть для нас относительно нормальным делом. Если в проекте нет модульного тестирования, нам сложно провести рефакторинг проекта без изменения существующей бизнес-логики. потеряться при рефакторинге, когдаcase
Инженера-разработчика может уже не быть в команде, а проектная документация может исчезнуть в архивах.wiki
(Больше проектов могут быть полностью незарегистрированы), единственным вещам, которые мы можем доверять рефакторингу, являются текущая логика кода (вероятно, неправильный) и единичные тесты (вероятно, нет).
Подводя итог, можно сказать, что отсутствие юнит-тестирования будет означать не только более низкое качество проектирования, но и сложность рефакторинга.Проект с юнит-тестированием не может гарантировать одинаковую логику до и после рефакторинга.Проект без юнит-тестирования не может гарантировать то же самое. логика до и после рефакторинга Качество самого проекта, скорее всего, будет иметь значение, не говоря ужеКак провести рефакторинг без потери бизнес-логики.
Тестируемый
Писать код несложно, но написать тестируемый код в проекте непросто, а элегантный код должен быть тестируемым.В этом разделе нам нужно обсудить, что такое код, который можно тестировать.
Если мы хотим выяснить, что можно тестировать, нам сначала нужно узнать, что такое тест? Под тестированием автор понимает контроль переменных.После выделения некоторых зависимостей в тестируемом методе, когда определены входные параметры функции, должно быть получено ожидаемое возвращаемое значение.
Как управлять модулями, которые зависят от тестируемого метода, очень важно при написании модульных тестов.Mock
Чтобы устранить неопределенность, чтобы уменьшить сложность каждого модульного теста, нам необходимо:
- Максимально уменьшите зависимости целевого метода, чтобы целевой метод зависел только от необходимых модулей;
- Зависимые модули также должны быть очень просты в использовании.
Mock
;
Выполнение модульных тестов не должно зависеть от каких-либо внешних модулей, будь то вызов внешних HTTP-запросов или данных в базе данных, мы должны максимально постараться смоделировать возможные ситуации, поскольку модульные тесты не являются интеграционными тестами, и их работа не должна зависеть от любая система, кроме кода проекта.
интерфейс
На языке Go, если мы вообще не используем интерфейсы, мы не можем писать код, который легко проверить. Как статический язык Голанга, только с помощью интерфейсов мы можем избавиться от дилеммы полагаться на конкретные реализации. Использование Интерфейсов могут принести нам более четкое абстракция помогает нам подумать о том, как разработать код, а также позволяет нам легче реализовывать зависимостиMock
.
Давайте вернемся к общим шаблонам, показанным во введении к интерфейсам в предыдущем разделе:
type Service interface { ... }
type service struct { ... }
func NewService(...) (Service, error) {
return &service{...}, nil
}
Приведенный выше код очень распространен в языке Go. Если вы не знаете, следует ли использовать интерфейс для предоставления услуг внешнему миру, вам следует использовать приведенный выше режим, чтобы без раздумий выставить метод. Этот режим можно использовать в большинство сценариев работают, по крайней мере, автор пока не видел ни одного неприменимого.
Простая функция
Другое предложение состоит в том, чтобы сохранить каждую функцию максимально простым. Простота здесь не только означает простую и одну функцию, но и означает, что функция легко понять, и именование является самоснабжением.
некоторые языкиlint
Инструмент на самом деле проверяет восприятие функции функции, то есть проверяет функции, которые появляются в функции.if/else
,switch/case
Количество веток и вызовов методов сообщит об ошибке при превышении согласованного порога. Rubocop в сообществе Ruby и вышеупомянутый golangci-lint имеют эту функцию.
Rubocop в сообществе Ruby имеет очень строгие ограничения на длину и сложность функций, по умолчанию количество строк функции не может превышать10
ОК, мы не можем понять сложность более чем7
, Кроме того, Rubocop на самом деле имеет другие ограничения сложности, такие как CyclomaticComplexity.Все эти ограничения сложности предназначены для обеспечения простоты и легкости понимания функции.
Организация
Стоит обсудить и организацию теста: файлы и код юнит-теста в Golang помещаются в ту же директорию, что и исходный кодpackage
организован,server.go
Тестовый код, соответствующий файлу, должен быть помещен в тот же каталогserver_test.go
в файле.
Если файл не начинается с_test.go
конец, когда мы бежимgo test ./pkg
Если тест кейс в файле не будет найден, код в нем не будет выполнен, что также является соглашением языка Go для метода организации тестирования.
Test
Наиболее распространенная организация модульных тестов по умолчанию — писать их на_test.go
В файле в конце все тестовые методы тоже начинаются сTest
запускается и принимает только одинtesting.T
Параметры типа:
func TestAuthor(t *testing.T) {
author := blog.Author()
assert.Equal(t, "draveness", author)
}
Если бы мы дали функции имяAdd
метод написания модульных тестов, то соответствующий метод тестирования обычно будет записан какTestAdd
, чтобы протестировать содержимое нескольких веток одновременно, мы можем организовать его следующим образомAdd
Функциональные тесты:
func TestAdd(t *testing.T) {
assert.Equal(t, 5, Add(2, 3))
}
func TestAddWithNegativeNumber(t *testing.T) {
assert.Equal(t, -2, Add(-1, -1))
}
В дополнение к этому распределению функционального теста на несколькоTest
метод, мы можем использоватьfor
Зацикливание для сокращения повторяющегося тестового кода, которое очень полезно в более сложных тестах, может уменьшить много повторяющегося кода, но также требует от нас тщательного проектирования:
func TestAdd(t *testing.T) {
tests := []struct{
name string
first int64
second int64
expected int64
} {
{
name: "HappyPath":
first: 2,
second: 3,
expected: 5,
},
{
name: "NegativeNumber":
first: -1,
second: -1,
expected: -2,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Add(test.first, test.second))
})
}
}
На самом деле, этот метод также может генерировать результаты теста в виде дерева.Add
Связанные тесты сгруппированы в группу, чтобы облегчить наше наблюдение и понимание.Однако этот метод организации тестирования требует от нас обеспечения универсальности тестового кода.Когда есть много функциональных зависимостей, нам часто нужно написать многоif/else
Условные операторы влияют на наше быстрое понимание теста.
Автор обычно использует первый метод организации, когда код теста относительно прост, и использует второй метод, когда много зависимостей и сложных функций, но это не вывод, нам нужно решить, как спроектировать тест в соответствии с реальным ситуация .
Suite
Второй, более распространенный способ — организация по кластерам, что на самом деле является простой инкапсуляцией стандартного метода тестирования языка Go.stretchr/testifyсерединаsuite
Пакеты организуют тесты:
import (
"testing"
"github.com/stretchr/testify/suite"
)
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
}
func (suite *ExampleTestSuite) TestExample() {
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
}
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
мы можем использоватьsuite
пакет, который организует тестовые кластеры в структуру,suite
который предоставилSetupTest
/SetupSuite
иTearDownTest
/TearDownSuite
Это метод ловушки до и после выполнения теста, а также до и после выполнения тестового кластера, в котором мы можем завершить инициализацию некоторых общих ресурсов и сократить код инициализации в тесте.
BDD
Последний способ организовать код — организовать модульные тесты в стиле BDD.ginkgoЭто наиболее распространенная структура BDD в сообществе Golang. Упомянутые здесь разработка на основе поведения (BDD) и разработка на основе тестирования (TDD) являются методологиями для обеспечения качества проекта. Чтобы практиковать этот тип мышления в проекте, все еще нужны некоторые изменения и адаптации в мышлении, то есть сначала написать спецификацию метода контракта модульного теста или теста поведения, а затем реализовать метод, чтобы позволить нашему тесту пройти.Это относительно научный метод, метод, он может вселить в нас большую уверенность.
Хотя нам не обязательно использовать идеи BDD/TDD для разработки проектов, мы можемСтильный способ использования BDDОрганизуйте очень читаемый тестовый код:
var _ = Describe("Book", func() {
var (
book Book
err error
)
BeforeEach(func() {
book, err = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`)
})
Describe("loading from JSON", func() {
Context("when the JSON fails to parse", func() {
BeforeEach(func() {
book, err = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488oops
}`)
})
It("should return the zero-value for the book", func() {
Expect(book).To(BeZero())
})
It("should error", func() {
Expect(err).To(HaveOccurred())
})
})
})
})
BDD Framework обычно содержитDescribe
,Context
а такжеIt
и т.д. блок кода, гдеDescribe
Роль заключается в описании независимого поведения кода.Context
несколько разных контекстов в рамках одного акта, окончательныйIt
Используемые для описания ожидаемого поведения, эти блоки кода в конечном итоге образуют предложение типа «опишите…, когда…, следует…», чтобы помочь нам быстро понять тестовый код.
Имитационный метод
Юнит-тест в проекте должен быть стабильным и не зависеть ни от какого внешнего проекта, это всего лишь проверка функций и методов в проекте, поэтому нам нужно Mock все сторонние нестабильные зависимости в юнит-тесте, т.е. , имитировать интерфейсы этих сторонних сервисов; кроме того, чтобы упростить контекст модульного теста, мы также имитируем другие модули в том же проекте, чтобы имитировать возвращаемые значения этих зависимых модулей.
Суть модульного тестирования состоит в том, чтобы изолировать зависимости и проверить правильность ввода и вывода.Являясь статическим языком, язык Go предоставляет меньше функций времени выполнения, что также затрудняет имитацию зависимостей в языке Go.
Основная функция Mock — гарантировать, что контекст тестируемого метода фиксирован.В это время, независимо от того, сколько модульных тестов мы запускаем для текущего метода, если бизнес-логика не меняется, он должен возвращать точную тот же результат. Подробно представлены различные методы Mock. Прежде чем мы должны сначала очистить некоторые общие зависимости. Общие зависимости функции или метода могут быть следующими:
- интерфейс
- база данных
- HTTP-запрос
- Редис, кеш и т.д.
Эти различные сценарии в основном охватывают ситуации, возникающие при написании модульных тестов.Мы расскажем, как справляться с вышеупомянутыми различными зависимостями, в следующем содержании.
интерфейс
Первое, что нужно представить, это на самом деле самый распространенный и распространенный метод Mock в языке Go, то есть метод, который может Mock интерфейса.golang/mockFramework, который может генерировать реализации Mock на основе интерфейсов, предположим, что у нас есть следующий код:
package blog
type Post struct {}
type Blog interface {
ListPosts() []Post
}
type jekyll struct {}
func (b *jekyll) ListPosts() []Post {
return []Post{}
}
type wordpress struct{}
func (b *wordpress) ListPosts() []Post {
return []Post{}
}
Наш блог может использоватьjekyll
илиwordpress
как двигатель, но они оба обеспечиваютListsPosts
Метод используется для возврата списка всех статей, в это время нам нужно определитьPost
Интерфейс, интерфейс, которому необходимо следоватьBlog
Структура должна реализоватьListPosts
метод.
когда мы определяемBlog
После интерфейса верхний слойService
Вам больше не нужно полагаться на конкретную реализацию движка блога, вам нужно полагаться только наBlog
Интерфейс может завершить функцию сбора пакетов статей:
package service
type Service interface {
ListPosts() ([]Post, error)
}
type service struct {
blog blog.Blog
}
func NewService(b blog.Blog) *Service {
return &service{
blog: b,
}
}
func (s *service) ListPosts() ([]Post, error) {
return s.blog.ListPosts(), nil
}
если мы хотимService
Для тестирования мы можем использоватьmockgen
Генерация команд инструментаMockBlog
struct используйте следующую команду:
$ mockgen -package=mblog -source=pkg/blog/blog.go > test/mocks/blog/blog.go
$ cat test/mocks/blog/blog.go
// Code generated by MockGen. DO NOT EDIT.
// Source: blog.go
// Package mblog is a generated GoMock package.
...
// NewMockBlog creates a new mock instance
func NewMockBlog(ctrl *gomock.Controller) *MockBlog {
mock := &MockBlog{ctrl: ctrl}
mock.recorder = &MockBlogMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockBlog) EXPECT() *MockBlogMockRecorder {
return m.recorder
}
// ListPosts mocks base method
func (m *MockBlog) ListPosts() []Post {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListPosts")
ret0, _ := ret[0].([]Post)
return ret0
}
// ListPosts indicates an expected call of ListPosts
func (mr *MockBlogMockRecorder) ListPosts() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPosts", reflect.TypeOf((*MockBlog)(nil).ListPosts))
}
этот абзацmockgen
Сгенерированный код очень длинный, поэтому мы показываем только часть этого. Его функция заключается в том, чтобы помочь нам проверить входные параметры любого интерфейса и имитировать возвращаемое значение интерфейса; и в процессе создания реализации MOCK, автор суммирует некоторые, которые могут быть общим опытом:
- существует
test/mocks
Все реализации Mock помещаются в каталог. Подкаталог совпадает с вторичным каталогом файла, в котором расположен интерфейс. Здесь расположение исходного файла находится вpkg/blog/blog.go
, его вторичный каталогblog/
, поэтому соответствующая имплементация Mock будет сгенерирована дляtest/mocks/blog/
в справочнике; - уточнить
package
заmxxx
,дефолтmock_xxx
кажется очень избыточным, вышеblog
Пакет Mock, соответствующий пакету,mblog
; -
mockgen
поместите команду вMakefile
серединаmock
При унифицированном управлении снижение появления предков;mock: rm -rf test/mocks mkdir -p test/mocks/blog mockgen -package=mblog -source=pkg/blog/blog.go > test/mocks/blog/blog.go
После того, как мы сгенерировали приведенный выше код реализации Mock, мы можем использовать следующие методы дляService
Напишите модульный тест, этот код проходитNewMockBlog
генерироватьBlog
Смоделируйте реализацию интерфейса, затем передайтеEXPECT
Метод, управляющий реализацией, будет вызван, когдаListPosts
возвращает пустойPost
Множество:
func TestListPosts(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockBlog := mblog.NewMockBlog(ctrl)
mockBlog.EXPECT().ListPosts().Return([]Post{})
service := NewService(mockBlog)
assert.Equal(t, []Post{}, service.ListPosts())
}
Из-за текущегоService
только зависит толькоBlog
Реализация , поэтому на данный момент мы можем утверждать, что текущий метод вернет[]Post{}
, то возвращаемое значение нашего метода связано только с переданными параметрами (хотяListPosts
Метод не имеет параметров), мы можем уменьшить контекст одного внимания и обеспечить стабильность и достоверность теста.
Это самый стандартный метод написания юнит-тестов на языке Go, все зависит отpackage
Причина, по которой внутри, так и снаружи проекта следует обрабатывать таким образом (в случае интерфейса), если нет языка интерфейса Go, тесты единиц будут очень сложными для записи, именно поэтому интерфейс сможет судить, являются качество проекта от проекта a.
SQL
Другая общая зависимость в проектах на самом деле является базой данных. Когда мы столкнулись с зависимостями базы данных, мы обычно используемsqlmockЧтобы имитировать подключение к базе данных, когда мы используем sqlmock, мы напишем модульный тест, подобный следующему:
func (s *suiteServerTester) TestRemovePost() {
entry := pb.Post{
Id: 1,
}
rows := sqlmock.NewRows([]string{"id", "author"}).AddRow(1, "draveness")
s.Mock.ExpectQuery(`SELECT (.+) FROM "posts"`).WillReturnRows(rows)
s.Mock.ExpectExec(`DELETE FROM "posts"`).
WithArgs(1).
WillReturnResult(sqlmock.NewResult(1, 1))
response, err := s.server.RemovePost(context.Background(), &entry)
s.NoError(err)
s.EqualValues(response, &entry)
s.NoError(s.Mock.ExpectationsWereMet())
}
Наиболее распространенными методами являютсяExpectQuery
иExpectExec
, первый в основном используется для моделирования операторов SQL-запросов, а второй используется для имитации добавления и удаления SQL.Из приведенных выше примеров мы можем видеть, как используются эти два метода.Рекомендуется прочитать соответствующиеДокументацияПопробуйте использовать снова.
HTTP
HTTP-запрос также зависит, мы часто встречаемся в проекте,httpmockЭто пакет для всех HTTP-зависимостей Mock.Он использует сопоставление с образцом для сопоставления URL-адресов HTTP-запросов и возвращает предварительно заданный ответ при совпадении определенного запроса.
func TestFetchArticles(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))
...
}
Если вы столкнулись с зависимостями HTTP-запроса, вы можете использовать приведенный вышеhttpmockПакет имитирует зависимые HTTP-запросы.
патч обезьяны
Последний патч с обезьянами, который мы представили, на самом деле является большим убийцей.bouk/monkeyМожно изменить, заменив указатели функцийпроизвольная функцияПоэтому, если ни один из вышеперечисленных методов не может удовлетворить наши потребности, мы можем полагаться только на более хакерский метод исправления обезьян, Mock:
func main() {
monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
s := make([]interface{}, len(a))
for i, v := range a {
s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
}
return fmt.Fprintln(os.Stdout, s...)
})
fmt.Println("what the hell?") // what the *bleep*?
}
Однако использование этого метода имеет некоторые ограничения, поскольку он заменяет указатель функции во время выполнения, поэтому, если вы столкнетесь с некоторыми простыми функциями, такими какrand.Int63n
иtime.Now
, компилятор может напрямую встроить эту функцию в код, где на самом деле происходит вызов, и не будет вызывать исходный метод, поэтому использование этого метода часто требует от нас указывать дополнительно при тестировании-gcflags=-l
Отключает встроенную оптимизацию компилятора.
$ go test -gcflags=-l ./...
bouk/monkeyREADME дает некоторые предостережения относительно его использования, кроме встроенной компиляции, на что нам нужно обратить внимание:Не используйте исправление обезьян вне модульных тестов, мы должны использовать этот метод только при необходимости, например, в зависимой сторонней библиотеке, которая не предоставляетinterface
или изменитьtime.Now
а такжеrand.Int63n
и т. д., когда возвращаемое значение встроенной функции используется для тестирования.
Теоретически с помощью Monkey Patch мы можем запускать все функции на языке Mock Go во время выполнения, что также дает нам окончательное решение для модульного тестирования, зависящего от MOCK.
утверждение
В конце мы просто вводим вспомогательное модульное тестированиеassertpackage, который предоставляет множество методов утверждений, помогающих нам быстро проверить ожидаемое возвращаемое значение и снизить нашу рабочую нагрузку:
func TestSomething(t *testing.T) {
assert.Equal(t, 123, 123, "they should be equal")
assert.NotEqual(t, 123, 456, "they should not be equal")
assert.Nil(t, object)
if assert.NotNil(t, object) {
assert.Equal(t, "Something", object.Value)
}
}
Здесь мы также кратко покажемassert
Для получения более подробной информации вы можете прочитать соответствующую документацию, поэтому я не буду показывать ее здесь.
резюме
Если вы вообще не писали модульные тесты или раньше не писали модульные тесты на языке Go, я считаю, что эта статья дала достаточно контекста, чтобы помочь нам начать это делать.Что нам нужно знать, так это то, что модульное тестирование нам не мешает. y прогресс в разработке, он дает уверенность в том, что мы приступим к работе, и является методом обеспечения качества с самой высокой окупаемостью инвестиций.
Обучение написанию хорошего модульного теста определенно потребует некоторого обучения и дискомфорта и даже повлияет на нашу эффективность разработки в краткосрочной перспективе, но после знакомства с этим набором процессов и интерфейсов модульный тест будет нам очень полезен. Модульные тесты представляют собой бизнес-логику.Выполнение модульных тестов при каждой отправке может помочь нам определить, что новый код не повлияет на существующую бизнес-логику с высокой вероятностью, что может значительно снизить риск рефакторинга и количество онлайн-аварий.
Суммировать
В этой статье мы познакомим вас с тем, как писать элегантный код на языке Go с трех аспектов.Автор дает максимально простой и эффективный метод:
- Спецификация кода: Используйте вспомогательные инструменты, чтобы помочь нам автоматически проверять код каждый раз, когда мы отправляем PR, уменьшая нагрузку на ручную проверку инженеров;
- Лучшие практики
- Структура каталогов: следуйте общепринятому языковому сообществу Go.Структура каталогов, снизить коммуникационную стоимость проекта;
- Разделение модулей: разделение различных модулей в соответствии с их обязанностями не должно появляться в проектах языка Go.
model
,controller
Такое имя пакета нарушает идею дизайна языка верхнего уровня; - Явное против неявного: устраните как можно больше
init
функция, которая гарантирует явный вызов метода и обработку ошибок; - Ориентированность на интерфейс: Ориентированность на интерфейс — это метод разработки, поддерживаемый языком Go, и он также может предоставить нам удобство для написания модульных тестов.Мы должны следовать фиксированному шаблону для предоставления внешних функций;
- использовать верхний регистр
Service
метод внешнего воздействия; - использовать строчные буквы
service
Реализовать методы, определенные в интерфейсе; - пройти через
func NewService(...) (Service, error)
инициализация функцииService
интерфейс;
- использовать верхний регистр
- Модульное тестирование: наиболее эффективный способ обеспечить качество разработки проекта;
- Тестируемый: это означает интерфейсно-ориентированное программирование и сокращение логики, содержащейся в одной функции, с использованием «маленьких методов»;
- Организация: используйте язык тестовой среды по умолчанию, с открытым исходным кодом.
suite
Или рационально организовать юнит-тесты в стиле BDD; - Мок-методы: четыре различных фиктивных метода модульного тестирования;
- Утверждение: использовать сообществоtestifyВозвращаемое значение метода быстрой проверки;
Само по себе написание элегантного кода — непростая задача, она требует от нас постоянного обновления и оптимизации нашей системы знаний, переворачивания предыдущего опыта и продолжения улучшения и рефакторинга проекта. постоянно подвергаться рефакторингу). Поведение случайного стека кода не поощряется и не должно происходить. Каждая строка кода должна быть спроектирована и разработана в соответствии с самыми высокими стандартами. Только так мы можем гарантировать качество проектирования.
Автор также усердно работал над тем, чтобы научиться писать более элегантный код. Написание хорошего кода — действительно непростая задача. Автор также надеется помочь инженерам, использующим язык Go, написать больше проектов в стиле Golang с помощью этой статьи.
Reference
- goimports vs gofmt
- Style guideline for Go packages
- Standard Package Layout
- Internal packages in Go
- The init function · Effective Go
О картинках и репринтах
В этой работе используетсяМеждународная лицензия Creative Commons Attribution 4.0Лицензия. При перепечатке просьба указывать ссылку на оригинал.При использовании рисунка просьба сохранять все содержание на рисунке.Его можно соответствующим образом увеличить и ссылку на статью,где находится рисунок,прикрепить к ссылке.Картинка нарисована с помощью Скетча.
Публичный аккаунт WeChat
О комментариях и сообщениях
Если эта статьяКак написать элегантный код GolangЕсли у вас есть какие-либо вопросы по содержанию, пожалуйста, оставьте сообщение в системе комментариев ниже, спасибо.Оригинальная ссылка:Как написать элегантный код на golang · Программирование
Follow: Draveness · GitHub