第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工程师的核心信号。


本章小结

  1. Junior到Mid的跨越是建立工程纪律,而不是更快地写代码
  2. 代码质量有具体标准:正确性、可读性、可维护性、性能
  3. 测试金字塔:大量单元测试 + 中量集成测试 + 少量E2E测试
  4. 调试要用科学方法(假设→验证),不是随机试错
  5. 主动管理经理期望,让自己"不需要被监管"

行动项:选一个你最近写的PR,用本章的代码质量清单重新检查一遍,列出3个你没做到的地方,下个PR改进。