当一个程序员从产品角度思考技术
近期做需求中有一些感想,在这里记录下。
1. 产品究竟是谁的
人人都是产品经理,并不是一句口号,每个人都是用户,即便你是研发、设计、测试,都有无数的时间,和世界上最优秀的产品打交道。最优秀的产品包括你的电脑、手机,和那些最流行的APP。
所以,如果某个产品经理一言堂,那这个产品大概率不会太成功。除非他真的厉害,并且有数据支撑,比如乔布斯这种。产品不是政治,产品是可以讨论的。
如果一些交互设计难以理解,研发都用着费劲,怎么在普通人中推广呢?
另一个问题,研发需要了解并思考产品的逻辑吗?
答案肯定是需要的,产品是有逻辑的,研发可以根据产品的逻辑,判断、预测未来的变化,抽出稳定性更强的代码。
一个工作3年和工作10年的研发的一个区别就在于,对于产品逻辑的理解,大概工作更久的人更容易预测产品未来的变化,会在代码编写、结构设计时就拆分出不变与变化,从而让代码更易维护。但是,如果每次新来一个需求,都从0开始,从不思考,就另当别论了。
2. 定制与通用
通用和定制往往是对立的,一个需求如果是定制的,意味着不够通用。
前一阵做了一个国际化的需求,国际化其实就代表通用,本地化代表定制。
但是如果同一类型定制的多了,就可以变为通用需求。本质上一个string/number/boolean
类型总是可以扩展成array
的。
比如H5的自动化测试报告处理,和小程序的处理,是可以通用化的。
这给我们一个启示是,要多沉淀东西,做过的那么多需求,比如表单系统、权限系统,这些工作即使一两年内不会重复,但是时间拉长来,总会在职业生涯的某个阶段重新做一次。
还有,我们加班那么多,是不是产品设计、需求提出、技术实现的时候,通用性考虑的就不足呢,所以今天在A上拧一个螺丝,明天在B上拧一个螺丝。
可怕的是,这两个螺丝都是一样的残次品。
通用性在不同的层次上有不同的表现,比如总监可以考虑业务的通用,产品经理考虑产品的通用,开发则考虑代码和系统层面的。
组件化的基础也是通用性,如果一个组件可以被复用,说明它是通用的。
3. 复用性
应当努力追求高复用,不论是组件,还是工具,为什么呢?
- 从零实现成本高
- 复制粘贴然后改一点,看似成本也不高,后续维护、迭代更新则成本高
实践证明,组件、工具的迭代更新的频率,一般是高于认知的。即便你当下觉得它很稳定了、很完美了,也很有可能在未来某个时候优化,因为人的认识总是有局限性的。
组件高复用,可以统一升级、统一优化、统一迭代。不同维度的组织都应该追求高复用,包括个人、项目、团队。
4. 字段名
发现一个现象,大家在字段命名的时候比较洒脱。
- 格式比较多样,比如角色Id:
roleId
、role_id
、roleid
,子活动Id:childid
、child_id
、childId
。 - 名称也多样,比如手机号:
phone
、mobile
,头像:head
、header
。
大家好像习惯了这种乱七八糟的命名,倘若统一了,反而觉得难受。
在其他地方没得到的自由,终于在命名时得到了。
这种字段命名问题和上面的通用化有些关系,如果不能统一,谈何通用呢,通用化的基础一定是标准化。
为什么大家会在命名时候,这么随意呢?大概率是因为他根本没考虑过下游开发者的感受,比如前端。
下游开发者做起来是很痛苦的,他知道这个协议会包含子活动Id,但是他必须去协议页面查看,才能知道究竟是哪种命名,是childid
、还是childId
、还是child_id
?并且,他在向他的下游或下级页面、组件传递时,需要考虑对方需要的是什么,必须做一层转换。如果他不知道转换或者不想转换,让下游直接修改字段名,那整个项目就更“精彩”了。
意识也是一种能力。能够预见到一些可能带来的问题,并尽量规避掉,也是一种好习惯。
5. 代码是资产还是负债
总看到有人说,代码是负债,个人不敢苟同,倘若代码都是负债,你还在这敲锤子啊,岂不是越欠越多,回家睡大觉不是更好?
大部分人之所以认为代码是负债,是因为维护起来费劲,一看代码就头疼,每次改代码都像是上刀山,这能不是负债吗。
或者改别人写的代码,小心翼翼、胆战心惊、如履薄冰,生怕解决一个问题,带来其他问题。
所以,个人认为,质量高的代码是资产,写的像X一样的是负债。
“大处着眼,小处着手”,架构、系统相关可以视为“大处”,代码规范、代码质量可以视为“小处”。
最近看到公共基础库很多很多的魔法字符串,开发没有意识去抽离,维护这样的代码就比较头疼了。
好的代码一般是模块化,抽象化,通用化,配置化。失控的代码,没有模块化,没有整理,没有规律,没法维护,没法复用。没有解耦,维护起来相当麻烦。
甚至有人说,“单元测试有什么用?“
6. 兜底
有一个需求,A类型的奖品不需要展示,B类型的奖品可以展示,后台让前端“兜底”,过滤下这个类型的奖品。
想到这个只是产品的上层逻辑,是极容易变化的,今天屏蔽A、明天屏蔽B,后天可能又要放开A、B。
兜底应该是对边界的兼容,比如:
- 多重
if else
中最后的else
- 对空值给默认值
- 对解析失败的捕获
- 对不同机型、不同环境的兼容性
像这种产品上的逻辑怎么会是兜底呢?这种过滤其实是数据层面的处理,越在上游处理越简单,因为用数据的地方总是无法控制的。
这其实反映出另一个问题,前后台责任不明确,这次你帮我多处理一下,下次我帮你多处理一下。本来后台该做的事,放到前端来做,下一次,本来前端该兼容的,让后台来做。
前端应该始终兼容空值边界,比如a.b
,当a
不存在时候的取值异常。组件、页面总是可能复用的,如果下个prop
或者cgi
没传,就要付出额外的时间成本处理错误。
前端兼容性大多是为了系统鲁棒性。
此外,处理数据的逻辑应该放在一起,如果这里增加一点字段、那里改变一下结构,又没有类型提示的话,后期维护起来很难受。
前端当变量为undefined
或null
时,会报错的语法或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。
如果不提供过渡版本,一般会导致开发者不敢升级,比如vue2
和vue3
就没有做到平滑过度,导致现在很多项目都是vue2
。
8. 变化
代码维护、迭代过程中最重要的事情之一就是控制变化,应该始终将变化的部分做到可控。
其实现代社会就很符合这个规范,电网、能源、航空、铁路等,这些核心产业都被国家握在手里,其他一些小打小闹的产业,比如餐饮、互联网等,随私企去折腾。
对于大型项目,核心模块,比如基础UI组件库、网络框架、核心逻辑,都应该以一种稳定的形式存在,比如npm包、基础库,不能今天改、明天又改。
为什么函数粒度要细呢,也就是常说的一个函数只完成一个功能?其实本质也是控制了变化,如果一个函数同时完成多个功能,那么改动的可能性就更高,就更容易出错。
稳定性是我们一直追求的目标,一般来说,我们更喜欢发挥稳定的球员,而不是“神经刀”。
代码也是一样,控制变化,其实就是保持稳定性。
为什么要追求稳定呢?本质上是变化的成本太高了,越复杂、越底层的组件、工具,改动风险越高,因为复用的地方多,很可能牵一发而动全身。即使做到了高内聚、低耦合,如果改动不是向下兼容的,上层就要一起更新,会增加时间成本、出错概率。
稳定并不意味着一成不变、一定没有变化,比如一个组件库,内部的优化可以一直做,只是对外的API、展现的形式需要稳定,也就是与之前保持一致。
9. 写文章
写文章会耽误工作吗?就个人经验来说,写文章不但不会影响工作,反而会提升效率。
因为写文章一定是因为有自己的思考才写,不论是解决了问题、还是总结了方法,都是有或多或少的思考,大脑一定是活跃的。
没写文章的时候,看似工作时间投入更多,其实脑子已经不转了,没有自己的理解,没有总结思考,工作效率其实非常低。
这就好像,那些成绩好的同学其他方面也有特长,比如体育、文艺,反而是那些成绩差的才干啥啥不行。
为什么会这样呢?因为思考这个东西是相通的,好多东西底层是一样的,会了一样就可以触类旁通、举一反三。
10. 低代码
最近人工智能生成代码的方式很火,更新了人们对“低代码”的认知。低代码、零代码的本质是为了提升开发效率,提高生产力,只要能达到此目的的都是低代码。
因此,广义的低代码有下面几种:
- 从手上已有的其他项目中复制粘贴,比如之前有了一个项目表格,现在要做一个操作记录表格,直接复制粘贴然后改改即可。
- 从搜索引擎中搜索,然后复制搜到的内容
- 从一些可视化界面,拖拽组件而成,即目前人们常说的低代码平台
- 人工智能生成
哪种会成为未来的主流呢?衡量标准有下面几个:
- 实现成本低,狭义的低代码平台需要搭建,而且越复杂的项目搭建成本越高
- 实现效果好,开发者改动少,并且接近需求
从上面的标准来看,个人目前看好AI。
11. Mixin
有许多人很排斥使用Mixin
,弃之如敝屣,其实只是使用姿势不对而已。
- 一个
Mixin
文件中的内容应该高内聚,需符合单一职责原则,围绕一个中心,不能做两件事。 - 尽量减少使用全局
Mixin
,多用单文件引入。
只要做到上面两点,Mixin
就可以化为高复用的利器。
12. 小而美
“小”的优点:
- 易于理解和学习。如果你想要写出全世界都是用的程序,那这一点很重要,无论是大牛还是小白,都能轻松是用,才能推广开来。
- 易于维护。即便是自己写的代码,过半年自己都忘记当时写的是什么了,要考虑这一点。
- 消耗更少的资源。“小”到制作一件事,用多少就消耗多少,不做一点额外的开销和浪费。
- 更易于和其他工具结合。即可扩展性更好,符合开放封闭原则。
让每个程序只做好一件事(单一职责原则),这个和准则1(小即是美)表达的意思一致。
只做好一件事,说明足够小。越是大型的系统,这个原则越重要,否则越大就越乱。
书中列举了一个范例 —— ls
命令。ls
本来是很简单的一个命令,现在却搞的有 20 多个参数,而且正在逐步增加。这就使得ls
慢慢变成一个很庞大的命令,但我们日常 90% 的场景都使用它最简单的功能。理想的做法是,ls
还保持简洁的功能,另外开发新的命令来满足其他配置参数实现的功能。这就例如,cat
可查看全部内容,想看头或者尾,分别使用head
和tail
——这就分的清晰了。
某BG就是典型的小而美,灵活。
13. 沉淀
沉淀了多少组件、公共方法,这才是业务开发中重要的。
沉淀是分等级的。项目越大,项目分层越多,沉淀等级越多。底层的更纯粹,应当尽可能沉到底层。以最近维护的几个项目为例:
t-comm > uni-plugin-light > press-ui
组件库由于必须依赖框架,所以不能和JS/TS工具一样可以沉到最底层。
一句话总结就是,能沉到多底就沉到多底。
14. 研效
很多人都在提研效,PPT里、总结邮件里,但其实并没懂研效的本质,个人看来,研效关键点有两个:
- 是否有足够多自动化的工具,比如CI、配置中心、自动化测试、自动同步工具、任务调度平台
- 是否沉淀了足够多的组件,新需求来了后,可以调用现成的能力,需求做完了后,又沉淀了另一些能力
如何判断一个部门、团队、个人是否对研效敏感呢?只要看他对上面两个方向的重视程度就行了。
堆人力,轻研效的特点是:
- 对工具轻视、恐惧,喜欢最原始的方式,喜欢刀耕火种
- 对沉淀轻视、恐惧,做什么都是一次性的,从不总结
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
- 尽量分类,不要什么都往
util
s目录下放,否则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”,这是它的格局,也是他们对自己产品的理解。
做组件库、基础库,甚至是普通需求也要有自己的理解。
来源:juejin.cn/post/7277798325637070889