系统设计 + CodeReview 全流程:从需求到上线 Phase 1: 系统设计 ✅ 理解需求边界 ✅ 画时序图/架构图 ✅ 确定数据模型 ✅ 评估QPS/容量 ✅ 列异常/降级方案 ❌ "先写再想" Phase 2: 编码实现 ✅ 按设计文档编码 ✅ 边写边写单测 ✅ 统一异常处理 ✅ 参数校验(@Valid) ✅ 日志埋点 ❌ 裸catch/吞异常 Phase 3: 自审 ✅ 跑全部单测通过 ✅ checkstyle/sonar ✅ 本地模拟生产数据 ✅ 自己diff全部改动 ✅ 写PR描述+截图 ❌ "应该没问题" Phase 4: CodeReview ✅ 逐行Review ✅ SQL索引检查 ✅ 并发安全检查 ✅ 安全(注入/XSS) ✅ 至少2人Approve ❌ "LGTM"走形式 CodeReview 必查清单(Reviewer视角) 🔴 P0 必查项(阻塞上线) □ SQL是否有索引失效函数(DATE/LIKE左%) □ 是否有N+1查询(循环内查数据库) □ 并发修改是否有锁/原子操作 □ 是否有SQL注入风险(拼接SQL) □ 敏感数据是否脱敏(密码/手机号) □ 异常是否被catch吞掉无日志 🟡 P1 建议项(改进后上线) □ 变量/方法命名是否达意 □ 是否有重复代码可抽取 □ 日志级别是否合理(INFO/WARN/ERROR) □ 单测覆盖率是否>70% □ 魔法数字是否定义为常量 □ 是否有更好的设计模式可应用
系统设计与CodeReview全流程:4个Phase + P0/P1检查清单,每步都有明确的"必做"和"禁忌"

项目设计与 CodeReview

Project Design & Code Review Guide — 从零设计项目到高质量交付,面试中讲出令人信服的项目经验

设计先行 · Review 保障 · STAR 讲述
没有项目经验不可怕,可怕的是不知道一个项目应该怎么从头到尾做出来

1. 需求分析

1.1 为什么需求分析是第一步

很多初级开发者拿到需求就写代码,这是最大的错误。先想清楚再做,是区分初级和高级工程师的第一道分水岭。

Factory Analogy

需求分析就像建工厂之前的可行性研究报告。如果没搞清楚要生产什么、产量多大、原料从哪来就开工建厂房,建成后要么产能不足,要么设备用不上,要么物流动线混乱 — 全部推倒重来。

1.2 高级工程师如何读需求

读需求不是"看一遍就过了",而是要追问五个维度:

拿到需求文档
功能拆解
问5个关键问题
划分优先级
输出技术方案

五个关键问题 (必须问清楚)

维度问题为什么重要影响什么
数据量 预计有多少数据?增长速度? 决定数据库选型和分表策略 1万条 vs 1亿条是完全不同的架构
并发量 同时多少用户访问?QPS 多少? 决定是否需要缓存、消息队列 10 QPS 和 10000 QPS 差1000倍
实时性 数据需要实时还是准实时? 决定是否需要 WebSocket/轮询 秒级 vs 分钟级 vs 小时级
用户数 目标用户群多大? 决定权限体系和部署方案 内部5人 vs 全厂500人 vs 对外
峰值 有没有流量高峰时段? 决定弹性扩容策略 月结报表时访问量是平时10倍

1.3 功能性 vs 非功能性需求

类型定义示例 (制造看板)如何量化
功能性 系统"做什么" 展示OEE指标、设备状态、产量趋势 功能清单 + 验收标准
非功能性 系统"做得怎样" 页面加载 <2s、可用性 99.9%、支持50并发 性能指标 + SLA

1.4 需求分析 Checklist

业务背景: 这个系统解决什么业务问题?谁会用?
数据来源: 数据从哪来?MES/SCADA/Excel/手工录入?
数据量级: 初始多少?年增长多少?需要保留多久?
更新频率: 实时秒级?分钟级?小时级?T+1?
并发用户: 同时在线多少人?峰值多少人?
权限体系: 几种角色?每种角色看到什么?
性能要求: 页面加载?报表生成?数据查询?
部署环境: 内网/外网?有无安全限制?
时间节点: 什么时候上线?分几期?
验收标准: 怎么算"做完了"?谁来验收?

Production Case: 需求理解偏差导致整个模块重做

Symptom / 现象

开发了一个设备维修管理模块,花了3周完成。交付后车间主任说:"我要的不是修设备,是设备保养计划 — 预防性的,不是事后维修记录。"

Root Cause / 根因

只看了需求文档标题"设备维护",没追问具体业务场景。"维护"在IT眼里是"修坏的东西",在车间眼里是"定期保养"。

Solution / 解决

3周代码全部废弃。重新做需求分析 — 跟车间主任聊了2小时,明确了是预防性保养计划:基于设备运行时长自动触发保养提醒,保养完成后记录并重置计时。

Lesson / 教训

需求文档里的每一个名词都要确认语义。尤其在不同行业,"同一个词"可能含义完全不同。写代码之前,先用白话把功能描述给业务方确认一遍。

2. 技术选型

2.1 选型决策框架

技术选型不是"哪个新用哪个",而是要综合考虑四个维度:

Technology Selection Decision Tree
技术选型
团队技能?
项目规模?
时间紧迫?
生态完善?
熟悉的 → 用
小型 → 单体
紧急 → 稳定版
完善 → 省时间
陌生的 → 学习
大型 → 微服务
充裕 → 可探索
薄弱 → 自研
💡 选型黄金法则

用团队最熟悉的,而不是最热门的。一个用 Vue 做过3个项目的团队,比用 React 从零开始的团队,交付速度快3倍。面试官问"为什么选这个技术"时,你要能说出理由,而不仅仅是"我喜欢"。

2.2 制造看板项目 — 完整选型表

以一个生产可视化看板项目为例,展示完整的技术选型过程:

层级选择为什么备选方案不选备选的原因
前端框架 Vue 3 团队熟悉、中文社区好、组合式API灵活 React 18 团队无 React 经验,学习成本高
图表库 ECharts 国内最流行、工业图表支持好、大数据量性能优 D3.js / Chart.js D3 学习曲线太陡;Chart.js 图表类型不够
UI 组件 Element Plus Vue 3 生态最成熟、表单/表格组件丰富 Ant Design Vue 都可以,Element 的文档更适合快速上手
后端框架 Java + Spring Boot 企业级首选、生态完善、MyBatis/JPA 成熟、运维工具链全 Node.js Express / Python Flask Node.js 单线程不适合 CPU 密集场景;Python 性能弱于 Java
关系数据库 MySQL 8 工业数据是结构化的、查询场景明确、运维成熟 PostgreSQL 都可以,MySQL DBA 更好招
缓存 Redis 看板数据高频读取、会话管理、排行榜/实时指标 Memcached Redis 支持更多数据结构,适用场景更广
部署 Docker + Nginx 环境一致性、快速部署、Nginx 反向代理+静态资源 K8s / 裸机部署 K8s 对小团队太复杂;裸机不好维护和迁移
Factory Analogy

技术选型就像选生产线设备。不是最贵最好的就合适 — 如果你的工厂只需要年产1000件,买一条年产100万件的全自动线就是浪费。同样,一个5人用的内部系统,不需要 Kubernetes + 微服务 + Kafka。

3. 架构设计

3.1 系统架构图: 制造看板系统

下面是完整的制造看板系统架构。面试中画架构图是必考环节,你需要能从左到右讲清楚每一层的职责。

Manufacturing Dashboard Architecture
Data Source Layer / 数据源层
MES 系统
SCADA
Excel 导入
手工录入
  ETL / 数据采集
Data Storage Layer / 数据存储层
MySQL
Redis 缓存
Backend Service Layer / 后端服务层
Node.js / Express
RESTful API
定时任务
Proxy Layer / 代理层
Nginx
反向代理
负载均衡
Client Layer / 客户端层
浏览器 (PC)
大屏展示
移动端
Frontend Proxy Backend Database Data Source Cache

面试中如何讲架构

🎓 讲解模板

"我们的系统分为5层。最底层是数据源,从MES和SCADA系统采集数据,经过ETL清洗后存入MySQL。Redis作为缓存层,存放高频访问的实时指标。后端是Java+Spring Boot+MyBatis,提供RESTful API。Nginx做反向代理和静态资源托管。前端是Vue3+ECharts,支持PC和大屏两种展示方式。"

3.2 架构模式选择

模式适合场景优点缺点什么时候用
单体架构 小型项目、团队 <5人 开发快、部署简单、调试方便 代码耦合、不好扩展 你的第一个项目 → 先从这开始
微服务 大型项目、多团队协作 独立部署、技术栈灵活 运维复杂、网络开销 QPS >10000、团队 >10人时考虑
分层架构 大多数企业应用 职责清晰、便于维护 跨层调用有性能损耗 推荐的默认架构
💡
面试关键点: 不要一上来就说"我用微服务"。面试官想听的是你的思考过程:"我最初用单体架构,因为项目规模小、团队小。随着业务增长,某个模块成为瓶颈,我才拆成微服务。" — 这比直接说"我用了微服务"高级得多。

3.3 设计原则

关注点分离 (Separation of Concerns)

Single Responsibility / 单一职责图示
✗ Bad: 上帝类
DashboardService
getData() + renderChart() +
sendEmail() + auth() +
saveToDB() + exportPDF()
✓ Good: 职责分离
DataService
getData() / saveToDB()
ChartService
renderChart() / updateView()
AuthService
login() / verify()
ExportService
exportPDF() / sendEmail()

4. 数据库设计

4.1 数据库设计五步法

1
识别实体 — 系统中有哪些"东西"?机器、产线、班次、产品、生产记录...
2
定义关系 — 实体之间的关系是什么?一对多?多对多?
3
设计字段 — 每个实体有哪些属性?数据类型?约束?
4
建索引 — 哪些字段会被频繁查询?提前建好索引!
5
优化 — 分表策略、冗余字段、查询优化

4.2 制造看板数据库设计 — ER 图

ER Diagram: Manufacturing Dashboard Database
DIMENSION TABLES (维度表)
machines
PK id INT
machine_code VARCHAR(50)
machine_name VARCHAR(100)
FK line_id INT
status ENUM
production_lines
PK id INT
line_name VARCHAR(100)
workshop VARCHAR(100)
status ENUM
shifts
PK id INT
shift_name VARCHAR(20)
start_time TIME
end_time TIME
products
PK id INT
product_code VARCHAR(50)
product_name VARCHAR(100)
standard_cycle DECIMAL
FACT TABLES (事实表)
production_records
PK id BIGINT
FK machine_id INT
FK product_id INT
FK shift_id INT
plan_qty INT
actual_qty INT
defect_qty INT
record_date DATE
downtime_records
PK id BIGINT
FK machine_id INT
reason VARCHAR(200)
start_time DATETIME
end_time DATETIME
duration_min INT
quality_records
PK id BIGINT
FK product_id INT
inspected_qty INT
passed_qty INT
failed_qty INT
inspect_time DATETIME
PK = Primary Key    FK = Foreign Key    维度表: 描述"是什么"    事实表: 记录"发生了什么"

4.3 索引设计原则

⚠ 为什么要提前设计索引?

很多人在建表时只写字段不建索引,等项目上线后查询慢了才想起来加。这时候表里已经有几百万条数据,加索引时数据库会被锁住,线上服务直接卡死。

原则说明示例
查询驱动 根据 WHERE / JOIN / ORDER BY 的字段建索引 production_records 经常按 machine_id + record_date 查询
最左前缀 联合索引 (a,b,c) 可以覆盖 a / a,b / a,b,c INDEX(machine_id, record_date) 覆盖按机器查和按机器+日期查
避免过度 索引不是越多越好,每个索引都增加写入开销 一张表 5-8 个索引合理,超过 10 个要注意
选择性高 区分度高的字段放前面 status 只有3个值,不适合做索引第一个字段
-- production_records 的索引设计
CREATE INDEX idx_machine_date  ON production_records(machine_id, record_date);
CREATE INDEX idx_product_date  ON production_records(product_id, record_date);
CREATE INDEX idx_record_date   ON production_records(record_date);

-- downtime_records 的索引设计
CREATE INDEX idx_downtime_machine ON downtime_records(machine_id, start_time);
CREATE INDEX idx_downtime_date    ON downtime_records(start_time);

Production Case: 后期加索引很痛苦

Symptom / 现象

看板页面打开需要 12秒。用户投诉"比以前慢了很多"。数据库CPU持续90%+。

Root Cause / 根因

production_records 表有 380万条 数据,查询 OEE 的SQL没有任何索引,每次都是全表扫描EXPLAIN 显示 type: ALL,扫描了全部380万行。

Solution / 解决

执行 ALTER TABLE production_records ADD INDEX idx_machine_date(machine_id, record_date);这条语句跑了8分钟,期间表被锁定,所有写入请求超时。最终查询时间从12秒降到 0.05秒

Lesson / 教训

在建表时就设计好索引。如果后期必须加索引,要在低峰期操作,或使用 pt-online-schema-change 等工具在线加索引。面试中讲这个故事非常有说服力。

5. API 设计

5.1 RESTful API 设计规范

📚 RESTful 核心原则
  • 名词用复数: /api/machines 而不是 /api/machine
  • 动词用HTTP方法: GET(查) / POST(增) / PUT(改) / DELETE(删)
  • 版本控制: /api/v1/machines 为未来兼容留余量
  • 过滤用参数: ?line_id=1&date=2025-06-01

5.2 制造看板 API 列表

方法路径功能关键参数
GET/api/v1/oee获取OEE指标(可用率×性能率×质量率)machine_id, line_id, start_date, end_date
GET/api/v1/production获取产量数据line_id, date, shift_id
GET/api/v1/downtime获取停机记录machine_id, start_date, end_date
GET/api/v1/quality获取质量数据product_id, date
GET/api/v1/energy获取能耗数据workshop, date
GET/api/v1/machines获取设备列表及状态line_id, status
GET/api/v1/machines/:id获取单个设备详情
POST/api/v1/production录入产量数据Body: JSON
POST/api/v1/downtime录入停机记录Body: JSON
PUT/api/v1/production/:id修改产量记录Body: JSON
DELETE/api/v1/downtime/:id删除停机记录

5.3 响应格式标准

// 标准成功响应
{
  "code": 200,
  "message": "success",
  "data": {
    "list": [...],
    "pagination": {
      "page": 1,
      "page_size": 20,
      "total": 1580,
      "total_pages": 79
    }
  },
  "timestamp": "2025-06-09T14:30:00Z"
}

// 标准错误响应
{
  "code": 400,
  "message": "Invalid parameter: machine_id is required",
  "error_detail": {
    "field": "machine_id",
    "reason": "must be a positive integer"
  },
  "timestamp": "2025-06-09T14:30:00Z"
}

5.4 分页/排序/过滤设计

// 分页 + 排序 + 过滤 统一参数格式
GET /api/v1/production?page=1&page_size=20&sort_by=record_date&sort_order=desc&line_id=3&start_date=2025-06-01&end_date=2025-06-09

// 后端实现 (Node.js / Express)
const { page = 1, page_size = 20, sort_by = 'record_date', sort_order = 'desc' } = req.query;
const offset = (page - 1) * page_size;

// MySQL 查询
const sql = `SELECT * FROM production_records
  WHERE line_id = ? AND record_date BETWEEN ? AND ?
  ORDER BY ${sort_by} ${sort_order}
  LIMIT ? OFFSET ?`;

Production Case: API没有分页,一次返回50万条数据

Symptom / 现象

前端页面直接白屏卡死。Chrome DevTools 显示接口返回了 120MB 的 JSON,浏览器内存溢出。后端日志显示单次查询耗时 45秒

Root Cause / 根因

GET /api/v1/production 接口没有分页参数,直接 SELECT * FROM production_records,返回了全部 50万条数据。

Solution / 解决

1. 加了 pagepage_size 参数,默认每页20条
2. 数据库层面加 LIMIT OFFSET
3. 强制最大 page_size=100,超过自动截断
4. 加了接口超时限制:超过10秒自动中断

Lesson / 教训

所有列表接口必须有分页,这是硬性要求。即使现在数据量小,将来也会增长。好的API设计是面向未来3-5年的。

6. CodeReview 指南

6.1 为什么 CodeReview 很重要

Factory Analogy

CodeReview 就像工厂里的质检工序。没有质检,次品直接流到客户手中 — 在软件里,次品就是 bug、安全漏洞、性能问题。Review 不是"找茬",而是两个人的智慧总比一个人强

收益具体表现量化指标
提高质量在合并前发现 bugGoogle 数据: Review 阻止了 60-90% 的严重 bug 上线
知识共享团队成员互相学习新成员通过 Review 快速了解代码风格和架构
统一风格代码库保持一致性减少 "看谁写的" 的时间
安全兜底发现 SQL 注入、XSS 等问题安全漏洞在 Review 阶段被发现,成本比线上修复低 100倍

6.2 CodeReview 流程图

CodeReview Flow / 代码审查流程
开发者提交 PR
自动检查: CI/Lint/单测
CI 通过?
No
修复后重新提交
Yes
Reviewer 审查代码
有问题?
Yes
Request Changes
No
Approve & Merge

6.3 Review Checklist (20项)

NAMING & READABILITY / 命名与可读性
变量/函数命名是否有语义 (不用 a, b, temp, data)
函数长度是否合理 (一般不超过 30 行)
是否有必要的注释 (复杂逻辑必须有)
代码格式是否一致 (缩进、分号、大括号)
ERROR HANDLING / 错误处理
是否处理了所有可能的错误情况
错误信息是否有意义 (不是 console.log(err) 就完事了)
是否有 try-catch / 全局错误处理
边界条件是否处理 (null, undefined, 空数组, 0)
SECURITY / 安全
是否有 SQL 注入风险 (是否使用参数化查询)
是否有 XSS 风险 (是否转义用户输入)
敏感数据是否加密 (密码、token)
权限校验是否完整 (不是只在前端隐藏按钮)
PERFORMANCE / 性能
是否有 N+1 查询问题
循环内是否有不必要的数据库查询
大列表是否有分页/懒加载
是否有内存泄漏风险 (未清理的定时器/事件监听)
ARCHITECTURE & TESTING / 架构与测试
是否符合项目架构分层 (不跨层调用)
是否有对应的单元测试
配置是否外置 (硬编码的 IP/端口/密钥)
改动是否影响已有功能 (回归测试)

6.4 Review 评论: 好与坏

风格评论示例为什么好/坏
坏的 "这代码写得不行" 攻击性、没有建设性、没有具体建议
坏的 "重构一下" 太笼统,不知道要重构什么、怎么重构
好的 "这个函数有60行,建议拆成3个小函数,每个负责一个职责。参考 XXX 模块的写法。" 具体、有建议、有参考
好的 "这里的 SQL 用了字符串拼接,有注入风险。建议改用参数化查询: pool.query(sql, [params])" 指出风险、给出方案、附带代码示例

6.5 如何接收 Review 反馈

💪 正确心态
  • Review 是对事不对人 — 评价的是代码,不是你这个人
  • 不懂就问 — "能解释一下为什么这样改更好吗?" 是好问题
  • 记录学到的东西 — 每次被 Review 都是一次免费学习
  • 不要反驳后不改 — 如果 Reviewer 说得有道理,就改
  • 有理有据地讨论 — 如果你不同意,用数据/文档说明原因
🔒

Production Case: CodeReview发现了一个SQL注入漏洞

Symptom / 发现

Review一段查询接口代码时,发现了这行:

// 危险! 直接拼接用户输入
const sql = `SELECT * FROM machines WHERE line_id = ${req.query.line_id}`;
pool.query(sql, (err, results) => { ... });
Root Cause / 风险

如果攻击者传入 line_id=1; DROP TABLE machines; --,就会执行 DROP TABLE,整个表被删除。这就是经典的 SQL 注入攻击。

Solution / 修复
// 安全: 使用参数化查询
const sql = 'SELECT * FROM machines WHERE line_id = ?';
pool.query(sql, [req.query.line_id], (err, results) => { ... });
Lesson / 教训

永远不要拼接 SQL。这条规则刻在脑子里。这个漏洞如果上线,攻击者可以读取、修改、删除数据库中的任何数据。Review 就是最后一道防线

7. 面试中如何讲项目

7.1 STAR 法则

面试中讲项目,永远用 STAR 法则。这是全球通用的结构化表达方式:

STAR Framework / 面试项目讲述框架
S
Situation
背景是什么?
公司/团队/业务场景
T
Task
你的任务是什么?
你负责哪个模块/功能
A
Action
你做了什么?
技术方案/实现细节
R
Result
结果怎样?
用数据说话!
⚠ 最常见的错误

花80%的时间讲背景,20%讲做了什么。 面试官想听的核心是 ActionResult。建议分配: S(15%) + T(10%) + A(50%) + R(25%)。

7.2 三个完整 STAR 项目范例

📊

Project 1: 生产可视化看板

Vue + ECharts + Node.js + MySQL

一家汽车零部件制造厂,有3条产线、45台设备。之前用Excel记录产量,每天花2小时手动汇总。管理层看不到实时数据,决策滞后。工厂决定上一套生产可视化看板系统

我是唯一的全栈开发,负责从需求分析到上线部署的全流程。需要在3个月内交付可用的看板系统,支持实时OEE展示和产量趋势分析。

需求分析: 与车间主任沟通,确定5个核心指标:OEE、产量、停机时长、不良率、能耗

技术选型: Vue3+ECharts(图表丰富)+Java Spring Boot(企业级后端,MyBatis+JPA生态)+MySQL(结构化工业数据)+Redis(实时指标缓存)

数据库设计: 设计了维度表(设备/产线/班次/产品)+事实表(生产记录/停机记录/质量记录),提前建好联合索引

性能优化: 引入Redis缓存OEE指标(5分钟刷新),查询从3秒降到50ms。大屏数据用WebSocket推送,实时性从5分钟提升到秒级

部署: Docker容器化+Nginx反向代理,一键部署到工厂内网服务器

✓ 看板页面加载 <1.5秒,数据刷新延迟 <3秒
✓ 每天节省2小时人工汇总时间,3条产线年节省约 1500工时
✓ 设备异常响应时间从30分钟缩短到3分钟(看板实时报警)
✓ OEE从85%提升到91%(可视化暴露了停机损失)
✓ 系统稳定运行6个月0故障,日活用户45人

📄

Project 2: 报表自动化系统

Python + SQL Server + Docker

质量部门每月要生成30+份报表,包含质量趋势、不良品分析、供应商评分等。之前全部手工从SQL Server导数据到Excel,再手动画图。一个报表平均要4小时,每月花在报表上的时间超过 120小时

我负责开发一套报表自动化系统: 配置好数据源和模板后,一键生成PDF报表。要求支持定时自动运行,并将结果邮件发送给相关人员。

架构设计: 三层结构 — 配置层(定义数据源+模板)、执行层(Python脚本+SQL查询)、输出层(生成PDF+发送邮件)

模板引擎: 用 Jinja2 模板定义报表结构,支持变量替换和条件渲染。用户只需修改模板,不需要改代码

SQL优化: 原来手工查数据用嵌套子查询(跑5分钟),我改写成窗口函数(跑8秒),性能提升37倍

定时调度: 用 APScheduler 实现定时任务,支持 cron 表达式。每月1号自动生成上月报表

容器化: Docker 打包,包含所有依赖,在新机器上5分钟部署完成

✓ 30+份报表从每月120小时降到2小时(自动生成+人工校验)
✓ 报表准确率从92%提升到99.8%(消除人工复制粘贴错误)
✓ SQL查询优化后,单次报表生成从5分钟降到8秒
✓ 系统运行1年,累计生成360+份报表,节省约 1400+工时

🔌

Project 3: ETL数据平台

Python + pandas + SQL Server + Redis

工厂数据分散在5个系统中: MES(生产)、QMS(质量)、SCADA(设备)、ERP(物料)、Excel(人工补录)。数据格式不统一,口径不一致,做一份跨系统分析需要3天。急需一个统一的数据平台。

我负责设计并开发一套 ETL数据平台: 从多个异构数据源抽取(Extract)数据,经过清洗转换(Transform),加载(Load)到统一的数据仓库中,供看板和报表使用。

数据抽取: 用 Python 连接 MES/MSSQL/Oracle/Excel 四种数据源,统一抽取为 DataFrame

数据清洗: 用 pandas 处理缺失值(用前值填充)、异常值(3-sigma检测)、重复数据(按时间戳+设备ID去重)

数据转换: 统一字段命名规范(machine_id vs MachineID)、时间格式(UTC+8)、单位换算

缓存层: Redis 缓存热点查询结果,ETL任务状态用 Redis 管理,支持断点续传

监控: 每次 ETL 记录日志(抽取行数/耗时/异常),异常时自动发邮件通知

最大挑战: SCADA 数据每秒1000条,直接写数据库导致 I/O 瓶颈。解决方案: 先缓存到 Redis List,每分钟批量写入数据库,写入性能提升50倍

✓ 整合5个数据源,日处理数据量 200万+条
✓ 跨系统分析从3天降到10分钟
✓ ETL 任务执行时间从45分钟优化到6分钟
✓ 数据一致性校验通过率 99.5%
✓ 系统稳定运行8个月,累计处理 5亿+条数据记录

7.3 高频面试问答

🎓 "你在项目中遇到的最大困难是什么?"

回答模板:

S: "在开发ETL数据平台时,SCADA系统每秒产生1000条数据,直接写入SQL Server导致数据库I/O打满,ETL任务频繁超时。"

T: "我需要在保证数据不丢失的前提下,解决高频写入的性能瓶颈。"

A: "我设计了缓冲队列方案: 先将数据缓存到Redis List中,然后用定时任务每分钟批量从Redis取出数据,用批量INSERT写入数据库。同时加了监控,如果Redis积压超过阈值就报警。"

R: "写入性能从每秒50条提升到每秒2500条(50倍),ETL任务超时率从30%降到0%,数据零丢失。"

🎓 "你做的最有技术含量的项目是什么?"

回答模板:

"最有技术含量的是ETL数据平台。技术难点在于三点: 第一,整合5个异构数据源,需要处理不同的数据库驱动、编码、时间格式。第二,SCADA高频数据的实时写入,我设计了Redis缓冲+批量写入的方案,写入性能提升50倍。第三,数据质量保证 — 我设计了数据校验规则引擎,能自动检测缺失值、异常值、重复数据,校验通过率99.5%。"

🎓 "你是怎么做技术选型的?"

回答模板:

"我主要考虑四个维度: 团队技能(我们团队熟悉 Java 和 Vue)、项目规模(内部系统,50并发以内)、时间线(3个月交付)、生态成熟度(出问题能快速找到解决方案)。基于这四个维度,前端选Vue3而不是React,后端选 Java Spring Boot — 企业级首选,生态成熟,团队最熟悉。数据库选MySQL因为工业数据是高度结构化的。Redis做缓存是因为看板有高频读取场景。"

💡 讲项目的黄金技巧
  • 用数字说话: "提升了50%"比"提升了"有说服力100倍
  • 讲挑战不讲流水账: 面试官不关心你用了什么组件,关心你解决了什么难题
  • 准备3个故事: 一个技术挑战 + 一个协作挑战 + 一个性能优化
  • 准备好追问: "为什么不用X?" "如果重新做你会怎么改?" "你学到了什么?"
  • 诚实: 没做过的不要说做过,但可以说"我了解原理,没有实际项目经验"

8. 15个设计原则

面试中经常问到设计原则,不需要背教科书,但要能用自己的话解释清楚,并结合实际举例。

1. DRY — Don't Repeat Yourself
不要写重复代码。如果同一段逻辑出现两次以上,提取成函数或公共组件。
Factory: 每条产线都单独买一套工具箱 vs 共享一个工具房
// Bad: 重复的日期格式化代码出现5次
const date1 = d.toLocaleDateString();
const date2 = d.toLocaleDateString();

// Good: 提取公共函数
const formatDate = (d) => d.toLocaleDateString();
2. KISS — Keep It Simple, Stupid
保持简单。能10行代码解决的问题,不要写100行。不要过度设计。
Factory: 一把扳手能搞定的事,不要设计一台专用设备
// Bad: 过度设计
class DateFormatterFactory { ... }

// Good: 简单直接
const formatDate = (d) => d.toLocaleDateString();
3. YAGNI — You Aren't Gonna Need It
不要提前写"可能用到"的功能。等真的需要时再写。
Factory: 不要在只需要100平米时建1000平米的厂房
// Bad: 提前设计了不需要的多语言支持
const i18n = { zh: {...}, en: {...}, ja: {...} };

// Good: 先只做中文,需要时再加
const labels = { title: '生产看板' };
4. Separation of Concerns
不同职责的代码要分开。数据层、业务层、展示层各管各的。
Factory: 原料仓、生产线、质检区、成品仓要分开管理
// routes/oee.js - 只管路由
router.get('/oee', oeeController.getOEE);

// controllers/oee.js - 只管业务逻辑
exports.getOEE = (req, res) => { ... };

// models/production.js - 只管数据
exports.getByMachine = (id) => { ... };
5. Single Responsibility (SRP)
一个类/函数只做一件事,只有一个修改的理由。
Factory: 一个工位只负责一道工序,不要一个人又焊接又喷漆又质检
// Bad: 一个函数做三件事
function processOrder() { /* 验证 + 计算 + 发邮件 */ }

// Good: 拆成三个函数
function validateOrder() { }
function calculateTotal() { }
function sendNotification() { }
6. Open/Closed (OCP)
对扩展开放,对修改关闭。加新功能时不改老代码,而是新增代码。
Factory: 增加新产品线,不需要改造旧产线,而是新建一条
// Bad: 每加一个报表类型就改switch
switch(type) { case 'oee': ...; case 'quality': ...; }

// Good: 注册机制,新报表只需注册
const reports = {};
const registerReport = (type, fn) => reports[type] = fn;
7. Liskov Substitution (LSP)
子类必须能替换父类使用,不能破坏父类的约定。
Factory: 所有型号的焊接机器人都能接入同一条产线的控制接口
// Bad: 子类改变了父类行为
class Bird { fly() {} }
class Penguin extends Bird {
  fly() { throw new Error("can't fly"); }
}

// Good: 更合理的继承结构
class Bird { move() {} }
class FlyingBird extends Bird { fly() {} }
8. Interface Segregation (ISP)
不要强迫使用者依赖它不需要的接口。接口要小而专。
Factory: 操作工不需要看到维护手册,维护工程师不需要看到操作手册
// Bad: 一个大接口包含所有方法
interface IWorker { work(); eat(); sleep(); }

// Good: 拆成小接口
interface IWorkable { work(); }
interface IFeedable { eat(); }
9. Dependency Inversion (DIP)
高层模块不依赖低层模块,两者都依赖抽象。
Factory: 产线设计不依赖具体某个品牌的设备,而是依赖"设备接口标准"
// Bad: 直接依赖具体实现
const db = new MySQLDatabase();

// Good: 依赖抽象,可替换
const db = createDatabase(config);
// 可以是 MySQL / PostgreSQL / SQLite
10. Composition over Inheritance
优先用组合(has-a)而不是继承(is-a)来复用代码。
Factory: 与其设计一台"万能机器",不如组合多台专用设备
// Bad: 深层继承
class AdminUser extends User { }

// Good: 组合
const user = {
  auth: new RoleAuth('admin'),
  logger: new ActivityLogger()
};
11. Law of Demeter (最少知道原则)
一个对象不应该知道它不需要的内部结构。不要写 a.b.c.d
Factory: 车间主任只需要知道"今天产量多少",不需要知道"第3台设备的第2个传感器读数"
// Bad: 知道太多内部结构
order.customer.address.city;

// Good: 通过方法获取
order.getShippingCity();
12. Fail Fast
尽早发现问题,尽早报错。不要让错误传播到下游。
Factory: 原料不合格立即退货,不要流到产线上才发现
// Bad: 不校验,错误到后面才爆
function createOrder(data) { save(data); }

// Good: 入口就校验
function createOrder(data) {
  if (!data.items) throw new Error('items required');
  save(data);
}
13. Boy Scout Rule
离开代码时,让它比你发现时更好一点。
Factory: 每次用完工具后放回原位,工位保持整洁
// 修 bug 时顺便:
// - 重命名一个不清楚的变量
// - 加一条有用的注释
// - 删掉一行无用代码
// 每次一点点,代码库越来越好
14. Avoid Premature Optimization
不要在还没遇到性能问题时就优化。先让它跑起来,再优化慢的地方。
Factory: 先让产线跑起来,再分析哪个工位是瓶颈、优化哪里
// Bad: 还没上线就加缓存
const cache = new Map();

// Good: 上线后,用数据证明需要缓存
// "这个接口平均响应 3s,加了缓存降到 50ms"
15. Convention over Configuration
遵循约定优于配置。用团队约定的方式做,减少配置项。
Factory: 标准件不需要每次都确认规格,只有非标件才需要特殊说明
// Bad: 每个路由都配置一遍
app.get('/api/v1/oee', ...);
app.get('/api/v1/production', ...);

// Good: 约定目录结构自动注册路由
// routes/oee.js → /api/v1/oee
// routes/production.js → /api/v1/production
autoRegisterRoutes(app);

8.1 SOLID 原则速查表

字母原则一句话违反的标志
SSingle Responsibility一个类只做一件事改一个需求要改这个类的3个地方
OOpen/Closed加功能不改老代码每次加功能都要改switch/if-else
LLiskov Substitution子类能替换父类子类throw了父类没声明的异常
IInterface Segregation接口要小而专实现接口时有空方法(用不到)
DDependency Inversion依赖抽象不依赖具体直接new一个具体类,不好测试
记住: 面试官不是要你背定义,而是看你能不能用这些原则解决实际问题
能举出自己项目中的例子 = 真正理解了
Project Design & Code Review Guide v1.0 · Production Guide Series

常见问题 FAQ

系统设计文档应该写多详细?

够用即可,不要过度文档化。核心内容五部分:① 需求理解(一句话描述要解决什么问题);② 架构图(系统间交互,用时序图或组件图);③ 数据模型(核心表结构+索引设计);④ 接口设计(API路径/入参/出参/状态码);⑤ 异常与降级(列出可能的失败场景和兜底方案)。小需求用半个页面,大需求用一个文档。

CodeReview时Reviewer总是"LGTM"怎么办?

三个改变:① 建立Checklist:给Reviewer一个明确的检查清单(SQL/并发/安全/异常),逐项确认而不是"看完说好看";② 轮换Reviewer:不要总是同一个人Review,不同视角能发现不同问题;③ Review质量计入绩效:如果Reviewer Approve的代码出了线上事故,Reviewer也有责任——这能倒逼认真Review。

系统设计怎么评估QPS和容量规划?

四步估算法:① 从业务量推导:如日活100万,平均每人每天10次操作 = 1000万次/天;② 换算QPS:1000万 ÷ 86400秒 ≈ 116 QPS(但按8小时活跃算 = 347 QPS);③ 乘以峰值系数:通常峰值是平均的3-5倍 → 1000~1700 QPS;④ 单机容量:一台4核8G服务器约能扛500~1000 QPS(简单CRUD),需要2~4台。

怎么判断一个系统设计是"过度设计"?

三个信号:① 当前QPS < 设计容量的1/10:设计支撑10万QPS但实际只有1000 → 资源浪费;② 引入了团队不熟悉的技术:为了"未来扩展"引入Kafka/ES,但团队只会MySQL → 运维成本暴增;③ ROI为负:花2周设计高可用方案,但业务半年后可能下线。原则:设计要超前一步,不要超前十步。

系统设计与CodeReview三原则

1️⃣ 设计先行:写代码前先画图、定模型、列异常 → 2️⃣ Review走清单:SQL/并发/安全/异常逐项确认,不是"看完说好看" → 3️⃣ 不过度设计:满足当前和近6个月需求即可,架构是演进出来的不是设计出来的