2023年3月

作者:京东科技 倪新明

在敏捷开发环境下,系统通过迭代增量的交付价值,系统架构也是如此。团队不可能在项目之初就建立完美的系统架构,系统架构应该随着系统迭代不断演进。架构演进和架构腐化是看待架构的不同视角:架构腐化着眼于现状,架构演进侧重于未来架构腐化不可避免,随着时间流转腐化现象必然发生。而我们需要做的是:通过某种方式及早发现和修正

1 面临的问题

把目光从宏观的演进和腐化视角聚焦在更加具体的问题和挑战层面,作为团队负责人或架构师,你是否面临以下问题:

•团队已经制定的
开发规范很难持续性的落地
,并在应用中保持较高的健康状态

•系统制定的架构决策在系统迭代过程中逐渐弱化、打破,甚至随着时间的推移,团队中已经没有人关注决策的落地和遵守情况

•历史的架构决策早已 ”无迹可寻“,更何谈对系统架构演进的追溯

•如何快速的判断当前系统腐化程度或健康情况?

•不论是架构升级、系统演进,系统缺失指导性的自动化约束,确保演进方向不会偏离

基于以上的问题,我相信团队都会做过一些实践,不管是流程的强约束,还是系统的自动化,都尝试解决如上所面临的所有或部分问题。典型的方式:

•方式一:
代码评审(强流程)

•方式二:
静态分析工具,并结合CICD流水线

•方式三:
基于ArchUnit的架构约束单元测试实践

1.1 代码评审

代码评审是最典型的方式,对每次迭代的代码交付进行强流程的评审,通过多干系人参与,可以灵活的、深入的评估实现对于约束的遵守情况。当然,评审越完备,需评审人员投入的精力和时间成本越大。

大量的开发实践表明
,系统无法持续保持高强度的、完备的代码评审,评审的力度会由于各种因素而下降,最终导致系统的腐化,这种人为不可控因素是代码评审的主要问题。

另外一个问题是,
代码评审在研发流程中的后置性
。一般情况下,代码评审介入的实际可能是在开发提测之后,联调结束之前,当然有些团队可能更后置。流程越后置,如果存在大规模的架构约束破坏,则可能导致重构成本与项目周期的冲突。


1.2 静态分析工具

代码的静态分析工具比较多,比如CheckStyle 、FindBug、Sonar,公司内部的EOS等等,这些静态分析工具能够对代码的规范,比如注释、命名、可能存在潜在缺陷的代码段、圈复杂度等进行分析。


静态分析工具的最大优势在于:


平台化支持
,丰富的产品矩阵:比如既有面客的PC端平台,又有方面研发使用的IDE插件等等

•校验的
自动化执行
能力:可以通过平台、IDE插件工具或是流水线(如果集成CICD)触发自动化执行并生成分析报告。

静态分析工具的不足主要表现在:

•一般情况下,这些工具仅仅是提供建议的扫描报告,不具强流程控制,在约束打破时不会阻断应用构建。部分工具(并不是所有)与CICD流水线进行了打通,通过质量门禁来干预构建流程,一定程度上能作为系统约束校验的最后卡点。

•静态分析工具的能力侧重点一般在于开发编码规范的约束,比如命名、注释、代码段规约等等,而对于高层的架构约束的校验较弱,比如分层架构约束、”组件“间约束、类位置约束、包与类的包含关系约束等等。

•约束执行的粒度更倾向于统一规范,比如在团队维度、编程语言维度,而实际上忽略了应用级的定制化约束。不同的应用工程具备特定的应用架构风格,基于特定风格下有不同的架构约束。这些差异化规划与团队统一规则存在潜在冲突,并不一定在跨应用下都适用。

1.3 基于ArchUnit

ArchUnit是一个基于Junit运行的架构约束类库,其能够通过单元测试的形式对系统架构约束进行自动化的校验。团队引入ArchUnit的成本并不高,由于是基于单元测试形式引入,并不影响应用程序的主线流程。团队在引入往往有集中形式:

•单个应用引入,每个应用都定义各自的单测

•公共jar包,多应用复用

上述应用模式也同样在统一规范和应用差异化层面存在类似的问题。


1.4 共性问题

以上的三种实践方案存在以下几个共性问题无法解决:

•团队统一规范和应用级别的定制化支持

•架构决策记录的管控与追踪

•多维度的架构指标分析


2 ArchKeeper 平台建设的核心目标

基于对已有问题及解决方案的分析,我们希望平台化的解决方案,ArchKeeper平台建设的核心目标如下:

•架构约束自动化测试能力:支持架构约束的自动化执行

•灵活、简单的规则扩展能力:规则的定制扩展应尽量保持简单、灵活,满足实际的定制化需求


团队统一规范与应用扩展规则的统一执行

•结果及时反馈,支持与CICD流水线集成:自动化执行前置到开发阶段,并通过CICD流水线作为最后卡点

•ADR管控与追踪:以产品线和应用为载体,对系统的架构决策进行统一管控,并具备ADR的可视化追溯能力

•架构约束静态分析模式,以评估架构腐化:基于代码仓库及规则库的静态分析能力,以提供高层的应用规则分析报表

•多维度架构治理指标分析能力:对应用提供更多维度的指标分析,比如组件耦合度等,为架构演进提供指引


3 ArchKeeper 核心理念


理念一:
架构约束是强制规则,应用必须遵守,否则应阻断应用构建

系统的架构决策是影响系统的“
重要
”的东西,决策评审通过之后应确保应用的遵循,团队应该评估校验决策执行的方式,比如哪些可以自动化校验,哪些需要基于人工审查。对于支持自动化校验的架构约束,应用必须遵守,不能破坏约束限制。因此,如果存在破坏约束情况,应当阻断应用构建。

理念二:
架构约束测试的执行应该尽量前置,及时反馈

流程后置带来的问题就是如果存在架构约束的破坏,研发进行重构需要一定时间成本。流程越后置,重构成本越高。因此,架构约束的自动化执行应该尽量前置,提前至开发阶段,并尽可能的及时反馈校验结果以提升效率。

理念三:
架构约束无法完全统一,在团队统一规范之外,允许应用级的定制,且须统一执行

架构约束的范围比较广,团队无法形成完全统一的、标准一致的约束规范。有些约束是团队层面的,比如编码规范中的某些强制约束。而有些则是应用级别的,比如特定应用下的分层约束等等。因此,这种定制化是必然存在的。虽然,团队统一约束和应用级别的定制化约束无法完全统一,但二者应该在统一的自动化流程中执行。

理念四:
系统的架构决策记录应当留存并可追溯

系统的架构决策记录(ADR)是团队的重要资产,ADR应该以文档化形式留存,并能够对ADR的演进提供追溯能力。

理念五:
多维度的架构指标分析有利于防止架构腐化,为架构演进提供指引

ArchKeeper平台之所以计划提供多维度架构指标的分析能力正是基于这样一个前提理念:对应用进行多维度的架构指标分析,有利于观测系统的腐化情况,并为系统的架构演进提供一定的指引。

4 结语

文章主要阐述了研发中存在的问题及ArchKeeper平台化建设理念及目标,并没有涉及具体的实现。后续的系列文章会对ArchKeeper能力规划、设计实现(基于DDD)进行持续的分享交流。

关联文章:

《 通过自动化单元测试的形式守护系统架构 》

《 轻量级的架构决策记录机制 》

前言

昨天群友问我能不能整理Go主流框架方面的面试题,安排!

这篇文章整理了gRPC、GoFrame、GoZero、GoMicro、GORM、Gin等主流框架的30道面试题。

需要大厂面经的朋友们也可以关注我博客园账号,我会持续更新大厂面经。

gRPC

1.gRPC是什么,有哪些优点?

gRPC是一种高性能、开源的远程过程调用(RPC)框架,它可以使不同平台和语言之间的服务相互通信。它的优点包括:高效性、跨平台、异步流处理、支持多种语言、安全、易于使用和开源。

2.gRPC和REST的区别是什么?

REST是基于HTTP协议的一种风格,而gRPC是一个独立于协议的RPC框架。 REST基于资源的状态转移,使用标准的HTTP方法,而gRPC使用协议缓冲区(Protocol Buffers)进行序列化和反序列化。 gRPC支持异步流处理和双向流,而REST通常只支持请求/响应模式。

3.Protocol Buffers是什么,为什么它被用于gRPC中?

Protocol Buffers是一种语言中立、平台中立、可扩展的序列化格式,它可以用于数据交换和持久化。它被用于gRPC中,因为它可以实现高效的序列化和反序列化,从而提高了gRPC的性能和效率。

4.gRPC的流程是什么?

gRPC流程分为四个步骤:定义服务、生成源代码、实现服务、启动服务。首先,需要定义要实现的服务及其接口,使用Protocol Buffers编写接口定义文件。其次,使用编译器生成客户端和服务器端的源代码。然后,实现生成的接口。最后,启动服务器并将其部署在适当的位置。

5.gRPC支持哪些类型的序列化?

gRPC支持两种类型的序列化:二进制(使用Protocol Buffers)和JSON。其中,二进制序列化在效率和性能方面比JSON序列化更优秀。但是,JSON序列化在可读性方面更好,可以方便地进行调试和测试。

GoFrame

1.什么是 GoFrame?与 Go 标准库有什么区别?

GoFrame 是一个强大的 Go Web 应用开发框架,它提供了一系列优秀的功能模块和常用工具,方便开发者快速构建高性能、高可用的 Web 应用程序。

相较于 Go 标准库,GoFrame 提供了更多的功能模块,例如:ORM、Cache、Session、WebSocket、邮件发送等等。此外,GoFrame 也提供了更友好的 API 和更好的性能。

2.goframe框架中,如何使用中间件?

在goframe框架中使用中间件很简单。只需要在路由定义时使用中间件函数,例如:

s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
    group.Middleware(MiddlewareFunc)
    group.ALL("/user", UserHandler)
})

3.goframe框架中,如何实现定时任务?

在goframe框架中实现定时任务很容易。可以使用gcron插件。该插件提供了简单的API用于创建和管理定时任务,例如:

// 创建定时任务
s := gcron.NewScheduler()
s.Every(1).Hour().Do(TaskFunc)

// 开始定时任务
s.Start()

4.goframe框架中,如何实现文件上传和下载?

在goframe框架中实现文件上传和下载很容易。可以使用ghttp的相关方法进行操作,例如:

// 文件上传
uploadFile, err := r.UploadFile("file")
if err != nil {
    return err
}
uploadFile.Save("/path/to/save")

// 文件下载
r.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
r.Response().ServeFile(filepath)

5.GoFrame 中的 gvalid 组件是什么?如何使用?

goframe框架提供了功能强大、使用便捷、灵活易扩展的数据/表单校验组件,由gvalid组件实现。

gvalid组件实现了非常强大的数据校验功能,内置了数十种常用的校验规则,支持单数据多规则校验、多数据多规则批量校验、自定义错误信息、自定义正则校验、自定义校验规则注册、支持i18n国际化处理、支持struct tag规则及提示信息绑定等等特性,是目前功能最强大的Go数据校验模块。

Go-Zero

1.Go-Zero 是什么?它的主要功能是什么?它与其他 Go 框架有什么不同?

Go-Zero 是一个基于 Go 语言的微服务开发框架。它旨在提供简单、高效和可靠的微服务开发解决方案。Go-Zero 主要功能包括 RPC、缓存、限流、熔断、监控等。相较于其他 Go 框架,如 Gin 或 Beego,Go-Zero 更加专注于微服务开发,并提供了更多的开箱即用的功能。

2.go-zero中如何实现JWT认证授权?

go-zero中提供了go-jwt包来实现JWT认证授权。具体步骤如下:

1)定义一个Claims结构体,包含需要存储到JWT token中的信息。

2)通过jwt.NewHS256([]byte("your-secret"))创建一个JWT签名实例,将Claims结构体转换成token字符串。

3)通过jwt.ParseHS256(token, []byte("your-secret"))验证token,并返回解析后的Claims结构体。

3.如何使用go-zero实现异步任务?

go-zero中提供了go-queue包来实现异步任务。具体步骤如下:

1)创建一个Redis连接池。

2)通过queue.New(queueConfig, redisConfig)创建一个队列实例。

3)通过producer := queue.NewProducer()创建一个生产者实例。

4)通过consumer := queue.NewConsumer(queueConfig, redisConfig)创建一个消费者实例。

5)通过producer.Enqueue(job)将任务放入队列。

6)通过consumer.Consume(processor)处理队列中的任务。

4. 如何使用go-zero实现分布式缓存?

go-zero中提供了go-cache包来实现分布式缓存。具体步骤如下:

1)创建一个Redis连接池。

2)通过cache.New(cacheConfig, redisConfig)创建一个缓存实例。

3)通过cache.Set(key, value, expireTime)将数据存入缓存中。

4)通过cache.Get(key)从缓存中获取数据。

5.如何使用go-zero实现限流?

go-zero中提供了go-ratelimit包来实现限流。具体步骤如下:

1)通过rate.NewLimiter(rate.Every(time.Second), 100)创建一个限流器实例,限制每秒处理100个请求。

2)通过limiter.Allow()方法判断当前请求是否被允许,若被允许则处理请求,否则返回错误提示。

GIN

1.Gin框架有什么特点?

Gin是一个基于Go语言的Web框架,它提供了一些轻量级的工具,使得编写高性能的Web应用程序变得非常容易。
Gin具有快速的路由器,中间件支持和错误管理功能,同时还提供了自动化的API文档生成和参数绑定等功能。

2.Gin框架中的中间件是什么?

中间件是Gin框架的一个重要概念,它是一种在处理请求之前或之后执行的函数。中间件通常用于处理请求,如身份验证,日志记录和性能监测等。
在Gin框架中,中间件可以按照需要在路由器级别或全局级别进行注册和使用。

3.如何在Gin框架中处理跨域请求?并给出代码

Gin框架提供了一个cors中间件,可以用来处理跨域请求。在使用cors中间件时,可以指定允许跨域请求的源地址,允许的请求方法,允许的头信息等。例如:

r := gin.Default()
r.Use(cors.New(cors.Config{
  AllowOrigins: []string{"http://localhost:8080"},
  AllowMethods: []string{"PUT", "PATCH", "GET", "POST", "DELETE"},
  AllowHeaders: []string{"Origin"},
}))

4.如何在Gin框架中实现文件上传?

在Gin框架中,可以使用MultipartForm函数来处理文件上传。MultipartForm函数会自动解析multipart/form-data格式的表单数据,并将文件保存在指定的目录中。例如:

func UploadFile(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    filename := filepath.Base(file.Filename)
    if err := c.SaveUploadedFile(file, filename); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, gin.H{"filename": filename})
}

5.GoFrame 中的 gvalid 模块是什么?如何使用?

goframe框架提供了功能强大、使用便捷、灵活易扩展的数据/表单校验组件,由gvalid组件实现。

gvalid组件实现了非常强大的数据校验功能,内置了数十种常用的校验规则,支持单数据多规则校验、多数据多规则批量校验、自定义错误信息、自定义正则校验、自定义校验规则注册、支持i18n国际化处理、支持struct tag规则及提示信息绑定等等特性,是目前功能最强大的Go数据校验模块。

5.如何在Gin框架中实现JWT认证?

在Gin框架中,可以使用jwt-go库来实现JWT认证。JWT认证流程通常包括登录,生成token,验证token等步骤。在Gin框架中,可以使用中间件来实现JWT认证,例如:

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authorization header required"})
            return
        }

        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
            }          

GORM

1.GORM 如何实现一对多和多对多关系的映射?

对于一对多关系,可以使用 GORM 的 BelongsTo 和 HasMany 方法实现映射。比如:

type User struct {
    ID        uint
    Name      string
    Addresses []Address
}

type Address struct {
    ID      uint
    UserID  uint
    Address string
}

// User 模型对应的表应该包含一个外键,指向 Address 表中的 UserID 字段
// 使用 BelongsTo 和 HasMany 方法进行关联
func (u *User) Addresses() []Address {
    var addresses []Address
    DB.Model(&u).Association("Addresses").Find(&addresses)
    return addresses
}

func (a *Address) User() User {
    var user User
    DB.Model(&a).Association("User").Find(&user)
    return user
}

对于多对多关系,可以使用 GORM 的 ManyToMany 方法实现映射。比如:

type User struct {
    ID       uint
    Name     string
    Articles []Article `gorm:"many2many:user_articles"`
}

type Article struct {
    ID      uint
    Title   string
    Content string
    Users   []User `gorm:"many2many:user_articles"`
}

// User 和 Article 之间的关系通过 user_articles 表进行映射

2.在使用 GORM 进行数据库查询时,如何避免 N+1 查询的问题?

N+1 查询问题指的是在查询关联表时,如果使用了嵌套循环进行查询,就会产生大量的 SQL 查询。为了避免这个问题,可以使用 GORM 的 Preload 方法预先加载关联数据。比如:

// 查询 Users 以及它们的 Addresses
var users []User
DB.Preload("Addresses").Find(&users)

这个查询会一次性加载所有 User 和 Address 数据,避免了 N+1 查询问题。

3. 如何使用 GORM 进行事务管理?

GORM 的事务管理使用 Begin、Commit 和 Rollback 方法实现。比如:

tx := DB.Begin()
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
    }
}()

// 在事务中执行一系列操作
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}

if err := tx.Create(&address).Error; err != nil {
    tx.Rollback()
    return err
}

// 提交事务
tx.Commit()

4.GORM 的 Preload 方法和 Joins 方法有什么区别?在什么情况下使用哪种方法更好?

Preload 方法是在查询时预加载关联数据,而 Joins 方法是通过 SQL JOIN 语句连接多个表查询数据。
Preload 方法适用于关联表较少、数据量不大的情况;而 Joins 方法适用于关联表较多、数据量较大的情况。

5.如何在 GORM 中使用原生 SQL 查询?

在 GORM 中,可以使用 Raw 方法来执行原生 SQL 查询。Raw 方法接受一个 SQL 查询字符串和可选的参数列表,并返回一个 *gorm.DB 对象,可以使用该对象进行进一步的查询操作。

下面是一个使用 Raw 方法执行原生 SQL 查询的示例:

import "gorm.io/gorm"

// ...

var users []User
result := db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)
if result.Error != nil {
    // 处理错误
}

在上面的示例中,我们使用 Raw 方法执行了一个简单的 SQL 查询,它将返回所有年龄大于 18 岁的用户。Scan 方法用于将查询结果映射到一个 []User 对象中。

还可以使用 Exec 方法来执行不需要返回值的 SQL 查询,如插入、更新或删除数据:

result := db.Exec("DELETE FROM users WHERE age < ?", 18)
if result.Error != nil {
    // 处理错误
}

在上面的示例中,我们使用 Exec 方法执行一个删除语句,它将删除所有年龄小于 18 岁的用户。由于这个SQL不需要返回值,我们只需检查 result.Error 是否为 nil 来判断查询是否成功。

请注意,使用原生 SQL 查询可能会使代码更难以维护和调试,因为它们不受 GORM 的自动迁移和其他便利功能的支持。因此,应该尽可能地使用 GORM 提供的高级查询功能。

Go-Micro

1.什么是 go-micro?

go-micro 是一个微服务框架,它可以用于构建可伸缩和可维护的微服务应用程序。它支持多种语言和传输协议,并提供了一系列服务发现、负载均衡、消息传递和远程过程调用(RPC)等基础设施功能。

2.go-micro 支持哪些传输协议?

go-micro 支持多种传输协议,包括 HTTP、TCP 和 NATS。其中,HTTP 和 TCP 协议适用于内部服务之间的通信,而 NATS 协议则适用于跨网络边界的通信。

3.如何使用 go-micro 进行服务发现和负载均衡?

go-micro 内置了服务发现和负载均衡功能,可以通过调用相关 API 实现。例如,可以使用 go-micro 的服务发现 API 来查找已注册的服务,并使用负载均衡 API 来将请求分配到多个实例之间。

4.go-micro 支持哪些消息传递方式?

go-micro 支持多种消息传递方式,包括同步和异步消息传递。同步消息传递使用 RPC 机制,而异步消息传递则使用消息队列和事件驱动模式。

5.如何使用 go-micro 进行测试?

go-micro 提供了一系列测试工具和测试框架,可以用于测试微服务应用程序的各个组件。其中,mock 测试工具可以用于模拟服务接口和行为,以及对服务进行单元测试。另外,go-micro 还提供了一些集成测试工具,例如 Docker 和 Kubernetes,可以用于测试多个服务之间的交互。

总结

有志者,事竟成,破釜沉舟,百二秦关终属楚。

苦心人,天不负,卧薪尝胆,三千越甲可吞吴。

就算就业环境再好,也有人找不到工作;即使就业环境不好,也肯定有人能找到工作。

停止抱怨和焦虑,要想找到好工作:项目+算法+八股,都要硬,学起来吧!兄弟们加油。

成功上岸

如果你不知道如何写好简历,不知道如何在面试中做好自我介绍和项目介绍:可以在评论区留言。

也欢迎大家关注我的账号,点赞、留言、转发。
你的支持,是我更文的最大动力!

背景

今天上午11点时客户打电话过来说医院的CIS系统一直有阻塞,导致系统卡慢严重,信息中心的电话都快被打爆了。赶紧要了远程登录到SQL专家云,看到了系统确实存在大量的阻塞。

点击紫色圆点进入活动会话原始数据,可以看到会话标识33是阻塞的源头,造成了大量的语句被阻塞,而且阻塞已经持续了很长的时间。

分析定位

在SQL Server管理工具里查询会话标识为33的语句为自动收缩的命令,进度为79%。

从SQL专家云体检项中可以看到该数据库自动收缩配置为开启状态。

通过和医院工程师交流得知,昨天下午三点半有做过数据迁移的操作,删除了100多G的数据。

结论

昨天下午三点半的时候删除了大量数据,触发了自动收缩数据库的任务。自动收缩的过程要对数据库的表进行数据整理,消耗巨大,执行的很慢,到今天上午11点的时候恰好整理到业务使用频繁的表,因此造成了阻塞,影响了业务的使用。

关于自动收缩
触发条件

在数据库开启自动收缩选项的情况下,SQL Server每隔半小时会检查文件使用情况。如果空闲空间大于25%,SQL Server就会启动自动收缩。微软官方链接:https://docs.microsoft.com/zh-cn/sql/t-sql/statements/alter-database-transact-sql-set-options?view=sql-server-2017&viewFallbackFrom=sql-server-2014。

带来的危害

对于一个磁盘空间很紧张的系统,收缩数据库无疑是有帮助的。但是从数据库自身的健康和性能考虑,并不建议使用,因为:

1、 数据文件收缩导致了索引的完全碎片化,索引的效率大大降低,严重影响性能;

2、 数据文件的收缩同样产生了大量的I/O操作,耗费大量的CPU,系统性能下降严重;

3、 在业务高峰期的时候可能会造成大量的阻塞。

英文资料:

https://www.sqlskills.com/blogs/paul/why-you-should-not-shrink-your-data-files/

中文资料:

https://www.cnblogs.com/kerrycode/archive/2013/06/04/3116339.html

如何解决

自动收缩任务无法手动结束,只能等待收缩完成或者重启SQL Server服务。

最佳实践

  1. 一定不要开启自动收缩选项;

  2. 不到万不得已,不要收缩数据文件,对性能影响极大;

  3. 需要做收缩的时候,一定要手工来做,而且是在维护窗口期间,尽量一次不要收缩太多空间,分几次收缩;

  4. 收缩完成后要重建或者重新组织索引;

  5. 同实例下的测试库也不要开启此选项,因为测试库删除数据操作比较多,运行自动收缩带来的I/O性能下降也会影响到业务库的性能。

北京格瑞趋势科技有限公司是聚焦于数据服务的高新技术企业,成立于2008年,创始团队及核心技术人员来自微软和雅虎。微软数据平台金牌合作伙伴,卫宁健康数据平台战略合作伙伴。通过产品+服务双轮驱动的业务模式,14年间累计服务4000+客户,覆盖互联网、市政、交通、电信、医疗、教育、电力、制造业等各个领域。

file

为了体验AI,晓衡这两周战斗力爆棚了!

每天大概睡了四~五个小时,而且中午也没有休息过,但精神却还很亢奋。

直到周一下午,身体才有种被掏空的感觉,晚上 10 点就睡了。可能是兴奋劲还在,早晨不到 6 点就醒了,立马就有一种想去战斗的冲动!

我想了想,还是算了,还是先好好总结一下,不要扯到蛋了。

01

说真心话,之所以这么肝,主要是晓衡内心深处有一种,不由自主的恐惧感!

2022 年底,大量公司疯狂裁员,很多公司连年会都没了。

企业为了存活下去,说不准,各种意想不到的事情就会落到自己身上。三体中有一句话说得很深刻:
我灭掉你,却与你无关!

晓衡负责的 Cocos Store 业务,虽然说在 2022 年有了些成绩,但 Store 商城的流水又不抽成,还要代卖家向购买的用户提供发票,整个业务线对公司来说,完全是亏损的。

而我这两年,技术完全在 TM 地退化,我也担心那一天 Cocos 会不要我了。

所以 2023 开年来,我就开始要求自己,一定要把之前欠下的债补上,老老实实从 Creator 3.x 的基础开始。

02

因此,才有了前段时间,晓衡花了10天时间写的一篇文章《Creator 3.x 入门修炼指南》。

并严格按文章中所说的进行修炼,在麒麟子《方块先生》教程基础上,编写一到两个自己的跑酷游戏来。

file

说到这里,我想给一些人泼点冷水!

因为在这段时间里,我收到一些初学者的反馈。

有些人是想要的太多,方法也太多,但大多数都停留在表面上,却做得很少。

罗翔老师说过这样一句话:
我觉得人最大的痛苦,就是无法跨越知道和做到的鸿沟。

想通过,仅仅是看视频、抄抄代码、报个班什么的,就想学会游戏开发,可以说是痴心妄想,首先这个认知就是错误的!

你想想,我们从小到大,老师在讲台上亲面授课,学会了的有多少?而我们身体中,后天生长出来的,能用于养家糊口的技能,它是怎么炼就出来的?

这个问题,真的值得去思考!来看下这张图——学习金字塔模型。

file

1964年埃德加.戴尔提出学习金字塔学习模型,后来成为美国缅因州的国家训练实验室研究成果。

该模型将学习分成两种:
被动学习和主动学习。

研究发现,一个人只是通过听讲、看书、看视频这类方式,接触到一个新的知识时,两周后,它在大脑中的留存率,最高不会超过 30%。

而通过对知识点的讨论,学习效果能达到 50% 以上,能亲身去实践使用它,能达到 75%。最好是能输出成果来,就像我这样去做一个新的游戏,或是写一篇总结文章,甚至是去教其他人,这样的学习效果能达到 90%。

在实践的过程中,你会遇到各种意外,运用你原有的旧知,去驯服新知的过程中,让它们产生关联,并交融在一起,只有这样才能将新知识内化为自己身体的一部分。

说实话,这个过程在刚开始时是很痛苦的!
但越是痛苦,你的收获将会越大。

因此,晓衡的想法是,先将《方块先生》中的知识点尽可能吃透、延伸,做出自己的一款跑酷游戏来(玩法完整的游戏 Demo),并要将它上线到微信或 Cocos Store 上。

只有将学习到的知识,使用起来且转化为经济价值,才算是完成了整个闭环。真的有了成果,我才会进入下一个阶段,学习巧哥的《3D俯视角割草》。

03

第二件事是关于 AI 的,想必大家都已经感受到了!没玩过ChatGPT看下面。

file

AI都已经可以写代码了,而且仅仅不到两月时间,ChatGPT 在以肉眼可见的速度变得聪明起来。

上面说了,很多公司面临裁员求生存的境地,利用 AI 提升企业效能,这将更加加剧普通打工人被淘汰的速度。

但大家也不用过多担心,目前的 AI 虽然能做很多事情,但更多还是需要『人』用专业的知识去向 AI 提问,去调教它,问题的质量决定了 AI 的生产力。

而要提出好的问题,则是需要具备专业的知识背景。像我这样,一个不会3D建模,不会使用 Blender 的人,目前就无法搞定 ChatGPT,让它帮我生成游戏模型素材。

不过,让我去调教 ChatGPT 写出 Creator 游戏代码,这个是可以的(目前 ChatGPT 使用的是截止 2021 年的数据,它储备的知识只到 Creator 3.0 Bate版,写出的代码大都是用的 2.x 的API)。

随着使用的人越来越多,以及 ChatGPT 后面升级后,能学习到 Creator 3.7.0 的 API 文档、官方手册,那就不得了。代码这个我不担心,我相信只有懂得编程的人,会做游戏的人,才会更好地利用 AI 去产出游戏代码,普通玩家还是很难玩得转的。

不过初学者倒是,可以借助 AI 大大降低学习编程和游戏开发的难度,在遇到一些不懂的概念、问题、代码时,可以问问 ChatGPT ,它可以很轻松地帮你解决(老司机也可以用)。

04

除了 ChatGPT,晓衡上周还研究了一下AIGC,使用 MidJourney 来生成游戏素材。

ChatGPT 是使用自然语言处理技术,专注于自然语言生成、理解和对话。

file

而 MidJourney 是绘图 AI,使用神经网络、深度学习和计算机视觉技术来学习图像数据的特征和规律,从而生成新的图像。

它们都是人工智能技术,但却有着不同的应用领域和场景。

现在有了 MidJourney 这种艺术创作AI,那对游戏公司和游戏开发者来说,曾经梦寐以求的幻想,如今已成现实。

就干它了!

用 AI 辅助开发游戏,这会是一种怎么样的体验?就用它来实现一个游戏 Demo 吧!而且最好是将整个过程记录下来,于是才了文章开头,我这个逗风格的视频。

请不要笑!

美术大佬真的是一边帮我处理素材,一边在哭泣,我真怕他情绪失控来打我!

美术大佬苦笑跟我说,有了 AI 画画,估计以后我只能当抠图的了,如果 MidJourney 能生成带透明的图,估计抠图师都不要了。

我很想安慰几句,但,唉......不知道怎么说。还是问问 ChatGPT 吧!

file

专注自己的风格、创作思维并利用好AI,在艺术领域保持竞争力——你觉得 ChatGTP 说得如何呢?

05

哦,为了录好这条视频,如果还没有看,建议你还是看一下。

file

我真的是豁出了,晓衡可是整整花了两天时间嘴巴子都说麻了的。我翻开手机像册,还存了31行,每行4个视频,一共是123条视频片段。

再算上没有保存、删除的视频,这得超过 200 条了。

不过,经过这一役,连我自己都没想到,我还会有这么逗比的一面。而且在短视频上的文案编写、镜头表现、语言表达、视频剪辑......等多个方面的能力,又前了一步,可谓是一举多得。

哦!对 2D 游戏感兴趣的朋友可到 Cocos Store 下载源码,后续晓衡还会计划在游戏中加上怪物,将游戏补充完整。

file

点击文末阅读原文查看详细下载,欢迎建议反馈!

06

唠唠叨叨,又写了快3000字了,最后做个小结:

  • 游戏开发,重在躬身入局,亲身实践;
  • AI 刚刚崛起,目前还替代不了大部分人工,但能使用并驾驭 AI 的人,将会替代不会使用 AI 的人;
  • 尽可能将所学的知识和技能去创作、生产,向他人、社会提供有价值的产品或服务。

今天就写这么多了,请继续关注晓衡我还会继续分享更多优质内容。

更多精彩请关注
Creator星球游戏开发社区

定义

使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式---百科。

通俗的说就是原型模式是一种创建型设计模式,
指定某个对象(通过某种方式)得到一个新的对象,在内存中拥有新的地址
,得到的对象与原对象是是相互独立的,即得到跟原对象一样的对象

当我们需要两个一模一样的实例时,使用原型模式非常方便,如果不使用原型模式,按照构造函数的方式初始化对象,我们需要传两次一模一样的参数如:

const dog = new BydCard('byd', '汉', '30w', '2023款')
const dog_copy = new BydCard('byd', '汉', '30w', '2023款')
// 使用原型模式
const dog_copy1 = Object.create(dog)

实现思路

通过目标对象得到一个全新的新对象,使新对象也具备跟目标对象一样的能力,这种一般思路有两种

  1. 深拷贝
  2. 指针引用:自身对象找不到,通过内部属性引用到目标对象上去找
    类似链表结构的
    next
    指针

其中大多数后台语言如java 有相关克隆接口规范,javaScript 是通过第二种方式来实现的。

javaScript 中的原型模式

在原型模式下,当我们想要创建一个对象时,会先找到一个对象作为原型,然后通过
克隆原型
的方式来创建出一个与原型一样(
共享一套数据/方法
)的对象。在 JavaScript 里,
Object.create
方法就是
原型模式的天然实现
——准确地说,

只要我们还在借助
Prototype

来实现对象的创建和原型的继承,那么我们就是在应用原型模式

有的设计模式资料中会强调,原型模式就是拷贝出一个新对象,认为在 JavaScript 类里实现了
深拷贝
方法才算是应用了原型模式。事实上在 JavaScript 中,通过指针的方式也可以得到目标对象、属性、方法的共享。克隆(深度拷贝)是实现这个目的的方法,但不是唯一的方法,也不是javaScript 的目的。

通过指针来引用,然后
动态执行的时候绑定上下文
this

,这样就不会造成实例之间的错乱,我觉得这也是
this
被设计成动态绑定的原因之一。

原型模式-编程范式

原型模式不仅是一种设计模式,它还是一种
编程范式
(programming paradigm),是 JavaScript
面向对象系统实现的根基
,原型编程范式的体现就是
基于原型链的继承
。即便现在
es6+
推出了class 关键字,支持了类的写法。

引入的 JavaScript 类本质上还是基于原型的继承的
语法糖(
class 只是一个语法糖
)

。类语法不会为 JavaScript 引入新的面向对象的继承模型。 当我们尝试用 class 去定义一个 Dog 类时:

class Dog {
  constructor(name ,age) {
   this.name = name
   this.age = age
  }
  
  eat() {
    console.log('肉骨头真好吃')
  }
}

其实完全等价于写了这么一个构造函数:

function Dog(name, age) {
  this.name = name
  this.age = age
}
Dog.prototype.eat = function() {
  console.log('肉骨头真好吃')
}

原型链核心点

每个构造函数都拥有一个
prototype
属性,它指向构造函数的
原型对象
,这个原型对象中有一个
constructor
属性指回构造函数;每个实例都有一个内部属性

__proto__
属性,当我们使用构造函数去创建实例时,实例的
__proto__
属性就会指向构造函数的原型对象。

// 输出"肉骨头真好吃"
dog.eat()
// 输出"[object Object]"
dog.toString()

明明没有在 dog 实例里手动定义
eat
方法和
toString
方法,它们还是被成功地调用了。
这是因为当我试图访问一个 JavaScript 实例的属性、方法时,它首先搜索这个实例本身;当发现实例没有定义对应的属性、方法时,它会转而去
搜索实例的原型对象
;如果原型对象中也搜索不到,它就去搜索原型对象的原型对象,这个搜索的链表就叫做

原型链

Object 是所有的基类,其中
Object.prototype
指向null,这样原型链就有终点了,而不是无脑的一直下去。

原型链其他关键点:

  1. 所有函数(普通函数,构造函数,内置的函数)都是内置函数(类)
    Function
    的实例,所以存在

    函数. __proto__
    === Function.prototype
    所有函数都可以直接调用Function原型上的方法

    (
    call / apply /bind
    )
  2. Function 确实很厉害
    ,他不仅是函数的类,还是自己的类。函数是Function 的实例,
    Function 也是Function 的实例
    Object.__proto__ === Function.prototype

    Function.__proto__===Function.prototype
  3. 对象的原型链最终指向
    Object.prototype
    ,
    object.prototype._proto_
    指向null

如下代码验证了这些结论:

function sayHi () {
    // console.log('hello joel')
}

// 所有函数都是Function 的实例即函数也是对象,
// 所以存在函数.__proto__ === Function.prototype
console.log(sayHi.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(String.__proto__ === Function.prototype) // true
console.log(Array.__proto__ === Function.prototype) // true
console.log(Number.__proto__ === Function.prototype) // true
console.log(Symbol.__proto__ === Function.prototype) // true

// Function.prototype 内部属性又指向Object的原型对象
console.log(Function.prototype.__proto__ === Object.prototype) // true
//  Function 也是Function 的实例
console.log(Function.__proto__ === Function.prototype)
// 对象最终指向object的原型
console.log(new sayHi().__proto__ instanceof Object) // true
console.log(new sayHi().__proto__ === sayHi.prototype) // true
console.log(Array.prototype.__proto__ === Object.prototype) // true
console.log(Object.__proto__.__proto__ === Object.prototype) // true

// 内置的array,string,number,object 等都是构造函数,同时也是对象
console.log(typeof Array) // function
console.log(typeof Object) // function

// 通过原型链找到object.prototype 上的方法
sayHi.valueOf() 

小结

  1. 原型是 JavaScript
    面向对象系统实现的根基,在这里更像是一种编程范式
  2. 在JavaScript 中原型模式无处不在,只要使用原型的模型创建对象就是在使用
    原型模式
Object.__proto__ === Function.prototype
Function.__proto__=== Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__prto__ === null