架构评审时被质疑的日子:如何用数据和实验回应

📅 2026/7/16 17:37:14 👁️ 阅读次数 📝 编程学习
架构评审时被质疑的日子:如何用数据和实验回应

架构评审时被质疑的日子:如何用数据和实验回应

一、个性化深度引言

架构评审会上,技术总监对着 PPT 上的方案图提出了一连串问题:"为什么选 gRPC 而不是 REST?" "你这个 Redis 缓存的过期时间有数据支撑吗?" "你说延迟能控制在 200ms,前提条件是什么?"

紧张之下,有些人选了对策:回避 + 模糊回答。但技术评审最有价值的时刻,恰恰是这些尖锐问题被提出来的时候。

因为我发现:那些能被数据回应的质疑,最终都变成了加固架构的砖石。而无法被数据回应的质疑,恰恰暴露了架构里还没想清楚的部分。见证奇迹的时刻,不是答辩顺利通过,而是看着改进后的方案比最初版本稳固了一倍。架构评审,是一次免费的专家同行审查。

二、个性化原理剖析

架构评审中的质疑通常分为三类:

  1. 性能相关质疑:"延迟多少?" "QPS 能到多少?" "内存占用多大?"
  2. 可靠性相关质疑:"挂了怎么办?" "数据一致性怎么保证?" "如何回滚?"
  3. 选型相关质疑:"为什么选 A 而不是 B?" "这个框架的社区活跃度怎么样?"

回应质疑的核心策略:

用数据说话:所有性能声明必须有 Benchmark 支撑。陈述格式:"在 X 环境下,用 Y 工具以 Z 配置压测,得到指标 A=数值。"

承认边界:架构没有银弹。明确说出方案的假设前提("这是在单机部署下的数据")和已知短板("当前未处理跨机房容灾")。

给出行动:无法当场回答的问题,不要编造答案。记下来,承诺补充时间和验证方式。

三、个性化代码实践

""" 设计原因:一套自动化 Benchmark 框架。 所有架构选型的性能声明,必须有这类测试数据支撑。 不是为了说服别人,而是为了说服自己:选型是理性决策。 """ import json import time import statistics from dataclasses import dataclass, field from typing import Callable, Dict, List, Optional @dataclass class BenchmarkResult: """ 设计原因:统一的 Benchmark 结果结构。 包含完整的测试环境信息,确保结果可复现。 """ name: str # 测试名称 iterations: int # 测试轮数 latencies: List[float] = field(default_factory=list) throughput: float = 0.0 # QPS environment: Dict = field(default_factory=dict) @property def p50(self) -> float: return self._percentile(50) @property def p95(self) -> float: return self._percentile(95) @property def p99(self) -> float: return self._percentile(99) @property def mean(self) -> float: return statistics.mean(self.latencies) if self.latencies else 0 @property def stddev(self) -> float: return statistics.stdev(self.latencies) if len(self.latencies) > 1 else 0 def _percentile(self, p: float) -> float: """设计原因:计算分位数用于展示延迟分布。""" if not self.latencies: return 0.0 sorted_vals = sorted(self.latencies) idx = int(len(sorted_vals) * p / 100) return sorted_vals[min(idx, len(sorted_vals) - 1)] def to_report(self) -> str: """设计原因:生成人类可读的报告,用于架构评审展示。""" return ( f"Benchmark: {self.name}\n" f" Iterations: {self.iterations}\n" f" Throughput: {self.throughput:.1f} QPS\n" f" Latency (mean): {self.mean * 1000:.1f} ms\n" f" Latency (P50): {self.p50 * 1000:.1f} ms\n" f" Latency (P95): {self.p95 * 1000:.1f} ms\n" f" Latency (P99): {self.p99 * 1000:.1f} ms\n" f" StdDev: {self.stddev * 1000:.1f} ms\n" f" Environment: {json.dumps(self.environment, indent=4)}" ) class BenchmarkRunner: """ 设计原因:可配置的 Benchmark 框架。 所有选型决策的性能数据都应通过这个工具获得。 """ def __init__(self): self._results: Dict[str, BenchmarkResult] = {} def run( self, name: str, target_fn: Callable, iterations: int = 1000, warmup: int = 100, environment: Dict = None, ) -> BenchmarkResult: """ 设计原因:标准的 Benchmark 执行流程。 包含预热、多轮测试、百分位统计。 """ # 设计原因:预热阶段排除 JIT 编译和缓存影响。 for _ in range(warmup): target_fn() latencies = [] start_time = time.perf_counter() for i in range(iterations): t0 = time.perf_counter() target_fn() latencies.append(time.perf_counter() - t0) total_time = time.perf_counter() - start_time result = BenchmarkResult( name=name, iterations=iterations, latencies=latencies, throughput=iterations / total_time, environment=environment or { "cpu": "Apple M1", "python": "3.11", "framework": "torch 2.1", }, ) self._results[name] = result return result def compare( self, name_a: str, name_b: str ) -> Dict: """ 设计原因:两个方案的对比分析。 用于架构评审中回答"为什么选 A 不选 B"。 """ result_a = self._results.get(name_a) result_b = self._results.get(name_b) if not result_a or not result_b: return {"error": "Result not found"} latency_diff = ( (result_b.mean - result_a.mean) / result_b.mean * 100 ) throughput_diff = ( (result_a.throughput - result_b.throughput) / result_b.throughput * 100 ) return { "comparison": f"{name_a} vs {name_b}", "latency": { name_a: f"{result_a.p95 * 1000:.1f}ms", name_b: f"{result_b.p95 * 1000:.1f}ms", "a_vs_b_diff": f"{latency_diff:+.1f}%", }, "throughput": { name_a: f"{result_a.throughput:.1f} QPS", name_b: f"{result_b.throughput:.1f} QPS", "a_vs_b_diff": f"{throughput_diff:+.1f}%", }, "winner": ( name_a if latency_diff < 0 and throughput_diff > 0 else name_b ), } def generate_review_report(self) -> str: """ 设计原因:生成架构评审可用的完整报告。 不依赖个人记忆,所有数据追溯可得。 """ sections = [] for name, result in self._results.items(): sections.append(result.to_report()) return "\n\n" + "=" * 40 + "\n\n".join(sections) # ── 使用示例:对比 gRPC vs REST ── def mock_grpc_call(): """设计原因:模拟 gRPC 调用。""" time.sleep(0.005) # 5ms 模拟网络 + 序列化 def mock_rest_call(): """设计原因:模拟 REST 调用。""" time.sleep(0.012) # 12ms 模拟 HTTP + JSON 解析 if __name__ == "__main__": runner = BenchmarkRunner() # 设计原因:每个待选方案独立跑 Benchmark,结果可对比。 runner.run("gRPC endpoint", mock_grpc_call, iterations=500) runner.run("REST endpoint", mock_rest_call, iterations=500) # 设计原因:直接产出架构评审可用的对比报告。 comparison = runner.compare("gRPC endpoint", "REST endpoint") print(json.dumps(comparison, indent=2, ensure_ascii=False))

四、个性化边界权衡

1. 回应方式:当场回答 vs 会后补充

关键问题(决定方案是否成立)必须当场给出明确答案。细节问题(具体配置参数)可以会后补充。判断标准:如果这个问题不回答就无法继续讨论——这就是关键问题。

2. 数据展示:原始数据 vs 汇总指标

原始数据(完整延迟分布)最精确但难以快速解读。汇总指标(P50/P95/P99)解读效率高但可能隐藏异常值。建议评审会用汇总指标,附录放原始数据的分布直方图。

3. Benchmark 范围:单元级 vs 端到端

单元级 Benchmark(如"一次推理调用 15ms")精确但无法反映系统整体行为。端到端 Benchmark(如"完整请求链路 200ms")反映真实用户体验但影响因素多。建议:单元级论证组件选型,端到端论证架构可行性。

4. 失败文化:指责 vs 复盘

架构评审中发现问题是好事——问题被发现了就有机会修复。未发现的问题会在生产环境暴露。评审会的目标不是让方案"一次过",而是让方案"上线后不出事"。

5. 文档化:PPT vs 设计文档

PPT 适合展示,但细节容易丢失且不可检索。设计文档(如 RFC/ADR)记录了决策上下文,半年后新人也能理解当时为什么这么选。建议:评审会前写 ADR(Architecture Decision Record),评审会用 PPT 讲要点。

五、总结

架构评审是技术方案的质量门禁。面对质疑,最有效的回应策略是:用 Benchmark 数据论证性能、用故障演练论证可靠性、用多方案对比矩阵论证选型。遇到无法回答的问题,应坦诚承认并记录为待补充项。架构评审的目标不是追求方案完美,而是确保已知风险被充分识别和控制,未知风险有应对预案。每一轮评审的质疑,都是让架构更稳健的免费输入。