架构选择和团队影响
转自:https://www.linkedin.com/today/post/article/20140920230659-284548454-%E6%9E%B6%E6%9E%84%E9%80%89%E6%8B%A9%E5%92%8C%E5%9B%A2%E9%98%9F%E5%BD%B1%E5%93%8D
架构是协作基础。架构层面的基本决策深刻影响着团队学习/协作的方式和效率。
在这里,我想尝试探讨技术架构有关的一些选择对团队工作的影响。题目有点大,希望我能在小心控制范围的同时,又不失深度。
1、对开源的使用和态度
要不要使用开源软件?以何种方式/态度对待开源和社区?
如今的软件/互联网企业,相信已经很难找出不使用开源软件/库的了,但对待开源的态度,差别还是很大。
有些人可能一直用着开源软件,但从不关心它的源码,更别提参与开发、做出贡献;而有的人则会仔细的研究代码,对设计水平和代码质量作出自己的判断,能够更好的调整/适应自身项目的需要,并且以 bug 报告、提交补丁、提出建议、讨论需求的形式回馈社区。
在一个封闭的、和开源社区鲜有接触的企业里,重复发明的轮子很常见;而在一个开放的、拥抱开源的企业里,则会把更多精力放在广阔世界里的发现、评估、创造、改进上,尽量不在重复发明轮子上浪费时间。
开源对团队的影响,就是积极参与/拥抱开源的团队,
- 投入更有效,不会大量浪费在重复发明轮子上;
- 视野更开阔,对技术/软件的认识更精湛;
- 团队的沟通/协作更开放、更高效( 注:和对代码相当了解的人讨论问题,比起对代码所知了了的人,当然更高效;而开源所体现出来的开放和坦诚,也更容易赢得信任和尊重)。
对于团队拥抱开源后的提醒,主要在于两点:
- 对发散的精力/兴趣的重新聚焦,聚焦于产品、目标、质量和进度等
- 开源世界里选择众多,深刻了解才能作出明智选择
2、代码组织和存取
是所有代码集中存放在一个大源码仓库,严格控制签入签出;还是不同模块/项目代的码存放不同的源码仓库,各自管理签入签出?
这个问题看起来不起眼,但其实影响巨大,请让我细细分析:
代码耦合 - 代码的集中存放,往往导致更紧密的代码耦合。虽说大源码仓库也可以不同目录设置不同的权限,并通过良好的架构设计和严格的开发规范,达到代码间松散耦合的目的;但是这些沉重的管理负担,往往使目标变得不现实。
代码质量和单元测试 - 集中存放的代码更容易实施统一的代码规范和单元测试要求,但分散存放的代码更容易带来高质量的代码和单元测试。这里我们要搞清楚一点,质量的关键在人、不在那繁琐而又无法涵盖一切的规范/要求;分散存放的代码更容易找到责任人,导致更谨慎更独立的设计、开发和测试,而另一方面,责任人也能更容易享受良好设计、单元测试带来的好处。
学习和交流 - 分散存放更能促进学习和交流,这也跟集中存放所带来的一切都在眼前的感觉相反。这里的要点是:分散后重新联结的代码,更容易分清主次、依赖关系;有疑惑也能更容易找到合适的人询问。
尝试/创新 - 放入大源码仓库的代码、第三方库很容易产生意想不到的依赖、这让后续的修改/调整更加困难,而更重要的是,谁来尝试/创新、范围多大;而这些问题在分散存放的结构下更容易解决。
工作效率 - 代码集中、严格控制签入签出的企业,往往也不大可能采用更加便捷高效分布式源码管理工具;这些工具让开发人员在离线状态下也能浏览所有历史、并提交更改,对实施控制的人来说是极大的挑战。
上述论证的关键是集中存放导致责任模糊,而分散存放容易做到责任明确;责任明确是很多管理问题的基础。
分散存放也带来了一些新问题,特别是在集中存放的人看来:
联结 - 如何把各自开发的模块/库联接到一起?maven、npm、bower 这些包管理工具以及公司自己搭建的发布/仓库服务器是这个问题的最好回答。
发现 - 如何发现、浏览独立开发的模块/库源码?github 以及 gitlab、gitbucket、gogs、Gitblit 都是现成的答案。
协同 - 如何协同开发和发布?如果对其他模块的修改是它现有结构的自然扩展,那由应用方统一修改比较合适;如果其他模块需要做较大调整才能支持新需求,那联合开发会是更好的选择。至于测试、发布,可以在被依赖库的私人发布版的基础上先进行测试,等改动被模块维护者接纳后再切换到依赖库的官方发布版、复查并发布。
划分 - 如何划分不同模块/库的职责?这是一个真正的问题,相对于设计模式、开发技巧、代码规范来说;但对这个问题的讨论超出了这篇文章的范围,我不打算在这展开。
3、框架选择
是选择一个大而全的框架作为主要开发框架、还是选择一个小而精的?
这个问题不好回答。要知道,我们选择任何一个主力开发框架,都会综合考虑很多因素:
开发效率 - 大而全的框架,如 AngularJs,往往强调角色分工,并提供配套工具,所以对于本身严格分工、并且有大量新手的企业,开发效率显然要高,换句话说,大而全的框架更容易出活。
功能完备性 - 这个粗略看来,当然是大而全的框架占优,毕竟这是它卖点;不过这只是表面现象,从潜在的、长远的来看,未必,小而精的框架可能功能更丰富。这方面一个很好的例子,是:extJs、dojo 和 jQuery。
定制可扩展性 - 大而全的框架往往也提供很多定制和扩展,像 Struts2、Play!,但这更像是赠品,毕竟它的成功,主要靠内置功能;小而精的框架这一块做得会更用心,毕竟它主要靠第三方参与。
可深入性 - 这是另一个小而精的框架胜出的地方,毕竟这个对取得成功影响巨大;写得糟糕、不知所云的代码,会直接降低参与者的兴趣。这个对于我们定位问题、fix bug 非常重要。
社区/生态 - 大而全的框架大企业/组织撑腰,往往能迅速建立起社区/生态,但这种社区往往一家独大、不是真正的众人拾柴/百花齐放,因为它更需要的是社区的推广、而不是贡献,一旦主导企业/组织放弃,其他参与者还能不能够支持/愿不愿意跟随就很难说了;相反,小而精的框架的社区来的更实在,任何一个人退出都可以找到人接上。这方面的例子可以举:AngularJs 和 Vue.js,Play!和 Scalatra。
其实归根结底,这里的主要焦点,是规模化和分工。大而全的框架促进分工,往往几个人搭起,大量人开工;大量一知半解、不求甚解的人也能组织在一起,开发出大量地东西。小而精的框架往往做不到。
这里的关键,不是什么页面模板和业务逻辑代码更好的分离、更好的分层之类的,其实只要你认真观察,就会发现使用小而精的框架的团队在这些方面其实做得更好;这里的关键是人员素质,小而精框架要求更高素质的开发人员,而它本身也擅长培养高素质的开发人才。小而精的框架更诱人深入,激发人从整体上写出更好的代码、做出更好的系统。
对于大多数的外包开发项目来说,选择大而全的框架绝对是明智之举,毕竟功能全、上手快的重要性要远远大于长期可维护性。对于很多需要长期演化、迭代开发的互联网产品来说,选择哪种框架确实是见仁见智,不过我建议给予小而精的框架更多的考虑;毕竟自主开发产品的公司在人员稳定性方面要比外包公司优越得多,需要增强的只是有效的激励。
最后让我用一句话来作为这一节的结尾: 一群人开发的东西、需要一群人来增强/维护;几个人开发的东西、几个人就可以增强/维护。
4、方案的简单/复杂
设计模块/方案的时候是尽量简单、够用就好,还是不怕复杂、一应俱全?是小心的控制复杂度,还是任他蔓延?
这个问题似乎不需要回答,当然是简单、够用就好,并且,要保持简单;这也是我前任上司在听到我做一个重大项目的方案时、总在思考这个可能那个可能的时候的第一反应。
不过事情并不是这么简单,你们谁会想到部门年度最大项目发布后设计者和主要开发者会马上被调离、由一群新入公司或没有参与这个项目的人着手定制、推广到相关产品线;这个时候就体现出深思熟虑的威力了,没有太多的问询和顾虑,事情进展的异常顺利,除了在一个项目原本就不支持的地方产生的一系列问题外;我太清楚初版对后续版本的影响,也太了解这家公司做事的方式了。
( 补充:这个项目实现 90% 以上逻辑的代码,只有 2000 来行,代码简单没有玄机,这是很多人自由修改而没有造成问题的原因;组件化和平台本身就是设计目标,这是它能迅速推广并作为产品线/部门大部分代码底层基础的根本;其实是有设计文档的,而且我一直鼓励阅读它以获得更深刻的认识,只是很多人一再用实践证明,不看文档也行。
考虑的很多并不意味着实现就复杂,相反,考虑全面才能真正取舍、提炼、触及本质,而本质往往是简单的。)
再来看看简单、够用的另一种方式:别想太多,做出来就行;效果出来后,又提一些要求,调整、打补丁;换人,继续 … 在已有架构下的 UI 调整,采取这种方式是可以的;但涉及到核心逻辑开发也这么做的话,几轮迭代下来,估计谁也说不清代码的逻辑和目的了;而不清楚逻辑和目的,就无法有效重构,无法偿还这笔技术债了。
这里的关键,是要对需求进行挖掘、提炼,从简单基本的入手,并通过取舍保持简单。
世界上有两种设计软件的方法,一种方法是设计的尽量简单,以至于明显的没有什么缺陷,另外一种方式是使他尽量的复杂,以至于其缺陷不那么明显。简单的东西可控,在变得异常复杂之前,一般都不会有什么问题;复杂的则自始自终问题不断。
5、接口的风格取向
选择工具/库时,是特别偏爱声明式/傻瓜式的,还是也会有意识的选择一些编程式的(看起来有门槛实际上更简单)?
这方面一个很好的例子是 Grunt 和 Gulp:
Grunt 主要提供声明式接口、有着庞大的插件社区、提供更为详尽的文档,对于新手更为友好一些;Gulp 则只有编程接口、插件较少、文档也简单很多,初看门槛挺高。但实际上正相反,Gulp 更简单:
- 只有编程接口,是因为它不想隐瞒底层原理;
- 它的基本模型–Stream(类似 Unix 管道)–是一个有点难懂、但实际上非常简单/强大的东西;
- 为它开发的插件都非常简单,没有什么需要特别说明的( p.s. 因为处理结果可以很简单容易传递给下一个插件,更为复杂的功能可以通过排序/组合来达到而不需要像 Grunt 插件那样去实现,所以 Gulp 插件只需实现非常单一的功能。同样目的的插件,Grunt 往往好几百行,Gulp 几十行就够了);
- Gulp 插件更少,实际上它也不需要那么多的插件,排列/组合能达到比 Grunt 更丰富的效果;
- Gulp 配置更简单,更容易理解;
- 最后值得一提的是,由于重用中间结果,Gulp 执行更快,在复杂任务中速度提升更加明显。
这种选择对团队的影响是:
- 傻瓜式接口在使用者和开发者之间划出了鸿沟,开发者精通内部细节、而使用者倾向于一知半解,久之就造成了开发者和使用者之间的严重脱节;
- 而编程式接口模糊了开发者和使用者之间的界限,开发者也使用这工具、因为他可能也做普通开发,而使用者也经常对工具作出改进,所以都是工具的主人。
更新:我们可能已经习惯了封装/隐藏,总是把对当前用户不熟悉的东西隐藏起来,但有时这些要隐藏的东西实际上并不复杂,一旦理解,就非常容易使用。
6、语言/范式和代码规范
是允许多语言/范式、多风格代码并存,还是单一语言、代码规范非常严格、连缩进用 tab 还是空格都有规定?
单一语言、严格代码规范的一个明显好处,就是可以简单粗暴的管理、搞一刀切、交给机器执行,连源码都不需要阅读。规范苛刻以至于变态的公司,会比较排斥多语言/范式,因为多范式就是多风格、而不同的语言都有适合它自己的不同风格,风格多了管理控制就复杂了。但不同语言/范式、风格对不同思维方式/习惯之间的相互学习和促进,是明显有好处的。
可维护的程序必须容易阅读,因此一定程度的代码规范/风格统一是必要的,但代码规范和代码质量之间不存在必然的联系。让我举两个简单的例子:
一次公司内部讨论,关于代码规范。在为代码缩进该用 tab 还是(手敲)空格、空两格还是空四格而纠结的时候,我“不适当”的插了一句:“你们都读过 JDK 的源码吧,那你们怎么看待这些代码?他们比我们自己写的优秀很多,但可能通不过你们今天讨论的代码规范。”
还是这个热烈推动代码规范的人,来找我做 code review。代码问题很多,但我只提了一点:“你开发的是组件。那你告诉我,你的组件跟应用代码之间的界限在哪?”(p.s. 在我这碰了灰,这个人就去找后来是他/她下属的人,轻易的通过了 code review。 那么,好吧。)
所以,真正的问题是:代码规范要把握的度在哪里?应该给自由表达留有多大的空间?
其实照顾读者最真诚最有效的方式,是不故弄玄虚、尽量用平实/简单的方式表达;它不排斥代码规范,但有自己的风格和主见。而这样用心写就的代码,应尽量帮助它通过代码工具的检查。
7、自动化/工具链
工具链、开发/运行环境不可避免的越来越复杂,应当有足够多的自动化工具来简化日常工作。这方面缺乏的话,开发人员每天光修改配置、部署服务就要花费大量时间,真正能投入开发的时间就寥寥了。
对自动化工具的有效使用,可以大幅降低规章、流程等管理工具的使用,提升做事敏捷性。
.
架构选择和技术策略、环境氛围、管理方式 经常是保持一致的。
在一个技术策略相对保守,高度集权、充满控制的环境中,隔离开源、代码集中存放、大而全的框架、复杂方案、傻瓜式接口、单一语言、严格代码规范、较少的自动化工具是很自然的结果,在企业内部环境下也是 适当的选择。其中自动化工具不足是一个有意思的结果:照理说自动化工具可以简化流程/管理、有着容易衡量的直接效益,理应蓬勃生长才对,但实际往往不是那样。
在一个技术策略相对激进,开放/信任、互相支持的环境中,拥抱开源、代码分散存放、小而精的框架、简单方案、编程式接口、多语言/范式、代码优雅不拘一格、丰富的自动化工具更容易产生和接纳,而这也反过来让环境更加开放值得信任。
不同风格不是不能混搭,只是要留意他们的不同特点。
继续上面提到的自动化工具不足的例子:造成这种结果的原因,是企业太关注标准化、而不关心合用,强求对规章的遵守、却不关心价值的实际创造,所以热情终将湮灭、富有创造力的人终投他处;所以如果这个部门的经理能够真正关心工具是否够用/合用、鼓励解决问题的行为、并有意忽略一些对琐碎规章的违反现象的时候,事情很快就会有改观;当然,部门经理之所以敢/愿意这样做,是他能够用所取得的成绩、来证明对一些规章违反的合理性。
(p.s. 话说一些规章规范的制定,确实不太注重它的合理性和可依从性,前面提到的代码规范是一例,把最佳实践(a.k.a 公认较好的建议)上升为规定是另一例。)
更多的管理行为分析在这就不展开了。
.
以上,是我对架构选择和团队影响的一些见解和观点。
.
* 文章开头的那张图片,其实是我目前架构和生态的截图。相关说明和补充如下:
- 图中涉及的多是 Scala 和 Javascript/Nodejs 的框架/库; 目前公司 主要工作语言是 Java,但我个人其实已经全面转向 Scala 和 Javascript/Nodejs 了
- 我自己的一些开源项目,像 slick-pg、form-binder、brbower,以及对其他开源项目的贡献,其实是对这一架构的增强
- 我提倡把大项目划分成独立小项目,每一个都安排专人负责;为了多项目环境下工作方便,我已经开发了一套自动化脚本(p.s. 暂未开源)
- 我的技术策略相对激进,主张直接面对看似不可能的问题,但要量力而行(不仅关心能不能实现,也要注意新方案/技术的影响、以及后续的维护成本);我把初始架构当作提供支撑的起点,方便他人和我进一步细化和调整