第02章:从Junior到Mid——代码质量、测试与工程纪律
第02章:从Junior到Mid——代码质量、测试与工程纪律
Junior工程师问"这能运行吗",Mid工程师问"这在生产环境能可靠地运行吗"。这个问题的转变,是第一次重要的成长。
2.1 Junior到Mid的核心跨越
从Junior到Mid,不是"更熟练地写代码",而是建立工程纪律。
工程纪律是什么?是一套不需要被提醒就会自然执行的工程实践:
- 写代码之前先想清楚边界情况
- 合PR之前先检查测试覆盖
- 遇到不确定的需求主动澄清而不是猜测
- 发现技术债务主动记录而不是假装没看见
Junior工程师需要外部提醒才能做到这些。Mid工程师把它内化成了习惯。
2.2 代码质量的工程定义
"高质量代码"不是抽象的,有具体的可测量标准:
CODE_QUALITY_FRAMEWORK = {
"correctness": {
"description": "代码做了它应该做的事",
"checklist": [
"所有边界情况都被处理(空值、零值、最大值)",
"错误处理清晰且有意义",
"行为有测试覆盖",
]
},
"readability": {
"description": "代码能被他人(和3个月后的自己)读懂",
"checklist": [
"函数和变量名清晰表达意图",
"函数保持单一职责(不超过30行是个好参考)",
"复杂逻辑有注释说明'为什么',不是'做什么'",
"没有魔法数字(magic numbers)",
]
},
"maintainability": {
"description": "代码可以被安全地修改",
"checklist": [
"没有重复代码(DRY原则)",
"依赖关系清晰,没有循环依赖",
"配置和业务逻辑分离",
"数据库查询没有N+1问题",
]
},
"performance": {
"description": "代码在预期负载下不会成为瓶颈",
"checklist": [
"没有不必要的全表扫描",
"缓存策略合理",
"没有阻塞调用在关键路径上",
]
}
}
2.3 测试策略:测试金字塔
大多数Junior工程师对测试的理解是:
“写了单元测试,测试覆盖率达到80%,好了。”
这是不够的。测试金字塔是更完整的框架:
/\
/ \
/ E2E\ ← 少量,贵,慢(Selenium/Playwright)
/------\
/ 集成 \ ← 中量,测试服务边界(API测试)
/----------\
/ 单元测试 \ ← 大量,便宜,快(pytest/jest)
/--------------\
实用测试策略:
import pytest
from unittest.mock import patch, MagicMock
# ====== 示例:支付服务的三层测试 ======
# 层1:单元测试(纯函数,无外部依赖)
class TestPaymentCalculation:
def test_basic_calculation(self):
"""测试基础金额计算"""
result = calculate_total(price=100, quantity=3, tax_rate=0.1)
assert result == 330 # 100 * 3 * 1.1
def test_zero_quantity(self):
"""边界情况:数量为零"""
result = calculate_total(price=100, quantity=0, tax_rate=0.1)
assert result == 0
def test_negative_price_raises(self):
"""异常情况:负价格应该抛出异常"""
with pytest.raises(ValueError, match="Price cannot be negative"):
calculate_total(price=-100, quantity=1, tax_rate=0.1)
def test_decimal_precision(self):
"""精度问题:货币计算不能用float直接比较"""
from decimal import Decimal
result = calculate_total_decimal(
price=Decimal("10.10"), quantity=3, tax_rate=Decimal("0.08")
)
assert result == Decimal("32.724")
# 层2:集成测试(测试服务间的交互)
class TestPaymentIntegration:
@patch("payment_service.stripe_client")
def test_payment_creation(self, mock_stripe):
"""测试支付创建(Mock外部Stripe API)"""
mock_stripe.PaymentIntent.create.return_value = MagicMock(
id="pi_test_123",
status="requires_payment_method"
)
result = create_payment(amount=1000, currency="usd", user_id="user_123")
assert result.payment_intent_id == "pi_test_123"
mock_stripe.PaymentIntent.create.assert_called_once_with(
amount=1000,
currency="usd",
metadata={"user_id": "user_123"}
)
@patch("payment_service.stripe_client")
def test_payment_failure_handled(self, mock_stripe):
"""测试支付失败时的错误处理"""
from stripe.error import CardError
mock_stripe.PaymentIntent.create.side_effect = CardError(
message="Your card was declined",
param="card",
code="card_declined"
)
with pytest.raises(PaymentFailedError) as exc_info:
create_payment(amount=1000, currency="usd", user_id="user_123")
assert "card_declined" in str(exc_info.value)
# 层3:E2E测试(完整用户流程,最少量)
class TestCheckoutFlow:
def test_complete_checkout(self, test_client, test_db):
"""测试完整结账流程(使用测试数据库和测试支付)"""
# 创建用户
user = create_test_user(test_db)
# 创建购物车
cart = create_test_cart(user, items=[{"product_id": 1, "quantity": 2}])
# 执行结账
response = test_client.post("/checkout", json={
"cart_id": cart.id,
"payment_method": "test_card_visa"
})
assert response.status_code == 200
assert response.json()["status"] == "success"
# 验证数据库状态
order = test_db.query(Order).filter_by(cart_id=cart.id).first()
assert order is not None
assert order.status == "paid"
测试覆盖率的正确理解:
80%覆盖率不是目标,关键路径100%覆盖才是目标。支付逻辑、权限验证、数据写入——这些地方的测试缺失才是真正的风险。
2.4 代码审查的接收方视角
Junior到Mid的一个重要转变是学会"善用代码审查":
Junior的CR模式(防御性):
- 每条评论都解释"我为什么这么写"
- 被批评感到沮丧
- 快速标记"已修改"关闭讨论
Mid的CR模式(学习性):
- 把每条评论当作学习机会
- 主动问"为什么这种方式更好?"
- 建立个人的"CR学习日志"
# CR学习日志模板
CR_LEARNING_LOG = {
"date": "2026-01-15",
"pr_link": "github.com/org/repo/pull/234",
"feedback_received": "避免在循环里执行数据库查询",
"original_code": """
for user_id in user_ids:
user = db.query(User).filter_by(id=user_id).first()
process_user(user)
""",
"improved_code": """
users = db.query(User).filter(User.id.in_(user_ids)).all()
for user in users:
process_user(user)
""",
"principle": "批量查询 vs N+1查询,减少数据库往返",
"applicable_scenarios": "任何在循环内进行IO操作的场景"
}
2.5 Debug方法论
调试能力是区分Junior和Mid工程师最实际的标准之一。
科学调试法(不是随机试错):
DEBUG_METHODOLOGY = {
"step1_reproduce": {
"action": "100%稳定复现问题",
"why": "无法稳定复现 = 无法验证修复是否有效",
"tactics": [
"找到最小可复现用例(Minimal Reproducing Example)",
"确定触发条件(数据?并发?时机?)",
"记录复现步骤"
]
},
"step2_locate": {
"action": "二分法定位问题范围",
"why": "不要在不确定问题在哪里时就开始修",
"tactics": [
"使用日志/打点缩小范围",
"怀疑最近的变更(git blame / git log)",
"检查监控图表(CPU/内存/错误率在何时开始异常)"
]
},
"step3_hypothesize": {
"action": "提出具体假设,而不是随机修改",
"why": "随机修改会引入新问题,也学不到东西",
"example": "假设:X函数在并发情况下有竞争条件,因为没有加锁"
},
"step4_verify": {
"action": "验证假设(写测试重现,修复,确认测试通过)",
"why": "修复的验证和修复本身同等重要"
},
"step5_prevent": {
"action": "修复后添加回归测试防止复发",
"why": "没有测试的修复是不完整的修复"
}
}
2.6 工程纪律的实际清单
以下是Mid工程师在日常工作中自然遵循的清单:
## PR提交前检查清单(个人习惯,不是强制流程)
### 正确性
- [ ] 我是否处理了空值/null的情况?
- [ ] 我是否处理了边界情况(最大值/最小值)?
- [ ] 我是否在错误路径上有适当的错误信息?
### 代码质量
- [ ] 函数名和变量名是否清晰表达了意图?
- [ ] 有没有重复代码可以提取?
- [ ] 复杂逻辑是否有注释解释"为什么"?
### 测试
- [ ] 核心业务逻辑是否有单元测试?
- [ ] 边界情况和错误情况是否有测试?
- [ ] 所有测试都能通过吗?
### 性能
- [ ] 有没有N+1查询问题?
- [ ] 有没有在热路径上做不必要的计算?
### 安全
- [ ] 用户输入是否被验证和清洁?
- [ ] 是否有敏感信息暴露在日志中?
### 文档
- [ ] README是否需要更新?
- [ ] API文档是否需要更新?
- [ ] 有没有破坏性变更需要在PR描述中特别说明?
2.7 与经理的沟通:主动管理期望
Junior工程师等着经理来管理自己的进度。Mid工程师主动管理经理的期望。
1-on-1会议的Mid工程师模式:
本周工作更新:
- 完成了X功能的开发,已上线,没有异常
- 在做Y模块的重构,预计下周完成
- 遇到了Z问题,这是我的处理方案:[具体方案]
我需要你的输入:
- 关于Q的优先级,你有新的看法吗?
- W这个技术决策,我想先和你确认一下
我在学习/成长的方向:
- 本周通过CR学到了N+1查询的优化方法
这种模式让经理能够信任你、不需要时时监控你,这是Mid工程师的核心信号。
本章小结
- Junior到Mid的跨越是建立工程纪律,而不是更快地写代码
- 代码质量有具体标准:正确性、可读性、可维护性、性能
- 测试金字塔:大量单元测试 + 中量集成测试 + 少量E2E测试
- 调试要用科学方法(假设→验证),不是随机试错
- 主动管理经理期望,让自己"不需要被监管"
行动项:选一个你最近写的PR,用本章的代码质量清单重新检查一遍,列出3个你没做到的地方,下个PR改进。