标签:comm ror 编辑 测试环境 建立 灵活 切换 处理 定义类
?
技术主管,又叫技术经理,英文一般是 Tech Leader ,简称 TL。随着工作经验的不断积累,能力的不断提升,每个人都有机会成为 Team Leader。
然而在机会到来前,我们必须提前做好准备,对 TL 的工作职责有一定了解。当然,这也会为当下更好地配合 TL 工作打下基础。
今天,阿里巴巴高级技术专家云狄将结合自己多年的经验,从以下三个角度出发,分享对技术 TL 这一角色的理解与思考:
开发规范
开发流程
技术规划与管理
技术主管是开发团队中的某位程序员需要对一起创建系统的整个开发团队负责时所承担的角色。
通常他既要对最终交付的软件系统负责,另外也会像一个程序员一样去开发实现系统。
一个技术主管的 60%~70% 的时间可能花在了开发任务分解分配、开发实践、技术架构评审、代码审核和风险识别上。
而余下的 30%~40% 的时间则花在为了保障系统按时交付所需的各种计划、协作、沟通、管理上。
和团队管理者不同的是,技术主管的大部分管理工作都是针对具体研发任务和技术事务的。
接下来基于我在技术 TL 这个角色上,在开发规范、开发流程、技术管理与规划等方面我的一些心路历程,和大家共勉。
开发规范
我当时负责的业务是集团收购一家子公司的业务,在整体技术标规范上与集团的技术标准存在很大的差异。
开发规范可以说是我来到这个团队干的第一件事,我当时面对的问题是 API 接口格式混乱,没有标准的 RPC 服务化,代码没有统一标准的开发规范,技术框架组件非标准化等一系列问题。
作为一名业务上的新人,我第一时间制定了一套相对标准、全面的技术开发规范,边写代码边梳理开发规范,引领团队走向统一标准化开发道路。
针对团队研发规范暴露的上述问题,主要制定了如下规范:
命名规范
我自己非常注重搭建项目结构的起步过程,应用命名规范、模块的划分、目录(包)的命名,我觉得非常重要,如果做的足够好,别人导入项目后可能只需要 10 分钟就可以大概了解系统结构。
具体规范包括包命名、类的命名、接口命名、方法命名、变量命名、常量命名。
统一 IDE 代码模板
约定了 IDEA/Eclipse IDE 代码的统一模板,代码风格一定要统一,避免不同开发人员使用不同模板带来的差异化以及代码 merge 成本。
使用 IDEA 的同学可以安装 Eclipse Code Formatter 插件,和 Eclipse 统一代码模板。
Maven 使用规范
所有二方库、三方库的版本统一定义到 parent pom 里,这样所有业务应用工程统一继承 parent pom 里所指定的二方库、三方库的版本,统一框架与工具的版本(Spring、Apache commons 工具类、日志组件、JSON 处理、数据库连接池等),同时要求生产环境禁用 SNAPSHOT 版本。
这样一来升级通用框架与工具的版本,只需要应用工程升级 parent pom 即可。
代码 Commit 规范
基于 Angular Commit Message 规范生成统一的 ChangeLog。
这样对于每次发布 release tag 非常清晰,Mac 下都需要安装对应的插件,IDEA 也有对应的插件,具体可以参考阮一峰老师的《Commit message 和 Change log 编写指南》。
此刻忽然想起 Linus 面对 pull request 里的骚操作所发的飚:
Get rid of it. And I don‘t ever want to see that shit again.
——Linus
代码的 Commit 的规范对团队非常重要,清晰的 Commit 信息生成的 release tag,对于生产环境的故障回滚业非常关键,能够提供一些有价值的信息。
统一 API 规范
统一 RPC 服务接口的返回值 ResultDTO,具体代码如下:
Success 代表接口处理响应结果成功还是失败,errorCode、errorMsg 表示返回错误码和错误消息。
Module 表示返回结果集,把 ResultDTO 定义到 common-api 顶层二方库,这样以来各个应用不需要来回转换返回结果。
Http Rest 接口规范约定同 ResultDTO 相差无几,需要额外关注一下加解密规范和签名规范、版本管理规范。
异常处理规范
异常处理不仅仅是狭义上遇到了 Exception 怎么去处理,还有各种业务逻辑遇到错误的时候我们怎么去处理。
Service 服务层捕获的异常主要包括 BusinessException(业务异常)、RetriableException (可重试异常) 到 common-api,定义一个公共异常拦截器,对业务异常、重试异常进行统一处理,对于可重试的异常调用的服务接口需要保证其幂等性。
另外其他业务层有些特殊异常不需要拦截器统一处理,内部可以进行自我消化处理掉。
根据场景对应的处理原则主要包括:
直接返回
抛出异常
重试处理
熔断处理
降级处理
这又涉及到了弹力设计的话题,我们的系统往往会对接各种依赖外部服务、API,大部分服务都不会有 SLA,即使有在大并发下我们也需要考虑外部服务不可用对自己的影响,会不会把自己拖死。
我们总是希望:
尽可能以小的代价通过尝试让业务可以完成。
如果外部服务基本不可用,而我们又同步调用外部服务的话,我们需要进行自我保护直接熔断,否则在持续的并发的情况下自己就会垮了。
如果外部服务特别重要,我们往往会考虑引入多个同类型的服务,根据价格、服务标准做路由,在出现问题的时候自动降级。
推荐使用 Netflix 开源的 Hystrix 容灾框架,主要解决当外部依赖出现故障时拖垮业务系统、甚至引起雪崩的问题。
目前我团队也在使用,能够很好的解决异常熔断、超时熔断、基于并发数限流熔断的降级处理。
分支开发规范
早期的时候源码的版本管理基于 SVN,后来逐步切换到 Git,分支如何管理每一个公司(在 Gitflow 的基础上)都会略有不同。
针对分支开发规范,指定如下标准:
分支的定义(master、develop、release、hotfix、feature)
分支命名规范
Checkout、merge request 流程
提测流程
上线流程
Hotfix 流程
虽然这个和代码质量和架构无关,按照这一套标准执行下来,能够给整个研发团队带领很大的便利:
减少甚至杜绝代码管理导致的线上事故。
提高开发和测试的工作效率,人多也乱。
减少甚至杜绝代码管理导致的线上事故。
方便运维处理发布和回滚。
让项目的开发可以灵活适应多变的需求,多人协同开发。
统一日志规范
日志是产品必不可少的一个功能,具备可回溯性、能够抓取问题现场信息是其独一无二的优点,尤其在生产系统上问题定位等方面具有不可替代的作用。
这里着重强调一下针对异常的日志规范:
WARN 和 ERROR 的选择需要好好考虑,WARN 一般我倾向于记录可自恢复但值得关注的错误,ERROR 代表了不能自己恢复的错误。
对于业务处理遇到问题用 ERROR 不合理,对于 Catch 到了异常也不是全用 ERROR。
记录哪些信息,最好打印一定的上下文(链路 TraceId、用户 Id、订单 Id、外部传来的关键数据)而不仅仅是打印线程栈。
记录了上下文信息,是否要考虑日志脱敏问题?可以在框架层面实现,比如自定义实现 Logback 的 ClassicConverter。
正确合理的使用日志,能够指引开发人员快速查找错误、定位问题,因此约定了一套日志使用标准规范,现在可以更多的参考《阿里经济体开发规约——日志规约》。
统一 MySQL 开发规范
表的设计和 API 的定义类似,属于那种开头没有开好,以后改变需要花 10x 代价的,我知道,一开始在业务不明确的情况下,设计出良好的一步到位的表结构很困难,但是至少我们可以做的是有一个好的标准。
统一工具与框架
对开发过程中所用到的公共组件进行了统一抽象与封装,包括 Dao 层框架 Mybatis、Cache 组件 Jetcache、Httpclient 组件、Common-tools (公共工具),同时抽取出全局唯一 ID、分布式锁、幂等等公共组件。
把以上公共组件进行集成到各个应用,进行统一升级、维护,这样以来方便大家将更多的精力集中到业务开发上。
开发流程
目前团队的开发模式还是基于传统的瀑布开发模式,整体开发流程涉及需求评审、测试用例评审、技术架构评审、开发与测试、验收与上线。
这里主要基于 TL 的角度从需求管理、技术架构评审、代码评审、发布计划评审几个关键重点环节进行探讨,欢迎拍砖。
需求管理
美国专门从事跟踪 IT 项目成功或失败的权威机构 Standish Group 的 CHAOS Reports 报导了该公司的一项研究,该公司对多个项目作调查后发现,百分之七十四的项目是失败的,即这些项目不能按时按预算完成。
其中提到最多的导致项目失败的原因就是"变更用户需求"。另外从历年的 Standish Group 报告分析看,导致项目失败的最重要原因与需求有关。
Standish Group 的 CHAOS 报告进一步证实了与成功项目最密切的因素是良好的需求管理,也就是项目的范围管理,特别是管理好项目的变更。
产品因需求而生,在产品的整个生命周期中,产品经理会收到来自各个方面的需求。
但是每一个需求的必要性、重要性和实现成本都需要经过深思熟虑的分析和计划,避免盲目的决定需求或者变更需求。
这样很容易导致工作混乱,技术 TL 如果不能正确的对需求进行把控,会导致整个项目偏离正确的轨道。
需求管理的第一步就是要梳理不同来源的需求,主要包括从产品定位出发、外部用户反馈、竞争对手情况、市场变化以及内部运营人员、客服人员、开发人员的反馈。
首先技术 TL 对产品有足够认知和把控,简单来说就是我的产品是为了满足哪些人的哪些需求而做,产品需求一定要根植于客户的需求、根植于客户的环境。
每款产品必定有其核心价值,能够为客户创造更多的价值,基于此考虑往往能得到一些核心需求,摒除价值不大的需求。
需求管理中最重要的就是对发散性需求的管理,往往因此也会导致产品在执行过程中不断的变更或增加需求。
由于人的思维是发散性的,所以往往在产品构思的过程中会出现各种新鲜好玩的想法,这些想法可能来自领导或者产品经理自己,但是这些想法往往都是和产品核心方向不相关的。
由于这些想法能够在当时带来诱惑,因此这些不相关的需求会严重干扰技术团队的精力,打乱或者延误产品原本的计划。
同样技术研发同学也需要建立对产品的深度思考,不要把自己定位成产品需求的实现者,同样需要对需求负责。
很多时候需求的变更或增加是因为我们面临太多选择和想要的太多,没有适当的控制自己的欲望,并以自己的喜好来决定需求,这些因素很容易导致产品没有明确的方向、团队成员疲于奔命,但是却没有实际的成果。
所以技术 TL 一定要能够评估出重新审视产品和筛选需求的优先级,识别每一个需求的必要性、重要性和实现成本。
通过深思熟虑给团队明确方向并专注,聚焦资源的支配,确保团队的精力都聚焦在产品的核心需求上。
技术架构评审
互联网时代,大家提倡敏捷迭代,总嫌传统方式太重,流程复杂,影响效率,什么都希望短平快。
在扁平化的组织中,经常是需求火速分发到一线研发,然后就靠个人折腾去了,其实技术架构评审这同样是一个非常重要的环节。
架构评审或技术方案评审的价值在于集众人的力量大家一起来分析看看方案里是否有坑,方案上线后是否会遇到不可逾越的重大技术问题,提前尽可能把一些事情先考虑到,提出质疑其实对项目的健康发展有很大的好处。
基于架构评审,我们的目标核心是要满足以下几点:
设计把关,确保方案合格,各方面都考虑到了,避免缺陷和遗漏,不求方案多牛,至少不犯错。
保证架构设计合理和基本一致,符合整体原则。
维持对系统架构的全局认知,避免黑盒效应。
通过评审发掘创新亮点,推广最佳实践。
架构设计既要保证架构设计的合理性和可扩展性,又要避免过度设计。架构设计不仅仅是考虑功能实现,还有很多非功能需求,以及持续运维所需要的工作,需要工程实践经验,进行平衡和取舍。
架构评审需要以下几点:
技术选型
为什么选用 A 组件不选用 B、C 组件,A 是开源的,开源协议是啥?基于什么语言开发的,出了问题我们自身是否能够维护?性能方面有没有压测过?
这些所有问题作为技术选型我们都需要考虑清楚,才能做最终决定。
高性能
产品对应的 TPS、QPS 和 RT 是多少?设计上会做到的 TPS、QPS 和 RT 是多少?而实际上我们整体随着数据量的增大系统性能会不会出现明显问题?
随着业务量、数据量的上升,我们的系统的性能如何去进一步提高?系统哪个环节会是最大的瓶颈?
是否有抗突发性能压力的能力,大概可以满足多少的 TPS 和 QPS,怎么去做来实现高性能,这些问题都需要我们去思考。
高可用
是否有单点的组件,非单点的组件如何做故障转移?是否考虑过多活的方案?是否有数据丢失的可能性?数据丢失如何恢复?
出现系统宕机情况,对业务会造成哪些影响?有无其他补救方案?这些问题需要想清楚,有相应的解决方案。
可扩展性
A 和 B 的业务策略相差无几,后面会不会继续衍生出 C 的业务策略,随着业务的发展哪些环节可以做扩展,如何做扩展?架构设计上需要考虑到业务的可扩展性。
可伸缩性
每个环节的服务是不是无状态的?是否都是可以快速横向扩展的?扩容需要怎么做,手动还是自动?扩展后是否可以提高响应速度?
这所有的问题都需要我们去思考清楚,并有对应的解决方案。
弹性处理
消息重复消费、接口重复调用对应的服务是否保证幂等?是否考虑了服务降级?哪些业务支持降级?支持自动降级还是手工降级?
是否考虑了服务的超时熔断、异常熔断、限流熔断?触发熔断后对客户的影响?服务是否做了隔离,单一服务故障是否影响全局?
这些问题统统需要我们想清楚对应的解决方案,才会进一步保证架构设计的合理性。
兼容性
上下游依赖是否梳理过,影响范围多大?怎么进行新老系统替换?新老系统能否来回切换?数据存储是否兼容老的数据处理?
如果对你的上下游系统有影响,是否通知到上下游业务方?上下游依赖方进行升级的方案成本如何最小化?
这些问题需要有完美的解决方案,稍有不慎会导致故障。
安全性
是否彻底避免 SQL 注入和 XSS?是否有数据泄露的可能性?是否做了风控策略?接口服务是否有防刷保护机制?
数据、功能权限是否做了控制?小二后台系统是否做了日志审计?数据传输是否加密验签?应用代码中是否有明文的 AK/SK、密码?
这些安全细节问题需要我们统统考虑清楚,安全问题任何时候都不能轻视。
可测性
测试环境和线上的差异多大?是否可以在线上做压测?线上压测怎么隔离测试数据?是否有测试白名单功能?是否支持部署多套隔离的测试环境?
测试黑盒白盒工作量的比例是怎么样的?新的方案是否非常方便测试,在一定程度也需要考量。
可运维性
系统是否有初始化或预热的环节?数据是否指数级别递增?业务数据是否需要定期归档处理?
随着时间的推移,如果压力保持不变的话,系统需要怎么来巡检和维护?业务运维方面的设计也需要充分考虑到。
监控与报警
对外部依赖的接口是否添加了监控与报警?应用层面系统内部是否又暴露了一些指标作监控和报警?系统层面使用的中间件和存储是否有监控报警?
只有充分考虑到各个环节的监控、报警,任何问题会第一时间通知到研发,才能阻止故障进一步扩散。
其实不同阶段的项目有不同的目标,我们不会在项目起步的时候做 99.99% 的可用性支持百万 QPS 的架构,高效完成项目的业务目标也是架构考虑的因素之一。
而且随着项目的发展,随着公司中间件和容器的标准化,很多架构的工作被标准化替代,业务代码需要考虑架构方面伸缩性、运维性等等的需求越来越少,慢慢的这些工作都能由架构和运维团队来接。
一开始的时候我们可以花一点时间来考虑这些问题,但是不是所有的问题都需要有最终的方案。
代码评审
代码质量包括功能性代码质量和非功能性代码质量,功能质量大多通过测试能够去发现问题,非功能性代码质量用户不能直接体验到这种质量的好坏。
代码质量不好,最直接的"受害者"是开发者或组织自身,因为代码质量好坏直接决定了软件的可维护性成本的高低。
代码质量应该更多的从可测性,可读性,可理解性,容变性等代码可维护性维度去衡量。
其中 CodeReview 是保证代码质量非常重要的一个环节,建立良好的 CodeReview 规范与习惯,对于一个技术团队是一件非常重要核心的事情,没有 CodeReview 的团队没有未来。
每次项目开发自测完成后,通常会组织该小组开发人员集体进行代码 review,代码 review 一般 review 代码质量以及规范方面的问题。
另外需要关注的是每一行代码变更是否与本次需求相关,如果存在搭车发布或者代码重构优化,需要自行保证测试通过,否则不予发布。
CodeReview 我会重点关注如下事情:
确认代码功能:代码实现的功能满足产品需求,逻辑的严谨和合理性是最基本的要求。
同时需要考虑适当的扩展性,在代码的可扩展性和过度设计做出权衡,不编写无用逻辑和一些与代码功能无关的附加代码。
在真正需要某些功能的时候才去实现它,而不是你预见到它将会有用。
—— RonJeffries
编码规范:以集团开发规约、静态代码规约为前提,是否遵守了编码规范,遵循了最佳实践。
除了形式上的要求外,更重要的是命名规范。目标是提高代码的可读性,降低代码可维护性成本。
潜在的 Bug:可能在最坏情况下出现问题的代码,包括常见的线程安全、业务逻辑准确性、系统边界范围、参数校验,以及存在安全漏洞(业务鉴权、灰产可利用漏洞)的代码。
文档和注释:过少(缺少必要信息)、过多(没有信息量)、过时的文档或注释,总之文档和注释要与时俱进,与最新代码保持同步。
其实很多时候个人觉得良好的变量、函数命名是最好的注释,好的代码胜过注释。
重复代码:当一个项目在不断开发迭代、功能累加的过程中,重复代码的出现几乎是不可避免的,通常可以通过 PMD 工具进行检测。
类型体系之外的重复代码处理通常可以封装到对应的 Util 类或者 Helper 类中,类体系之内的重复代码通常可以通过继承、模板模式等方法来解决。
复杂度:代码结构太复杂(如圈复杂度高),难以理解、测试和维护。
监控与报警:基于产品的需求逻辑,需要有些指标来证明业务是正常 work 的,如果发生异常需要有监控、报警指标通知研发人员处理,review 业务需求对应的监控与报警指标也是 CodeReview 的重点事项。
测试覆盖率:编写单元测试,特别是针对复杂代码的测试覆盖是否足够。
实际上维护单元测试的成本不比开发成本低,这点团队目前做的的不到位。
针对以上每次代码 review 所涉及到的经典案例会统一输出到文档里,大家可以共同学习避免编写出同样的 Ugly Code。
发布计划评审
涉及到 10 人日以上的项目,必须有明确的发布计划,并组织项目成员统一参加项目发布计划 review。
发布计划主要包含如下几点:
明确是否有外部依赖接口,如有请同步协调好业务方。
发布前配置确认包括配置文件、数据库配置、中间件配置等各种配置,尤其各种环境下的差异化配置项。
二方库发布顺序,是否有依赖。
应用发布顺序。
数据库是否有数据变更和订正,以及表结构调整。
回滚计划,必须要有回滚计划,发布出现问题要有紧急回滚策略。
生产环境回归测试重点 Case。
技术规划与管理
我在带技术团队的这些年,对团队一直有一个要求,每周都要做系统健康度巡检,未雨绸缪、晴天修屋顶,避免在极端场景下某些隐藏的 Bug 转变成了故障。
系统健康度巡检
为什么要把系统健康度巡检放到技术管理里,我觉得这是一个非常重要的环节。
像传统的航空、电力、汽车行业都要有一定的巡检机制,保障设备系统正常运转,同样软件系统也同样需要巡检机制保障业务健康发展。
随着业务的不断发展,业务量和数据量不断的上涨,系统架构的腐蚀是避免不了的,为了保障系统的健康度,需要不断的考虑对系统架构、性能进行优化。
系统的监控与报警能够一定程度发现系统存在的问题,系统存在的一些隐患需要通过对系统的巡检去发现,如果优化不及时在极端情况会导致故障,巡检粒度建议每周巡检一次自己所负责的业务系统。
系统巡检重点要关注如下几点:
系统指标:系统 CPU、负载、内存、网络、磁盘有无异常情况波动,确认是否由发布导致,还是系统调用异常。
慢接口:通常 RT 大于 3s 的接口需要重点关注,极端并发场景下容易导致整个系统雪崩。
慢查询:MySQL 慢查询需要重点关注,随着数据量上涨,需要对慢查询进行优化。
错误日志:通过错误日志去发现系统隐藏的一些 Bug,避免这些 Bug 被放大,甚至极端情况下会导致故障。
技术规划
技术规划通常由团队的 TL 负责,每个财年 TL 需要从大局的角度去思考每个季度的技术优化规划,去偿还技术债。
技术债也是有利息的,因为利息的存在,技术债务不及时偿还的话,会在未来呈现出非线性增长,造成始料不及的损失。
这里的技术规划包括如下几点:
架构优化:一些结构不良、低内聚高耦合的代码则会使得哪怕是微小的需求变更或功能扩展都无从下手,修改的代价很可能超过了重写的代价。
同样系统之间的耦合也需要重点去关注,遵循微服务化的原则,系统也要遵循单一职责原则,对于职责不清晰的系统去做解耦优化,进行一些模块化改造、服务隔离、公用服务抽象。
性能优化:基于财年对于业务量、数据量的发展评估,根据目前系统服务的 QPS\RT,需要提前规划对系统性能进行一些升级策略,包括重点关注对一些慢接口、慢查询的优化。
弹性与可靠性:系统提供的服务需要保障数据一致性、幂等、防重攻击,同时也需要从熔断降级、异地多活的角度去考虑存在哪些问题,目前系统的SLA指标是否能够达到高可用,需要做哪些优化保障系统的高可用。
可伸缩:应用服务是否保证无状态,关键节点发生故障能够快速转移、扩容,避免故障扩大化。
总结
大家不知道有没有类似的经历,某个时间段突然一些线上故障频发,各种技术债、业务债被业务方穷追猛打要求还债,如果出现这种现象很大程度这个 TL 已经失位了,这个团队失控了。
也曾经有人跟我吐槽他的 TL 把活都分给他们,而 TL 自己什么都不干!这个技术 TL 真的什么都不干?
曾经有一段时间我也在思考技术 TL 的核心职责到底是什么?技术 TL 应该具备哪些素质?
首先技术说到底是为业务服务的,除非技术就是业务本身,必须体现它的商业价值。
在很多公司里技术研发真的就成了实现其他部门需求的工具,我觉得这样的技术 TL 肯定是不合格的。
首先它不能影响业务发展,需求提出方会经过很多转化,如果不是不假思索传递需求,整个过程会失真。
第二个,我认为最最重要的是架构设计的能力,可能管理能力还次之。对于管理能力我认为最重要的是对团队的感知能力。
因为一旦到了技术 TL 这个级别,不能脱离一线太远,业务细节可以不清楚,大的方向必须要明确。如果没有很细腻的感知能力,很多的决策会有偏差。
如果他不是一个业务架构师,不是一个能给团队指明更好方向的人,他最终会沦为一个需求翻译器,产品经理说怎么做就怎么做。
他更多的只是负责保证产品的质量、开发的速度,最终被肢解成一个很琐碎的人。
一旦团队上了一定的规模,团队就会从单纯的需求实现走向团队运营,而运营是需要方向的,业务架构就是一个基于运营和数据的一种综合的能力。
关于技术层面,技术 TL 需要具备如下素养:
技术视野良好,解决问题能力与架构设计能力出色
技术 TL 要有良好的技术视野,不需要各种技术都样样精通,但是必须要有所涉猎,有所了解,对各种技术领域的发展趋势,主流非主流技术的应用场景要非常了解。
知道在什么场景应用什么技术,业务发展到什么规模应该预先做哪些技术储备。
产品架构的设计要有足够的弹性,既能够保证当前开发的高效率,又能够对未来产品架构的演进留出扩展的余地。
动手能力要强,学习能力出色
技术 TL 并不需要自己亲自动手写代码,但是如有必要,自己可以随时动手参与第一线的编码工作,技术 TL 不能长期远离一线工作,自废武功,纸上谈兵。否则长此以往,会对技术的判断产生严重的失误。
另外,技术 TL 也应该是一个学习能力非常出色的人,毕竟 IT 行业的技术更新换代速度非常快,如果没有快速学习能力,是没有资格做好技术 TL 的。
技术 TL 除了管人和管事之外,其他还有很多事情要做,包括建立团队研发文化、团队人才培养与建设、跨部门协调与沟通等。
这样也要求技术 TL 同时需要具备良好的沟通和管理能力,以上观点仅是个人的一些思考和观点,仅供参考。
作者:云狄
编辑:陶家龙、孙淑娟
标签:comm ror 编辑 测试环境 建立 灵活 切换 处理 定义类
原文地址:https://www.cnblogs.com/kevin860/p/10463788.html