
创业公司的技术架构评审机制让决策有据可依而非拍脑袋一、深度引言创业公司的技术架构评审和大厂的ATPArchitecture Review Process有本质区别。大厂可以投入3个P8、5个P7开两小时的评审会逐一审查文档中的每个设计决策。创业公司呢很可能全公司只有一个能做架构决策的人而这个人同时还在修Bug、写需求、做客户支持。在这种资源极度受限的条件下架构评审很容易退化到两个极端要么无人评审导致先干了再说的随意决策积累技术债务要么过度评审导致工期严重延误、团队对评审本身产生抗拒心理。本文提出的方案是面向3-15人技术团队的轻量架构评审体系核心理念是够用不浪费——在有限资源下抓住最重要的风险点而不是追求大厂式的完备性。flowchart TD A[技术方案提出] -- B{影响范围判定} B --|影响核心链路| C[正式评审流程] B --|影响边缘模块| D[轻量评审: 异步文档审查] C -- E[方案文档提交] E -- F[评审会议: 30分钟] F -- G{投票: 3票通过制} G --|通过| H[执行 2周回溯] G --|有条件通过| I[修改并重新提交] G --|否决| J[方案重做] I -- F H -- K[ADP归档] style B fill:#F39C12,color:#fff style F fill:#4A90D9,color:#fff style G fill:#E74C3C,color:#fff style K fill:#27AE60,color:#fff二、原理剖析分级评审不是所有决策都需要开会创业公司最常见的评审错误是把所有技术决策放在同一张桌子上。引入Redis还是Memcached用消息队列还是数据库轮询微服务还是单体这些问题的工作量、影响面、可逆性完全不同放在同一个流程里处理就是浪费。我实践的分级标准是基于两个维度影响范围和可逆性。影响范围决定决策错误后需要多少人加班修复可逆性决定修复成本的时间窗口。一个影响核心支付链路且难以回滚的决策需要最严格的评审流程一个影响内部管理后台且可以随时切换的决策异步文档审查就足够了。30分钟评审会的结构正式评审会控制在30分钟不是60分钟。时间约束倒逼每个环节的产出高度聚焦。前5分钟方案提出者讲清楚要解决什么问题和为什么现在的方案不够用。如果这两个问题讲不清楚说明方案本身可能就不成立应当直接终止评审而非继续讨论技术细节。中间15分钟评审者提出质疑重点关注三个维度——是否存在更简单的替代方案、失败场景是否被覆盖、运维成本是否可承受。注意是提出质疑而非给出答案答案应当由提案者自己寻找。最后10分钟投票和记录。采用3票通过制——至少3位评审者投赞成票才算通过。规则是少数服从多数但必须记录反对票的理由。反对票不是对提案者的否定而是对风险的标记在后续回溯时可以验证这些风险是否真的发生了。ADPArchitecture Decision Record的写法和价值架构决策记录是大厂ATP的核心产出物但大厂的ADR往往写得过于形式化包含十几项必填字段。在创业公司ADP只需要记录四件事背景当时面临什么约束条件技术选型是在预算、时间、人力三重约束下做出的缺少约束描述的决策记录没有参考价值。选项你认真考虑过的替代方案有哪些至少要列出2个被否决的方案及否决理由。这比记录最终选择更重要——它证明了你的决策是经过权衡的。决策最终做了什么决定用一句话说清楚。后果这个决定可能带来什么正面和负面影响六个月后再回看这个字段是你的决策校验器。三、生产级代码以下是一个轻量架构评审系统的核心实现支持决策生命周期管理和回溯分析。from dataclasses import dataclass, field from datetime import datetime, timedelta from enum import Enum from typing import Dict, List, Optional import json import hashlib import logging import uuid logger logging.getLogger(arch_review) # 决策模型 class DecisionImpact(Enum): 影响范围 —— 决定评审严格程度 CORE core # 核心链路支付、鉴权、数据一致性 BROAD broad # 广泛影响API设计、数据模型、公共组件 MODULE module # 模块级单服务内部重构 EDGE edge # 边缘内部工具、管理后台 class DecisionReversibility(Enum): 可逆性 —— 影响回溯成本 EASY easy # 随时可回滚成本1人天 MEDIUM medium # 可以回滚成本1-5人天 HARD hard # 难以回滚成本5人天 IRREVERSIBLE irreversible # 不可逆数据迁移、API breaking change class ReviewStatus(Enum): DRAFT draft IN_REVIEW in_review APPROVED approved CONDITIONAL conditional REJECTED rejected SUPERSEDED superseded # 被后续决策取代 dataclass class ReviewVote: 评审投票 reviewer: str vote: str # approve / conditional / reject reason: str timestamp: datetime field(default_factorydatetime.now) dataclass class ArchitectureDecision: 架构决策记录ADP id: str field(default_factorylambda: fADP-{uuid.uuid4().hex[:8]}) title: str status: ReviewStatus ReviewStatus.DRAFT # 分级信息 impact: DecisionImpact DecisionImpact.MODULE reversibility: DecisionReversibility DecisionReversibility.MEDIUM # 四大核心字段 context: str # 背景面临什么约束 options: List[Dict] field(default_factorylist) # 选项考虑过哪些方案 decision: str # 决策做了什么选择 consequences: str # 后果积极的/消极的影响 # 评审记录 proposer: str reviewers: List[str] field(default_factorylist) votes: List[ReviewVote] field(default_factorylist) created_at: datetime field(default_factorydatetime.now) resolved_at: Optional[datetime] None retrospect_at: Optional[datetime] None # 回溯时间 # 关联信息 related_adps: List[str] field(default_factorylist) tags: List[str] field(default_factorylist) def needs_formal_review(self) - bool: 判定是否需要正式评审流程 为什么核心链路和不可逆决策必须正式评审 这两类决策出错的影响无法通过简单回滚消除 需要多人审查来降低风险 return ( self.impact in (DecisionImpact.CORE, DecisionImpact.BROAD) or self.reversibility in ( DecisionReversibility.HARD, DecisionReversibility.IRREVERSIBLE ) ) def can_vote(self) - bool: 检查是否可以投票 —— 至少要有3个评审者 return len(self.reviewers) 3 def tally_votes(self) - Tuple[int, int, int]: 计票 —— 返回(赞成, 有条件, 反对) approve sum(1 for v in self.votes if v.vote approve) conditional sum(1 for v in self.votes if v.vote conditional) reject sum(1 for v in self.votes if v.vote reject) return approve, conditional, reject def resolve(self) - ReviewStatus: 根据投票结果确定最终状态 为什么3票通过制而非半数 小团队中半数门槛太低2/3能更好地确保决策质量。 如果评审者总数不足3人只要有赞成票就算通过 approve, conditional, reject self.tally_votes() total_votes len(self.votes) if total_votes 0: return ReviewStatus.IN_REVIEW # 至少需要3票中的2票赞成 if total_votes 3 and approve 3: self.status ReviewStatus.APPROVED elif approve 0 and reject 0: # 无人反对但有条件性意见 if conditional 0: self.status ReviewStatus.CONDITIONAL else: self.status ReviewStatus.APPROVED elif reject 0: self.status ReviewStatus.REJECTED self.resolved_at datetime.now() # 设置2周后的回溯时间 self.retrospect_at self.resolved_at timedelta(days14) return self.status def to_markdown(self) - str: 生成ADP Markdown文档 —— 用于代码仓库存档 为什么用Markdown而非YAML/JSON Markdown在代码审查工具GitHub/GitLab中渲染效果最好 不需要额外工具即可阅读 return f# {self.title} - **ADP ID**: {self.id} - **状态**: {self.status.value} - **提出者**: {self.proposer} - **日期**: {self.created_at.strftime(%Y-%m-%d)} - **影响范围**: {self.impact.value} - **可逆性**: {self.reversibility.value} ## 背景 {self.context} ## 考虑的选项 {self._format_options()} ## 决策 {self.decision} ## 后果 {self.consequences} ## 评审记录 {self._format_votes()} --- *首次回溯日期: {self.retrospect_at.strftime(%Y-%m-%d) if self.retrospect_at else 待定}* def _format_options(self) - str: if not self.options: return _无记录_ lines [] for i, opt in enumerate(self.options, 1): status opt.get(status, 考虑中) name opt.get(name, f选项{i}) reason opt.get(reason, ) lines.append(f### {i}. {name} ({status})) lines.append(f{reason}\n) return \n.join(lines) def _format_votes(self) - str: if not self.votes: return _暂无投票_ lines [] for v in self.votes: emoji {approve: ✅, conditional: ⚠️, reject: ❌} e emoji.get(v.vote, ❓) lines.append(f- {e} **{v.reviewer}**: {v.reason}) return \n.join(lines) # 评审引擎 class ArchitectureReviewEngine: 架构评审引擎 —— 管理评审流程的生命周期 def __init__(self): self.decisions: Dict[str, ArchitectureDecision] {} def propose( self, title: str, proposer: str, context: str, options: List[Dict], decision: str, consequences: str, impact: DecisionImpact DecisionImpact.MODULE, reversibility: DecisionReversibility DecisionReversibility.MEDIUM, ) - ArchitectureDecision: 创建一条架构决策 为什么强制要求context字段 缺少背景描述的决策在6个月后无人能理解当初为什么这么做 context是ADP中最重要的字段直接决定决策的可追溯性 if not context.strip(): raise ValueError(背景(context)不能为空 —— 缺少约束描述的决策没有参考价值) if len(options) 2: raise ValueError( 至少需要2个备选方案 —— 只有一个方案的决策不是决策而是既成事实 ) adp ArchitectureDecision( titletitle, proposerproposer, contextcontext, optionsoptions, decisiondecision, consequencesconsequences, impactimpact, reversibilityreversibility, ) self.decisions[adp.id] adp logger.info(f创建ADP: {adp.id} - {title}) return adp def add_reviewer(self, adp_id: str, reviewer: str): 添加评审者 adp self.decisions.get(adp_id) if not adp: raise ValueError(fADP {adp_id} 不存在) if reviewer not in adp.reviewers: adp.reviewers.append(reviewer) adp.status ReviewStatus.IN_REVIEW def cast_vote( self, adp_id: str, reviewer: str, vote: str, reason: str ) - ReviewStatus: 投出评审票 adp self.decisions.get(adp_id) if not adp: raise ValueError(fADP {adp_id} 不存在) if reviewer not in adp.reviewers: raise ValueError(f{reviewer} 不是本ADP的评审者) # 检查是否已投票 —— 每人只能投一次 # 为什么不允许改票避免反复修改导致评审流程失控 # 如果需要更改意见应通过后续ADP来修正 if any(v.reviewer reviewer for v in adp.votes): raise ValueError(f{reviewer} 已经投过票不能重复投票) adp.votes.append(ReviewVote( reviewerreviewer, votevote, reasonreason, )) # 检查是否可以结算 if len(adp.votes) len(adp.reviewers): return adp.resolve() return ReviewStatus.IN_REVIEW def find_stale_decisions(self, days: int 14) - List[ArchitectureDecision]: 查找需要回溯的决策 —— 用于定期审查 为什么默认14天 两周足够验证大部分技术决策的效果 同时不会让回溯工作量过大 cutoff datetime.now() - timedelta(daysdays) stale [] for adp in self.decisions.values(): if adp.status in ( ReviewStatus.APPROVED, ReviewStatus.CONDITIONAL ): if ( adp.retrospect_at and adp.retrospect_at datetime.now() ): stale.append(adp) return stale def search_by_tag(self, tag: str) - List[ArchitectureDecision]: 按标签搜索ADP —— 例如搜索所有数据库相关决策 return [ adp for adp in self.decisions.values() if tag in adp.tags ] def export_all(self) - str: 导出所有ADP为Markdown索引 if not self.decisions: return # 暂无架构决策记录 lines [# 架构决策记录 (ADP) 索引\n] lines.append(f共 {len(self.decisions)} 条记录\n) for adp in sorted( self.decisions.values(), keylambda x: x.created_at, reverseTrue ): status_emoji { ReviewStatus.APPROVED: ✅, ReviewStatus.CONDITIONAL: ⚠️, ReviewStatus.REJECTED: ❌, ReviewStatus.IN_REVIEW: , ReviewStatus.DRAFT: , ReviewStatus.SUPERSEDED: , } lines.append( f- {status_emoji.get(adp.status, ❓)} f[{adp.id}] {adp.title} f({adp.created_at.strftime(%Y-%m-%d)}) fby {adp.proposer} ) return \n.join(lines) # 使用示例 if __name__ __main__: engine ArchitectureReviewEngine() # 案例数据库选型决策 adp engine.propose( title用户服务的数据存储选型MySQL还是PostgreSQL, proposer董清悦, context( 用户服务当前使用MySQL 5.7每秒写入量约500。 未来6个月计划支持JSON Schema校验和全文搜索功能 需要考虑是否趁早切换到PostgreSQL。 团队规模3人PostgreSQL运维经验有限。 迁移窗口为2周允许不超过4小时的停机。 ), options[ { name: 保持MySQL 5.7, status: 否决, reason: JSON和全文搜索功能需要用Elasticsearch补充 增加运维复杂度与简化技术栈的目标冲突 }, { name: 升级MySQL 8.0, status: 备选, reason: JSON支持有改善但全文搜索仍需外部依赖。 升级风险低于迁移PostgreSQL }, { name: 迁移PostgreSQL 15, status: 推荐, reason: 原生JSONB和全文搜索一个数据库解决所有需求。 团队有限但学习曲线可控 }, ], decision迁移到PostgreSQL 15使用逻辑复制做近零停机迁移, consequences( 正面统一数据存储减少Elasticsearch依赖简化运维 负面团队需要2-4周学习PostgreSQL运维迁移期间存在性能风险 风险缓解措施先在预发布环境完整验证保留MySQL回滚方案 ), impactDecisionImpact.BROAD, reversibilityDecisionReversibility.HARD, ) # 添加评审者 for reviewer in [王工, 李工, 张工]: engine.add_reviewer(adp.id, reviewer) # 模拟投票 engine.cast_vote( adp.id, 王工, approve, PostgreSQL的JSONB确实比MySQL强很多同意迁移 ) engine.cast_vote( adp.id, 李工, conditional, 同意方向但建议先做POC验证迁移脚本 ) engine.cast_vote( adp.id, 张工, approve, PostgreSQL的运维我们可以学长期收益大于短期成本 ) print(f评审结果: {adp.status.value}) print(f\n{adp.to_markdown()})四、边界权衡评审深度vs交付速度是持续冲突的核心。实践中采用风险比例原则——评审投入的时间应当和决策出错可能造成的损失成正比。影响核心链路的决策投入4-8小时评审是合理的影响内部工具的在Slack群里发一段话就足够了。判断标准是如果这个决策做错了6个月后需要多少人、多少天来修复投票权重的问题在大厂P9的投票权重天然高于P7。但在创业公司不建议给任何人加权投票权。原因是早期团队规模小每个成员都对系统有全局视角技术决策的正确性更多取决于信息充分度而非资历深浅。ADP的维护成本ADR文档一旦开始腐化团队的信任就会崩塌。建议将ADP和代码放在同一个仓库架构变更时同步更新ADP。如果发现ADP和代码不一致超过2个月说明ADP机制已经失效应当废弃而非强行维护。五、总结创业公司的架构评审不需要大厂的重量级流程但需要比大家商量着来更结构化的决策机制。核心原则是分级、聚焦、可追溯。分级让资源集中在高风险决策上聚焦用30分钟的时间约束倒逼高效讨论可追溯通过ADP让每个决策在6个月后依然能理解当初的上下文。技术债务不是一次糟糕决策造成的而是一系列缺乏记录的决策层层叠加的结果。建立轻量ADP制度是创业团队在技术治理上回报率最高的投资。