《软件工程实务》课程学习心得:从理论到实践的蜕变之旅
《软件工程实务》课程学习心得:从理论到实践的敏捷蜕变
关键词:软件工程、敏捷开发、Scrum、微服务、DevOps、Codeup、能源管理系统
可在该链接内学习相关内容:
一、写在前面
本学期我修读了《软件工程实务》课程,从课程概要到项目实战,系统学习了软件工程全生命周期。不同于以往只关注编码,这门课让我真正理解了产品愿景、用户故事、架构设计、进度管理、DevOps等工程化实践。
本文将结合课程内容、个人项目实践(能源管理系统)以及使用的工具(Codeup),分享我的学习心得。
一点背景:在修这门课之前,我对“软件工程”的理解停留在“写文档”三个字上。我以为它是一堆繁文缛节的规章制度,甚至会拖慢开发进度。但经过一个学期的学习,我彻底改变了这个看法——软件工程不是束缚,而是对复杂性的管理。当项目规模从几百行代码扩展到几千行、几万行,当团队从1个人变成5个人,当交付时间从“随便”变成“第8周周五下午5点截止”时,工程方法的价值就显现出来了。
二、课程结构速览
| 单元 | 核心内容 | 实践工具/产出 | 我的掌握程度(自评) |
|---|---|---|---|
| 1 | 课程介绍 + Codeup入门 | Git仓库、Markdown笔记 | ⭐⭐⭐⭐⭐ |
| 2 | 产品愿景 + 进度管理 | 甘特图、WBS | ⭐⭐⭐⭐ |
| 3 | 产品定义 + Git入门 | Codeup协作 | ⭐⭐⭐⭐⭐ |
| 4 | 用户故事 | 用户故事卡 | ⭐⭐⭐⭐ |
| 5 | 功能设计 + 能源功能清单 | 功能列表 | ⭐⭐⭐⭐ |
| 6 | 软件架构 + 微服务 | 架构图 | ⭐⭐⭐ |
| 7 | 业务架构设计 | 业务流程图 | ⭐⭐⭐⭐ |
| 8 | 技术架构 + 数据字典 | 数据库设计 | ⭐⭐⭐⭐ |
| 9 | 敏捷 + 产品积压项 | PB列表 | ⭐⭐⭐⭐⭐ |
| 10 | Scrum框架 | Sprint计划 | ⭐⭐⭐⭐ |
| 11 | 可靠编程 + 安全隐私 | 代码规范、加密示例 | ⭐⭐⭐ |
| 12 | 云计算 + DevOps | CI/CD流水线 | ⭐⭐⭐ |
📌 从自评可以看出,我对工具类和敏捷实践类的掌握更好,架构设计类还需要继续深入学习。
三、五大核心收获(详细展开)
1️⃣ 产品愿景驱动开发
在第二单元中,我们为“能源管理系统”制定了产品愿景。这不是随便写一句话,而是要回答三个核心问题:为谁做?解决什么问题?做到什么程度?
我们小组最终确定的产品愿景:
为园区物业管理人员提供实时、可视化的能耗监控与智能告警服务,帮助用户在6个月内实现15%以上的能源成本节约。
为什么这个愿景有效?
-
✅ 明确目标用户:园区物业管理人员
-
✅ 明确核心价值:实时、可视化、智能告警
-
✅ 明确量化指标:15%能源成本节约、6个月时间节点
我的体会:愿景不是贴在墙上就完事的。在后续的每个Sprint中,我们都会回到愿景来问自己:“这个功能真的帮助用户节约能源了吗?”有一次我们想做一个复杂的报表导出功能,但回顾愿景后发现用户更需要的是实时告警,于是调整了优先级。这就是愿景对开发的实际指导作用。
2️⃣ Git + Codeup 团队协作
在第三单元中,我们正式使用阿里云Codeup进行团队协作。我们小组有4个人,分别是:前端1人、后端2人、测试兼文档1人。
我们采用的Git工作流:
bash
# 1. 每天开始工作前,同步develop分支
git checkout develop
git pull origin develop
# 2. 基于develop创建功能分支(用feat/前缀 + 用户故事ID)
git checkout -b feat/US-003-user-login
# 3. 开发过程中,小步提交(每个提交只做一件事)
git add src/main/java/com/energy/controller/UserController.java
git commit -m "feat(login): 实现用户登录接口,返回JWT token"
# 4. 开发完成后,推送到远程并创建合并请求
git push origin feat/US-003-user-login
# 然后在Codeup网页上创建MR → 指定评审人 → 通过后合并到develop
我们踩过的坑:
| 问题场景 | 错误做法 | 正确做法 |
|---|---|---|
| 两个人改了同一个文件的同一行 | 直接 push 被拒绝,然后强制 push ❌ | 先 pull → 解决冲突 → 再 push |
| 功能开发到一半,紧急需要切分支修bug | 直接 checkout 导致工作区混乱 ❌ | 用 git stash 暂存当前改动 |
| 合并请求被驳回,不知道哪里改 | 重新提交一个MR ❌ | 在同一个分支上继续 commit,MR会自动更新 |
我的体会:Git不是“把代码传上去就行”的工具,它是团队协作的通信协议。谁的commit message写得不清楚,谁就在给别人挖坑。我们小组后来约定:commit message必须按照 类型(模块): 简短描述 的格式,例如 fix(auth): 修复token过期后未刷新问题。
🖼️ 图片说明(模拟)
图中展示了主分支(main)、开发分支(develop)和三个功能分支(feat/energy-chart、feat/alert-rule、feat/user-profile)的合并记录,以及最后一次CI/CD流水线的状态(绿色通过)。
3️⃣ 用户故事与敏捷积压
第四单元的用户故事是让我印象最深刻的内容之一。以前我写需求就是“做一个登录功能”,但用户故事强迫我们思考:谁要用?为什么要用?怎么才算完成?
标准格式:
md
作为 [角色]
我想要 [功能]
以便 [价值]
我们编写的真实案例:
md
作为 能源管理员
我想要 查看指定时间范围(日/周/月/自定义)的用电趋势图
以便 发现高峰用电时段并制定错峰用电策略。
验收标准(Acceptance Criteria):
-
✅ 可以选择时间范围:今日、本周、本月、自定义日期区间
-
✅ 以折线图形式展示,X轴为时间,Y轴为用电量(单位:kWh)
-
✅ 鼠标悬停时显示具体数值
-
✅ 可以导出图片(PNG格式)
产品积压项详细表(第9-10单元内容):
| 优先级 | 用户故事ID | 用户故事内容 | 估算(故事点) | 负责人 | 状态 |
|---|---|---|---|---|---|
| 🔴 高 | US-001 | 作为管理员,我希望通过账号密码登录系统 | 5 | 张三 | ✅ 已完成 |
| 🔴 高 | US-002 | 作为管理员,我希望查看所有设备的实时能耗数据 | 8 | 李四 | ✅ 已完成 |
| 🟡 中 | US-003 | 作为管理员,我希望为指定设备设置能耗告警阈值 | 5 | 王五 | 🔄 进行中 |
| 🟡 中 | US-004 | 作为管理员,我希望按日/周/月导出能耗报表(Excel) | 8 | 赵六 | ⏳ 待开发 |
| 🟢 低 | US-005 | 作为管理员,我希望对比不同设备的能耗排名 | 3 | - | 📋 积压中 |
| 🟢 低 | US-006 | 作为管理员,我希望接收邮件告警通知 | 5 | - | 📋 积压中 |
我的体会:用户故事是“沟通工具”而不是“合同文档”。在和产品经理、测试同学讨论时,用故事的形式比用长长的需求文档高效得多。验收标准写清楚了,测试用例也就有了基础,开发过程中扯皮的情况大大减少。
4️⃣ 软件架构:从单体到微服务
在第六和第七单元,我们学习了软件架构设计。最初我们设计的是一体化架构(前端+后端+数据库都在一个项目里),但随着功能增加,我们发现:
-
❌ 编译时间越来越长(从10秒到2分钟)
-
❌ 改一个小功能需要重新部署整个应用
-
❌ 某个模块出问题(比如告警模块内存泄漏)会导致整个系统不可用
于是我们决定向微服务演进。最终设计如下:
text
┌─────────────────┐
│ 前端(Vue.js) │
└────────┬────────┘
│ HTTPS
┌────────▼────────┐
│ API网关(Spring Cloud Gateway) │
└────────┬────────┘
│
┌────────┬───────────┼───────────┬────────┐
│ │ │ │ │
┌───────▼──────┐ ┌▼──────────▼──┐ ┌───────▼──────┐ ┌▼──────────┐
│ 认证服务 │ │ 数据采集服务 │ │ 能耗分析服务 │ │ 告警服务 │
│ (JWT) │ │ (Modbus/TCP) │ │ (InfluxDB) │ │ (SMTP/钉钉)│
└───────┬──────┘ └──────┬───────┘ └──────┬───────┘ └──────┬─────┘
│ │ │ │
└────────┬───────┴────────────────┴────────┬───────┘
│ │
┌────────▼────────┐ ┌────────▼────────┐
│ MySQL │ │ InfluxDB │
│ (用户/权限) │ │ (时序数据) │
└─────────────────┘ └─────────────────┘
各服务职责:
| 服务名称 | 职责 | 端口 | 技术栈 |
|---|---|---|---|
| API网关 | 路由转发、限流、鉴权 | 8080 | Spring Cloud Gateway |
| 认证服务 | 登录、Token颁发与校验 | 8081 | Spring Security + JWT |
| 数据采集服务 | 从电表读取数据,写入InfluxDB | 8082 | Netty + Modbus协议 |
| 能耗分析服务 | 查询、聚合、趋势计算 | 8083 | Spring Boot + InfluxDB驱动 |
| 告警服务 | 检测阈值并发送通知 | 8084 | Spring Boot + 钉钉机器人 |
🖼️ 架构图说明(模拟)

我的体会:微服务不是银弹。我们小组只有4个人,其实单体架构完全够用。但我们选择微服务是出于学习目的。真正让我体会到微服务好处的是独立部署:有一次告警服务出了bug,我们只重启了那个服务,主流程(数据采集和展示)完全不受影响。这在单体架构中是做不到的。
5️⃣ DevOps + 云计算
第12单元的DevOps是整门课的“压轴戏”。我们使用阿里云Codeup自带的流水线功能,实现了从代码提交到自动部署的全流程。
CI/CD流水线配置(.codeup/pipeline.yml):
yaml
# Codeup流水线配置文件(简化版)
name: 能源管理系统CI/CD
stages:
- name: 代码检出
type: git-checkout
- name: 后端构建(Java)
type: maven
script: mvn clean package -DskipTests
artifacts:
- target/*.jar
- name: 单元测试
type: maven
script: mvn test
# 测试报告收集
reports:
junit: target/surefire-reports/*.xml
- name: Docker镜像构建
type: docker-build
dockerfile: Dockerfile
image: registry.cn-hangzhou.aliyuncs.com/energy/energy-service:${CI_COMMIT_SHORT_SHA}
- name: 部署到测试环境
type: ssh-deploy
host: 47.xxx.xxx.xxx
script: |
docker pull registry.cn-hangzhou.aliyuncs.com/energy/energy-service:${CI_COMMIT_SHORT_SHA}
docker stop energy-service || true
docker rm energy-service || true
docker run -d --name energy-service -p 8080:8080 registry.cn-hangzhou.aliyuncs.com/energy/energy-service:${CI_COMMIT_SHORT_SHA}
我们对DevOps的理解变化:
| 阶段 | 我们的做法 | 问题 | DevOps后的做法 |
|---|---|---|---|
| 第1周 | 用U盘拷代码 | 版本混乱、覆盖丢失 | Git + Codeup统一仓库 |
| 第3周 | 手动scp上传jar包 |
忘记传配置文件 | 流水线自动打包+部署 |
| 第5周 | 部署前想起来要测试 | 经常在线上发现bug | 流水线自动执行单元测试 |
| 第7周 | 部署后手动验证 | 耗时且容易遗漏 | 部署后自动运行冒烟测试 |
我的体会:DevOps最打动我的不是技术,而是安全感。以前上线新功能就像“赌一把”,现在有了流水线,每次提交都会自动运行300多个单元测试,有什么问题5分钟之内就能发现。这种信心是之前没有过的。
🖼️ 系统截图模拟

图中显示近7天用电量趋势折线图,X轴为日期(5月7日-5月13日),Y轴为用电量(kW)。可以看到5月10日(周五)有一个明显的高峰(156kW),分析原因是空调集中使用。右侧显示各区域实时功率排行:生产车间(45kW)、办公楼(28kW)、食堂(12kW)。
四、完整的功能实现代码示例(带详细注释)
根据用户故事“US-002 查看实时能耗”,我实现了完整的REST API + Service + 数据库查询。
4.1 Controller层
java
package com.energy.controller;
import com.energy.common.Result; // 统一响应封装
import com.energy.entity.EnergyData; // 能耗实体类
import com.energy.service.EnergyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 能耗数据接口
* 对应敏捷用户故事:US-002 作为管理员,我希望查看所有设备的实时能耗数据
*/
@RestController
@RequestMapping("/api/energy")
@Api(tags = "能耗管理")
@RequiredArgsConstructor
public class EnergyController {
private final EnergyService energyService;
/**
* 获取指定设备的最新实时能耗
* GET /api/energy/realtime/{deviceId}
*
* @param deviceId 设备编号,如 "DEV-METER-001"
* @return 最新能耗数据,包含数值、单位、采集时间
*/
@GetMapping("/realtime/{deviceId}")
@ApiOperation("获取实时能耗")
public Result<EnergyData> getRealtime(@PathVariable String deviceId) {
// 参数校验:设备编号不能为空
if (deviceId == null || deviceId.trim().isEmpty()) {
return Result.error("设备编号不能为空");
}
EnergyData data = energyService.getLatest(deviceId);
// 如果没有数据,返回友好的提示(而不是null或异常)
if (data == null) {
return Result.error("未找到设备 " + deviceId + " 的能耗数据");
}
return Result.success(data);
}
/**
* 批量获取多个设备的最新数据
* POST /api/energy/realtime/batch
*
* @param deviceIds 设备编号列表
* @return 设备编号 → 能耗数据的Map
*/
@PostMapping("/realtime/batch")
@ApiOperation("批量获取实时能耗")
public Result<Map<String, EnergyData>> getBatchRealtime(@RequestBody List<String> deviceIds) {
if (deviceIds == null || deviceIds.isEmpty()) {
return Result.error("设备编号列表不能为空");
}
// 限制单次查询数量,防止恶意请求或缓存穿透
if (deviceIds.size() > 100) {
return Result.error("单次查询设备数量不能超过100个");
}
Map<String, EnergyData> result = energyService.getLatestBatch(deviceIds);
return Result.success(result);
}
}
4.2 单元测试(TDD实践)
java
package com.energy.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* EnergyController单元测试
* 采用TDD思想:先写测试(定义期望行为),再实现功能
*/
@WebMvcTest(EnergyController.class)
class EnergyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private EnergyService energyService;
@Test
void testGetRealtime_Success() throws Exception {
// Arrange:准备测试数据
EnergyData mockData = EnergyData.builder()
.deviceId("DEV-METER-001")
.value(120.5)
.unit("kW")
.timestamp(LocalDateTime.now())
.build();
// 模拟Service层行为
when(energyService.getLatest("DEV-METER-001")).thenReturn(mockData);
// Act & Assert:执行请求并验证响应
mockMvc.perform(get("/api/energy/realtime/DEV-METER-001"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.value").value(120.5))
.andExpect(jsonPath("$.data.unit").value("kW"));
}
@Test
void testGetRealtime_DeviceNotFound() throws Exception {
// 测试设备不存在的情况
when(energyService.getLatest("INVALID-DEVICE")).thenReturn(null);
mockMvc.perform(get("/api/energy/realtime/INVALID-DEVICE"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(500))
.andExpect(jsonPath("$.message").value(containsString("未找到设备")));
}
@Test
void testGetRealtime_EmptyDeviceId() throws Exception {
// 测试空设备编号的边界情况
mockMvc.perform(get("/api/energy/realtime/"))
.andExpect(status().isNotFound()); // Spring MVC会对空路径返回404
}
}
五、课程中遇到的最大困难与解决方法
困难1:第一次做架构设计,完全不知道从哪下手
背景:第六单元讲微服务架构时,老师让我们画出自己项目的架构图。我盯着空白页看了20分钟,一个字都写不出来。
解决过程:
-
我先找了一个成熟的开源项目(若依微服务版),看懂它的分层
-
然后用“抄作业”的方式,先把结构框架画出来
-
再根据自己的项目替换具体内容(把“用户服务”改成“数据采集服务”等)
-
最后找助教review,修改了3版才定稿
反思:不会画不是能力问题,是还没见过足够多的好例子。以后遇到陌生领域,先看10个例子再动手。
困难2:团队两个人同时改一个文件,Git冲突不会解决
背景:有一次王小和李四同时修改了application.yml配置文件,两人都push了,导致远程分支冲突。
解决步骤(后来整理成文档):
bash
# 1. 拉取最新代码,发现冲突提示
git pull origin develop
# 输出:CONFLICT (content): Merge conflict in application.yml
# 2. 打开冲突文件,找到冲突标记
<<<<<<< HEAD
port: 8080
=======
port: 8081
>>>>>>> feat/alert-service
# 3. 两人沟通后决定:李四的功能需要8081,王小改成8081
# 4. 手动删除标记,保留正确内容:port: 8081
# 5. 标记为已解决并提交
git add application.yml
git commit -m "resolve: 解决application.yml端口冲突,统一使用8081"
git push origin develop
困难3:自动化测试覆盖率一直达不到要求
问题:课程要求单元测试覆盖率≥80%,我们一开始只有52%。
解决方法:
-
使用JaCoCo插件生成覆盖率报告,逐行分析未覆盖代码
-
优先补全核心业务逻辑(Service层)的测试
-
Controller层的简单CRUD不做过度测试
-
最终覆盖率:82%(Service层92%,Controller层73%)
六、课程考核方式与我的得分
| 考核项 | 占比 | 我的得分 | 失分原因(反思) |
|---|---|---|---|
| 平时实验 + Codeup提交记录 | 30% | 95/100 | 有一次忘记提交周报 |
| 团队项目(能源管理系统) | 40% | 90/100 | 架构文档写得太简略,扣了10分 |
| 个人学习笔记 + 博客 | 20% | 92/100 | 有一篇博客排版问题 |
| 课堂互动与汇报 | 10% | 88/100 | 汇报时间超时2分钟 |
总评:86分(良好)
自我评价:知识上收获很大,分数上没有拿到优秀有点遗憾,但问题出在表达和规范上,不是能力问题。下学期选修高级软件工程继续努力。
七、总结与展望
《软件工程实务》让我从一个“只会写代码”的学生,成长为具备工程思维、团队协作、架构意识的准软件工程师。我深刻体会到:
| 如果只会... | 后果 | 学完之后我懂得了... |
|---|---|---|
| 写代码 | 项目一复杂就维护不了 | 编写单元测试 + 遵循代码规范 |
| 自己一个人开发 | 无法与他人协作 | Git分支模型 + 代码审查 |
| 跟着感觉做需求 | 做出来没人用 | 用户故事 + 验收标准 |
| 直接部署上线 | 经常出问题且回滚困难 | CI/CD流水线 + 灰度发布 |
三个“不再”:
-
❌ 不再写没有用户故事的功能
-
❌ 不再直接 push 到 main 分支
-
❌ 不再“我觉得没问题”就上线
三个“开始”:
-
✅ 开始写测试用例
-
✅ 开始画架构图
-
✅ 开始做回顾复盘
未来我会继续深入学习:
-
🔧 云原生技术(K8s、Service Mesh、Istio)
-
🔐 软件安全与隐私合规(GDPR、等级保护)
-
🧠 AI辅助软件工程(GitHub Copilot、AutoDev)
-
📊 可观测性(Metrics、Logging、Tracing三位一体)
八、致谢与资源推荐
感谢:
-
课程老师
-
队友
-
Codeup提供的学生免费资源
推荐资源:
| 类型 | 名称 | 简介 |
|---|---|---|
| 书籍 | 《软件工程:实践者的研究方法》 | 经典教材,案例丰富 |
| 书籍 | 《用户故事与敏捷方法》 | 把用户故事讲透了 |
| 书籍 | 《DevOps实践指南》 | 学DevOps必看 |
| 在线教程 | 阿里云开发者学堂-敏捷课程 | 免费 + 实战 |
| 工具 | Codeup | 阿里云一站式研发平台(学生优惠) |
| 社区 | 思否敏捷话题 | 国内活跃的敏捷社区 |
如还有疑惑可点击下方链接解惑:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)