《软件工程实务》课程学习心得:从理论到实践的敏捷蜕变

关键词:软件工程、敏捷开发、Scrum、微服务、DevOps、Codeup、能源管理系统

可在该链接内学习相关内容:

https://www.bilibili.com/


一、写在前面

  本学期我修读了《软件工程实务》课程,从课程概要到项目实战,系统学习了软件工程全生命周期。不同于以往只关注编码,这门课让我真正理解了产品愿景、用户故事、架构设计、进度管理、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,我们只重启了那个服务,主流程(数据采集和展示)完全不受影响。这在单体架构中是做不到的。

https://www.baidu.com/


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)。

https://image.baidu.com/search/index?tn=baiduimage&fm=result&ie=utf-8&word=%5B%E5%9B%BE3%EF%BC%9A%E8%83%BD%E8%80%97%E4%BB%AA%E8%A1%A8%E6%9D%BF%E6%88%AA%E5%9B%BE%5D%20%E5%9B%BE%E4%B8%AD%E6%98%BE%E7%A4%BA%E8%BF%917%E5%A4%A9%E7%94%A8%E7%94%B5%E9%87%8F%E8%B6%8B%E5%8A%BF%E6%8A%98%E7%BA%BF%E5%9B%BE%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%90%84%E5%8C%BA%E5%9F%9F%E7%9A%84%E5%AE%9E%E6%97%B6%E5%8A%9F%E7%8E%87%E6%8E%92%E8%A1%8C%E3%80%82


四、完整的功能实现代码示例(带详细注释)

根据用户故事“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分钟,一个字都写不出来。

解决过程

  1. 我先找了一个成熟的开源项目(若依微服务版),看懂它的分层

  2. 然后用“抄作业”的方式,先把结构框架画出来

  3. 再根据自己的项目替换具体内容(把“用户服务”改成“数据采集服务”等)

  4. 最后找助教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 阿里云一站式研发平台(学生优惠)
社区 思否敏捷话题 国内活跃的敏捷社区

如还有疑惑可点击下方链接解惑:

https://www.deepseek.com/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐