注册

当一个程序员从产品角度思考技术

近期做需求中有一些感想,在这里记录下。


1. 产品究竟是谁的


人人都是产品经理,并不是一句口号,每个人都是用户,即便你是研发、设计、测试,都有无数的时间,和世界上最优秀的产品打交道。最优秀的产品包括你的电脑、手机,和那些最流行的APP。


所以,如果某个产品经理一言堂,那这个产品大概率不会太成功。除非他真的厉害,并且有数据支撑,比如乔布斯这种。产品不是政治,产品是可以讨论的。


如果一些交互设计难以理解,研发都用着费劲,怎么在普通人中推广呢?


另一个问题,研发需要了解并思考产品的逻辑吗?


答案肯定是需要的,产品是有逻辑的,研发可以根据产品的逻辑,判断、预测未来的变化,抽出稳定性更强的代码。


一个工作3年和工作10年的研发的一个区别就在于,对于产品逻辑的理解,大概工作更久的人更容易预测产品未来的变化,会在代码编写、结构设计时就拆分出不变与变化,从而让代码更易维护。但是,如果每次新来一个需求,都从0开始,从不思考,就另当别论了。


2. 定制与通用


通用和定制往往是对立的,一个需求如果是定制的,意味着不够通用。


前一阵做了一个国际化的需求,国际化其实就代表通用,本地化代表定制。


但是如果同一类型定制的多了,就可以变为通用需求。本质上一个string/number/boolean类型总是可以扩展成array的。


比如H5的自动化测试报告处理,和小程序的处理,是可以通用化的。


这给我们一个启示是,要多沉淀东西,做过的那么多需求,比如表单系统、权限系统,这些工作即使一两年内不会重复,但是时间拉长来,总会在职业生涯的某个阶段重新做一次。


还有,我们加班那么多,是不是产品设计、需求提出、技术实现的时候,通用性考虑的就不足呢,所以今天在A上拧一个螺丝,明天在B上拧一个螺丝。


可怕的是,这两个螺丝都是一样的残次品。


通用性在不同的层次上有不同的表现,比如总监可以考虑业务的通用,产品经理考虑产品的通用,开发则考虑代码和系统层面的。


组件化的基础也是通用性,如果一个组件可以被复用,说明它是通用的。


3. 复用性


应当努力追求高复用,不论是组件,还是工具,为什么呢?



  1. 从零实现成本高
  2. 复制粘贴然后改一点,看似成本也不高,后续维护、迭代更新则成本高

实践证明,组件、工具的迭代更新的频率,一般是高于认知的。即便你当下觉得它很稳定了、很完美了,也很有可能在未来某个时候优化,因为人的认识总是有局限性的


组件高复用,可以统一升级、统一优化、统一迭代。不同维度的组织都应该追求高复用,包括个人、项目、团队。


4. 字段名


发现一个现象,大家在字段命名的时候比较洒脱。



  • 格式比较多样,比如角色Id:roleIdrole_idroleid,子活动Id:childidchild_idchildId
  • 名称也多样,比如手机号:phonemobile,头像:headheader

大家好像习惯了这种乱七八糟的命名,倘若统一了,反而觉得难受。


在其他地方没得到的自由,终于在命名时得到了。


这种字段命名问题和上面的通用化有些关系,如果不能统一,谈何通用呢,通用化的基础一定是标准化。


为什么大家会在命名时候,这么随意呢?大概率是因为他根本没考虑过下游开发者的感受,比如前端。


下游开发者做起来是很痛苦的,他知道这个协议会包含子活动Id,但是他必须去协议页面查看,才能知道究竟是哪种命名,是childid、还是childId、还是child_id?并且,他在向他的下游或下级页面、组件传递时,需要考虑对方需要的是什么,必须做一层转换。如果他不知道转换或者不想转换,让下游直接修改字段名,那整个项目就更“精彩”了。


意识也是一种能力。能够预见到一些可能带来的问题,并尽量规避掉,也是一种好习惯。


5. 代码是资产还是负债


总看到有人说,代码是负债,个人不敢苟同,倘若代码都是负债,你还在这敲锤子啊,岂不是越欠越多,回家睡大觉不是更好?


大部分人之所以认为代码是负债,是因为维护起来费劲,一看代码就头疼,每次改代码都像是上刀山,这能不是负债吗。


或者改别人写的代码,小心翼翼、胆战心惊、如履薄冰,生怕解决一个问题,带来其他问题。


所以,个人认为,质量高的代码是资产,写的像X一样的是负债。


“大处着眼,小处着手”,架构、系统相关可以视为“大处”,代码规范、代码质量可以视为“小处”。


最近看到公共基础库很多很多的魔法字符串,开发没有意识去抽离,维护这样的代码就比较头疼了。


好的代码一般是模块化,抽象化,通用化,配置化。失控的代码,没有模块化,没有整理,没有规律,没法维护,没法复用。没有解耦,维护起来相当麻烦。


甚至有人说,“单元测试有什么用?“


6. 兜底


有一个需求,A类型的奖品不需要展示,B类型的奖品可以展示,后台让前端“兜底”,过滤下这个类型的奖品。


想到这个只是产品的上层逻辑,是极容易变化的,今天屏蔽A、明天屏蔽B,后天可能又要放开A、B。


兜底应该是对边界的兼容,比如:



  • 多重if else中最后的else
  • 对空值给默认值
  • 对解析失败的捕获
  • 对不同机型、不同环境的兼容性

像这种产品上的逻辑怎么会是兜底呢?这种过滤其实是数据层面的处理,越在上游处理越简单,因为用数据的地方总是无法控制的。


这其实反映出另一个问题,前后台责任不明确,这次你帮我多处理一下,下次我帮你多处理一下。本来后台该做的事,放到前端来做,下一次,本来前端该兼容的,让后台来做。


前端应该始终兼容空值边界,比如a.b,当a不存在时候的取值异常。组件、页面总是可能复用的,如果下个prop或者cgi没传,就要付出额外的时间成本处理错误。


前端兼容性大多是为了系统鲁棒性。


此外,处理数据的逻辑应该放在一起,如果这里增加一点字段、那里改变一下结构,又没有类型提示的话,后期维护起来很难受。


前端当变量为undefinednull时,会报错的语法或API:


Object.keys(null)
Object.keys(undefined)
// 会报错,Uncaught TypeError: Cannot convert undefined or null to object

a.toString(); // a 为 undefined 或 null
// Uncaught TypeError: Cannot read properties of null (reading 'toString')

7. 兼容


上面提到了兼容,其实兼容有两种:



向前兼容(forward compatibility) = 向上兼容(upward compatibility),也就是向未来兼容,即现在设计的软件要考虑未来还能不能用。
向后兼容(backward compatibility) = 向下兼容(downward compatibility),也就是向过去兼容,即现在设计的软件要考虑旧版本的数据还能不能用。



一般说的兼容指的是向后兼容。在框架、基础库升级的过程中,如何实现向后兼容、平稳过渡呢?


通常是设置一个过渡版本,比如v1版本的旧API,在v2版本时候同时提供新老两种写法,并标明旧APi即将废弃,然后在v3版本正式废弃掉旧API。


如果不提供过渡版本,一般会导致开发者不敢升级,比如vue2vue3就没有做到平滑过度,导致现在很多项目都是vue2


8. 变化


代码维护、迭代过程中最重要的事情之一就是控制变化,应该始终将变化的部分做到可控。


其实现代社会就很符合这个规范,电网、能源、航空、铁路等,这些核心产业都被国家握在手里,其他一些小打小闹的产业,比如餐饮、互联网等,随私企去折腾。


对于大型项目,核心模块,比如基础UI组件库、网络框架、核心逻辑,都应该以一种稳定的形式存在,比如npm包、基础库,不能今天改、明天又改。


为什么函数粒度要细呢,也就是常说的一个函数只完成一个功能?其实本质也是控制了变化,如果一个函数同时完成多个功能,那么改动的可能性就更高,就更容易出错。


稳定性是我们一直追求的目标,一般来说,我们更喜欢发挥稳定的球员,而不是“神经刀”。


代码也是一样,控制变化,其实就是保持稳定性。


为什么要追求稳定呢?本质上是变化的成本太高了,越复杂、越底层的组件、工具,改动风险越高,因为复用的地方多,很可能牵一发而动全身。即使做到了高内聚、低耦合,如果改动不是向下兼容的,上层就要一起更新,会增加时间成本、出错概率。




稳定并不意味着一成不变、一定没有变化,比如一个组件库,内部的优化可以一直做,只是对外的API、展现的形式需要稳定,也就是与之前保持一致。


9. 写文章


写文章会耽误工作吗?就个人经验来说,写文章不但不会影响工作,反而会提升效率。


因为写文章一定是因为有自己的思考才写,不论是解决了问题、还是总结了方法,都是有或多或少的思考,大脑一定是活跃的


没写文章的时候,看似工作时间投入更多,其实脑子已经不转了,没有自己的理解,没有总结思考,工作效率其实非常低。


这就好像,那些成绩好的同学其他方面也有特长,比如体育、文艺,反而是那些成绩差的才干啥啥不行。


为什么会这样呢?因为思考这个东西是相通的,好多东西底层是一样的,会了一样就可以触类旁通、举一反三。


10. 低代码


最近人工智能生成代码的方式很火,更新了人们对“低代码”的认知。低代码、零代码的本质是为了提升开发效率,提高生产力,只要能达到此目的的都是低代码。


因此,广义的低代码有下面几种:



  • 从手上已有的其他项目中复制粘贴,比如之前有了一个项目表格,现在要做一个操作记录表格,直接复制粘贴然后改改即可。
  • 从搜索引擎中搜索,然后复制搜到的内容
  • 从一些可视化界面,拖拽组件而成,即目前人们常说的低代码平台
  • 人工智能生成

哪种会成为未来的主流呢?衡量标准有下面几个:



  1. 实现成本低,狭义的低代码平台需要搭建,而且越复杂的项目搭建成本越高
  2. 实现效果好,开发者改动少,并且接近需求

从上面的标准来看,个人目前看好AI。


11. Mixin


有许多人很排斥使用Mixin,弃之如敝屣,其实只是使用姿势不对而已。



  1. 一个Mixin文件中的内容应该高内聚,需符合单一职责原则,围绕一个中心,不能做两件事。
  2. 尽量减少使用全局Mixin,多用单文件引入。

只要做到上面两点,Mixin就可以化为高复用的利器。


12. 小而美


“小”的优点:



  1. 易于理解和学习。如果你想要写出全世界都是用的程序,那这一点很重要,无论是大牛还是小白,都能轻松是用,才能推广开来。
  2. 易于维护。即便是自己写的代码,过半年自己都忘记当时写的是什么了,要考虑这一点。
  3. 消耗更少的资源。“小”到制作一件事,用多少就消耗多少,不做一点额外的开销和浪费。
  4. 更易于和其他工具结合。即可扩展性更好,符合开放封闭原则。

让每个程序只做好一件事(单一职责原则),这个和准则1(小即是美)表达的意思一致。


只做好一件事,说明足够小。越是大型的系统,这个原则越重要,否则越大就越乱。


书中列举了一个范例 —— ls命令。ls本来是很简单的一个命令,现在却搞的有 20 多个参数,而且正在逐步增加。这就使得ls慢慢变成一个很庞大的命令,但我们日常 90% 的场景都使用它最简单的功能。理想的做法是,ls还保持简洁的功能,另外开发新的命令来满足其他配置参数实现的功能。这就例如,cat可查看全部内容,想看头或者尾,分别使用headtail——这就分的清晰了。


某BG就是典型的小而美,灵活。


13. 沉淀


沉淀了多少组件、公共方法,这才是业务开发中重要的。


沉淀是分等级的。项目越大,项目分层越多,沉淀等级越多。底层的更纯粹,应当尽可能沉到底层。以最近维护的几个项目为例:


t-comm > uni-plugin-light > press-ui

组件库由于必须依赖框架,所以不能和JS/TS工具一样可以沉到最底层。


一句话总结就是,能沉到多底就沉到多底。


14. 研效


很多人都在提研效,PPT里、总结邮件里,但其实并没懂研效的本质,个人看来,研效关键点有两个:



  1. 是否有足够多自动化的工具,比如CI、配置中心、自动化测试、自动同步工具、任务调度平台
  2. 是否沉淀了足够多的组件,新需求来了后,可以调用现成的能力,需求做完了后,又沉淀了另一些能力

如何判断一个部门、团队、个人是否对研效敏感呢?只要看他对上面两个方向的重视程度就行了。


堆人力,轻研效的特点是:



  1. 对工具轻视、恐惧,喜欢最原始的方式,喜欢刀耕火种
  2. 对沉淀轻视、恐惧,做什么都是一次性的,从不总结

15. 简单



Keep it simple. Keep it stupid.



这句话固然是对的,个人理解更重要的是“化繁为简”。业务不可能一直简单,组件库、工具不可能没有复杂的部分,重要的是有化繁为简的能力。


什么是化繁为简的能力呢,应该包括抽象能力、理解能力、总结归纳、聚类、举一反三等。


16. 耦合


什么是耦合?


程序里的耦合指的是依赖关系,比如A模块中引入了B、C、D模块的内容,就产生了耦合。


如果A、B两个项目用脚本同步一些代码,算耦合吗?当然不算,根本没有依赖关系的产生,删掉A,B一样可以运行。


JS中函数是一等公民,函数中的参数传递是耦合的最佳方式。挂载在window或文件中的全局变量上,这种耦合方式是最差的,难维护的,难以追踪的。


17. 大组件


大组件并不意味着大文件,press-ui可以提供由多个小组件构成的大组件,但不能是大文件,因为这样不灵活,如果需要变更、扩展的话,更改的东西多,容易出错。


同时,大组件意味着events会很多,props很多或者很大。


大组件在有些场景下,是有好处的。比如这个组件很多地方要用,比如横竖版、管理端都要用,那么把这个组件封装下,一个mode就可以解决多端问题,到了业务层就不用写太多重复代码了。


18. 就近原则


组件的样式应该就近写在组件附近,而不能是在page层覆盖。为什么呢,因为一旦多个page都使用这个组件,那么覆盖关系就很难追踪。而如果只有一个page使用,那么就应该写在page的附近,不必提取到组件库中。


19. 新工具


前端框架更新很快,有人可能会说华而不实,花里胡哨,okr项目,没有vue2香。


这里简单分析下用新技术的必要性。




  • 从学习角度上,一个社区流行的新框架、新工具,能让诸多项目自发迁移、升级,不是公司因为各种利益强推的,一定是做对了什么,不管是原理、方法论还是思想,都值得去学习。




  • 从生产力角度上,新框架大规模取代旧工具,一定在开发效率、性能、可维护性等方面有提升,而且一般不止一个维度。作为上层应用开发者(API工程师),更应该利用好这些工具。




  • 先发优势,一般新框架稍微稳定一点时,做有关它的生态工具会比较容易得到广泛应用。比如vue3刚稳定时,做个vue3组件库,容易推广,如果现在做,基本很难推了,该趟过的坑都趟过了,也经过了生产环境的考验,凭什么让别人用你的呢?




当然了,你永远无法叫醒一个装睡的人。


20. 人


一个工具会被写成什么样子,取决于人。


举个例子,某库迭代了一次又一次,一直没有稳定,又开始改,个人觉得基本还是白搭。因为开发者还是那一批人,他们之前写什么样的代码,之后大概也会写成什么样。


没有方法论的升级,没有任何反思,不懂抽离,不懂封装,只是换个地方,套个壳而已。


21. t-comm



  • 尽量分类,不要什么都往utils目录下放,否则utils目录会爆炸,难以寻找

    • 如果贪图一时快,都放到utils中,无疑是给自己挖坑,以后还需要重构
    • index.ts中行数,等于ll src | grep '^d'| wc -l的值-1,排除types


  • 一定在导出的第一层,进行导出文件的指定,第二层、第三层等后面的导出要用*

    • 第一层不能用*,否则一些方法不想导出,也被导出了
    • 后面不能指定,因为前面指定过了,后面再指定就重复了,而且改很麻烦,容易遗漏



// base/function/index.ts
export {
parseFunction,
cached,
} from './function';


// base/index.ts
export * from './function';
export * from './list';
export * from './number';
export * from './object';
export * from './string';


// index.ts
export * from './base';


  • 按照文件的ASCII顺序,也就是文件/文件夹的默认顺序,来导出文件,这样容易对比,不容易遗漏

export * from './base';
export * from './canvas';
export * from './clipboard';
export * from './color';
export * from './cookie';
export * from './cron';

22. 理解


有时候我们能把事情做成什么样,取决于我们对它的理解。


你如果把微信当成抖音做,大概做不好。苹果就有一个口号,“Think Different”,这是它的格局,也是他们对自己产品的理解。


做组件库、基础库,甚至是普通需求也要有自己的理解。


作者:Novlan1
来源:juejin.cn/post/7277798325637070889

0 个评论

要回复文章请先登录注册