商家营销工具架构升级总结

ae9bc046e3fb92ed40c920c561e1d6ab.gif

今年以来,商家营销工具业务需求井喷,需求数量多且耗时都比较长,技术侧面临很大的压力。因此这篇文章主要讨论营销工具前端要如何应对这样大规模的业务需求。

cbd3f65ce5860773c71fc2f50a26209b.png

问题拆解

我们核心面对的问题主要如下:

1. 人力有限

  • 我们除了要支撑存量页面的日常迭代,还需要完成大量页面的新增,虽然有短期的人力支援,但总体还是捉襟见肘。

2. 如何保障交付质量和体验?

  • 商家营销工具核心的业务目标之一是体验优化,因此对前端交付页面的质量和体验,都有一定要求。而我们有大部分人力是新人,如何保证交付质量和体验?

3. 支援撤出后,长尾需求如何应对?

  • 新增页面的持续优化迭代会带来大量长尾需求。支援撤出后如何应对?

问题总结一句话其实就是:如何高效高质量地支撑相比以往更多的需求

最初的思路其实很简单:提高代码复用率

这样:

1. 一方面提高了研发效率,以应对人力瓶颈。

2. 另一方面能通过沉淀下来的标准代码,拔高整体交付产物的质量和体验下限。

9ea99f9cb356477e30acffb50dd9f598.png

统一产品形态与设计规范

前端提高代码复用率的方式,最常用的就是组件沉淀(基础组件基本配套齐全,这里主要指业务组件)。但组件沉淀的实际效果,会受很多因素的影响:

1. 业务形态是否稳定?

  • 针对一个业务场景沉淀的组件,会因该场景的业务调整而面临改造,不稳定的业务形态,会降低组件沉淀的收益。“三天两变”的业务形态下沉淀组件,可能会带来负收益。

2. UI是否稳定?

  • 道理和业务形态相同。

我们当然有一些方法能够尽可能降低这些因素带来的影响。比如可以将 业务逻辑 与 UI 分离,让他们可以独立变更,互不影响;也可以对组件做更多层次更抽象的封装,来让组件能“灵活”地适应不同场景。

但在螺丝壳里做道场,不如考虑从源头解决问题。下边是几个存量工具的PC端页面:

1552cd31b56c170b9f40fad5f50278f5.png

营销工具的产品形态特点鲜明:

1. 大部分工具都有 管理、创建、编辑、查看 四种能力,主要通过 列表页 和 表单页 两个页面来承载。

2. 列表页、表单页各模块的样式和交互模式非常相近。比如列表页顶部的Banner区,创建页的图片预览区。

如果能统一部分模块甚至页面框架的交互模式和样式,那么根据这些场景沉淀的组件,可复用性是很高的。

因此我们对营销工具域内,可规范化的场景做了枚举。去推动产品和设计统一产品形态和设计规范。出乎意料的顺利,产品、设计、前端 三方很快达成了一致,最终我们确定了营销工具域产品&设计规范。

规范的种子其实早已种下,只是缺了一个浇水的人。

  思考

当统一产品形态与设计规范这件事确定以后,技术上我们有了一些新的思路和要面对的问题:

  1. 接口的数据模型是否也能形成规范,我们可以将接口调用与数据处理也内置在组件内,做到更极致的提效。

  2. 不止于组件,我们是否可以将大的业务区块或者整个页面模版都沉淀下来?

  3. 一些数据源会频繁变更,业务逻辑又非常简单的模块,是否可以完全交由PD自行配置,免去迭代的开发工作量?

  4. 过去其实也有约定过一些产品&设计的规范,但都由于 规范粒度不够细、实践过程中产品&设计没有严格遵守规范、一遇到特殊定制逻辑就选择跳出规范等问题,导致最终各工具之间又逐渐趋于差异化,如何避免这种问题?

1cda2da7daf900cd50ce006b48bb9a5c.png

架构设计

基于上述的思考,我们针对商家营销工具场景,设计了一套技术架构,划分为以下几层:

  • 页面容器层:这一层,我们用来收敛不同工具之间的共性部分。

    • 如:页面UI框架、可配置化的模块(如列表页顶部Banner区)、请求方法、工具方法、公共依赖等。

  • 场景模版层:这一层,我们用来收敛一些 共性 > 差异 的业务模块中的共性部分。它是可选的,当页面中没有 共性 > 差异 的模块时,那也就无需拆分出这一层。

    • 如:首页的列表区块(包含:Tab区、筛选表单、列表、分页器),其中Tab切换、分页、筛选、样式布局等在不同工具下都是一致的,将这些逻辑固定下来,同时在模块中预留一些拓展点,支持不同工具定制,它就成为了列表场景的模版。

  • 业务定制层:这一层,用来实现不同工具之间的差异部分。

    • 如:在列表页,基于列表模版预留的拓展点进行定制;在表单页,实现单工具创建、编辑、查看态表单。

b716ef669ceb9664df13c650ab91c3bd.png

这样的架构设计:

1. 实现了我们最初的提高代码复用率的目标,提升开发效率,保障质量和体验下限。

2. 实现了一些共性需求或规范的更新,一次开发,整个业务域生效。

3. 会导致代码变更的影响范围被放大,页面容器层、场景模版层的变更,会一次影响到多个工具。但这是一把双刃剑:

  • 对开发在变更代码时提出了更高的要求。

  • 也让产品设计变得更加谨慎,因为越规范的部分越底层,越底层的部分影响范围越大,增强了规范的约束力。

4bba4da1dec7eae2d80de3e7a71765f9.jpeg

架构串联

我们首先要面对的问题,是如何串联实现上述的三层架构。

参考微前端架构,我们可以先简单地将工具的共性部分放到一起作为主应用;而每个工具的定制部分作为微模块。

在主应用中,我们通过路由区分不同工具,以加载不同工具的微模块。同时我们会维护一个页面上下文,在微模块加载后注入进去,用来实现模块与主应用之间的数据通信。

ef5d088968654f8b0609a0081409fc38.png

我们可以快速的搭建出一个示例:

主应用:

import { MicroModule } from '@ice/stark-module';// 路由与为模块的映射
const MPathToModuleBaseInfo = {'/工具A/home': { name: '工具A-home-module', url: 'https://xxx.xxx.xxx' },'/工具A/create': { name: '工具A-create-module', url: 'https://xxx.xxx.xxx' },...
}// 页面主应用页面
export default function App() {const [state, setState] = useState(1);// 页面上下文const pageContext = useMemo(() => ({ setState }), []);return (...// 渲染微模块<MicroModulemoduleInfo={MPathToModuleBaseInfo[location.pathname]}// 将上下文作为 prop 注入进微模块pageContext={pageContext}/>;... )
}

微模块:

export default function Module(props) {// 获取上下文const { pageContext } = props || {};const handleClick = () => {// 与页面容器做数据通信pageContext.setState((preState: number) => preState + 1);}// 渲染业务定制模块return (<button onClick={handleClick}>state + 1</button>);
}

到这里,我们实现了页面容器层和业务定制层的串联。但场景模版层,有些无处安放。

  • 将它放进主应用中?不太合适,因为场景模版的迭代频率是高于页面容器的,将这两层耦合在一起,会扩大场景模版的影响范围,增大维护压力。

  • 将它放进微模块中?也不合适,这样每个工具都会单独维护一份场景模版,失去了这一层抽象的意义。

有个简单的方式能解决上述问题,将场景模版封装成一个 npm 包,在每个微模块中引入。但这个方案的缺陷在于,每次场景模版迭代,需要到各个工具中去升级npm版本。发布工作量让人头疼;同时还很难管控版本统一,很容易出现多个版本同时在线上运行的情况。

我们在微内核架构中,找到了灵感。这个名字可能会有点陌生,但如果叫它插件系统,大家应该就很熟悉了。像我们工作中经常接触的 Chrome、VS Code,就拥有强大的插件系统。

55e3c6c0c2384a661805ee1c1de88251.png

在微内核架构下,应用被分割为独立的插件模块和核心系统。在我们的设计中,场景模版就可以被看作是一个业务场景下的核心系统;而每个工具对场景模版的定制拓展,就是一个个插件。下边是一段示例:

插件基座:

// 路由与为插件资源的映射
const MPathToPluginInfo = {'/工具A/home': { url: 'https://xxx.xxx.xxx' },...
}
// 核心系统
export default function CoreApp() {const [tabList, setTabList] = useState([]);// 插件API生成const pluginApi = useMemo(() => {return {tabs: {add: setTabList;},}}, []);// 加载并运行插件const pluginResult = useMemo(() => {const plugin = registerPlugin(MPathToPluginInfo[location.pathname]);plugin(pluginApi);}, []);// 核心系统内部渲染 Tab List 的逻辑return (<Tab>{tabList.map((tab) => (<Tab.Item key={tab.key} title={tab.title}>{tab.content}</Tab.Item>))}</Tab>)
}

插件模块:

function Content() {return <div>content</div>
}export default function plugin(api) {// 使用 api 对核心系统进行定制api.tabs.add((preState) => ([...preState, { key: 'demo', title: '标题', content: <Content /> }]))
}

为了和上述微前端的实现结合,我们可以将核心系统作为一个常驻微模块由主应用加载。而加载插件的逻辑可以放在核心系统内部。

  总结

到这里,架构的实现思路基本清晰了。

在表单页,我们可以直接通过 主应用 加载 微模块 的方式就能实现。

在列表页,我们针对列表场景,多做了一层抽象,将列表场景的共性逻辑封装成一个核心系统,各工具的差异逻辑则作为插件,对核心系统进行定制。通过 主应用 加载 核心系统,核心系统 加载 插件 的方式实现。

思路到最终实现,还有很多很多问题要解决。工程链路,渲染链路,发布链路,性能优化 等等。这也非一人之功,其中饱含了团队成员们的巧思,内容足以单开数篇文章,在本篇中就不做展开了。

630890f89bd18141dfe29cd9515c68bf.jpeg

分层实现

在上述架构下,开发业务定制层跟我们平常开发业务代码没什么区别,因此下边主要介绍 页面容器 和 场景模版 两层的一些实现思路。

  页面容器层
  • UI框架

通常最底层的容器,是不耦合UI的。相比于页面逻辑,UI 的变化频率要高得多。耦合UI后,会导致容器要频繁迭代,影响健壮性。

但在统一了产品形态和设计规范以后,有很多页面框架层面的 UI 被固化下来,因此我们决定把整个页面框架的逻辑和UI都放进容器里。里边包含了:页面布局、骨架屏、Error兜底、权限校验 等等。

4afd740b5f46fa6bb6ade841277a6bf7.png

  • 配置化

页面中同样存在一些UI一致,但是内容会频繁变化的模块,比如列表页顶部的Banner、公告、工具介绍、新人引导弹窗 等等。这些模块的变更,一般都是数据源的更新,因此我们决定把这些模块配置化,支持数据源远程下发;同时将配置产品化,将数据模型以表单形式表达,让PD能够自行修改,独立发布。

68d746632a16268524fe30a64b94277a.png

因已有配置产品化平台,接下来的工作就是对数据模型进行设计,并在项目中完成接入。

  • 模块加载

在产品&设计规范统一后,我们可以,也应该把页面的UI框架固定下来。但出于对未来不确定性的担忧,我们在容器的实现上,给自己留了一些后路。

参考了一些C端搭建场景的容器设计,我们将页面按照垂直方向划分为一个个区块,通过一份配置进行渲染。区块与区块通常彼此独立,但也可以通过页面上下文或自建事件通信来实现交互。

0b5ec7c84fdf5dc6a3a2c6e4c3463160.png

import Entry from './components/Entry';
import Header from './components/Header';// 预设的本地区块
const MPresetNameToComponent = {'home-entry': Entry,'home-header': Header,
}const pageConfigs = [{ name: 'home-header', type: 'preSetComponent' },{ name: 'home-entry', type: 'preSetComponent' },{name: 'home-table-layout',type: 'microModule',moduleInfo: {...},},
]export default function App() {const pageContext = useMemo(() => {}, []);// 解析协议,渲染模块const renderModule = (config) => {const { name, type, moduleInfo } = config || {};if (type === 'preSetComponent') {const Component = MPresetNameToComponent[name];if (Component) {return <Component pageContext={pageContext} />}}if (type === 'microModule') {return (<MicroModule {...moduleInfo} pageContext={pageContext} />)}return null;}return pageConfigs.map((config) => renderModule(config));
}

这份配置目前直接写死在容器中,但在需要时,我们可以将其改造为远程下发。

这样的渲染模式给页面容器带来了一定的灵活性,它支持了一个工具对页面框架层面的定制诉求。比如工具A想在页面Banner区上方,添加策略推荐模块。按照固定框架的方式实现容器,我们想要支持这样的诉求则需要迭代容器,同时对工具A做特判,来渲染策略推荐模块,这会使得容器越来越臃肿。但通过协议渲染,则不会有这样的问题。

  场景模版层

目前,我们仅在列表页做了这一层抽象。在规范中,列表区被固定为四个部分:Tab 区、筛选表单、列表、分页器。

0faf41696995e537fdfd49afed81601a.png

  • 基础逻辑

Tab区包含的逻辑很少,我们在核心系统中定义好Tab区的数据源,并在插件中通过API对数据源进行定制即可。

  • 最终方案中,在插件里定制数据源:

// 添加Tab1
api.appendItem({ extensionId: EExtensionId.xx, title: 'xxx', type: 'table' });
// 添加Tab2
api.appendItem({ extensionId: EExtensionId.xx, title: 'xxx', type: 'table' });

筛选表单本质是列表接口入参的表达,因此逻辑上相对简单,很少有联动,且表单项的类型都很固定,最常用的就是 Input、Select、DatePicker、Radio 这几种,很适合通过 JSON Schema 的方式渲染,而这种渲染方式,已经有很成熟的解决方案了,比如 formily。我们也是基于它实现的。在核心系统中,我们完成表单实例的创建和提交的监听,插件只需要专注于声明表单项。

  • 最终方案中,在插件里声明筛选表单项:

ExtensibleTable.modifyFilter(() => {return (form) => {// form 为核心系统中注入的表单实例return {// 表单项1A: {type: 'string','x-decorator': 'FormItem','x-component': 'Input','x-component-props': {placeholder: 'A',}},// 表单项2B: {type: 'number','x-decorator': 'FormItem','x-component': 'Select',enum: [{ label: 'xxxx', value: 0 },],'x-component-props': {placeholder: 'B',hasClear: true,}},}}
})

列表最核心的逻辑其实就是分页、筛选。我们已经完成了筛选表单的定制并能获取到表单值,剩余只需要串联搜索、分页的交互,管理列表、分页器的状态即可。ahooks 的 useFusionTable 已经为我们封装的很完整了,我们在核心系统中将他集成了进来,而在开发插件时,我们只用声明列表接口。

  • 最终方案中,在插件里声明请求方法:

const listXXX = () => {return fetch('https://xxx.xxx.xxx')
}ExtensibleTable.modifyActions((columns, ctx) => {return {search: {// 声明列表接口request: ({ current, pageSize, filterValue }) => {// filterValue 中每个 key 和声明的表单项一一对应return listXXX({ current, pageSize, filterValue });}}
});
  • 数据模型

上述的基础逻辑内置在核心系统中,已经能相当程度上减少列表区的开发工作量,但我们仍想更进一步。

列表核心是对服务端数据模型的表达,而数据模型又是对业务模型的抽象。在商家营销工具域内,不同工具之间有很多相似的业务模型,比如活动模型,它包含:活动名称、ID、时间、状态、绑定的商品等等字段信息。

在统一了产品形态和设计规范以后,我们顺其自然的想要推动服务端数据字段的统一。因此我们同样对可规范数据字段的场景进行了枚举,并以此去推动服务端统一数据字段与一些常用的功能型接口。最终我们达成了一致。

基于统一的数据,我们将部分列表单元格的渲染也内置到了核心系统中,根据插件中声明的单元格类型,核心系统自动去接口返回中找对应字段,并使用对应组件进行渲染。

  • 最终方案中,在插件里声明列表列:

ExtensibleTable.modifyColumns((columns, ctx) => {return [// 活动信息模型 AA,对应字段 A、B、C{ title: '活动', 'x-component': 'AA', width: 180 },// 活动状态模型 BB,对应字段 D{ title: '活动状态', 'x-component': 'BB', width: 90 },// 活动时间模型 CC,对应字段 E、F、G{ title: '活动时间', 'x-component': 'CC', width: 180 },{title: 'xx',dataIndex: 'xx',cell: (value, index, record) => {// 该工具独有的单元格,自定义渲染},width: 240,},// 操作列数据模型 TT,对应字段 H{title: '操作',width: 150,'x-component': 'TT','x-component-props': { maxCol: 3, maxRow: 5 },},];});
  • 交互模型

到这里,列表的主要逻辑中还剩余最后一块拼图,操作列交互。在中后台系统中,列表的操作是逻辑繁重的部分。我们在产品&设计规范中,对操作列的交互类型也做了统一。常见的交互类型有:二次确认、复制链接、导出文件、跳转 等。

我们将一个交互恒定的部分在核心系统中实现,在需要变化的部分预留好拓展点,由插件进行定制。

同时一些功能型接口,比如文件导出等,我们也推动了服务端统一,这样在插件中我们只需声明业务参数,甚至无需封装接口调用方法。

  • 最终方案中,在插件里定义操作交互:

ExtensibleTable.modifyRowActions((columns, ctx) => {return {// 自定义交互custom: (option, rowData, tableContext) => {// 操作的业务类型为 A 时,对应的交互if (option.type === 'A') {// do things}// 操作类型为 B 时,对应的交互if (option.type === 'B') {// do things}},// 二次确认交互doubleCheck: (option, rowData) => {let message = '';// 不同业务类型对应不同的提示文案if (option?.type === 'C') { message = 'CCC'; };if (option?.type === 'D') { message = 'DDD'; };return {message,onConfirm: () => {// 操作类型为 E,点击确认时要调用的接口if (option.type === 'E') {return fetch(params).then(() => {Message.success('成功');});}if (option.type === 'F') {return fetch(params).then(() => {Message.success('成功');})}},}},// 导出文件交互export: (option, rowData) => {let type = '';if (option?.type === 'G') type = 'GGG';if (option?.type === 'H') type = 'HHH';// 声明导出文件的业务参数,无需封装接口调用方法return {params: {type,}}},}
});

5fe3da11a67382ceb5f6d69e966e440d.jpeg

结语

基于上述这套方案开发的增量页面进行了研发效能核算。以使用常规方案开发的排期耗时为基准,最终的提效收益是很明显的,都在 50% 以上。

除了研发提效外,这套方案还带来了一些额外的收益

1. 一些在工具之间有共性的需求,比如资损校验等,在统一了设计规范之后,将其集成进 页面容器层 或 场景模版层 能做到一次开发,所有工具生效;

2. 部分模块可完全交给产品配置,变更无需排期;

3. 拔高了交付页面的质量和体验下限,视觉一致性也得到了保障。

同时也引入了一些问题

1. 架构复杂度提升很多,对稳定性和页面性能是一个考验;

2. 同时也对线上问题的排查、变更影响范围的评估带来了一定影响。

因此,未来这套方案目前能想到的迭代方向有几个:

1. 降低由架构引入的问题带来的影响;

2. 探索表单场景是否也能做场景模版层的抽象;

3. 提升存量页面需求开发的研发效率。

816465139f292ee5551559b88df0dd23.jpeg

团队介绍

我们是淘天集团-营销中后台前端团队,负责核心的淘宝&天猫营销业务,搭建千级运营小二、百万级商家和亿级消费者三方之间的连接通道,在这里将有机会参与到618、双十一等大型营销活动,与上下游伙伴协同作战,参与百万级流量后台场景的前端基础能力建设,通过标准化、归一化、模块化、低代码化的架构设计,保障商家与运营的经营体验和效率;参与面向亿级消费者的万级活动页面快速生产背后的架构设计、交付手段和协作方式。目前团队25届秋招进行中,对我们团队感兴趣的同学可以将简历发送至邮箱:wuzhiwei.wzw@taobao.com,欢迎加入!

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之题451题根据字符出现频率排序

题目&#xff1a; 题解&#xff1a; #define HASH_FIND_CHAR(head, findint, out) HASH_FIND(hh, head, findint, sizeof(char), out) #define HASH_ADD_CHAR(head, intfield, add) HASH_ADD(hh, head, intfield, sizeof(char), add)struct HashTable {char key;int val;UT_ha…

《数据密集型应用系统设计》笔记——第二部分 分布式数据系统(ch5-9)

第5章 数据复制 目的&#xff1a; 地理位置更近&#xff0c;降低延迟故障冗余提高读吞吐量 主节点与从节点&#xff08;主从复制&#xff09; 主从复制&#xff1a; 写请求发送给主节点&#xff0c;主节点将新数据写入本地存储&#xff1b;主节点将数据更改作为复制的日志发送…

SAP学习笔记 - Basis01 - 创建Client ,拷贝Client

最近工作当中用到了Client间数据移送的内容&#xff0c;想把自己的虚机给弄两个Client。 最后也没完全弄成&#xff0c;先把过程整理一下&#xff0c;以后有空接着弄。 目录 1&#xff0c;SALE - 新建逻辑系统 2&#xff0c;SCC4 - 分配Client到集团 3&#xff0c;RZ10 - 取…

python-FILIP/字符串p形编码/数字三角形

一&#xff1a;FILIP 题目描述 给你两个十进制正整数 a,b​&#xff0c;输出将这两个数翻转后的较大数。 「翻转」在本题中的定义详见「说明 / 提示」部分。输入 第一行&#xff0c;两个十进制正整数 a,b。输出 第一行&#xff0c;a 和 b 翻转后的较大数。样例输入1 734 893 样…

Microsoft Edge 五个好用的插件

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏 插件_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 Microsoft Edge 一.安装游览器 ​编辑 二.找到插件商店 1.打开游览器后&#xff0c;点击右上角的设置&#…

【深度学习基础模型】深度残差网络(Deep Residual Networks, DRN)详细理解并附实现代码。

【深度学习基础模型】深度残差网络&#xff08;Deep Residual Networks, DRN&#xff09;详细理解并附实现代码。 【深度学习基础模型】深度残差网络&#xff08;Deep Residual Networks, DRN&#xff09;详细理解并附实现代码。 文章目录 【深度学习基础模型】深度残差网络&a…

Python小示例——质地不均匀的硬币概率统计

在概率论和统计学中&#xff0c;随机事件的行为可以通过大量实验来研究。在日常生活中&#xff0c;我们经常用硬币进行抽样&#xff0c;比如抛硬币来决定某个结果。然而&#xff0c;当我们处理的是“质地不均匀”的硬币时&#xff0c;事情就变得复杂了。质地不均匀的硬币意味着…

Spring Boot中线程池使用

说明&#xff1a;在一些场景&#xff0c;如导入数据&#xff0c;批量插入数据库&#xff0c;使用常规方法&#xff0c;需要等待较长时间&#xff0c;而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先&#xff0c;创建一个Spr…

每日学习一个数据结构-树

文章目录 树的相关概念一、树的定义二、树的基本术语三、树的分类四、特殊类型的树五、树的遍历六、树的应用场景 树的遍历一、前序遍历二、中序遍历三、后序遍历使用java代码实现遍历总结 树的相关概念 树是一种重要的非线性数据结构&#xff0c;在计算机科学中有着广泛的应用…

24-10-4-读书笔记(二十四)-《一个孤独漫步者的遐想》下([法] 让·雅克·卢梭 [译]陈阳)

文章目录 《一个孤独漫步者的遐想》下&#xff08;[法] 让雅克卢梭 [译]陈阳&#xff09;目录阅读笔记记录总结 《一个孤独漫步者的遐想》下&#xff08;[法] 让雅克卢梭 [译]陈阳&#xff09; 十月第四篇&#xff0c;这次应该能拿到流量券吧&#xff01;《一个孤独漫步者的遐想…

A Learning-Based Approach to Static Program Slicing —— 论文笔记

A Learning-Based Approach to Static Program Slicing OOPLSA’2024 文章目录 A Learning-Based Approach to Static Program Slicing1. Abstract2. Motivation(1) 为什么需要能处理不完整代码(2) 现有方法局限性(3) 验证局限性: 初步实验研究实验设计何为不完整代码实验结果…

C#串口温度读取

背景&#xff1a;每天学点&#xff0c;坚持 要安装好虚拟串口和modbus poll&#xff0c;方便调试&#xff08;相关资源在文末&#xff0c;也可以私信找我要&#xff09; 传感器部分使用的是达林科技的DL11B-MC-D1&#xff0c;当时42软妹币买的&#xff08;官网上面有这个传感…

网络编程(12)——完善粘包处理操作(id字段)

十二、day12 之前的粘包处理是基于消息头包含的消息体长度进行对应的切包操作&#xff0c;但并不完整。一般来说&#xff0c;消息头仅包含数据域的长度&#xff0c;但是如果要进行逻辑处理&#xff0c;就需要传递一个id字段表示要处理的消息id&#xff0c;当然可以不在包头传i…

Linux网络编程

文章目录 参考资料在前1. 前置知识2. 进程概述2.1 fork()函数2.2 守护进程 3. 浅谈printf()函数与write()函数3.1 printf()函数缓存问题3.2 write()函数思考 4. 网络编程剖析4.1 listen()监听套接字4.2 阻塞/非阻塞IO4.3 同步/异步IO4.4 TCP/IP设计4.4.1 三次握手4.4.2 四次挥手…

机器人的性能指标

1. 负荷能力 负荷能力负荷能力是指机器人在满足其他性能要求的情况下,能够承载的负荷重量。例如,一台机器人的最大负荷能力可能远大于它的额定负荷能力,但是达到最大负荷时,机器人的工作精度可能会降低,可能无法准确地沿着预定的轨迹运动,或者产生额外的偏差。机器人的负荷量与…

【重学 MySQL】四十一、子查询举例与分类

【重学 MySQL】四十一、子查询举例与分类 引入子查询在SELECT子句中引入子查询在FROM子句中引入子查询在WHERE子句中引入子查询注意事项 子查询分类标量子查询列子查询行子查询表子查询 子查询注意事项子查询的位置子查询的返回类型别名的使用性能考虑相关性错误处理逻辑清晰 总…

Flet介绍:平替PyQt的好用跨平台Python UI框架

随着Python在各个领域的广泛应用&#xff0c;特别是在数据科学和Web开发领域&#xff0c;对于一个简单易用且功能强大的用户界面&#xff08;UI&#xff09;开发工具的需求日益增长。传统的Python GUI库如Tkinter、PyQt虽然功能强大&#xff0c;但在易用性和现代感方面略显不足…

数据结构--二叉树的顺序实现(堆实现)

引言 在计算机科学中&#xff0c;二叉树是一种重要的数据结构&#xff0c;广泛应用于各种算法和程序设计中。本文将探讨二叉树的顺序实现&#xff0c;特别是堆的实现方式。 一、树 1.1树的概念与结构 树是⼀种⾮线性的数据结构&#xff0c;它是由 n(n>0) 个有限结点组成…

【HTML5】html5开篇基础(5)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

vue-live2d看板娘集成方案设计使用教程

文章目录 前言v1.1.x版本&#xff1a;vue集成看板娘&#xff08;暂不使用&#xff0c;在v1.2.x已替换&#xff09;集成看板娘实现看板娘拖拽效果方案资源备份存储 当前最新调研&#xff1a;2024.10.2开源方案1&#xff1a;OhMyLive2D&#xff08;推荐&#xff09;开源方案2&…