技术选型
技术栈
从前端到后端,从缓存到数据库
前端
- 前端框架
- Angular
- React
- 前端页面选择模板引擎还是动静分离?
后端中间层 - 是否需要 Node.js
后端
- 服务端选择Java还是Node.js?
- 服务治理选择DubboX还是Spring Cloud?
- 是否需要使用
- 消息队列选择RocketMQ还是Kafka?
- 数据库选择MySQL 还是Oracle?
- 是否需要缓存 - 缓存选择Redis Cluster 还是 Codis?
- 全文检索选择Solr还是ES?
其他因素
产品特征
产品本身的特征将影响技术选型时的很多因素。
短生命周期产品和长生命周期产品
短生命周期的产品通常要求快速起步:门槛低、书写自由、不强制遵循任何最佳实践。当它的使命结束时,代码会被直接抛弃。所以,对于这类产品,“快糙猛”的技术是较好的选择,当然,能做到“快精猛”更佳。
而长生命周期的产品则会强烈要求可维护性,因为它们在很长时间内都是不可报废的。甚至对于一些生命线产品,连重写都会要求在重写期间线上系统平稳过渡,一点点迁移到新技术。
这种要求对团队的工程化能力是个极端的考验。如果没有相应的工程能力,其代价甚至会高于用新技术重新写一个功能相同的系统。
探索型的产品和守成型的产品
探索型产品往往也是短周期产品,但是同时也有自己的特点。它要求快速,但往往同时会要求高质量。探索型的产品如果证明了可行性,那么过渡到长生命周期的可能性很大。
这就要求它最好是一个微内核系统,提前留出一些扩展的空间。当然,设计微内核系统对架构师的能力具有相当的考验,如果没有一个优秀的架构师,建议还是不要刻意做任何预留,优先保障系统的简单性。
除此之外,探索型产品的技术栈必须支持可靠的、自动化的重构。因为探索型产品的迭代速度很快,如果完全靠人工去添加功能并手动重构,那么一旦出现 BUG,将给此产品的用户体验带来严重的负面影响。
所以,除非由于人才储备等原因而被迫做出折中,否则探索型产品的技术栈一定要快速而严谨。
当然,“大力出奇迹”定律也是成立的。也就是说,如果你有决心也有力量在将来对这个探索型产品进行彻底的重写,那么采用快糙猛的技术快速把它搭建起来,也未尝不可。如果你的业务确实能如预期般爆发,那么只要把重点放在系统延展性等方面即可;但是如果不能如预期般爆发,可能就会导致维护成本在中期开始飙升,在竞争中处于劣势。这是一种“不成功便成仁”的策略。据我所知某独角兽企业就是在业务起来之后通过巨额投入来偿还技术债的。但这对于 CTO 的技术直觉是一项极大的考验,不要轻易效仿。
而对守成型产品的选型则会侧重于与现有技术栈的相似程度和无缝整合能力。如果整合时需要借助很多技巧,那么可能你就是在给自己挖坑。
在引入新技术的过程中,要尽可能符合现有的开发流程、基础设施和开发习惯。当然,如果现有的这些已经严重过时,那么应该找新老技术的专家,共同帮你设计一个路线图,让你可以平稳地引入新技术,这份投资绝对值得。如果老技术已经有新版本,则应该优先考虑升级它。不要幻想换个技术栈就能解决一切问题,事实上,它带来的问题往往会更多。
边缘产品和生命线产品
在人员的学习能力和意愿允许的前提下,边缘产品是最佳的试验场,适合探索各种候选技术,试验各种激进方案,积累经验教训。其影响范围可控,即使失控也不会带来太大的损失。当然,即使探索,也应该有计划地探索,不要每个边缘产品都采用不同的技术方案,那样会给人才供应带来巨大的挑战。
而生命线产品则应该稳妥优先,采用保守方案。所以应该优先采用团队内部积累了一定经验或具有稳定的强力外援的技术。
所有的生命线产品几乎必然是长周期产品,所以其可维护性同样是重中之重。
产品维度总结
在目标产品维度上,低价值产品优先考虑门槛低的技术,但是高价值产品应该尽早进行投资性技术积累,优先考虑天花板高的技术,这样才不至于在若干年之后被迫重写。如果工程化能力不足,这种重写往往会成为灾难。
目标用户
用户的特点对于技术选型具有显著的影响,甚至可能会导致产品不可用。
浏览器版本
在前端领域,浏览器版本是永远的痛,但这是需要权衡的。高版本浏览器甚至是单一的低版本浏览器会显著节省开发成本,但是可能会损失一些用户。该怎么解决呢?当然不能拍脑袋决定。
如果你们已经有同领域的线上系统,那么应该统计这些线上系统的访问情况,得出一个最准确的、针对目标客户群的统计,然后分析一下不同版本的浏览器有多大价值,有没有可能通过非技术手段让用户使用你们的目标浏览器。即使没有线上系统,也可以随机对目标用户群发一些调查问卷,确定他们的实际使用情况,以及安装新版浏览器的可能性。
下下之策是查一下百度公布的全网浏览器数据,然后说“我们要支持某某浏览器,它还有 10% 市场占有率呢!”,这是懒。
用户带宽
同样是前端领域,文件的下载体积可能会被一些人当做亮点进行宣传,但是你要清楚,现在已经是 4G 时代了,更不用说很多企业内部应用都是千兆带宽。就算能比候选技术小 100k,在 4G 带宽下(假设现实带宽是 2MB/s)也就是 100 毫秒,有谁能感觉到这部分差异? 这就是一个明显的“误导读者”的例子。
可访问性
在产品的用户群体中,不但有健康人,还有色盲以及盲人等残疾人。特别是对于面向消费者的产品,尽可能的考虑这些人的需求不但能体现出产品的“人文关怀”,而且也在一定程度上扩大客户群。比如苹果和微软等大公司都把可访问性放在了核心位置。如果你决定要实现可访问性,那么就应该把它作为一项需求,纳入到选型时要考虑的因素中。
之所以要把它纳入到技术选型过程中,是因为添加可访问性支持的代价比较高,而很多第三方库并没有提供这方面的支持。所以应该提早考虑。
国际化
与可访问性相似,国际化也是一个后期添加代价比较高,但很多候选技术却没有提供支持的特性。
如果你的产品在预期生命周期的相当一段时间内需要供多语言用户使用,那么,在初期选型时,就要把候选技术的国际化能力和质量纳入你的主要考量。
访问频度
用户的访问频度对前后端的技术选型都有很大影响。
比如说一个一年只用一次的功能,考虑其性能很可能是没有必要的,在一小时内跑完和在一分钟内跑完往往没有显著的价值差异。但是这两种技术方案却可能有着硬盘占用、编程复杂性、运维复杂性等方面的成本差异。你需要考虑:那种能在一分钟内跑完的技术是否能给你带来足够的价值。
对于前端来说,频繁访问的、面向消费者的应用通常会要求更高的流畅度,那么在技术选型时,就要选择流畅度更高的技术。但是这个流畅度一定要设计一个仿真的场景,亲自验证一下,甚至做一些灰度发布在现实场景下进行验证,而不要只看其官网宣称的流畅度。比如阿里的闲鱼团队就对 Flutter 技术进行了长时间的灰度验证,最终替换成了完全使用 Flutter 的版本,堪称对新技术进行选型的模范。
用户维度总结
要特别小心,不要根据错误的、片面的信息作出决策。很多第三方的技术选型指南背后都有着它们自己的场景,但大多数都不会给你写清楚,有的甚至复杂到想给你写清楚都做不到。甚至有些选型指南还有着强烈的主观立场,为了证明自己的预设立场甚至不惜造假。所以,你要先清点出你们的产品最应该重视的那些指标,然后拿这些指标对候选技术进行可行性测试,甚至为此专门开启一些 SPIKE 项目,而不要迷信第三方选型指南。
目标团队
目标团队的因素确实很重要,但并不像你认为的那么重要。除非你的人才供应真的有问题(难道不应该先反思一下是不是钱给少了?),否则应该优先考虑提升团队能力,而不是削足适履。
技术背景
目标团队的技术背景对新技术的选型确实很重要,但是没必要去精确匹配。
比如 Java 团队要做前端,选择 GWT 看似很好,但 GWT 也有自己的问题,几乎完全无法利用前端生态。他们更好的选择可能是 Angular:从语言上,TypeScript 跟 Java 有诸多相似之处;从架构模式上,对 MVC 的理解稍微往前推一步就是 Angular 的 MVVM 模式;从特性上,依赖注入不要太熟悉;从生态上,你可以自由决定是否使用前端生态,取长补短。
同样,前端团队如果打算自己写 BFF,也不一定非要在 Node 生态下打转。你完全可以使用 Java 世界的 Reactor 或者 WebFlux 进行响应式编程。这样可以和后端的其它 Java 体系更好地进行集成,并减少运维的复杂度。
团队规模
团队规模可能是团队维度中对技术选型影响最大的因素。
四位开发人员以下的小规模团队,如果大家都很专业,那么其沟通成本就很低,在技术选型上可以更倾向于选择灵活的技术,因为较高的人员能力和较低的沟通成本,可以让灵活的框架更好地发挥其作用,最终更加高效、高质量的推出产品。这种场景通常出现在由牛人组成的创业团队中。
如果开发人员经验不足或者做事不够专业,就需要更强的约束,特别是对于职场新鲜人,在早期养成好的开发习惯是非常重要的。而开发习惯中最重要的一点就是:约束 —— 知道不该做什么。这时候,偏向自由的技术可能会一时爽,但最终会构筑一个玻璃天花板,导致迟迟无法突破到下一个层次。
如果团队规模过大,那么首要的选择是用 DDD 等宏观技术把问题域细分,使其可以被小规模团队承接。如果暂时还做不到,就要考虑建设完善的基础设施和交付纪律,来为团队协作提供自动化保障。如果这些都做不到,就应该选择强约束性的具体技术,让大家避免犯错,或者尽早发现错误。在争取到时间之后,再逐渐深入化解根本性原因。
组织架构
康威定律深刻地影响着很多方面,技术选型也不例外。特别是做宏观技术选型时,必须考虑它在最终技术架构中的位置,以及与团队沟通结构的匹配程度。即使是一项很先进的技术,假如它与体系中的其它技术栈不匹配,也可能导致翻车。
当选择多个第三方库的时候更要加倍小心,因为它们开发时互相不知道彼此的存在,特别是对于一些较新的技术,可能都没人把它们搭配使用过。
除了开发架构之外,还要考虑更广泛的运维架构。假如你们引入了 DevOps,可能这个问题会得到一定程度的缓解。假如没有,那就要充分考虑上下游环节的人员能力和配套设施是否完备。比如如果运维部门缺乏 NodeJS 运维技能,就不要盲目引入基于 NodeJS 的后端,一定要拿到他们“我能”的承诺之后再开始。
除非你是个前后端 + DevOps 全栈,否则就需要尽早对组织架构方面的因素进行验证并排除风险。也就是说,在一个可控的演习环境中,用一个小型案例,完整地走一遍开发、上线、发新版的流程。在这个过程中,一些显著的风险将会暴露出来,要评估其影响,来决定如何选型。
人员流动性
人员流动带来的损失比大多数人所认为的要大得多。人员流动会带走知识和文化。企业要避免损失,就要把这些知识和文化尽可能记录在代码中。
当然,这并不意味着应该要求大量写注释,而应该使用那些能留存知识的技术,比如类型系统和规范化命名。类型系统和规范化命名可以半强制性地要求开发人员把原本只存在于自己脑子里的知识记录到代码中。如果更有追求一点,可以再尝试普及单元测试。这样,当他离开的时候,即使没有文档,这些知识也仍然能留存下来。从效果上说,代码往往比文档和注释更好。
而文化的留存则更加困难,事实上,代码中的奇葩注释往往留存的是负面文化。应该在代码中留存的文化,是严谨、专业的工作态度。虽然自由也是文化的一部分,甚至在管理领域是非常值得向往的文化,但在工程领域,它往往是一种负面文化,因为软件开发领域并没有公认的法律甚至道德。你可以想象一下管理领域中没有约束的自由会导致怎样的后果。
所以,要想应对人员流动的风险,除非你有信心留存知识与文化,否则就应该在技术选型时,倾向于选择更加严谨的、隐式信息更少的技术。
团队维度总结
鞋子好不好,只有脚知道。错误的选型,也只能由团队自身来承受。阿波罗神庙上镌刻着一句警世名言——了解你自己。所以,请先客观认识自己的团队,然后再据此进行选型,千万不要懒于思考,盲从潮流。
技术本身
对技术本身的考量,主要是代入其它维度之后,看其匹配程度。
技术本身在选型中可能反而是最不重要的一个维度。这些年的历史早已证明:优秀的技术未必能流行起来;很多技术的流行,也并非是由于其优秀。
明确的定位
一项优秀的技术,应该有其明确的定位和发展路线。这些定位能清楚地表明自己要做什么、不做什么。而其发展路线应该至少有一年以上的提前规划,而且在定位上要能与其前辈做出有效区隔,而不是亦步亦趋,没有自己的特长。
代码质量
虽然流行的未必优秀,优秀的也未必流行,但技术选型不是赶时髦。所以,在条件允许的情况下,还是应该尽可能选择优秀的技术。代码质量高的技术,将来技术本身由于维护成本飙升而被放弃的可能性也较小。
衡量代码质量的标准有很多,其中最常用、也比较有效的是单元测试的覆盖率。而那些从一开始就具有比较完备的单元测试的代码库,往往优于后补测试的代码库。因为这证明的是开发组的工程化能力和意识,而这些是该技术长期可维护性的根本保障。当然,除非该技术特别复杂或应用场景的容错性特别小,否则也不必苛求超过 90% 的覆盖率。
维护团队
维护团队的规模和能力,对于一项技术在长跑中的表现非常重要。在历史上如流星般划过的技术数不胜数,但最终能长期留下来的却不多。维护一项技术的成本远高于创建它,所以如果没有一个健康、可持续的商业模式,一个像 Linus 那样的志愿者,以及一个愿意出钱的超级大金主,那么它在未来的竞争中落败只是迟早的事。除非这项技术的需求集足够小而稳定,否则这些因素缺一不可。
社区
社区的质量,决定着这项技术长远的未来,一些草根型技术的隐患就在于此。如果社区人员的素质过低,喜欢无原则的站队,而不能理性的对该技术提出尖锐的意见甚至批评,那么这个社区迟早会衰落。这类社区有一个显著地特征就是喜欢宣扬它“包治百病”,也就是说它适合一切场景,而不会先问你一些问题再决定是否要推荐给你。另一个特征就是喜欢通过刻意选取某些标准来做出片面的对比,这种行为在学术界属于学术造假行为,但在我们工业界却被习以为常,这不能不说是我们的悲哀。
好的社区应该是一个君子社区。他们会自觉遵守共同的、理性的行为规范。会把精力放在对技术本身做贡献,而不是通过诡辩、群殴等手段来攻击竞争技术。社区的主要领导者会对社区的不良行为提出批评、做出约束,甚至为社区成员的不良行为道歉,而不是放任不管。
技术维度总结
不要把技术看得太重。对所有的主观性宣传文章,留一些心眼,多问一句——那缺点呢?将来决定你们是否会掉在坑里的,就是它的缺点。
对于那些会如实告诉你缺点的宣传文章,请高看一眼,因为作者是真的希望对你们团队的未来负责。
非功能性需求
非功能性需求都包含哪些内容呢?
- 性能(响应时间)
- 可扩展性(适应需求的快速变化)
- 可用性 (四个9,五个9,必要时的限流和降级)
- 安全性(防范各种恶意攻击,实现风控)
- 可监控(完善的监控和报警机制)
- 灵活性(便于非开发人员进行配置)
- 可维护(持续集成,持续部署)
- 国际化(冲出国门)