所谓最优,就是从一堆非最佳的方案中找到最好的一个,或者整合一个。
在我最近的三个项目里,项目的周期都太短了,大抵都在 3~6 个月之间。短周期的项目里,对于开发人员的能力增长并不是一件好事——我们所能沉淀的知识比较少。但因为项目周期太短,也带来了一定的挑战性,或许是关键的 “坑位” 带来的挑战。
几乎所有的长期项目都是从短期项目开始的——多数情况下,只有我们验证了 idea 是可行的,我们才会投入更多的时间、精力去演化这个项目。长期项目带来的主要挑战是:人员能力增长、架构演进以及代码实践。而短期项目的主要挑战是:技术实践与业务进度的冲突。即我们在追赶业务进度的同时,如何实施好的技术实践。
于是乎,在最近的日子里,我开始在思考:如何进行最优的技术实践。按我的理解,我将项目初期的实践分为了三个时期:
技术准备期。进行一系列充分、不充分的技术工作,从搭建脚手架,到部署测试等等。
业务回补期。填补第一个时期造成的业务落后问题,技术实践业务来证明技术的价值。
成长优化期。持续地对项目的技术和业务进行优化,以实现开发及业务人员的诉求。
每个时期都有自己要所要面对的挑战。
技术准备期:探索技术
这是一个 技术 First,业务 Second 的阶段。
一旦开发一个新的项目,我们就得做大量的技术准备工作。哪怕是我们已经有了一个现成的脚手架,但随着时间的推移,其中的一些依赖版本就需要更新,又或许是有一些依赖在这个项目又用不上,还需要进行一系列的定制化。
对于复杂的项目,我们还需要进行一系列的技术探索方面的工具。以为将来的开发,打下深厚的基础(避免被骂)。
概念验证:原型
在我的上一个项目里,才算正式接触 PoC ( 概念验证)这个阶段。但是,我发现并不是上一个项目独有的,只是在那个项目里,PoC 的阶段比较长,达到几周的长度。而一般的项目,这个阶段可能是一周,也可能是几天,几个小时——因为之前已经有了相关的经验。
概念验证(英语:Proof of concept,简称POC)是对某些想法的一个较短而不完整的实现,以证明其可行性,示范其原理,其目的是为了验证一些概念或理论。概念验证通常被认为是一个有里程碑意义的实现的原理。wiki_poc
在这个概念验证阶段里,实践的是对于一些技术理论的探索。比如我们看上了微技术、微前端等新技术,或者 GraphQL 等新框架,并且计划在这个项目中使用,那么我们就需要去验证它是否能真正的被用上?
可当我们预先设想的技术和架构不能应用时,那么我们到底是采用原有的系统架构,还是新设计一个合理的架构,还是采用 B、C 方案?这个问题就变成一种考验,在这个时候到底什么是第一优先级,技术、业绩还是业务?但是不可避免的,我们又花费了大量的时间在原型设计上。
迭代 0
完成概念验证之后,I0 就要完成项目配套的技术准备工作,如创建脚手架、搭建持续集成、进行更细粒度的技术选型等。
迭代 0,又名为 I0,看这名称就可以发现它与敏捷软件开发的关系。开始一个项目开发的时候,进入的是迭代 1 的开发,那么迭代 0 呢?我们可以将其视之为,所有迭代之前的准备工作。它在这种定义之下,在项目不复杂的情况下,PoC 阶段会被列入迭代 0 的工作中。而项目复杂的时候,PoC 则会独立于迭代 0。有意思的是,在方案和咨询公司里,如我司,是 PoC 都是在开始项目前进行一部分,而在项目进行时进行另外一部分。
在 PoC 编写的是一些粒度较粗的代码,并且为了追求效率和验证,可能有大量的 Code Smell(代码坏味道),诸如注释的代码、未使用的代码、不经测试的代码等等。因此我们需要在迭代 0 ,对架构进行更细粒度的整理和优化。同时,我们还需要确认大部分的未来将使用到依赖软件,这些依赖在可见的未来还会经过修改,但是进入开发阶段时应该是可用的。除此,我们还要进行部署的准备工作,尝试进行第一次部署,包含内部部署。
迭代 0 除了技术准备工作,还需要进行内部的技术培训——只是简单的技术培训,用于介绍系统的架构,开发注意事项等等的内容。当然了,即使已经有相应的培训,还需要准备基础的架构方面的文档,以及必要的一些规范。
这个阶段结束的标志是,项目成员可以进行正常的项目开发,这个开发和未来开发没有区别。
业务回补期:应对第一次 Deadline
在这个阶段里,业务开发已经进入了正轨,但是可能存在一定的进度落后。于是变成了业务 First,技术 Second。
毕竟欠下的债(业务)总是要还的。 这是一步价值证明的阶段,也是调配落后的进度与先进的生产力的时期。
除此,由于内部技术的问题已经解决了,我们还需要关注于与第三方集成,并开始着手准备应用的测试,即第一次上线的相关事宜。
追补业务
…作者太懒,没什么想补充的…
上线准备
在这个时期里,我们所要面临的第一个挑战是:第一次 Deadline 及其上线。
如果我们的项目依赖于第三方服务、平台,并且他/她们的上线周期,和我们是相近的。那么,这个时候与他们联合进行调试,就是一种挑战。特别是上线日期临近时,如果遇上 Bug,那么就需要反反复复确认。
当依赖 ready 时,我们就可以着手准备上线相关的事宜。小公司的购买相应的服务器,大公司的申请相应的资源、审批流程等等。
因此,在 Deadline 提前,进行磨合也是不错的策略。
测试
在一个前后端分离的项目里,我们在平时的开发里,已经进行了大量的联调,基本可以应对上线。
我们可能还没有进行测试,无法验证业务的完整性。
我们可能没有完整的测试规范,来保证整个流程正常。
我们可能还没有准备好一个稳定的测试环境,以提供给测试人员进行测试。
即使是敏捷项目,在第一次时,测试人员进入项目的时间,也和瀑布型项目类似,略晚于开发人员进入项目测试。一来,前后端之间的联调可能没有完全 ready,二来,可测试的业务内容也相对比较少。但是敏捷项目在 QA 进入测试之后,便一直在项目中进行测试。测试除了带来功能的稳定,还会带给开发人员一堆的 bug,这些 bug 会进一步地影响项目的进度。
对于复杂的项目而言,自动化测试(单元测试、UI 测试等)在前两个阶段可能并不会出现——大量的时间被花费在相关的技术实现上。覆盖率在这一阶段要定一个基本的值,比如说 30%,先从 0 开始,然后再进入更高的数值。基于此此,我们才有机会进一步地提升代码的质量。
但是不管怎样,在第一次上线之后,我们实现了从 0 到 1 的阶段,接下来就是从 1 到 100 的时期了。
成长优化期:技术债与演进
这是一个技术证明业务、技术提升开发体验的阶段。同时,随着业务代码的堆砌,我们开始面对它们带来的挑战。
值得注意的是:
如果一个项目的时间太短,那么它就不会遇到这些挑战。
如果领导们看来技术不重要,那么这个时期实践就少一些。
如果我们的能力不够,那么我们只能努力去提升。
但是不论怎样,作为一个手工艺人,我们总得有点 “追求”。
弥补技术债
这个时期,我们面临了一些遗留的技术问题,不得不去弥补这些债务,免得被利息拖垮了。
在技术准备期,我们花费了大量的时间在构建技术基础;在业务回补期,花了大量的时间、精力在支持业务的开发上。在这两个时期,我们都或多或少地采取了一些妥协方案,为的是能加快速开发流程。而在当前这个时期看来,这些问题将在未来成为我们的开发负担。因此,我们将其称为技术债。
这些债务,包含了很多方面,比如:
代码质量。常见的问题有:接口、函数重复实现,即 a 成员在自己的功能内,实现过这个功能,但是 b 成员又实现一份,没有提取到公共的方法中。实现方式、模式不统一,这一点常见于我们采用了某个框架来解决问题,但是真实场景下,可能又会采取过去的实践方式,诸如使用 Lambda、RxJava、Rx.js、Ramda 等框架来进行函数式编程。少部分代码未按规范实践,这个问题更加常见,不仅仅存在于没有代码检视(Code Review)的项目,还存在于拥有 Code Review 的项目,未 Code diff 过的代码,往往容易被遗漏。
测试覆盖率,在面对技术不熟悉,业务又过急的情况下,测试往往是最先被抛弃的一环。值得商榷的是,国内的互联网公司都不会有测试这种东西,所以它们就存在这种长期的债务了。
依赖问题。依赖对于短期项目来说,并不是问题。对于长期项目来说,依赖没有及时更新是一个很严重的问题。诸如,我们使用的是 Redux 3.0 的版本,当 4.0 发布的时候,按照语义化版本的规则,它可能修改大量的代码,而我们不得不追随这个变化——除非,我们决定未来重写现在这个应用,否则大量依赖过旧的问题,会导致我们难以对代码进行重构,因而不得不重写应用。
除此,还有大量的前期我们设定的技术目标,我们也会在这个时期实施。
提升开发体验
时间一长,业务的进一步实施,我们便开始在堆砌业务代码。堆砌业务代码,对于某些技术人员来说是一件难熬的事情——每天都在重复工作经验,于是需要帮他们寻找一些挑战。对于技术负责人来说,堆砌业务代码就会轻松一点,毕竟风险会小一点。除此,有时候会为了绩效,也需要创造一些机会。基于多方面考虑,便需要去努力地提供开发体验。
在这个过程中,我们可能写了大量的类重复性代码。这些代码表面看上去并不是重复的,但是从抽象层来看是重复的。应对这种方式的方式之一,是可以用诸如《如何在业务代码中提升:创建领域特定语言》中的方式来进一步抽象出内部 DSL 代码。那么,我们就可以减少花费在业务代码上的时间,进一步地花费时间去抽象这一层抽象层。
除此,还会思考怎样去自动化一些代码的编写,比如在 UI 层采用的 Sketch2Code,又比如 Java 中的 XML 编程。
结论呢?
……这部分稿子被台风吹走了……
你说呢?