当前位置: 首页 > article >正文

微服务系列(二) 微服务拆分不是拍脑袋-WMS怎么拆

微服务拆分不是拍脑袋WMS 怎么拆用 DDD 给仓储系统划边界我们踩过的弯路第一次拆分的翻车经历说实话我第一次拆微服务的时候自信满满。那时候公司有个单体 WMS仓储管理系统代码量大概 15 万行团队 20 多个人。每次发版都像在拆炸弹——你改了一个入库单的字段测试同学要把出库、盘点、报表全跑一遍生怕哪里连带崩了。老板拍板拆必须拆成微服务我当时想这还不简单微服务嘛就是把代码拆开部署。于是我大手一挥按技术层拆wms-controller-service放所有 Controller负责接 HTTP 请求wms-service-service放所有 Service负责业务逻辑wms-dao-service放所有 DAO 和数据库交互拆完之后我还挺得意看多清晰标准的 MVC 三层架构各管各的结果呢上线第一天就炸了。有个最简单的场景用户想查一个入库单的详情。这个请求先到 controller-servicecontroller-service 调 service-serviceservice-service 再调 dao-service。一个查询跨了 3 个服务走了 4 次网络调用。latency 从原来的 20ms 飙到了 400ms高峰期直接超时。更离谱的是有一次 dao-service 挂了controller-service 和 service-service 跟着全挂。本来是想解耦的结果耦合得更深了——只不过从代码层面的耦合变成了网络层面的耦合。那天晚上我和运维老哥一起 rollback 到单体版本凌晨三点才下班。路上我就在想微服务拆分到底该怎么拆后来我读到一句话醍醐灌顶微服务拆分的第一原则——按业务领域拆不是按技术层拆。说白了如果你按技术层拆只是把原来的一个应用变成了三个应用业务逻辑还是缠在一起的。真正该拆的是业务边界。这就引出了我们今天的主角DDD领域驱动设计。DDD 领域分析WMS 的五大核心域DDD 听起来很高大上其实核心思想就一句话先搞清楚业务里有哪些域再按域的边界拆服务。咱们以 WMS 为例跟业务专家也就是仓库的老大哥们聊了几轮之后我们梳理出了五大核心域。1. 入库域Inbound这个域管的是货怎么进来。核心流程收货 → 质检 → 上架。核心实体大概长这样// 入库域的核心实体classInboundOrder{StringorderNo;// 入库单号StringownerCode;// 货主编码引用基础资料ListInboundDetaildetails;InboundStatusstatus;// 待收货、收货中、质检中、已上架、已关闭}classInboundDetail{StringskuCode;// 商品编码BigDecimalqty;// 计划入库数量BigDecimalreceivedQty;// 已收货数量BigDecimalqualifiedQty;// 质检合格数量}边界说明入库域只关心货从到仓到上架这件事。它不需要知道库存最终有多少那是库存域的事。它只需要在质检完成、上架之后给库存域发一个事件“这批货已上架你记一下。”2. 出库域Outbound这个域管的是货怎么出去。核心流程波次分配 → 拣货 → 复核 → 打包 → 发货。// 出库域的核心实体classOutboundOrder{StringorderNo;StringownerCode;ListOutboundDetaildetails;OutboundStatusstatus;// 待分配、待拣货、拣货中、待复核、待打包、已发货}classWave{StringwaveNo;// 波次号ListStringorderNos;// 包含的出库单PickStrategystrategy;// 拣货策略按单拣、边拣边分、先拣后分}边界说明出库域的核心是让订单高效、准确地发出去。它会在拣货时向库存域查询可用库存在发货时通知库存域扣减库存。但它不维护库存余额只消费库存域的接口。3. 库存域Inventory这是 WMS 里最核心、也最敏感的域。核心职责库存记录、库存变动、库存冻结/释放。// 库存域的核心实体classInventory{StringwarehouseCode;StringownerCode;StringskuCode;StringlocationCode;// 库位编码BigDecimalqty;// 可用库存BigDecimalfrozenQty;// 冻结库存BigDecimallockedQty;// 锁定库存已被波次占用}classInventoryTransaction{StringbizNo;// 业务单号入库单/出库单/盘点单TransactionTypetype;// 入库增加、出库扣减、盘点调整、移库BigDecimalbeforeQty;BigDecimalafterQty;BigDecimaldeltaQty;}边界说明库存域是唯一的事实来源Single Source of Truth。任何域想改库存都必须通过库存域的接口。入库域说上架了库存域就加出库域说发货了库存域就扣库内作业域说盘亏了库存域就调。4. 库内作业域Internal这个域管的是仓库里不进货也不出货的日常操作。核心流程盘点、移库、补货、加工。// 库内作业域的核心实体classStockTakeOrder{StringorderNo;StockTakeTypetype;// 全盘、循环盘、动碰盘ListStockTakeDetaildetails;}classStockTakeDetail{StringlocationCode;StringskuCode;BigDecimalsystemQty;// 系统库存从库存域查询BigDecimalactualQty;// 实盘数量BigDecimaldiffQty;// 差异 实盘 - 系统}classMoveOrder{StringorderNo;StringfromLocation;StringtoLocation;StringskuCode;BigDecimalqty;}边界说明库内作业域会读取库存域的数据比如盘点时查系统库存也会产生库存变动盘点调整、移库。但和入库、出库不同的是库内作业不跟外部货主直接打交道它是仓库内部的自我管理。5. 基础资料域Master Data这个域是后勤部给其他所有域提供基础数据。核心实体商品SKU、货主Owner、仓库Warehouse、库位Location、承运商Carrier。// 基础资料域的核心实体classSku{StringskuCode;StringskuName;StringownerCode;Stringbarcode;BigDecimalweight;BigDecimalvolume;BooleanisFragile;// 是否易碎BooleanisColdChain;// 是否冷链}classLocation{StringwarehouseCode;StringlocationCode;LocationTypetype;// 拣货位、存储位、退货位、不良品位BigDecimalmaxWeight;BigDecimalmaxVolume;}边界说明基础资料域的数据相对静态变更频率低但读取频率极高。入库域要查 SKU 信息出库域要查库位信息库存域也要查。这个域的关键是接口稳定——一旦接口变了下游全得跟着改。边界划分的实战技巧光知道五大域还不够真正落地的时候你会发现很多灰色地带。比如入库单里要不要带 SKU 名称如果带是不是耦合了基础资料域库存变动日志该放在库存域还是单独拆一个库存日志服务报表该跟谁放在一起这些问题我们当时争论了很久。后来总结出了三个实战技巧分享给大家。技巧 1看数据归属——谁创建谁负责这是划分边界最朴素也最有效的一条规则。入库单是谁创建的入库域创建的那入库单的数据模型、生命周期、状态机都由入库域负责。SKU 是谁创建的基础资料域创建的SKU 的增删改查归基础资料域管。库存余额是谁创建的库存域创建的任何其他域只能用不能直接改。问题来了入库域展示入库单详情时需要显示 SKU 名称怎么办我们的做法是入库单里只存skuCode不存skuName。展示的时候前端分别调入库域和基础资料域的接口自己组装。或者由 BFFBackend for Frontend层去组装。这样做的好处是基础资料域改了 SKU 名称不需要通知入库域去同步。数据归属清晰边界就清晰。技巧 2看变更频率——高频变更的模块独立出来不同业务模块的变更频率差别很大。如果把它俩硬塞在一个服务里会互相拖累发版节奏。以 WMS 为例库存域几乎每天都在改今天加个冻结逻辑明天优化个锁库存性能后天对接一个新的电商平台。基础资料域一个月可能改不了一次SKU 新增都是运营在后台批量导入的。如果把库存和基础资料放一个服务里库存域每次发版都要带着基础资料域一起测、一起部署风险大、效率低。所以必须拆开。再举个例子报表模块需求变化极快老板今天想看这个维度明天想看那个维度后天要导出 Excel。核心作业模块出库流程一旦稳定不能轻易改动。所以报表一定要单独拆一个服务甚至单独一个数据库。报表查得慢、跑挂了都不能影响核心出库流程。技巧 3看团队边界——一个服务最好对应一个 2-pizza 团队亚马逊有个著名的2-pizza 团队原则一个团队的人数不能超过两张披萨能喂饱的规模大概 6-10 个人。这个原则在微服务拆分里特别有用一个服务最好由一个团队全权负责。我们当时有 20 多个人如果拆 5 个服务正好每个服务配 4-5 个人有点少如果拆 3 个服务每个服务 7-8 个人比较舒服。但注意不要为了凑团队人数而强行合并域。比如你把入库域和出库域合并成一个收发服务看起来团队规模合适了但入库和出库的业务逻辑差异很大一个团队很难同时精通两边最后变成谁都不敢大改。我们的折中方案是拆 6 个服务其中库存域团队 8 个人因为最核心、最复杂其他域各 3-4 个人基础资料域 2 个人就够了。我们最终的拆分方案聊了半天直接上图看看我们最后是怎么拆的。┌─────────────────────────────────────────────────────────────┐ │ 网关层 (Gateway) │ │ 统一鉴权、路由、限流、日志 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ BFF / 前端适配层 │ │ 组装数据、适配不同端PC、PDA、大屏 │ └─────────────────────────────────────────────────────────────┘ ↓ ↓ ↓ ↓ ↓ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 入库服务 │ │ 出库服务 │ │ 库存服务 │ │ 库内服务 │ │ 基础资料 │ │ Inbound │ │ Outbound│ │Inventory│ │ Internal│ │ Master │ │ Service │ │ Service │ │ Service │ │ Service │ │ Service │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ └────────────┴────────────┴────────────┴────────────┘ ↓ ┌─────────────────┐ │ 消息队列 MQ │ │ (RocketMQ) │ └─────────────────┘ ↓ ┌─────────────────┐ │ 报表服务 │ │ Report Service │ └─────────────────┘为什么报表单独拆报表有几个特点查询重、计算重一个大盘报表可能要扫几百万条记录做聚合、分组、排序。数据来源杂要同时查入库、出库、库存、库内各个域的数据。变更频繁老板的需求你懂的今天加个字段明天换个图表。如果把报表逻辑塞在库存服务或者出库服务里轻则拖慢核心接口重则把核心服务搞挂。所以报表必须独立甚至可以用单独的数据库通过 MQ 同步各域数据用 OLAP 引擎来跑。为什么库存不能拆太细有人可能会问库存域那么大能不能再拆比如拆成可用库存服务、“冻结库存服务”、“库存流水服务”我们一开始也想过这么拆但后来放弃了。原因是库存的核心是强一致性。一次出库操作要同时扣减可用库存、增加锁定库存、记录库存流水。如果把这三件事拆成三个服务就要引入分布式事务复杂度指数级上升。而且可用库存、冻结库存、锁定库存本质上都是同一张核心表的不同字段。拆得太细数据库层面还是耦合的没什么意义。我们的结论是库存域内部可以按模块分层比如分成库存查询模块、库存变动模块、库存冻结模块但对外还是一个服务。服务间调用关系伪代码// 场景用户下了一个出库单系统要分配库存并创建波次// Step 1: 出库服务接收请求创建出库单outboundService.createOrder(request);// Step 2: 出库服务调用库存服务锁定库存// 关键这里只传 skuCode 和 qty不耦合库存的内部实现LockResultresultinventoryService.lockStock(newLockRequest(skuCode,qty,outboundOrderNo));// Step 3: 如果库存不足返回错误如果锁定成功创建波次if(result.isSuccess()){waveService.createWave(outboundOrderNo);// 发送事件波次已创建mqProducer.send(newWaveCreatedEvent(outboundOrderNo));}else{thrownewBizException(库存不足无法分配);}// Step 4: 库内服务监听 WaveCreatedEvent生成拣货任务RocketMQMessageListener(topicwms-wave-topic)classPickTaskConsumer{voidonMessage(WaveCreatedEventevent){internalService.createPickTask(event.getOrderNo());}}// Step 5: 拣货完成后出库服务调用库存服务扣减库存并释放锁定inventoryService.deductAndUnlock(newDeductRequest(skuCode,qty,outboundOrderNo));// Step 6: 库存变动后发送事件给报表服务同步数据mqProducer.send(newInventoryChangedEvent(...));关键点同步调用OpenFeign只用在强依赖、必须立即知道结果的场景比如锁库存。异步调用MQ用在最终一致、可以延迟处理的场景比如生成拣货任务、同步报表数据。每个服务只暴露最小化、稳定的接口内部实现对外不可见。拆分 checklist这三个问题必须想清楚拆微服务不是目的目的是让系统更好维护、更好扩展。拆之前建议用下面这个 checklist 自检一下1. 数据是否高度内聚一个服务里的数据应该由这个服务自己创建、自己维护、自己负责一致性。如果某个表被三个服务同时读写那说明边界还没划清楚。2. 对外接口是否稳定微服务之间的接口就是契约一旦定了不能轻易变。如果你的接口三天两头改下游服务会崩溃的。基础资料域尤其要注意这一点。3. 能否独立部署、独立扩展这是微服务最核心的价值。大促时出库量暴增你可以只扩容出库服务报表跑得太慢你可以单独给报表服务加机器。如果拆完之后几个服务还是必须一起打包、一起部署那说明拆了个寂寞。写在最后微服务拆分没有标准答案但有一些明确的错误答案——比如按技术层拆。我们第一次拆 WMS 的时候走了不少弯路。按技术层拆完又 rollback按功能拆得太细又合并反复折腾了两三个月才找到现在的方案。回过头看我觉得最重要的收获不是拆成了几个服务而是整个团队对业务领域有了更深的理解。DDD 的价值很多时候体现在大家一起画边界图、一起讨论数据归属的过程中。当然微服务也不是银弹。如果你的团队不到 10 个人系统日活不到一万单体应用可能更香。别为了拆而拆合适的架构才是最好的架构。你们团队在微服务拆分的时候踩过哪些坑是按业务域拆的还是按技术层拆的WMS 或者其他业务系统你有什么更好的拆分思路欢迎在评论区交流咱们一起进步

相关文章:

微服务系列(二) 微服务拆分不是拍脑袋-WMS怎么拆

微服务拆分不是拍脑袋,WMS 怎么拆?用 DDD 给仓储系统划边界,我们踩过的弯路第一次拆分的"翻车"经历 说实话,我第一次拆微服务的时候,自信满满。 那时候公司有个单体 WMS(仓储管理系统&#xff09…...

高性能Photoshop图层批量导出工具:智能优化算法解析与80%性能提升方案

高性能Photoshop图层批量导出工具:智能优化算法解析与80%性能提升方案 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. …...

HALCON copy_obj算子保姆级教程:从‘复制粘贴’到高效数据管理的避坑指南

HALCON copy_obj算子深度实战:工业视觉项目中的数据管理艺术 在工业视觉项目的开发过程中,数据管理往往是最容易被忽视却又至关重要的环节。当我们面对复杂的多步骤图像处理流程时,如何确保数据在不同处理阶段的安全传递和可靠备份&#xff1…...

职业安全感缺失?软件测试从业者构建技术护城河的3步策略

技术变革下的职业焦虑2026年的软件测试领域正经历前所未有的重构。AI测试工具实现90%用例自动生成,云原生架构重构测试流程,大模型驱动的缺陷预测系统逐步普及。行业报告显示,基础功能测试岗位需求缩减40%,而具备自动化与业务建模…...

2026年最易被淘汰的测试角色,你中招了吗?

随着人工智能和自动化技术的飞速发展,软件测试行业正经历一场前所未有的变革。2026年,AI驱动的测试工具已从概念走向大规模落地,重塑了工作流程和岗位需求。Gartner数据显示,全球测试岗位年均萎缩率达5%,企业正加速部署…...

JAVA智能配电房管理系统源码:含数据字典、完整文档及多种功能实现

JAVA智能配电房管理系统源码带数据字典及完整文档JAVA智能配电房管理系统源码带数据字典及完整文档实现各模块数据显示,报警显示,报表导出功能。 此次监控的电力系统有两个配电房,总共四个变压器,54条供电线路。 能通过电路拓扑图…...

计算机毕业设计:Python降雨量智能监测与预警系统 Flask框架 数据分析 可视化 大数据 AI 大模型 爬虫 数据大屏(建议收藏)✅

1、项目介绍 技术栈 采用 Python 语言开发,基于 Flask 框架搭建后端服务,使用 sqlite 数据库进行数据存储,前端利用 Echarts 实现数据可视化与数据大屏展示,通过爬虫技术采集降水数据。 功能模块降水量分析主页降水量数据展示…...

计算机毕业设计:Python城市气候分析与预测平台 Flask框架 随机森林 K-Means 可视化 数据分析 大数据 机器学习 深度学习(建议收藏)✅

1、项目介绍 技术栈 采用 Python 语言开发,基于 Flask 框架搭建后端服务,使用 sqlite 数据库进行数据存储,前端利用 Echarts 实现数据可视化,运用机器学习中的 K-Means 聚类算法进行天气预测,采用随机森林回归算法预测…...

Windows热键冲突终极指南:Hotkey Detective帮你3分钟定位键盘“小偷“

Windows热键冲突终极指南:Hotkey Detective帮你3分钟定位键盘"小偷" 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-…...

7个步骤掌握Bioicons:科研小白的生物图标免费宝库

7个步骤掌握Bioicons:科研小白的生物图标免费宝库 【免费下载链接】bioicons A library of free open source icons for science illustrations in biology and chemistry 项目地址: https://gitcode.com/gh_mirrors/bi/bioicons 还在为科研论文配图发愁吗&a…...

魔兽争霸III终极兼容指南:如何让经典游戏在现代Windows系统完美运行

魔兽争霸III终极兼容指南:如何让经典游戏在现代Windows系统完美运行 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸III在W…...

技术测试驱动开发的先测试后编码

技术测试驱动开发:先测试后编码的智慧 在软件开发领域,技术测试驱动开发(TDD)以其独特的“先测试后编码”理念,逐渐成为高效开发的重要方法论。TDD的核心思想是在编写功能代码之前,先定义测试用例&#xf…...

SourceGit:跨平台Git图形化客户端的完全使用指南

SourceGit:跨平台Git图形化客户端的完全使用指南 【免费下载链接】sourcegit Windows/macOS/Linux GUI client for GIT users 项目地址: https://gitcode.com/gh_mirrors/so/sourcegit SourceGit是一款功能强大的跨平台开源Git图形化客户端,支持W…...

Linux网络模拟实战:用NetEm和TC命令打造你的专属弱网环境(附常见问题排查)

Linux网络模拟实战:用NetEm和TC命令打造你的专属弱网环境 在分布式系统开发与测试中,网络环境的不稳定性往往是隐藏最深的"性能杀手"。想象一下,当你的应用在本地开发环境运行流畅,一旦部署到真实网络环境中&#xff0…...

Go语言如何做文件断点续传_Go语言断点续传下载教程【详解】

...

华硕ROG XBOX 掌机 X RC73XA 原厂Win11 24H2 系统分享下载

华硕ROG XBOX掌机X RC73XA自带一键恢复功能,即使系统异常或更换硬盘后也能通过原厂工厂文件轻松恢复到出厂设置。支持Windows 11 24H2家庭版系统,用户仅需准备一个容量大于20G的U盘,并按照提供的安装教程操作即可完成恢复过程,确保…...

如何在企业级层面将知识图谱和大型语言模型(LLM)结合起来

专注于知识图谱构建与应用开发,提供一站式定制服务。 涵盖数据采集、实体与关系抽取、图谱建模及优化,支持科研与企业场景。 可开发智能问答、语义查询与推荐系统,并提供可视化分析与Neo4j图数据库搭建,助力高效挖掘知识价值&am…...

大模型应用开发实战(6)——做一个能上线的 AI 应用,最小技术栈到底需要哪些东西

🤵‍♂️ 个人主页:小李同学_LSH的主页 ✍🏻 作者简介:LLM学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞&#x1f4…...

Matplotlib散点图高级玩法:如何用颜色条和随机数据提升可视化效果

Matplotlib散点图高级玩法:如何用颜色条和随机数据提升可视化效果 在数据可视化领域,散点图是最基础也是最强大的工具之一。但很多开发者仅仅停留在绘制简单点的阶段,忽略了Matplotlib提供的丰富功能。本文将带你探索如何通过颜色条(Colormap…...

Deebot智能扫地机如何无缝融入Home Assistant?3大核心价值解析

Deebot智能扫地机如何无缝融入Home Assistant?3大核心价值解析 【免费下载链接】Deebot-4-Home-Assistant Home Assistant integration for deebot vacuums 项目地址: https://gitcode.com/gh_mirrors/de/Deebot-4-Home-Assistant 还在为多个智能家居App切换…...

魔兽争霸III现代化改造:5大核心功能让你的经典游戏焕发新生

魔兽争霸III现代化改造:5大核心功能让你的经典游戏焕发新生 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 魔兽争霸III作为一代经典RTS游…...

轮足式机器人:从STM32到ROS,构建多传感器融合的智能运动控制核心

1. 轮足式机器人:当轮子遇上机械腿 第一次看到轮足式机器人时,我正调试着一台卡在碎石堆里的轮式机器人。当时就在想:要是它能像昆虫一样抬起轮子跨过去该多好。这种结合轮式高速移动和足式越障能力的混合体,正是现代机器人应对复…...

控制管理化技术过程控制与质量检查

控制管理化技术过程控制与质量检查是现代工业生产中不可或缺的核心环节。随着科技的进步和市场竞争的加剧,企业对产品质量和生产效率的要求越来越高。通过科学的过程控制和严格的质量检查,企业能够确保产品的一致性和可靠性,从而提升客户满意…...

别再只会用SPI了!手把手教你用STM32的QSPI驱动外部Flash(附完整代码)

突破SPI性能瓶颈:STM32 QSPI驱动外部Flash的实战优化指南 在嵌入式系统开发中,外部Flash存储器已成为存储固件、图形资源和日志数据的标配组件。许多开发者习惯使用传统的SPI接口与Flash通信,但当遇到高分辨率图形加载、实时数据记录或OTA升级…...

详细介绍有机化学里面的SN1和SN2的反应

下面为你系统介绍有机化学中两种最基本的亲核取代反应机理——SN1和SN2。理解这两种机理是掌握有机反应的基础。 一、基本概念 亲核取代反应:一个富电子的亲核试剂(Nu⁻ 或 Nu:)进攻缺电子的碳原子,取代原有的离去基团&#xff0…...

详细介绍标准摩尔生成焓和标准摩尔燃烧焓

这是一个非常核心的化学热力学问题。下面为你详细介绍标准摩尔生成焓和标准摩尔燃烧焓,包括它们的定义、要点、应用以及两者之间的重要联系。 一、标准摩尔生成焓 标准摩尔生成焓是衡量物质相对“能量水平”的基准,可以理解为从“零”开始构建一个化合物…...

CISSP 域5知识点 身份认证与授权

🔐 CISSP必修课⑤ | 身份认证与授权(官方核心考点完全拆解) 🌊 CISSP Domain 5 身份认证与授权 | 官方核心定位 📍 归属:Domain 5 身份与访问管理(Identity and Access Management, IAM&#x…...

CISSP 域5知识点 身份全生命周期管理

CISSP考点速记|Domain5 身份全生命周期管理 👤🔄 官方定位:Domain 5 身份与访问管理的核心模块,占Domain5权重40%以上;对应OSG第十版**第13章《Managing Identity and Access》**全部内容 Domain1合规要求…...

【电路设计】LDO旁路电容的选型误区与实战解析

1. 为什么你的LDO电路总是不稳定? 很多工程师在设计LDO电路时,经常会遇到这样的困惑:明明按照数据手册推荐值选择了电容,电路却总是出现振荡、噪声超标等问题。这往往是因为忽略了旁路电容的非理想特性。就像给汽车加油&#xff0…...

Mac上用Xcode学C语言

尽管多数大学教材以Windows系统为主进行讲解,但C语言的核心知识点在不同平台上差异不大。本文详细介绍如何利用Mac自带的开发工具Xcode进行C语言学习,为使用MacBook的学生提供了一种便捷高效的学习路径,具有较强的实用性和参考价值。 1、 前往…...