Uber/Google Golang编码标准深度分析

news2024/12/27 3:41:18

良好的代码风格对于开发优秀的产品至关重要,本文通过分析比较三部流传甚广的Golang代码风格指南,介绍了Go代码风格要点,并介绍了通过工具实现代码检查的方式。原文: Mastering Go: In-Depth Analysis of Uber and Google’s Coding Standards[1]

题图来自Unsplash
题图来自Unsplash

在软件开发过程中,遵守代码风格指南和编码标准不仅是为了保持代码在视觉上的一致性,更重要的是为了使代码更易于理解、维护以及避免错误,以简单高效著称的 Golang 也不例外。本文通过深入研究从 Effective Go[2]Google Go Style Guide[3]Uber Go Style Guide[4] 等资料中获得的通用标准和实践,揭示 Go 编程风格指南的精髓,讨论有助于执行标准的工具,强调自动化检查的局限性,并指出开发人员应内化的关键方面。

简化Go:顶级风格指南的启示

三份指南的侧重点各有不同,但都是关于Go编码风格、格式和惯例的。

  • Effective Go 包含所有数据结构的基本使用、初始化、控制结构、并发和错误处理,对初学者更友好。例如,介绍了什么是 init() [5]
  • Uber Go Style Guide 深入探讨了现实中的 Go 编码实践。例如,它建议避免使用 init(),并解释了原因。
  • Google Go Style Guide 是 Uber 指南的升级版,进一步扩展了详细示例和最佳实践。例如,介绍了注释,并在格式、包注释和文档注释部分给出了示例。
关于编码约定的重要启示
命名约定

命名不仅是代码的外观特征,也是任何人阅读代码时的第一行文档,有效的命名可以让代码不言自明。

如果有其他语言的使用经验,你会发现这些指南有着几乎相同的标准,例如变量应该使用有意义的名称,常量应该全部使用大写字母,包名应该简洁明了。不过,有时"经验"恰恰相反,例如,Go语言中的Getter/Setter方法不需要以GetSet开头,而在Java中则需要;在Go语言中,最好不要在包中使用通用名称[6]

Uber 指南中只在包名[7]函数名[8]错误名[9]中提到了命名约定,并且没有提供任何示例,而在 Google 指南的第一章中,命名约定就涵盖了变量名[10]接收器名[11]常量名[12]等命名方法,以及需要避免的重复[13]getter函数[14]

错误处理

Go 采用了独特的错误处理方法,鼓励开发人员检查出现的错误,并通过及时、可预测的方式进行处理。正确的错误处理还包括为错误提供上下文,使调试更加简单。

无论是 Uber 指南还是 Google 指南,关于错误处理[15]的内容都大同小异,包括错误定义、错误返回和处理,以及panic处理。

格式化

清晰一致的格式使代码具有很高的可理解性和可读性。常见的代码格式,如缩进、括号对齐、组合变量定义、行的最大长度等,不仅适用于 Go,也适用于所有语言。Google 指南中的字面量格式[16]函数格式[17]以及条件和循环[18]提供了很好的参考。

数据结构的构建和使用

这三部指南都涵盖了数据结构,如mapsslicesarrayschannels,每种结构都有其特定用途。

  • 不同数据类型的零值。例如, slice 不需要初始化,而是通过声明 var s []int 直接使用,那么 s := []int{} 就是"坏"代码。
  • 不同数据类型的初始化。例如通过 new进行 make、声明 slicemap 的容量,以提高代码效率。在 Uber 指南中的 初始化结构 [19]初始化Maps [20]中,可以找到更多做法。
  • chanfile 等资源的使用和回收。例如,Uber 指南中的 channel大小为一或无 [21]章节会告诉你如何确定 chan 是否需要缓冲区以及设置多少缓冲区。

此外,我们还可以在 Google 指南中了解更多有关 Go 特定数据结构(如接口[22]goroutine、生命周期[23]泛型[24])的代码风格。

测试

Google 指南强调测试的清晰性和可维护性。

  • 为输出和关键功能编写测试。
  • 针对多种场景使用表格驱动测试。
  • Test 开头,对测试功能进行描述性命名。
  • 保持测试简单,避免测试 Go 标准库。
  • 记录复杂的测试逻辑,以便更好理解。

Uber 指南只提到了表格测试模式[25]

并发性

并发是 Go 的固有特性,其主要特点是使用 goroutines 和 channels,使代码高效且并行。不过,需要进行谨慎的同步和通信,从而避免死锁和竞争条件等常见陷阱。

在Uber 指南中,并发相关部分穿插在使用 go.uber.org/atomic[26]避免全局变量[27]不要忘记goroutines[28]接收器和接口[29]channel大小是1或空[30]等小节中。在Google 指南中,并发性在最佳实践章节中有详细介绍。

错误代码示例

请看下面的代码片段,其中充斥着许多常见错误,如无组织导入、命名不当、channel错误、无效代码等。

package main

import (
 "time"

  "fmt"       
 "io/ioutil" 
 "math/rand"
 "os"
)


var globalData int 

const a = 1
const b = 2

type user struct {
 id        int       `json: "id"` // BadSyntax: should be `json:"id"`(no extra space)
 nameStr   string    
 data      *userData 
 LinkedUrl string    
}

type userData struct {
 description string
 detailsID   int
}


func processdata(u *user, params ...string) { 
 if len(params) > 10 { 
  fmt.Println("Too many parameters")
  return
 }

 
 file, err := os.Open("data.txt")
 if err != nil {
  fmt.Println(err) 
  return
 }
 defer file.Close() 

 content, _ := ioutil.ReadAll(file) 
 fmt.Println("File content:"string(content))

 src := rand.NewSource(time.Now().UnixNano())
 rnd := rand.New(src)
 fmt.Println("Random number:", rnd.Intn(100))

 go func() {
  fmt.Println("Asynchronous operation")
  // Assume more complex logic...
 }()

 if u.id > 100 {
  fmt.Println("ID is high")
 } else {
  fmt.Println("ID is normal")
 }

 u.data = &userData{description: "", detailsID: 1}

 dataSlice := []int{} 
 for i := 0; i < 100; i++ {
  dataSlice = append(dataSlice, i)
 }

 ch := make(chan int10)
 ch <- 1 
 
 if u.nameStr == "" {
  fmt.Println("Name is empty")
 } else {
  fmt.Println("Name is not empty")
 }
 unreachableCode()

 if err := doSomething(12""3""4""5); err != nil {
  panic(err) 
 }
}

func unreachableCode() {
 return
 fmt.Println("This will never be called")
}

func uncalledFunc() {
 return
}

func doSomething(p1 int, p2 int, p3 string, p4 int, p5 string, p6 int, p7 string, p8 int) error {
 return nil
}

func main() {
 u := &user{id: 1, nameStr: "John Doe"}
 processdata(u, "param1""param2""param3")
}

你可以发现多少问题?可以在Github上找到我的答案[31]

工具化的合规之路
Go 工具

gofmtgovetgolint 的正式设计目的是促进 Go 代码的合规性。

gofmt[32] 主要用于格式化,确保代码遵循标准格式约定,例如:

  • 一致的缩进和间距:与推荐的代码结构和可读性做法保持一致。
  • 正确的换行和括号位置:遵守控制结构和复合类型的惯例。
  • 有组织的导入:与建议的导入语句分组和排序保持一致。

govet[33] 会检查代码是否存在潜在错误,如无法实现的代码或有问题的类型断言,并与风格指南的健壮性和可维护性目标保持一致。

  • 错误处理:检测无法访问的代码或可能被绕过的检查。
  • 并发:识别goroutine和channel使用中的常见错误。
  • 代码正确性:标记可疑结构,如格式字符串不正确的 Printf 调用。
  • 变量声明:警告变量可能无意中被覆盖。

golint[34] 侧重于风格,标记不理想的代码模式或偏离Go风格的代码,特别是处理以下问题:

  • 命名约定:确保变量、常量、函数和其他标识符按照 Go 的大小写敏感规则正确命名。
  • 注释格式化:检查导出类型、函数和方法的注释是否格式正确、位置恰当。
  • 导出实体:验证导出的函数、变量和类型是否有正确的文档记录。
  • 代码简化:标记可简化的不必要的复杂结构。
golangci-lint

golangci-lint 包含 golint,并在社区支持下引入了更多扩展,解决了 Effective Go、Uber 和 Google 指南中强调的各种问题。

  • 错误处理:确保正确检查和处理错误。
  • 代码复杂性:标记过于复杂的函数,提高可读性和可维护性。
  • 并发问题:检测并发原语的竞争条件和不当使用。
  • 性能优化:识别低效代码模式,加以改进以提高性能。
  • 编码风格:执行命名约定和其他与风格相关的准则,与惯用的 Go 实践保持一致。
实践

现在我们尝试使用工具来检查"错误代码示例"。

首先使用 gofmtgovetgolint,分别运行以下脚本。

#!/bin/bash

echo "Running gofmt..."
# List & Write formatting differes and results to stdout
gofmt -l -w .

echo "Running go vet..."
go vet ./...

echo "Running golint..."
# Show as many warnings as possible (default threshold min_confidence=0.8)
golint -min_confidence=0.1 ./...
alt

gofmt 没有输出。原因是我们在使用集成开发环境(IDE)时没有额外添加格式化功能,例如,当我使用 VSCode 和 Golang 扩展时,一些 gofmt 功能(如缩进和导入排序)会默认提供,而下面的导入问题超出了 gofmt 的能力范围。

import (
 "time"
 // 额外的空行,gofmt无法解决
 "fmt"       // BadImportOrdering: "fmt"应该与其他标准库导入分组
 "io/ioutil" // 弃用api: io/ioutil自Go 1.19起已弃用
 "math/rand"
 "os"
)

govet 只能发现两个问题,即 JSON 标记语法和无法访问的代码。

golint 还发现了两个小的编码规范问题,在将阈值调整到最低后,软件包注释和命令都不见了。

golangci-lint 性能怎么样?

首先,我们配置一下 golangci-lint 的执行,有两种方法:一种是通过命令行启用参数 --enable-all ,然后执行以下命令将所有警告和错误信息导入 issues.txt 文件。

golangci-lint run --enable-all --out-format=json ./... | jq 'del(.Report)' > issues.txt

另一种方法是配置 .golangci.yml 文件以启用所有检查,然后执行 golangci-lint run --out-format=json ./...| jq 'del(.Report)' > issues.txt

run:
  timeout: 5m
  modules-download-mode: readonly

linters:
  enable-all: true

issues:
  exclude-use-default: false
  max-issues-per-linter: 0
  max-same-issues: 0

太棒了!golangci-lint 返回了 37 个问题,例如无效代码,如 struct 中的 globalData、全局常量 a、b 和函数 uncalledFuncerrorHandling里的未处理错误;格式化问题,如注释中缺少句号和结尾的空白;API 使用问题,如使用 math/rand 而非 crypto/rand;不可调用代码,如 unreachableCode 方法中的代码;命名问题,如 LinkedUrl 等。如果感兴趣,请查看完整问题列表[35]

超越工具
工具有局限性

golangci-lint 在查找问题方面表现出色,但仍有局限性,更不用说其他 Go 工具了。

例如,在上面的示例中,doSomething 方法传递了 8 个参数,但却没有检测到过长的参数列表,这无疑违反了代码约定。

此外,在第 72 行中,代码使用 []int{} 来初始化切片,根据 Uber 和 Google 指南,应该避免使用这种方法,因为 nil 是有效切片[36],我们应该在声明后直接使用切片。

工具无法做到的
  • 命名的上下文:工具无法判断名称是否反映了变量的目的,选择有意义的名称取决于开发者。
  • 正确处理错误:虽然工具可以捕捉被忽略的错误,但提供适当的上下文和优雅的处理错误是开发人员的责任。
  • 优化数据结构:了解复杂性和选择正确的数据结构超出了自动化工具的范畴。
  • 并发模式:要正确实现并发模式、避免死锁并确保 goroutines 之间的高效通信,就必须深入了解 Go 的并发模型。
  • 设计选择:何时使用接口、指针或特定数据结构等决定取决于开发人员的判断。
总结

虽然 gofmtgo vetgolintgolangci-lint 是维护干净、可读性和标准代码库不可或缺的工具,但对 Go 最佳实践和常见陷阱的细致入微的理解才是精通 Go 的开发人员的与众不同之处。优秀的开发者会利用工具提高工作效率,但依靠自己的判断力和知识实现卓越。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

Mastering Go: In-Depth Analysis of Uber and Google’s Coding Standards: https://laiyuanyuan-sg.medium.com/mastering-go-in-depth-analysis-of-uber-and-googles-coding-standards-3b3fb9391ee3

[2]

Effective Go: https://go.dev/doc/effective_go

[3]

Google Go Style Guide: https://google.github.io/styleguide/go/decisions

[4]

Uber Go Style Guide: https://github.com/uber-go/guide/blob/master/style.md

[5]

Effective Go: init: https://go.dev/doc/effective_go#init

[6]

Avoid package names like base util or common: https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common

[7]

Uber Go Sytle Guide: package name: https://github.com/uber-go/guide/blob/master/style.md#package-names

[8]

Uber Go Style Guide: function name: https://github.com/uber-go/guide/blob/master/style.md#function-names

[9]

Uber Go Style Guide: error naming: https://github.com/uber-go/guide/blob/master/style.md#error-naming

[10]

Google Go Style Guide: variable names: https://google.github.io/styleguide/go/decisions#variable-names

[11]

Google Go Style Guide: receiver names: https://google.github.io/styleguide/go/decisions#receiver-names

[12]

Google Go Style Guide: constant names: https://google.github.io/styleguide/go/decisions#constant-names

[13]

Google Go Style Guide: variable names: https://google.github.io/styleguide/go/decisions#variable-names

[14]

Google Go Style Guide: getters: https://google.github.io/styleguide/go/decisions#getters

[15]

Uber Go Style Guide: errors: https://github.com/uber-go/guide/blob/master/style.md#errors

[16]

Google Go Style Guide: literal formatting: https://google.github.io/styleguide/go/decisions#literal-formatting

[17]

Google Go Style Guide: func formatting: https://google.github.io/styleguide/go/decisions#func-formatting

[18]

Google Go Style Guide: conditionals and loops: https://google.github.io/styleguide/go/decisions#conditionals-and-loops

[19]

Uber Go Style Guide: initializing structs: https://github.com/uber-go/guide/blob/master/style.md#initializing-structs

[20]

Uber Go Style Guide: initializing maps: https://github.com/uber-go/guide/blob/master/style.md#initializing-maps

[21]

Uber Go Style Guide: channel size is one or none: https://github.com/uber-go/guide/blob/master/style.md#channel-size-is-one-or-none

[22]

Google Go Style Guide: interfaces: https://google.github.io/styleguide/go/decisions#interfaces

[23]

Google Go Style Guide: goroutine lifetimes: https://google.github.io/styleguide/go/decisions#goroutine-lifetimes

[24]

Google Go Style Guide: generics: https://google.github.io/styleguide/go/decisions#generics

[25]

Uber Go Style Guide: test tables: https://github.com/uber-go/guide/blob/master/style.md#test-tables

[26]

Uber Go Style Guide: use go.uber.org/atomic: https://github.com/uber-go/guide/blob/master/style.md#use-gouberorgatomic

[27]

Uber Go Style Guide: Avoid Mutable Globals: https://github.com/uber-go/guide/blob/master/style.md#avoid-mutable-globals

[28]

Don't fire-and-forget goroutines: https://github.com/uber-go/guide/blob/master/style.md#dont-fire-and-forget-goroutines

[29]

Receiver and Interfaces: https://github.com/uber-go/guide/blob/master/style.md#receivers-and-interfaces,

[30]

Channel Size is One or None: https://github.com/uber-go/guide/blob/master/style.md#channel-size-is-one-or-none,

[31]

Bad Go code example: https://gist.github.com/slaise/c473bb6ca996d5f8a627c1bdafc27fb0

[32]

gofmt: https://pkg.go.dev/cmd/gofmt

[33]

govet: https://pkg.go.dev/github.com/golangci/govet

[34]

golint: https://pkg.go.dev/golang.org/x/lint/golint

[35]

错误代码示例问题列表: https://github.com/slaise/goguides/blob/main/issues.txt

[36]

Uber Go Style Guide: nil is a valid slice: https://github.com/uber-go/guide/blob/master/style.md#nil-is-a-valid-slice

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1507647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法50:动态规划专练(力扣514题:自由之路-----4种写法)

题目: 力扣514 &#xff1a; 自由之路 . - 力扣&#xff08;LeetCode&#xff09; 题目的详细描述&#xff0c;直接打开力扣看就是了&#xff0c;下面说一下我对题目的理解: 事例1&#xff1a; 输入: ring "godding", key "gd" 输出: 4. 1. ring的第…

【网络原理】TCP 协议中比较重要的一些特性(一)

目录 1、TCP 协议 2、确认应答 2.1、确认序号 3、超时重传 4、连接管理 4.1、建立连接&#xff08;三次握手&#xff09; 4.2、断开连接&#xff08;四次挥手&#xff09; 1、TCP 协议 TCP 是工作中最常用到的协议&#xff0c;也是面试中最常考的协议&#xff0c;具有面…

spring-data-elasticsearch官方文档解读(部分)

Spring Data Elasticsearch 这里主要学习的是4.4.16版本的文档 1. 版本 下表显示了 Spring Data 发行版系列使用的 Elasticsearch 版本和其中包含的 Spring Data Elasticsearch 版本&#xff0c;以及引用该特定 Spring Data 发行版系列的 Spring Boot 版本。给出的 Elastics…

关于yolov8的DFL模块(pytorch以及tensorrt)

可以参考我改的项目&#xff0c;不过目前推理结果不对&#xff0c;还在修复&#xff1a; https://github.com/lindsayshuo/yolov8-cls-tensorrtx先看代码 class DFL(nn.Module):"""Integral module of Distribution Focal Loss (DFL).Proposed in Generalized…

视频占用内存太大了怎么办 如何快速又无损的压缩视频 快来学习吧

视频文件太大是很多人在使用电脑或移动设备时经常遇到的问题。如果视频文件过大&#xff0c;不仅会占用过多的存储空间&#xff0c;还会让播放和传输变得困难。为了解决这个问题&#xff0c;我们需要学会如何缩小视频文件大小。那么如何缩小储存视频的大小呢&#xff1f;下面给…

【JAVA】CSS2:样式、选择器、伪类、颜色、字体、边框、列表、背景、盒子、布局、浮动

本文介绍了CSS样式、选择器、伪类、像素、颜色、字体、边框、列表、表格属性、背景、盒子、布局与浮动 1.样式 1.1 行内样式 <h1 style"color: aqua;font-size: large;">123</h1> 1.2 内部样式 <style>h1{color: red;font: 100;}</style>…

高速电路顶级会议DesignCon 2019年会议总结和论文资料分享

会议基本介绍 DesignCon 2019年是一场专注于电子设计和信号完整性的国际会议&#xff0c;于在美国加利福尼亚州举行。 主题丰富&#xff1a;DesignCon 2019年聚焦于电子设计和信号完整性的多个领域&#xff0c;包括高速串行链接、射频和微波设计、功率完整性、模拟设计、信号…

Unity3d Shader篇(十三)— 透明度混合(AlphaBlend)

文章目录 前言一、什么是透明度混合&#xff1f;1. 透明度混合原理2. 透明度混合优缺点优点&#xff1a;缺点&#xff1a; 3. 纹理图 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三、效果四、总结 前言 在计…

ArmSoM Rockchip系列产品 通用教程 之 PCIe 使用

1. PCIe 简介​ PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;是一种用于连接主板和外部设备的高速串行接口标准。它是 PCI 技术的后继者&#xff0c;旨在提供更高的带宽和更好的性能。 高速传输&#xff1a; PCIe接口提供了高速的数据传输通道&am…

【STA】SRAM / DDR SDRAM 接口时序约束学习记录

1. SRAM接口 相比于DDR SDRAM&#xff0c;SRAM接口数据与控制信号共享同一时钟。在用户逻辑&#xff08;这里记作DUA&#xff08;Design Under Analysis&#xff09;&#xff09;将数据写到SRAM中去的写周期中&#xff0c;数据和地址从DUA传送到SRAM中&#xff0c;并都在有效时…

使用QEMU来模拟运行Linux系统

第一步&#xff1a;安装 执行命令 假设我们呢开发板需要arm64架构的Ubuntu 得通过apt-file去找&#xff0c;可以找到qemu-system-arm 所以直接按照qemu-system-arm就行了 apt-file list会列举这个软件包里所有的文件 这个命令可以列举出所有安装好的包名&#xff0c;有点像pip…

什么是PLC远程控制模块?

随着工业自动化的不断发展&#xff0c;可编程逻辑控制器&#xff08;PLC&#xff09;已成为现代工业设备中不可或缺的核心组件。然而&#xff0c;传统的PLC管理方式往往受限于现场操作和维护&#xff0c;难以满足日益复杂的工业需求。在这一背景下&#xff0c;PLC远程控制模块应…

【数据库系统概论】第2章:关系数据库

文章目录 0. 前言2.1 关系数据结构及形式化定义2.1.1关系2.1.2 关系模式 2.2 关系操作2.3 关系的完整性2.4 关系代数 0. 前言 关系数据库系统是支持关系模型的数据库系统。第一章初步介绍了关系模型及其基本术语。本章将深入介绍关系模型。 按照数据模型的三个要素&#xff0c;…

基础刷题50之五(重复的子字符串)

文章目录 前言一、题目二、力扣官方解释1、枚举2、字符串匹配 三、文心一言解释1、枚举2、字符串匹配 总结 前言 刚上研一&#xff0c;有人劝我好好学C&#xff0c;当时用的不多就没学&#xff0c;现在毕业上班了。在此亡羊补牢了 在此感谢力扣和文心一言 一、题目 给定一个…

Python图像处理【22】基于卷积神经网络的图像去雾

基于卷积神经网络的图像去雾 0. 前言1. 渐进特征融合网络2. 图像去雾2.1 网络构建2.2 模型测试 小结系列链接 0. 前言 单图像去雾 (dehazing) 是一个具有挑战性的图像恢复问题。为了解决这个问题&#xff0c;大多数算法都采用经典的大气散射模型&#xff0c;该模型是一种基于单…

ECharts饼图图例消失踩的坑

在使用Echarts的饼图时&#xff0c;当时做法是在图例数小于8时显示全部的图例&#xff0c;在大于8的时候显示前8个图例。于是用了两种不同的方式处理。导致出现切换时间后图例不显示的情况。 错误过程&#xff1a; 在进行图例生成时采用了两种不同的方式&#xff1a; ①如果…

Redis底层源码分析系列(前提准备)

文章目录 一、 面试题二、 源码分析1. 源码导入2. 源码核心部分 一、 面试题 1. redis跳跃列表了解吗&#xff1f;这个数据结构有什么缺点&#xff1f; 2. redis项目里面怎么用&#xff1f; redis的数据结构都了解哪些&#xff1f; 3. redis的zset底层实现&#xff1f; redi…

深入理解Servlet

目录&#xff1a; ServletWeb开发历史Servlet简介Servlet技术特点Servlet在应用程序中的位置Tomcat运行过程Servlet继承结构Servlet生命周期Servlet处理请求的原理Servlet的作用HttpServletRequest对象HttpServletResponse对象ServletContext对象ServletConfig对象Cookie对象与…

Constrained Iterative LQR 自动驾驶中使用的经典控制算法

Motion planning 运动规划在自动驾驶领域是一个比较有挑战的部分。它既要接受来自上层的行为理解和决策的输出,也要考虑一个包含道路结构和感知所检测到的所有障碍物状态的动态世界模型。最终生成一个满足安全性和可行性约束并且具有理想驾驶体验的轨迹。 通常,motion plann…

微信小程序开发系列(二十八)·小程序API如何发送网络请求以及网络请求失败后的解决方法

目录 1. 小程序API介绍 2. 网络请求 2.1 网络请求失败解决方法 2.2 如何跳过域名校验 1. 小程序API介绍 小程序开发框架提供丰富的微信原生API&#xff0c;可以方便的调起微信提供的能力&#xff0c;例如&#xff1a;获取用户信息、微信登录、微信支付等&#xff0c;小…