中国物流与采购联合会数据 2024年冷链物流需求总量同比增长4.3%
随着消费需求的持续释放,我国冷链物流行业在2024年展现出了蓬勃的发展态势。作为冷链物流行业的领军企业,河南华鼎冷链仓配科技有限公司(简称“华鼎冷链科技”)凭借其强大的服务网络和数字化能力,在推动行业发展中发挥了重要作用。
据中国物流与采购联合会冷链物流专业委员会的最新数据显示,2024年我国冷链物流需求总量达到了3.65亿吨,同比增长4.3%。这一增长不仅体现了消费者对冷链物流服务的旺盛需求,也彰显了冷链物流行业在国民经济中的重要地位。同时,从物流总收入来看,2024年全年冷链物流总收入为5361亿元,同比增长3.7%,进一步证明了冷链物流行业的经济价值和市场潜力。
作为锅圈实业(上海)有限公司的全资子公司,华鼎冷链科技自2019年创立以来,便以“中国的Sysco”为定位,依托国内领先的大数据、物联网技术,为餐饮连锁、食材工厂、商贸客户提供高效协同的数字化冷链供应链解决方案。
截至目前,华鼎冷链科技在全国已设立25个分支机构,23个省级区域中心仓,仓储总面积达到40万平方米。其干支线网络更是遍布全国,共计3157条线路,辐射全国除西藏和港澳台以外的全部省区,覆盖290个地级市、2245个县。这一庞大的服务网络为华鼎冷链科技提供了强大的物流配送能力,也为客户提供了更加便捷、高效的服务体验。
在数字化方面,华鼎冷链科技自主研发的SAAS系统已经成为公司服务客户的重要工具。通过这一系统,华鼎冷链科技已经连接了连锁餐饮、冻品工厂、商贸客户等2500余家合作伙伴,服务餐饮终端门店超过20万家。这一数字化的平台不仅提高了物流效率,还为客户提供了更加透明、可追溯的服务体验,实现了供应链各环节的高效协同。
除了数字化转型,华鼎冷链科技还积极响应国家环保政策,致力于绿色节能技术的研发和应用。在冷链物流设施方面,公司不断进行节能改造,提升设施的能效水平;在运输方面,华鼎冷链科技加大了对新能源冷藏车的投入力度,不断提升车队的绿色化水平。这些举措不仅有助于降低运营成本,还有助于减少环境污染,实现可持续发展。
消费者对冷链物流服务的需求不断提升和国家对环保政策的持续加强,冷链物流行业将迎来更加广阔的发展前景。华鼎冷链科技将继续秉承“创新、协同、高效、绿色”的发展理念,不断拓展服务网络,提升数字化和绿色化水平,为更多客户提供更加优质的冷链供应链解决方案。
收起阅读 »不是,哥们,谁教你这样处理生产问题的?
你好呀,我是歪歪。
最近遇到一个生产问题,我负责的一个服务触发了内存使用率预警,收到预警的时候我去看了内存使用率已经到了 80%,看了一眼 GC 又发现还没有触发 FullGC,一次都没有。
基于这个现象,当时推测有两种可能,一种是内存溢出,一种是内存泄漏。
好,假设现在是面试,面试官目前就给了这点信息,他问你到底是溢出还是泄漏,你怎么回答?
在回答之前,我们得现明确啥是溢出,啥情况又是泄漏。
- 内存溢出(OutOfMemoryError):内存溢出指的是程序请求的内存超出了 JVM 当前允许的最大内存容量。当 JVM 试图为一个对象分配内存时,如果当前可用的堆内存不足以满足需求,就会抛出 java.lang.OutOfMemoryError 异常。这通常是因为堆空间太小或者由于某些原因导致堆空间被占满。
- 内存泄漏 (Memory Leak):内存泄漏是指不再使用的内存空间没有被释放,导致这部分内存无法再次被使用。虽然内存泄漏不会立即导致程序崩溃,但它会逐渐消耗可用内存,最终可能导致内存溢出。
虽然都与内存相关,但它们发生的时机和影响有所不同。内存溢出通常发生在程序运行时,当数据结构的大小超过预设限制时,常见的情况是你要分配一个大对象,比如一次从数据中查到了过多的数据。
而内存泄漏和“过多”关系不大,是一个细水长流的过程,一次内存泄漏的影响可能微乎其微,但随着时间推移,多次内存泄漏累积起来,最终可能导致内存溢出。
概念就是这个概念,这两个玩意经常被大家搞混,所以多嘴提一下。
概念明确了,回到最开始这个问题,你怎么回答?
你回答不了。
因为这些信息太不完整了,所以你回答不了。
面试的时候面试官就喜欢出这种全是错误选项的题目来迷惑你,摸摸你的底子到底怎么样。
首先,为什么不能判断,是因为前面说了:一次 FullGC 都没有。
虽然现在内存使用率已经到 80% 了,万一一次 FullGC 之后,内存使用率又下去了呢,说明程序没有任何问题。
如果没有下去,说明大概率是内存溢出了,需要去代码里面找哪里分配了大对象了。
那如果下去了,能说明一定没有内存泄漏吗?
也不能,因为前面又说了:内存泄漏是一个细水长流的过程。
关于内存溢出,如果监控手段齐全到位的话,你就记住左边这个走势图:
一个缓慢的持续上升的内存趋势图, 最后疯狂触发 GC,但是并没有内存被回收,最后程序直接崩掉。
内存泄漏,一眼定真假。
这个图来自我去年写的这篇文章:《虽然是我遇到的一个棘手的生产问题,但是我写出来之后,就是你的了。》
里面就是描述了一个内存泄漏的问题,通过分析 Dump 文件的方式,最终成功定位到泄漏点,修复代码。
一个不论多么复杂的内存泄漏问题,处理起来都是有方法论的。
不过就是 Dump 文件分析、工具的使用以及足够的耐心和些许的运气罢了。
所以我不打算赘述这些东西了,我想要分享的是我这次是怎么对应文章开始说的内存预警的。
我的处理方式就是:重启服务。
是的,常规来说都是会保留现场,然后重启服务。但是我的处理方式是:直接执行重启服务的预案。没有后续动作了。
我当时脑子里面的考虑大概是这样的。
首先,这个服务是一个边缘服务,它所承载的数据量不多,其业务已经超过一年多没有新增,存量数据正在慢慢的消亡。代码近一两年没啥改动,只有一些升级 jar 包,日志埋点这类的横向改造。
其次,我看了一下这个服务已经有超过四个月没有重启过了,这期间没有任何突发流量,每天处理的数据呈递减趋势,内存走势确实是一个缓慢上升的过程,我初步怀疑是有内存泄漏。
然后,这个服务是我从别的团队那边接手的一个服务,基于前一点,业务正在消亡这个因素,我也只是知道大概的功能,并不知道内部的细节,所以由于对系统的熟悉度不够,如果要定位问题,会较为困难。
最后,基于公司制度,虽然我知道应该怎么去排查问题,命令和工具我都会使用,但是我作为开发人员是没有权限使用运维人员的各类排查工具和排查命令的,所以如果要定位问题,我必须请求协调一个运维同事帮忙。
于是,在心里默默的盘算了一下投入产出比,我决定直接重启服务,不去定位问题。
按照目前的频率,程序正常运行四五个月后可能会触发内存预警,那么大不了就每隔三个月重启一次服务嘛,重启一次只需要 30s。一年按照重启 4 次算,也就是才 2 分钟。
这个业务我们就算它要五年后才彻底消亡,那么也就才 10 分钟而已。
如果我要去定位到底是不是内存泄露,到底在哪儿泄露的,结合我对于系统的熟悉程度和公司必须有的流程,这一波时间消耗,少说点,加起来得三五个工作日吧。
10 分钟和三五个工作日,这投入产出比,该选哪个,一目了然了吧?
我分享这个事情的目的,其实就是想说明我在这个事情上领悟到的一个点:在工作中,你遇到的问题,不是每一个都必须被解决的,也可以选择绕过问题,只要最终结果是好的就行。
如果我们抛开其他因素,只是从程序员的本职工作来看,那么遇到诸如内存泄漏的问题的时候,就是应该去定位问题、解决问题。
但是在职场中,其实还需要结合实际情况,进行分析。
什么是实际情况呢?
我前面列出来的那个“首先,其次,然后,最后”,就是我这个问题在技术之外的实际情况。
这些实际情况,让我决定不用去定位这个问题。
这也不是逃避问题,这是权衡利弊之后的最佳选择。
同样是一天的时间,我可以去定位这个“重启就能解决”的问题,也可以去做其他的更有价值事情,敲一些业务价值更大的代码。
这个是需要去权衡的,一个重要的衡量标准就是前面说的:投入产出比。
关于“不是所有的问题都必须被解决的,也可以选择绕过问题”这个事情,我再给你举一个我遇到的真实的例子。
几年前,我们团队遇到一个问题,我们使用的 RPC 框架是 Dubbo,有几个核心服务在投产期间滚动发布的时候,流量老是弄不干净,导致服务已经下线了,上游系统还在调用。
当时安排我去调研一下解决方案。
其实这就是一个优雅下线的问题,但是当时资历尚浅,我认真研究了一段时间,确实没研究出问题的根本解决方案。
后来我们给出的解决方案就是做一个容错机制,如果投产期间有因为流量不干净的问题导致请求处理失败的,我们把这些数据记录下来,然后等到投产完成后再进行重发。
没有解决根本问题,选择绕过了问题,但是从最终结果上看,问题是被解决了。
再后来,我们搭建了双中心。投产之前,A,B 中心都有流量,每次投产的时候,先把所有流量从 A 中心切到 B 中心去,在 A 中心没有任何流量的情况下,进行服务投产。B 中心反之。
这样,从投产流程上就规避了“流量老是弄不干净”的问题,因为投产的时候对应的服务已经没有在途流量了,不需要考虑优雅的问题了,从而规避了优雅下线的问题。
问题还是没有被解决,但是问题被彻底绕过。
最后,再举一个我在知乎上看到的一个回答,和我想要表达的观点,有异曲同工之妙:
http://www.zhihu.com/question/63…
这个回答下面的评论也很有意思,有兴趣的可以去翻一下,我截取两个我觉得有意思的:
在职场上,甚至在生活中,一个虽然没有解决方案但是可以被绕过的问题,我认为不是问题。
但是这个也得分情况,不是所有问题都能绕开的,假如是一个关键服务,那肯定不能置之不理,硬着头皮也得上。
关键是,我在职场上和生活中遇到过好多人,遇到问题的时候,似乎只会硬着头皮往上冲。
只会硬着头皮往上冲和知道什么时候应该硬着头皮往上冲,是两种截然不同的职场阶段。
所以有时候,遇到问题的时候,不要硬上,也让头皮休息一下,看看能不能绕过去。
来源:juejin.cn/post/7417842116506058771
慎重!小公司到底该不该自己封装组件库?
前端开发与组件库
注:全文所说的小公司特指:资源不足、技术能力不足的团队
在一些小公司的项目开发中,我们可能常常听到这样的想法:
- 我们公司的需求比较特殊,用现有的组件库不够灵活,不如自己封装一套!
- 现有的组件库样式不符合我们的产品需求,我们需要统一风格和功能,不如自己开发一套组件库吧!
以前我会很天真的支持这样的想法,现在,我会给提出者一个大嘴巴子
!看似高瞻远瞩,实则全是陷阱,甚至可能成为整个团队的噩梦。
一个loading组件引起的生产事故
我先讲一个我们公司因为组件库导致的财产损失生产事故!
之前,我们业务有实现过一个表格,由于接口非常快,我们并没有增加loading
样式。
代码实现也非常简单:
<template>
<section class="table-section">
<m-table :columns="columns" :data-source="tableData">
</m-table>
</section>
</template>
<script setup>
const tableData = ref([]);
const columns = []
const getTableData = async () => {
// ...接口调用逻辑
queryManageList()
};
// 获取表格数据
getTableData();
onMounted(() => {
// 动态设置表头数据
columns = []
});
</script>
m-table
是我们公司的内部表格组件,上面的代码在生产稳定运行。随着数据的增多,接口有些慢了,于是客户希望加个loading。
我们公司的Loading组件模仿自Elemnet Plus,api的调用也非常相似
参考文档,代码的更改也就非常容易
<template>
<section class="table-section">
<m-table :columns="columns" :data-source="tableData">
</m-table>
</section>
</template>
<script setup>
const tableData = ref([]);
const columns = []
const getTableData = async () => {
loadingInstance = Loading('.table-section');
// ...接口调用逻辑
await queryManageList()
loadingInstance.destroy
};
// 获取表格数据
getTableData();
onMounted(() => {
// 动态设置表头数据
columns = []
});
</script>
代码看着严丝合缝,十分完美,然而,部署生产后,发现columns直接没了!
经过线上排查,发现loadingInstance = Loading('.table-section')
这段代码直接替换了section
标签内部的所有dom元素,造成表格的vue实例出现异常,onMounted
钩子根本没有执行!
反观Element PLUS,人家直接是在section标签下生成的遮罩层,就不会存在这个问题!
小公司开发的组件,由于开发者技术参差不齐,很容易出现线上问题啊!这种问题在我们的日常开发中非常常见,害人啊!
为什么小公司不要轻易封装组件库
通过上面的案例,可以看出:小公司的开发人员技术参差不齐,组件库的质量也就无法得到保证。
当然,技术还不是主要原因,毕竟技术是可以提升的,但下面的几个问题才是真要命的!
资源不足:人力和时间的双重消耗
封装组件库并非单纯的开发任务,它需要大量的人力和时间投入。对小公司而言,团队往往规模有限,开发资源紧张。
- 开发人员:为了封装一个组件库,原本负责业务开发的人员必须抽出精力进行组件封装工作,业务开发的进度被迫拖延。
- 时间成本:开发一个组件库不仅仅是写几个按钮或者表单,还涉及到设计体系、文档编写、单元测试、性能优化和浏览器兼容性处理等,这是一项长期工程。
就拿我们公司举例,我们一遍要写业务代码,一遍要维护组件,非常消耗时间!
就这,公司还不断地给我们加任务,把我们当牛马,直接开启996
!
加班费没有我们就忍了,996一次不够,还梅开二度
!
业务开发都没时间,还维护组件库,这不是自己坑自己么? 小公司没钱没实力,再别开发组件库了,来来回回坑自己人!
维护成本高:一时造轮子,一世修轮子
自己封装组件库容易,但长期维护它却很困难。随着项目的迭代和需求的变化,组件库也需要不断更新和优化。
- 需求增加: 业务需求多样化导致组件库功能膨胀,原本简单的组件变得复杂不堪。
- Bug 修复: 自己封装的组件库缺乏大规模使用的验证,隐藏的 Bug 往往在上线后爆发,修复工作耗费大量时间。
- 兼容性问题: 浏览器兼容、新技术支持(如 Vue 3、React 18)的适配工作更是让人头疼。
我们的组件库更新非常频繁,因为随着产品的迭代,要增加很多新功能。有时候,为了使用组件的新功能或者样式,我们不得不升级组件版本。然而,有一次升级后,组件库内部存在严重bug,导致我们原有的许多界面崩溃,造成了重大生产事故!
这种组件升级导致的页面问题时常发生,加了一个功能,导致一个隐藏bug,后续为了解决这个隐藏bug,又引入其他bug,最终一个小功能要发好几个组件版本才能解决。我已经无力吐槽,害人啊!
而且,由于组件的功能不完善,经常要花费非常多的沟通成本
技术负债:短期便利,长期拖累
自建组件库在开发初期可能感觉很“顺手”,但随着项目规模扩大,组件库的缺陷会逐渐显现,成为团队的技术负债。
- 缺乏标准化: 自建组件库的规范不够完善,不同开发者在实现同一功能时可能写出风格完全不同的代码。
- 文档不足: 由于时间和人力限制,自建组件库的文档往往不完善,后期新成员加入时难以上手。
- 升级困难: 自建组件库的每次升级都可能影响到现有业务,增加维护和测试成本。
员工离职风险:组件库成孤岛
小公司人员流动较为频繁。
如果负责组件库开发的员工离职,组件库很可能会变成“孤岛”,无人维护,直接影响到项目的可持续性。
经济形式不好,我们公司近几年也裁了不少员,导致一些组件直接没人维护了,直接影响项目进度。
所以,资金、时间不充足,咱小厂还是别学大厂维护组件库了,要钱没钱,要时间没时间,来来会会坑的都是自己!
总结
封装组件库对小公司来说是一个高风险、高成本、低收益的选择。本人建议如下:
- 优先选择成熟的开源组件库: 如 Ant Design、Element Plus 等,它们功能完善且生态丰富,能够快速适配业务需求!
- 定制而非重造: 如果开源组件库无法完全满足需求,可以在其基础上进行二次封装(各位leader注意看,组件库基本都支持样式定制的!) ,而不是从零开始构建。
- 聚焦业务: 小公司开发团队的首要任务是满足业务需求,组件库的开发应该是锦上添花,而非拖慢进度的负担。
各位Leader注意,技术的最终目的是为业务服务!别为了凸显自己牛逼,强行开发维护一个组件库,最终只会害人害,搬起石头砸自己的脚!
小公司不要让组件库拖住脚步,把资源投入到更有价值的地方,才是发展的正确道路。
注:以上所有言论只针对公司的产品、项目而言!对于个人,你怎么玩都行,玩的越花越好,毕竟以后出去都是面试的资本!
啥也不是,散会!
来源:juejin.cn/post/7440850542585266227
我为什么选择成为程序员?
前言:
我选择成为程序员不是兴趣所在,也不是为了职业发展,全是生活所迫!
第一章:那年,我双手插兜,对外面的世界一无所知
时间回到2009年,时间过得真快啊,一下就是15年前的事情了,15年前我刚小学毕业,在此之前我从未接触过电脑,电脑对于我来说是个遥远的“物件”,从未用过电脑,也从来没想过电脑会给我的生活带来什么改变,还记得初中第一次去电脑室上电脑课的时候,我连电脑如何开机都不知道,班上条件好的同学,或者那些“不乖”的同学会去网吧玩电脑游戏,可能了解一点电脑,对于我们这种家里条件不好的,相对也比较“乖”的同学来说几乎没去过网吧,电脑可能也就是出现在书本里的知识,第一次去上电脑课的那种兴奋以及到了电脑室以后连怎么开机都不会的尴尬,现在都还历历在目!
我对上网没什么兴趣,主要是老师和父母反对,自己也没什么钱,唯一能接触到电脑的时候就是上电脑课的时候学一下怎么打字,我学会玩的第一款游戏叫做“蜘蛛纸牌”。
在我的印象里,那时候的电脑还是用的window xp系统,那是我对电脑的最初印象。
老家的紫外线强的让人想死,我的父母每天顶着烈日劳作,皮肤被晒的黝黑,三十岁的他们却苍老得像五十岁一样,但是一年下来却挣不到几个钱,开学前是最难熬的,因为父母经常会为了凑足我的费用而不断争吵,那时候的梦想很纯粹,能正常上学,能多有一点零花钱,能吃饱饭,以后长大了不用再下地干活(我真的很讨厌晒太阳),电脑这种遥远的东西,我真的没有想过我能拥有它!介于当时的生长环境,怎么也想不到以后的我会和程序员这个行当有任何的交集。
第二章:命运的齿轮开始转动
整个初中能接触到电脑的机会不多,到了初二和初三电脑课也被停了,老师给的解释是“这东西不考试,我们要把精力花在刀刃上!”
所以我也全身心投入到文化课中,这时候对我来说也没什么影响,因为我本身对电脑也"不感兴趣",中考结束以后我的成绩还算理想,考上了市里一所还算可以的高中,学校的配套设施很完善,老师讲课都使用电脑,课间休息的时候经常使用,电脑看篮球比赛,放假的时候别人都回家了,我们就几个同学留在学校,一起用电脑看电影,有什么问题就会用电脑去网上搜一下,我们的数学老师天天用电脑玩"三国杀"这款卡牌游戏。
我们也会经常用电脑下载音乐,电影之类的东西!我离电脑的距离也越来越近了,但是那时候电脑对于我来说只是一个娱乐工具,并不知道他能给我带来任何的经济价值,那时候的我打字还是用一根手指,这个习惯一直保留到我上大学接触到英雄联盟这款游戏!这里先按下不表,后面我再说这个事。
第三章:歪打正着之面向薪资选专业
不要和穷人谈理想,因为穷人已经尝了很多生活的苦,不习惯理想这么甜的东西,会让他们水土不服!
时间来到2014年的七月,那时的我刚经历了一场失败的高考,感觉人生非常的暗淡,我想到复读,可是父母没有什么文化,他们听了很多,“本来考上了,复读一年没考上”的故事,死活不让我复读,因为他们觉得我已经考上一本了,已经很好了,复读反对,再加上自己家里如此贫困,我也只能选择去报志愿,我选志愿的策论很简单,就两个:
1.那些学校是一本学校且招的人数多
2.哪些专业毕业以后收入高!
上网查了一圈后很多人都说通信工程和网络工程毕业以后薪酬待遇相对较高,我也不知道是真是假,我直接把所有学校的所有拍在全面的专业都写成了通信工程和网络工程!我上大学了,并且上了通信工程专业,这是我新的开始,从此以后我将会经常以电脑为伴!
第四章:划水的大学生涯
大学下学期我开始接触编程,我接触了第一门编程《C语言程序设计》,
这时候我也有了自己的个人电脑,大部分时间电脑还是被用在娱乐上,偶尔也会用电脑做写实验课的作业,外加一些电脑的基础课是需要考试的,这时候我几乎每天都要花时间去接触电脑,很多的电脑软件安装了卸载,卸载了又重新安装,也有一些电脑软件虽然安装了但是一直没用过。“差生文具多”,这句话绝对有他的道理!
不管你承不承认,农村的孩子和城市的孩子始终是有差距的,比如在对电脑这一点就很有差距,城里的孩子接触的早,所以长大了学习电脑方面的知识也更快,农村的孩子起步比较晚,所以我到了大学学编程就会感到”水土不服“!学习编程我感觉是很痛苦的,很多时候我学不懂,导致我更没什么兴趣了,所以我的大学生活学习基本上都是在划水,基本上都是60分飘过,或者给老师发短信,求老师让我过,编程课基本都是靠背,硬是把编程学成了语文,操啊!这种痛估计只有学渣才能理解得了!
但是这个时候我入坑了英雄联盟,这款游戏好玩,但是也很气人,我经常会在游戏中和别人对喷,每次和别人对喷我都因为自己一个手指打字,被别人骂的气死,为了和别人对骂不落于下风,我改掉了自己一个手指打字的习惯,慢慢的,我打字速度也变快了,从此别人要是骂我,我就骂死他,成了真正的键盘侠!(当时我一般不会主动骂人,也不会在网络上和别人对线,只在游戏里和别人对线)
第五章:放弃吧,少年
可是我也知道,日子不能这样过呀,像我这样的孩子,我能靠谁呢,反正编程是学不进去了,那就想别的法子吧,于是我就尝试别的出路,我去做过很多兼职,去图书馆当图书管理员,去发传单,去来着三轮车给别人送水,也自己做过一些小生意,当学生开学的时候我们室友就会去进货,搞一些新生用品来卖,赚点生活费,同时也是为了锻炼自己的能力,印象最深的是,大二的暑假,那个暑假我没有回家,我做了一份有挑战的兼职,你可以理解为销售,个人对商家销售一些产品,然后我们再从公司赚取佣金,没有底薪,没有提成,只有佣金,我搞了一个假期,没卖出一份产品,每顿都吃一块三一碗的热干面,我真的吃吐了,后面我看到热干面我都干呕!
第六章:少年,回头是岸
这个暑假的经历,让我不得不思考,我是否真的适合做销售这份工作,编程虽然难,但是学好了确是一辈子的手艺,古人云:“天旱饿不死手艺人”,于是我又硬着头皮开始学,可是学什么呢?那时候Java比较流行,而我又刚好有一门课就是Java,刚考完没多久,还有一些印象,于是我就每天学习,误打误撞的终于在毕业的时候找到了一份软件开发的工作我,我也顺利成为了一名Java程序员!
终章:回望过去,全是无奈
我不是一个有梦想的人,也不是一个很有规划的人,成为一名程序员既不是我的兴趣爱好,也是不起球为了职责发展,只是在贫瘠的环境中,让我时刻需要为了自己生计发愁,为明天的饱和饥而担忧,如果可以的话,我也愿意一双拖鞋,一条短裤,几串钥匙,每逢月底收收租,躺在沙发上,喝着绿茶,摇着蒲扇,好不快活!然后告诉我的朋友们,真羡慕你们能够出门闯荡,长见识,我在家可太无聊了,所以我喝了好多绿茶!
来源:juejin.cn/post/7356485240804606006
人类发财指北
·人永远不可能通过出卖时间来致富,不管你是医生还是律师,一个小时几百美金还是几千人民币,这是一个简单的算术问题,人一天只有24小时,你拿计算器把下半辈子所有可以卖的时间都算上,依然达不到「富」。
·出卖时间有很多种不同变体,比如个体户/自由职业者虽然摆脱了为雇佣者上供剩余价值的命运,但是自身承担市场风险与接案的波动,工作流无法自动化,本质上还是出卖个人时间,时间从哪里来,牺牲健康牺牲睡眠。
·不管你是什么title、在什么有名的地方工作、福利有多少、西装有多贵,只要还有别人来告诉你你需要在什么时间出现在什么地点,你就还是一个奴隶,如果你已经是大老板,每天必须参加数个会议,那你就成了自身事业的奴隶。
·开会毫无意义
·钱其实就是大风刮来的,对于大部分自立的成功人士来说,「变富」过程本身就是几年光景,只是事先耕耘了许多年,但是刮到哪也不会刮到上班族那里,因为就算刮到了,上班族也没有时间弯腰捡钱,因为上班族的时间已经为了每月的固定收入,已经卖光了。对一个佃农来说,天上掉钱你也没法弯腰捡,因为你在弯腰刨地。
·人类脱离金本位已经很久了,「钱」只是一个符号,今天的钱拿回原始人社会毫无价值,这个符号只在共同相信的人群中有意义,不存在独立于「人类社会网络」的钱,钱是一种人与人关系的总合,财富是个人对社会其他个体所「做功」的标记。
·人类整个经济在走向虚拟化,并且从很早以前就开始了,曾经人们买DVD不是为了那张光碟本身,是为了上面的内容,而现在随着互联网的发展,「内容」的发行投放已经变得无比方便和正常。
·任何从零开始发家致富的过程总结下来都是「为社会其他成员提供想要但一直没能得到的产品」的过程,这总是和人的欲望高度相关,所有受欢迎的产品都是某种欲望的载体。你满足他人的欲望,让他人感觉良好,他人就会回馈你。
·世界上几乎没有任何事情是线性的,在所有的领域都存在「效应放大器」,在某一个阶段/时机/领域做一倍的工作,会有百倍的回报,真正重要的东西总是很少的。如果还没有找到某一个「效应放大器」,有一个,每个人都有,那就是「把时间放在重要的事上」,刷短视频十分钟和运动十分钟同样是十分钟,但效用是完全不一样的。
·赛博时代会逆转工业时代所留下的很多印记,比如8小时工作制,集体劳动,打卡,一日三餐,企业雇佣制,以及养老金。工业时代需要抹杀个体的特色和想法,投入集体劳动,积累剩余,赛博时代则会解放个体特色,或者说个体特色本身将会是盈利的关键。人很难被标准化的冷冰冰工业品感动,但人会被另一个人类感动。
·人们会需要工作更多年,因为人类的寿命延长了,青年、中年、老年每一个阶段都延长了,可能性也延长了,这一代的人类大概要活到110岁,职业的转换也会变得更频繁。
·赛博时代的赛道是无限的,因为「内容」和「产品」的界限模糊了,而内容可以是无限的,只要加一点新花样,就又是新的了。
·财富不是「零和游戏」,意即如果你需要赚一笔钱,那必须坑害他人这一笔钱,这是金本位农耕时代生产力上限固定后的思维。
·人口其实是影响发财最大的要素,因为财富本身其实等同于其他社会个体的一种投票行为,财富的创造和成立都只是一个在人与人之间成立的游戏,而人口本身不复存在,就意味着音乐没有听众、电影没有观众、明星没有粉丝、产品没有消费者、培训没有学生,没有了人本身市场就不复存在了,一切将自然凋零。
·地球上的一切资源都来自太阳,植物、动物、煤炭都是太阳能的不同固定形式,太阳一天对地球的照射相当于5085亿吨煤完全充分燃烧的能量,这些能量可以供全球70多亿人口使用24年。
·人类的财富总量在增加的最好证据是:肥胖人数的增加。因为人体的脂肪也是一种太阳能,能量从太阳到动植物,再到人体内。肥胖人口增加说明出现了总体剩余。
·「经济上行」和「经济下行」都不是一种客观的现实,它只是一种集体幻觉,因为经济运作依赖于「预期」,而「预期」其实只是人的一种心理感受。从这个角度来说,其实就是悲观的人越多,越下行。乐观的人越多,越上行。一切只是一种心情而已。
一切只是一种心情而已。
来源:juejin.cn/post/7362078214381600806
写给我前端同事,从事一年多应该要怎么成长的路线
写给我前端同事,从事一年多前端应该要怎么成长的路线
我知道在很多中大型公司,其实有好多领导前辈、以及师傅会给那些校招生
,以及应届生
规划一定的学习成长路线,可能还会有定期的大佬分享。有这么一个领路人、环境在其实成长是飞速的。
我入职了一家新单位,这家单位的没有太多规范,没有太多的组件封装积累,还会考核每周的代码量
,我发现有些阶段代码量(测试阶段、需求阶段等)不够的时候大家都是往项目中塞没用的代码,还有些同学会复制公共组件的代码进自己的模块充代码,技术栈使用的是vue3 + js + ant-design-vue
,一大部分人做的都是项目
。
苏洋同学
(化名)工作了2年左右,换过2家公司,第一家是个小公司也是在写一些后台管理的功能,这是他的第二家公司入职这家公司也有一年左右时间了。他逻辑,工作态度都没问题,也算是个积极上进的零零后好青年。他当前项目中的代码开发没问题,项目中多是一些业务表单,工作流之类的东西,有用户权限控制。有的时候他会请教我一些问题我俩也就从问题聊了聊技术,并且我把我的博客分享给了他,他说他有空会看看学习下。
我跟他从我的博客文章中聊了下他可能需要怎么做来获得成长,当然现在这个环境下我不能跟他说你多学一点可能工资
就会高一些,假如在3年前我一定会告诉他,你学完这些其实可以换个公司去试试一定会比你现在的要高一点。可能学习完假如被迫换工作(裁员)了,机会会大点吧
大佬请绕路,我可能给的建议并不是最好的,但是我觉得对他来说现阶段是最使用的
我知道在很多中大型公司,其实有好多领导前辈、以及师傅会给那些
校招生
,以及应届生
规划一定的学习成长路线,可能还会有定期的大佬分享。有这么一个领路人、环境在其实成长是飞速的。
我入职了一家新单位,这家单位的没有太多规范,没有太多的组件封装积累,还会考核每周的代码量
,我发现有些阶段代码量(测试阶段、需求阶段等)不够的时候大家都是往项目中塞没用的代码,还有些同学会复制公共组件的代码进自己的模块充代码,技术栈使用的是vue3 + js + ant-design-vue
,一大部分人做的都是项目
。
苏洋同学
(化名)工作了2年左右,换过2家公司,第一家是个小公司也是在写一些后台管理的功能,这是他的第二家公司入职这家公司也有一年左右时间了。他逻辑,工作态度都没问题,也算是个积极上进的零零后好青年。他当前项目中的代码开发没问题,项目中多是一些业务表单,工作流之类的东西,有用户权限控制。有的时候他会请教我一些问题我俩也就从问题聊了聊技术,并且我把我的博客分享给了他,他说他有空会看看学习下。
我跟他从我的博客文章中聊了下他可能需要怎么做来获得成长,当然现在这个环境下我不能跟他说你多学一点可能工资
就会高一些,假如在3年前我一定会告诉他,你学完这些其实可以换个公司去试试一定会比你现在的要高一点。可能学习完假如被迫换工作(裁员)了,机会会大点吧
大佬请绕路,我可能给的建议并不是最好的,但是我觉得对他来说现阶段是最使用的
针对他的成长经历规划
他的js
基础可能没那么好,像一些数据处理上是有些问题,那么我建议他:
- 重新学习下
js
针对数组
,字符串
等API,像字符串的cancat、includes、indexOf、lastIndexOf、endsWith、startsWith
等等,像数组的forEach、map、filter、reduce、find、findIndex、some、every
等等,他说他有些好像没有使用过。 学习了解用法,并且写一样的源码。例如:
'123'.endsWith('3'); // true
export const _endsWith = (source: string, target: string) => {
const len = target.length;
const substrValue = source.substr(source.length - len, len);
return substrValue === target;
};
_endsWith('123456', '56'); // true
- 对
堆
和栈
要有一定的理解,对深拷贝、浅拷贝有一定的理解。 - 对
宏任务
和微任务
以及事件执行的理解。 - 对
防抖
和节流
有一定的理解 - 对
this
有一定的理解并写出apply
、call
、bind
的实现。 - 对类型判断
instanceof
、typeof
、Object.prototype.toString.call
等方法有理解。 - 对对象方法的使用
Object.keys、Object.values、Object.entries、Object.assign
等等
- 去看下
lodash
的源码,例如:throttle、debounce、cloneDeep、groupBy、get、difference
等等一些常用的方法函数要会写源码,最好自己写一遍。
- 对正则表达式能对有一定的理解,并且写出一些常用的正则。
CSS
中对主题适配能有一定的理解,例如使用 less
和 Scss
变量做主题适配,以及使用2套样式,或者使用css全局变量做主题适配。能区分出这几种的不同点
如果能把js
的以上都能掌握了,那么你的基础算是扎实的了,差的可能就是工作经验以及深入了解一些框架的源码了。
- 这个时候可以学习下代码规范了,其实
vue
的话可以看看element ui
组件代码的规范,组件的设计以及源码具体。至少能实现message
组件以及按钮组件
- 学习下设计模式,例如:
单例模式
、策略模式
、代理模式
、发布订阅模式
等等。 - 可以多看看怎么写防御式编程,让你的代码更加健壮(这也就是为啥项目中bug多的问题,代码写的还不够严谨)
- 可以去学习下
TS
,可能不用去特别做类型体操,基本的泛型能用,例如:Array
、Record
、Partial
、Pick
、Omit
、Exclude
、Extract
等等。 - 如果你对
vue
和react
想深入研究,可以对比着使用看看它们之前的差异,有自己的认识。 webpack
的配置,对打包优化有理解,对loader和plugin有理解,对打包工具有使用过。- 了解下
npm
,对npm 发布等命令有一定的理解,并且尝试自己发布一个包。 - 对
git
提交规范有理解,并且使用。可以深入了解下git规范定义以及拦截。 - 对
nginx
有一定的了解,并且使用。因为现在好多项目都是多页应用了,nginx就显得很重要了。 echarts
是图表库,可以学习下他的那些简单图表怎么使用canvas
画出来的。
恭喜,假如你上面都能学会,我觉得你很了不起了,至少算是中级前端工程师
。
- 制定公司代码规范
eslint
, git
提交规范等等 git CI
制定工作流是很重要的,可以学习下。- ...
- ...
- ...
他的
js
基础可能没那么好,像一些数据处理上是有些问题,那么我建议他:
- 重新学习下
js
针对数组
,字符串
等API,像字符串的cancat、includes、indexOf、lastIndexOf、endsWith、startsWith
等等,像数组的forEach、map、filter、reduce、find、findIndex、some、every
等等,他说他有些好像没有使用过。 学习了解用法,并且写一样的源码。例如:
'123'.endsWith('3'); // true
export const _endsWith = (source: string, target: string) => {
const len = target.length;
const substrValue = source.substr(source.length - len, len);
return substrValue === target;
};
_endsWith('123456', '56'); // true
堆
和栈
要有一定的理解,对深拷贝、浅拷贝有一定的理解。宏任务
和微任务
以及事件执行的理解。防抖
和节流
有一定的理解this
有一定的理解并写出apply
、call
、bind
的实现。instanceof
、typeof
、Object.prototype.toString.call
等方法有理解。Object.keys、Object.values、Object.entries、Object.assign
等等lodash
的源码,例如:throttle、debounce、cloneDeep、groupBy、get、difference
等等一些常用的方法函数要会写源码,最好自己写一遍。CSS
中对主题适配能有一定的理解,例如使用 less
和 Scss
变量做主题适配,以及使用2套样式,或者使用css全局变量做主题适配。能区分出这几种的不同点如果能把js
的以上都能掌握了,那么你的基础算是扎实的了,差的可能就是工作经验以及深入了解一些框架的源码了。
vue
的话可以看看element ui
组件代码的规范,组件的设计以及源码具体。至少能实现message
组件以及按钮组件
单例模式
、策略模式
、代理模式
、发布订阅模式
等等。TS
,可能不用去特别做类型体操,基本的泛型能用,例如:Array
、Record
、Partial
、Pick
、Omit
、Exclude
、Extract
等等。vue
和react
想深入研究,可以对比着使用看看它们之前的差异,有自己的认识。webpack
的配置,对打包优化有理解,对loader和plugin有理解,对打包工具有使用过。npm
,对npm 发布等命令有一定的理解,并且尝试自己发布一个包。git
提交规范有理解,并且使用。可以深入了解下git规范定义以及拦截。nginx
有一定的了解,并且使用。因为现在好多项目都是多页应用了,nginx就显得很重要了。echarts
是图表库,可以学习下他的那些简单图表怎么使用canvas
画出来的。恭喜,假如你上面都能学会,我觉得你很了不起了,至少算是中级前端工程师
。
eslint
, git
提交规范等等git CI
制定工作流是很重要的,可以学习下。结语
其实如果从事这个行业,可以把上面当作一个学习清单,然后按照顺序学习,这些都是必须且要学会的。然后把学习到的东西都记录下来。多总结,多思考,
作者:三原
来源:juejin.cn/post/7448899248475684899
其实如果从事这个行业,可以把上面当作一个学习清单,然后按照顺序学习,这些都是必须且要学会的。然后把学习到的东西都记录下来。多总结,多思考,
来源:juejin.cn/post/7448899248475684899
DeepMind天才科学家疑抑郁自杀!41岁SuperGLUE之父英年早逝,AI圈悲痛不已
就在刚刚,一个令人悲伤的消息传来。
谷歌 DeepMind 研究科学家 Felix Hill,于 2024 年 12 月 5 日英年早逝,年仅 41 岁。
自 2023 年初以来,他一直在与严重的精神疾病作斗争。期间,他表现出了重度抑郁和严重的自杀倾向,终于还是没有扛过去。
Felix 是一位学术成果颇丰的 AI 学者,谷歌总引用量为 19680,参与创建了自然语言理解基准 GLUE 和 SuperGLUE。
Felix Hill 本科在牛津大学学习数学,随后在剑桥大学拿到了语言学硕士学位,和计算语言学博士学位。
毕业后,他曾有 8 个月在高中担任数学老师,随后进入谷歌 DeepMind,当了将近 8 年的 AI 研究者。
消息传来,他的 AI 圈好友无不表示悲痛和难过。
1 月 11 日,Felix 的葬礼将于伦敦北部举行,届时将进行现场直播。
许多认识他的人悲痛地留言说:Felix 是一个很特别的人,超前于这个时代。
左右滑动查看
最令人心碎的博客
是什么样的原因,让这样一位成果丰硕的 AI 学者的生命逝去?
他生前的一篇博客,揭露了许多细节。
他详细描述了 2023 年母亲离世后,正在精神病院接受治疗的自己症状更严重了。接下来的 12 个月内,他更是陷入极度焦虑和自杀的抑郁状态。
他也写道,自己当初投身 AI 研究并不是为了赚钱,但 AI 大爆发后,自己仿佛被迫进入「战争」状态,写论文、搞研究、创业,都令人压力重重,找不到出路。
即使积累了大量财富,自己也依然出了问题。
英伟达高级研究者 Jim Fan 读完悲痛地表示:这是我读过最令人心碎的博客,因为它如此真实,如此贴近内心。
Jim Fan 表示,AI 不应该是 200B 权重的压力和痛苦。
曾经,这是一个充满咖啡因带来的灵光乍现的地方,是令人兴奋的深夜 arxiv 探索之旅,是能让研究者脸上露出笑容的绝妙想法。但所有涌入的资本和关注,似乎正在迫使每个人竞相逐底。
黄仁勋经常对员工们强调,不要用「打败这个,碾压那个」的措辞。因为大家的目的是为了提升整个生态,而不是让任何人陷入深渊。AI 学者的工作是做大蛋糕,越大越好,然后再分配。
AI 不是零和博弈,事实上,它可能是人类有史以来拥有的最正和的博弈。大家应该做的,是向竞争对手传递爱意。
Jim Fan 写道,虽然并未有幸在现实生活中认识 Felix,但自己很喜欢他的研究品味,为他的每一篇新论文都设置了 Google Scholar 提醒。他在 AI 智能体和 VLM 方面的工作也对自己影响很深。
「他本应该成为一个很好的朋友。我想认识他,但现在已经不可能了。」
「安息吧,Felix。愿来世你不需要去战斗。」
2000 亿权重的责任:现代 AI 工作中的压力
下面这篇博客中,Felix 详细回顾了 AI 爆火的几年,给自己的生活带来的剧变——
过去两年,AI 领域发生了不可逆转的变化。
ChatGPT 的月活数接近 2 亿人。Gemini 在 2024 年 5 月的访问量接近 3.2 亿次,AI 爱好者现在可以使用 AI 微波炉、AI 牙刷,甚至 AI 足球。
然而,对于我们这些在 AI 领域工作的人来说,这种大众兴趣的激增既是福也是祸。
诚然,薪资水平上涨了,股价和市值也随之提高。但另一方面,这种变化也带来了独特的压力。
这篇博客讲述的是现代 AI 领域的压力。它面向所有在 AI 领域工作的人(按保守估计,现在全球人口中大约有 87% 的人在从事 AI 相关工作),特别是那些从事 AI 研究的人。
最终,我希望通过讨论 AI 研究中的压力源,让我们这些有幸在该领域工作的人生活得更快乐一些。
因为,尽管目前一片混乱,这仍然是一个美妙而充实的职业——它有潜力解决科学、哲学乃至人类本身的诸多重大问题。
无处可逃
几个月前,我参加了一个朋友的 40 岁生日派对。在那些我不太熟悉的人中,我注意到一个奇怪的现象。
尽管我那时身体不适,而且明显不太想讲话,但我周围还是形成了一个包围圈,原因仅仅是,大家知道我在 DeepMind 上班。
而且,他们想聊的不是足球或 80 年代音乐,恰恰是我最想避免思考的主题——AI。
虽然很感激这么多人对我的工作感兴趣,但这也提醒我过去两年发生了多大的变化。
银行家、律师、医生和管理顾问都想听听我对 ChatGPT 的看法;虽然很少有人声称在工作中直接使用了 LLM,但他们确信,AI 领域正在发生一些他们应该了解的事情。
作为一名研究人员,我相信你也能体会到这种在社交场合无法放松的感觉。
但情况更糟。就连在自己家里,我也找不到安宁。
我早已不再看新闻,因为害怕引发焦虑。但即使是在看足球、VH1、蒙塔尔巴诺探长,或者那部出色的《那不勒斯四部曲》改编剧时,广告中也充斥着与 AI 相关的内容。
在这段时间,我时常幻想着收拾行李,跨越大洲去加入一个隐居群体。但很可能连内观禅修(Vipassana)也被 AI 渗透了,这不会让我惊讶。
无形的竞争
几家大公司竞相开发最大、最好的 LLM,这一事实本身就令人压力重重——无论你为哪家公司工作。
现在从事 AI 研究,感觉就像参与一场战争。希特勒和达奇 · 舒尔茨的例子告诉我们,参战可能导致精神病态、离婚和自杀等严重后果。
当然,我并不是要把参与 AI 研究等同于参与真实战争,但我的亲身经历却表明,这种类比是很真实的。
关乎底线的工作
通常,业界的研究人员并不习惯于自己的工作对雇主的底线产生直接且即时的影响。
当然,许多研究人员都梦想能够产生这样的影响。只是以前,这种机会可能是十年难遇。
如今,对 LLM 基础研究的结果,往往只会对模型性能产生微小、短期的波动。然而,由于公司估值与 LLM 性能(难以分割地?)挂钩,这些波动可能导致股价出现数十亿美元的起伏。
这种动态令人倍感压力,而且这也不是 AI 研究人员在研究生阶段、博士后期间,甚至在 2022 年之前的工作中所能预料到的。
**钱,**钱,还是钱
大多数 AI 研究人员,尤其是我们这些超过某个年龄的人,当初投身研究并不是为了赚钱。
做自己热爱的工作还能获得丰厚报酬听起来是个完美方案,但这也可能引发强烈的焦虑。特别是当推动你收入增长的外部因素不在你的控制范围内,且 / 或者这些因素让你对工作的热爱程度大不如前时。
无论 AI 是否与此有关,突然积累财富可能会导致各种问题,看看那些经过多年努力终于成名的演员或歌手就知道了。成瘾、感情破裂、友谊破碎,甚至自杀只是一些较为常见的症状。
这些症状,我确实都感同身受。
科学家角色缺失
LLM 的规模、简单性和有效性使得做出「相关」的「科学研究」变得困难,这里的相关指的是能立即改进 LLM。
领先的 LLM 研究人员已经开始认同 Rich Sutton 的「苦涩教训」:除了规模之外,几乎不需要任何创新。
而且,即使理论上可能存在实质性创新,实现它往往需要在不同条件下反复训练最大规模的 LLM。这甚至连最大的公司都负担不起。
对于一个「普通」的研究科学家来说,这感觉简直令人绝望。
对于习惯于在 5 至 10 人的小团队中工作的工业界科学家来说,这些已经很艰难。但学术界的人所遭遇的无疑更加严峻,比如那些博士生、博士后和 AI/CS / 机器学习领域的教职人员。
发表论文
虽然学术界的人可以(也应该)继续发表从 LLM 实验中获得的见解,但对于工业界的科学家来说,发表论文的问题就没那么明确了。
发表论文一直是科学过程的内在组成部分,也一直是 AI 研究的核心原则。我接触过的大多数 AI 研究人员,特别是研究科学家,都同意我的观点:发表论文是我们职业生涯的关键。
但是,至少在工业界,过去 2 年来,研究成果是否能够发表的问题变得越来越不明确。能够改进 LLM 的小技巧可能等同于 LLM 之战中的关键武器。将这些秘密公开是否对资助研究的组织有利,这始终是一个需要深思熟虑的问题。
这一切都意味着,研究人员经常对自己想法的前途毫无把握,至少,这会对我造成巨大的压力。
创业公司
当然,摆脱这些困扰的一个可行出路,就是规划科研方向,筹集资金并成立创业公司。事实上,目前 AI 创业公司(无论大小)的激增表明有多少科学家选择了这条路。
但成为创始人并不能必然地规避相关压力。
众所周知,创业的压力也很大;即使在当前投资者热情高涨的情况下,许多资金充足的 AI 创业公司仍然失败了。
我知道,成为创始人是一段特别孤独的旅程。这无疑是当下雄心勃勃的科学家们的一个可行选择,但这既不会让做研究变得容易,也不会减轻压力。
为什么要写关于压力的博客
过去两年在 AI 领域可谓混乱而疯狂,而对我个人而言,这更是一段特别动荡的时期。
2023 年 4 月,我的母亲在与阿尔茨海默症长期抗争后离世。那时的我正在精神病院接受治疗,因为出现了严重的精神症状,其中压力很可能是重要诱因。
在接下来的 12 个月里,表面上我是在康复中,但实际上却陷入了极度焦虑和自杀倾向的抑郁状态。
所幸在这期间,我遇到了非常理解我的处境(以及认可我对公司的价值)的雇主,他们一直为我提供治疗和精神上的支持。
经过另外 6 个月的重度抑郁之后,我的状况开始好转,最近也感觉自己有能力写下这些经历。
我深刻地认识到压力和焦虑是密不可分的;事实上,它们本质上可能是同一件事。诚然,像任何适应性特征一样,焦虑有时也能带来积极影响(比如提高生产力),但一旦焦虑变得失控,后果可能相当严重。
正是在尝试重新学习如何成为一名 AI 研究员的过程中,回顾 AI 领域这两年的发展,让我获得了这篇博客中所分享的见解。
诚然,仅仅分享这些见解并不能从根本上解决问题,但在最艰难的时期,能给我带来希望的少数事情之一就是意识到我并不是一个人在战斗。
如果你现在也在经历类似的困扰,请记住——你并不孤单。
社交焦虑
我已经讨论了当前从事 AI 研究的人可能遭受的诸多压力或焦虑的诱因。
然而,还有一种压力我尚未提及,这是因为我很幸运从未亲身经历过。我对它的了解,完全来自于与朋友和同事的深入交谈。
这种压力就是社交焦虑。
据朋友们反映,那些有社交焦虑的人往往会觉得群体互动充满挑战。在现代 AI 领域,这是一个格外严峻的挑战,因为大型项目团队和大规模的(通常是跨洲际的)协作已成为必需。
目前行业中的高流动率更是雪上加霜,因为已建立的团队(通常作为社交「安全网」)可能在一夜之间解散瓦解。
人员流动还可能引发信任危机,因为曾经可靠的伙伴可能会转投竞争对手的研究团队。
值得欣慰的是,社交焦虑和我此前讨论过的所有焦虑或压力表现一样,都是可以克服的。克服的第一步是培养以家人和「非 AI 圈」朋友为主的自然支持系统。
而关键的第二步,则是我们所有从事 AI 工作的人,都要开始并持续保持关于压力的坦诚对话。
因此,诚挚邀请你在社交媒体上分享自己的经历和感受。让我们携手努力,不仅将 AI 研究打造成一个充满活力和智力挑战的领域,更要使其成为一个充满同理心和善意的温暖家园。
AI 大佬发长文缅怀
Contextual AI 首席执行官 Douwe Kiela 发文表示,我真的很难过,我亲爱的朋友 Felix Hill 离开了我们。他在世界各地有很多朋友和同事。为了让更多人知道,他的家人希望我们分享这个网页,一起纪念他的一生:
EPFL 教授,前 DeepMind 研究科学家 Caglar Gulcehre 表示,听到 Felix 离开我们的消息,真是令人心碎!
他回忆道,「我第一次见到 Felix 是在蒙特利尔读博二的时候。那时候,我正经历抑郁症,头两年的生活很难熬。我搬到一个新国家,再加上冰天雪地的天气,让我感到特别不适应。
Felix 总是充满活力,乐观开朗。但有时候,你很难知道别人生活中正在经历什么。所以,对他人多一份理解,不要轻易下结论很重要。或许他们可能正经历着不为人知的困境。可惜的是,很多人仍然低估了心理健康的重要性。
寻求帮助并不是软弱的表现。刚到 DeepMind 工作的头两年,我的父亲突发心脏病,后来还失明了;我的姐姐也被诊断出癌症。那时候,我又搬到了另一个国家。如果不是寻求了专业帮助,我不知道自己该如何应对这一切。
无论何时需要帮助,都要勇敢去寻求。我很感激身边有许多支持我的人,在困难时期给予了我很大的帮助。如果心理状态不佳,很难在工作上取得成功。向 Felix 家人致以深切的慰问」。
DeepMind 研究科学家 Andrew Lampinen 发文怀念其这位曾经指导过自己的导师——
Felix Hill 是一位非常出色的导师——偶尔也是我的冬泳伙伴。我能加入 DeepMind,以及形成如今做研究的方式,很大程度上都要归功于他。都过去一个月了,我还是觉得难以相信他已经离开了。
Felix 在选择研究方向上有着超强的眼光,而且直觉特别准。每当遇到新想法,他总会表现出孩子般的热情和好奇心,还特别幽默,能够与他一起工作真的让人备受启发。
他也特别看重跟他共事的每个人。在我刚到 DeepMind 的时候,他和 Jane Wang 一起把一群超棒的人聚在了一起,比如 Stephanie Chan、Aaditya Singh、Allison Tam,还有其他很多朋友和合作伙伴。
不过他也经历过一段艰难时期,尤其是最近这几年。我最后一次和他聊天是在他离世前大概一个月,那时他跟我分享了一个雄心勃勃又有点疯狂的项目想法,让我仿佛看到了他当年的影子。但那会儿我太忙了,一直没有再跟进,直到现在为时已晚,至今这件事让我特别后悔。
纽约大学计算机科学和数据科学教授 Kyunghyun Cho 更是写了一篇长文缅怀逝者——「再见了,Felix」。
文章地址:kyunghyuncho.me/bye-felix/
这段文字写于 2024 年 12 月 9 日,但当时我既不愿意也无法接受所发生的事实,所以一直没有勇气发布。即便到现在,每当想起这件事,我的心仍然隐隐作痛。我选择在 2024 年的最后一天发布这段文字,以此缅怀 Felix。
时间回溯到 2014 年初夏。我当时在蒙特利尔大学担任 Yoshua Bengio 教授的博士后研究员,而 Felix 是一位刚刚抵达蒙特利尔的访问学生。
那时,我正在致力于开发一个能够处理长句子的神经机器翻译系统(Neural Machine Translation,NMT),为此我尝试了所有能想到的方法(注意力机制当时并不在其中,直到同年夏天 Dima Bahdanau 作为实习生来到蒙特利尔才有了突破)。
在这些探索性尝试中,我构思了使用门控卷积编码器(Gated Convolutional Encoder)来替代基于循环神经网络(RNN)的编码器。通过对门控机制施加适当的约束,我成功训练出了这个模型,并使其具备了一定的可解释性。
当 Felix 来到我的办公桌前,以语言学家和计算机科学家的身份做自我介绍时,我很兴奋地想要向他展示这个新模型所揭示的可解释结构。
于是我向他展示了:
Felix 以极其笃定的语气对我说,「Kyunghyun,语法并不重要」。这大概是我听过非韩国人中最标准的名字发音。
那一刻我就预感到,Felix 一定会成为我最好的朋友之一——事实证明,从那时起直到现在确实如此)。而他说的这句话,也在往后几年里频频出现在我的学术演讲 slides 中。
在我们共度的那些愉快时光中,除了进行深入却不失趣味的哲学探讨,我们也开展了一些研究合作。
除了共同发现的诸多有趣成果外,我们最具标志性的「贡献」反而是一个相当特别的现象:从 2016 年开始,我们无意中引领了一股持续 3-5 年的潮流,就是人人都仿佛着了魔似的要在论文中塞入一个装满海量数字的「超级大表格」(The Really Enormous Table)。
当时我们在写「Learning distributed representations of sentences from unlabelled data」这篇论文时,完全没想那么多,但最终还是放入了两个巨大的数据表格:
2018 年初,Felix 在 ICLR 的论文集中发现了好几篇包含「超级大表格」的论文,这让我们不禁哑然失笑。
即使在严谨的学术研究中,我们依然保持着这种愉快的合作方式。
时光飞逝,将近十年后,Felix 作为 2023 年拉美人工智能会议 Khipu 的组织者之一,邀请我担任演讲嘉宾。他兴致勃勃地向我描绘我们将要进行的活动:观看足球比赛,游览布宜诺斯艾利斯等等。
然而,当我在 2023 年 3 月抵达蒙得维的亚参加 Khipu 时,却发现 Felix 并未到场。其他组织者告诉我,他因为健康问题无法前来。那时的我还不曾想到,这竟是我最后一次有机会与他相见。
Felix 在 2023 年第二届 Khipu 上未领取的参会正件
2024 年 6 月,经过漫长岁月,我终于有机会造访伦敦,便给 Felix 发消息约他共进午餐。尽管我清楚见面的可能性渺茫,但我真的非常期待能与他相聚、畅谈,一起消磨时光。
我们上次见面还是在疫情之前,之后就只能靠偶尔的远程视频联系。我也暗自期待能看到他康复的喜人变化。
几个月后(2024 年 8 月),Felix 回复了消息,并为迟复致歉,这实在让人心疼。他还附上了一张我们的合影(不是 AI 生成的,但是 Felix「生成」的),唤起了我们上次在伦敦相聚的美好回忆。
左图:这是一张由 Felix 本人「生成」的与我的合影;右图:这是一张真实拍摄的 Felix 与我的合影
上周五,我收到了 Douwe 发来的 WhatsApp 消息。正是由于 Felix 在 2014 年的引荐,我才认识了 Douwe,并与他建立了深厚的友谊。我立即尝试联系 Felix,但不管是 WhatsApp 还是手机,都已经无法接通。
Felix,愿你现在已不再痛苦,在天国与母亲团聚。
参考资料:
http://www.paperlesspost.com/go/7BbrzXXh…
来源:juejin.cn/post/7455518848273498124
2024:踏平坎坷成大道,斗罢艰险又出发!
一、开篇
12月今年最后一个月了,相逢的人已走散,Q4的OKR已经定型了,很平淡无味、闲的无聊,提前写个年终总结吧。25年,再过一个月就35岁了,一个人来北京也已经11年了。年近末尾,思绪良多。回顾过去来看,这一年还真的经历了很多的事情,我的生活也发生了翻天覆地的变化!时光走笔,岁月成章,书写一本名为《我》的彩色童话,刻画属于自己的千种情绪、万般色彩。如果用一句话概括下过去的一年,那就是:力学笃行倍道而进,从不缺少挑战的勇气;脚步坚实步伐坚定,从未停止奔跑的脚步。
- 历经苦难,方知生命可贵!
- 承受困难,方懂世事艰辛!
- 无惧苦难,方能勇往直前!
二、历历在目,回首成长之路
2.1 天地风尘三尺剑,江湖岁月一诗篇
这个世界,人有万算,但却不如老天一算,做人要坦坦荡荡,做事要问心无愧。
回顾2024年,经历了非常多的大事情,有幸这一年全家人平平安安,在稳步前进。算是折腾的一年,刚开年之初,就喜得医院三日游,还记得那晚独自一人站在窗口,望着远处的万家灯火。依然不记得当时在想什么,只记得呆呆的站了一晚。也许过去的23年命里跟小人犯冲,又喜得公司裁员大礼包。原本想新年之后在重新找工作的,可麻绳专挑细处断,厄运只找苦命人,3月又被迫到医院“营业”,好在“苍天有眼见可怜,善恶有报分两岸”,还是顺利从手术台上走下来。医院是离生死最近的地方,再次面对时,本觉得可以洒脱些,然而,不然!真的真的是再也不想去医院了!
这一年,有过迷茫和无助,更多的是家里人带来的幸福和开心,有家人们的支持,也有了新的目标。也许是大病一场后,很多东西彻底的看清了、放下了。最大的问题是,今年颓废了,丢掉了自己的早起习惯,开始习惯性的熬夜娱乐,总是晚起床。有时候,我也会羡慕当初年少的自己,那时的自己,敢爱敢恨、敢打敢杀,更加不顾后果。而现在的自己呢,懂得控制自己的情绪,学会了顾全大局,也是能屈能伸。但这样的自己,也有弊端,隐藏了太多情绪,同时也控制了太多感情,活的也不如当初那般潇洒了。可没办法,成长,是人的必经之路,尤其是男人。成熟的男人,不能一味追求潇洒。他可以不已天下人的安危为己任,但至少要保障身边人的安危。为求生计赴他乡,今朝重温儿时梦,却已青丝染白霜。沧海桑田,世事变迁,回不去了,终究还是回不去啊……
我单枪匹马的走到现在,任何人都不是我的靠山。苦,我吃了;委屈,我咽了……伤痕累累走到现在,流言蜚语又能奈我何!再穷,我也没有骗过朋友;再苦,我也没有坑过身边人;再难,我也没有算计过谁……其实,我更喜欢好多年前的自己,他比我有胆量,比我遗憾少,比我懂得少,比我相信的很多……
2.2 书海遨游,陶冶情操
读书百遍,其义自见。 ——《三国志》
如今,手机已成为我们生活和工作的必需品,仿佛离开了它,就失去了飞翔的翅膀。我们依赖手机获取信息、沟通联络、娱乐消遣,几乎每一刻都离不开这个小小的屏幕。然而,这份过度依赖,却让我们的心灵逐渐陷入了一片荒芜之地。风沙漫天,孤寂如影随形,我们急需一股神秘而强大的力量,在这片荒芜中播撒希望的种子,使之绿意盎然,生机勃勃。这股力量,便是阅读。
在这个快节奏的时代,我们或许会因为忙碌而忽略阅读的价值。但请铭记,无论生活多么喧嚣,都要为自己的心灵保留一片净土,用来播种那些能够滋养我们灵魂的书籍。我每天抽出半小时的时间,远离手机、电视等电子产品的干扰,静下心来读一本书。当沉浸在书海中时,我发现那些曾经困扰我们的烦恼逐渐消散,内心变得宁静而充实。让我暂时忘却现实的痛苦,在无形中给予我力量,教会我如何在困境中寻找出路,如何在绝望中看到希望的曙光。
那啥,虽然 24 年阅读的书籍着实不多,大部分是闲书,惭愧惭愧,25 年要加油了,
三、我的程序人生
3.1 缘起性空,归来不少年
有所选择,有所放弃,做到“尽人事,听天命”就好了。
岁月匆匆流逝,回顾这一年最大的变化就是心态也有所起伏,整体有点躺平了,研究技术的动力也消失了,职业发展上感觉有点停滞不前了,也不太能跟生机勃勃的年轻人一起卷了。还记得从医院刚回来的那几周,整天在家里拉着窗帘,除了吃饭就是躺在床上刷手机,让我尽可能分散注意力,减少内心的痛苦。但是这样的状态也不是事儿啊,那只能去找个工作先干着了。活下来,是我目前的首要任务。于是在网上海投了一遍,结果惨不忍睹,根本没几家公司招人,前前后后两个月,真正靠谱的面试就那么几家。好在等到7月底顺利拿了offer,重新踏上“牛马”的大道。薪资也没有原来的多,但是拿到offer那一刻我依然有些激动,我感觉我活下来了,不管怎样,现在能喘口气了。
现在上班已经四个多月了,新公司挺好,不加班,基本上7点前就都走了。每天就是按部就班上下班,完成老板给的任务,其他的事情也不用自己操心,终于又做起自己熟悉且擅长的事情。如今逐渐适应了新公司的节奏,也算成功融入了团队,同时和同事相处的也十分的融洽。现在所在公司很少有加班的情况,回想上家公司总是天天加班到12点,劳心劳力而最后却卸磨杀驴。经过上半年的与天搏命、下半年奋发图强,关于工作我只有几点反思:
- 长时间处于过度忙碌的状态会导致效率下降,影响工作和生活的质量,甚至可能会成为不去做很多事情的借口。过度忙碌会遏制个人成长,让你没有时间接触新东西,没有时间总结和沉淀,没有时间去做未来的规划,最终陷入成长的死循环。
- 我们之前接受到的教育基本都是从自身找问题,但我这么多年的经历其实看到,很多时候问题根本就不是自身的问题。很多事不是单靠自己就能解决的,可能你本身所处的环境就有问题,你得到的输入就是不够多,你能获取到的资源就是不够多,你就是没有得到足够的指导和支持,别人给的建议就是不适合你…… 这种情况下重点不只是把锅甩给别人,而是应该去关注外部的改变,而不是自身的不足,适时寻求外部力量的帮助。
- 和优秀的同事共事是一种幸运。这一年,确实新接触了很多的同事,有合作的很愉快的,有希望能向他学习的,也有一些不太能理解的。但总的来说同事们的职业素养还是很高的, 相处的也十分的融洽。
3.2 坚定持续,攻破重重难关
人生不可能一帆风顺,就像大海不可能风平浪静一样。在生活中还是会遇到很多的困难与挑战,但庆幸的是我都扛下来了~
春耕夏耘,秋收冬藏。回望来时路,满心皆澎湃。我个人感觉今年几乎没有太多成长,并没有对于某项技术有非常深刻的理解或者突破,连博客的阅读量和涨粉量也是靠着之前的老博文来的。今年最重要的事情之一就是在新工作上稳定下来,当然这也是工作以来最不容易的一年。在这个飞速变化的技术环境中,在程序员的世界里,写代码、修Bug、加班常常成为了日常,作为程序员,技术更新的速度是无法忽视的。
虽然已经34岁了,不能熬夜写代码了,但是我还是喜欢偶尔敲敲代码,还是期待代码运行起来的效果,排除bug之后还是会高兴,这大概就是程序人生的乐趣!前些天翻了翻前端历史,突然发现,前端的屎事💩的确特别多。前端折腾来折腾去,好在现在才略微回到正轨上。作为后端 Java 程序员的我,并不反对前端折腾,对一些清晰的方向,我们还是应该全力以赴去折腾的。单一技能已经不足以满足现代项目需求,尤其是在公司需要「全栈开发」的趋势下,拥有一定的跨领域能力是你脱颖而出的关键。而且编程领域的变化是快速且持续的,持续学习是程序员生涯中的重要部分。可以通过参加技术分享会、研讨会,阅读最新的技术书籍和博客,或者加入技术社区来保持与行业趋势同步。此外,还可以通过在线课程或认证来提升自己的技能,为未来职业生涯积累资本。
四、心怀远方,踏上逐梦之旅
既已达成目标,也绝不懈怠,做好规划,奔向下一个目标⭐
4.1 厚积薄发,突破自我
学如不及,犹恐失之。——《论语·泰伯》
程序员的职业发展并非一帆风顺,许多人会在某个阶段遇到瓶颈,感到技术不再进步或工作内容重复。如果感觉工作内容乏味且没有新的挑战,可以尝试向上级申请更多复杂的项目或转向其他技术领域发展。接受新的挑战不仅能让你重新激发兴趣,还能快速提升技能。或者,也可以考虑参与开源项目,这不仅是学习新技能的绝佳方式,还能与其他开发者互动,团队协作、沟通能力等软技能更进一步。
我自认为是一个爱学习的人,从事互联网行业以来,一直都是从事 Java 的相关工作,除此还系统学过React、JS等,浅尝即止的就不说了,每一项都投入了较多时间,在这些技术的学习高峰,估计能达到中级开发者的开发效率吧。但是随着时间的流逝,其中大部分都忘的差不多了,如果面试官问我相关问题,我大概率是答不出来的,重新去做相关事情也需要去温习才能想起,很多人可能也有类似的感觉。去年的未来展望是打脸了,不少原先的计划没有完成,还因为突发的安排打乱了原先自己的部署,不过失之东隅、收之桑榆,收获也不少,那么我对 25 年的展望如下(写委婉点怕打脸太狠):
- 「学习 Python」:未雨绸缪,只盯着一个方向很容易触及天花板,真正有能力的人应博学多才,触类旁通,我差得远呢。
- 「保持阅读」:之前买的很多书籍、专栏、小册、视频什么的不能浪费了,要看起来了(收藏从未停止,学习从未开始 🤪)。
- 「锻炼身体」:经历那几年的疫情,大家也明白 🐶 命的重要,锻炼身体要坚持下来,行远自迩/
- 「保险」:在考虑是不是要给自己上一个保险,虽然还年轻。
- 「圆满」:
4.2 向上破圈,遇见无限可能
也是因着有这样的经历,我也去尝试了很多之前完全没有尝试过的事情,真正感受到了什么叫做【脱离舒适圈】
25 年的我 35 岁了,算是半只脚已经迈过而立之年的门槛,但距离成家立业还隔着老远,父母将老未老,知己零落四散。说实话,我现在知道了,山的那边还是山,我不知道什么时候才能看到海,甚至我可能一辈子都看不到海了。确实事情有些多且似乎都很重要,罗曼·罗兰的一句话怎么说来着 「有些人二十岁就死了,等到八十岁才被埋葬」,但至少到目前为止,生活还没把我扼杀不是嘛哈哈~。
在这个飞速发展的时代,我们生活在一个个无形的圈子中。这些圈子可能是由我们的职业、兴趣、社交圈子或其他因素所定义的。每个人都有自己的舒适区,舒适是堕落的开始,而开拓则是成长的基石。只有从旧圈子进入更高的圈子,你才会进步。敢于挑战自己,才能经历成长和蜕变,领略不一样的风景。摧毁那些限制你发展的信念,敢于跳出舒适区,勇往直前,闯出属于自己的天地!
五、新的一年,一起加油
长路漫漫,奋斗不息。学会和自己和解,学会接受自己的平庸,但是依然要努力,毕竟在这个阴雨连天的环境下,没有伞的孩子只能努力奔跑。我们的目标不仅局限于眼前的山川,我们的征途是星辰大海。那是一片辽阔无垠的天地,充满了未知与挑战。乾坤未定,每个人都有成为黑马的潜力。胜负尚未分明,未来充满无限可能。
- 愿你展翅高飞,化作飞鸟翱翔,自由随风。
- 愿你扎根大地,长成参天大树,傲骨铮铮。
- 愿你扬帆起航,翻飞万丈理想,意气昂扬。
- 愿你手握星火,燃尽塞途荆棘,前路坦荡。
- 祝你每次前行都能通往心之所向,每刻驻足都能遍赏锦绣河山。行己所爱,爱己所行,所有遇见皆是最好的安排。
来源:juejin.cn/post/7453611125453864996
三行五行的 SQL 只存在于教科书和培训班
教科书中 SQL 例句通常都很简单易懂,甚至可以当英语来读,这就给人造成 SQL 简单易学的印象。
但实际上,这种三行五行的 SQL 只存在于教科书和培训班,我们在现实业务中写的 SQL 不会论行,而是以 K 计的,一条 SQL 几百行 N 层嵌套,写出 3K5K 是常事,这种 SQL,完全谈不上简单易学,对专业程序员都是恶梦。
以 K 计本身倒不是大问题,需求真地复杂时,也只能写得长,Python/Java 代码可能会更长。但 SQL 的长和其它语言的长不一样,SQL 的长常常会意味着难写难懂,而且这个难写难懂和任务复杂度不成比例。除了一些最简单情况外,稍复杂些的任务,SQL 的难度就会陡增,对程序员的智商要求很高,所以经常用作应聘考题。
这是为什么呢?
其中一个原因是我们之前讲过的,SQL 像英语而缺乏过程性,要把很多动作搅合在一句中,凭空地增大思维难度。
但是我们会发现,即使 SQL 增加了步骤化的 CTE 语法,面对稍复杂的任务时,仍然会写的非常难懂。
这是因为,SQL 的描述能力还有不少重要的缺失,这导致程序员不能按自然思维写代码,要换着方法绕。
我们通过一个简单的例子来看一下。
简化的销售业绩表 T 有三个字段:sales 销售员,product 产品,amount 销售额。我们想知道空调和电视销售额都在前 10 名的销售员名单。
这个问题并不难,可以很自然地设计出计算过程:
1.按空调销售额排序,找出前 10 名;
2.按电视销售额排序,找出前 10 名;
3.对 1、2 的结果取交集,得到我们想要的
用 CTE 语法后 SQL 可以写成这样:
with A as (select top 10 sales from T where product='AC' order by amount desc),
B as (select top 10 sales from T where product='TV' order by amount desc)
select * from A intersect B
这个句子不太短,但思路还是清晰的。
现在,我们把问题复杂化一点,改为计算所有产品销售额都在前 10 名的销售员,延用上述的思路很容易想到:
1. 列出所有产品;
2. 算出每种产品销售额的前 10 名,分别保存;
3. 针对这些前 10 名取交集;
遗憾开始出现,CTE 语法只能写出确定个数的中间结果。而我们事先不知道总共有多个产品,也就是说 WITH 子句的个数是不确定的,这就写不出来了。
好吧,换一种思路:
1.将数据按产品分组,将每组排序,计算出每组前 10 名;
2.针对这些前 10 名取交集;
这需要把第一步的分组结果保存起来,而这个中间结果是一个表,其中有个字段要存储对应的分组成员的前 10 名,也就是字段的取值将是个集合,SQL 不支持这种数据类型,还是写不出来。
我们可以再转换思路。按产品分组后,计算每个销售员在所有分组的前 10 名中出现的次数,若与产品总数相同,则表示该销售员在所有产品销售额中均在前 10 名内。
select sales from (
select sales from (
select sales, rank() over (partition by product order by amount desc ) ranking
from T ) where ranking <=10 )
group by sales having count(*)=(select count(distinct product) from T)
在窗口函数支持下,终于能写出来了。但是,这样的思路,绕不绕呢,有多少人想到并写出来呢?
前两种简单的思路无法用 SQL 实现,只能采用第三种迂回的思路。这里的原因在于 SQL 的一个重要缺失:集合化不彻底。
SQL 有集合概念,但并未把集合作为一种基础数据类型提供,不允许字段取值是集合,除了表之外也没有其它集合形式的数据类型,这使得大量集合运算在思维和书写时都非常绕。
我们刚才用了关键字 top,事实上关系代数理论中没有这个东西,这不是 SQL 的标准写法。
没有 top 如何找前 10 名呢?
大体思路是这样:找出比自己大的成员个数作为是名次,然后取出名次不超过 10 的成员
select sales from (
select A.sales sales, A.product product,
(select count(*)+1 from T
where A.product=product and A.amount<=amount) ranking
from T A )where product='AC' and ranking<=10
注意,这里的子查询没办法用 CTE 语法分步写,因为它用到了主查询中的信息作为参数。
或可以用连接来写,这样子查询倒是可以用 CTE 语法分步了:
select sales from (
select A.sales sales, A.product product, count(*)+1 ranking from T A, T B
where A.sales=B.sales and A.product=B.product and A.amount<=B.amount
gr0up by A.sales,A.product )
where product='AC' and ranking<=10
无论如何,这种东西都太绕了,专业程序员也要想一阵子,仅仅是计算了一个前 10 名。
造成这个现象的原因就是 SQL 的另一个缺失:缺乏有序支持。SQL 继承了数学上的无序集合,与次序有关的计算相当困难,而可想而知,与次序有关的计算会有多么普遍(诸如比上月、比去年同期、前 20%、排名等)。
SQL2003 标准中增加的窗口函数提供了一些与次序有关的计算能力,这在一定程度上缓解 SQL 有序计算的困难,前 10 名可以这样写:
select sales from (
select sales, rank() over (partition by product order by amount desc ) ranking
from T )
where ranking <=10
还是要用子查询。
窗口函数并没有根本改变 SQL 无序集合的基础,还是会有许多有序运算难以解决。比如我们经常用来举例的,计算一支股票最长连续上涨了多少天:
select max(ContinuousDays) from (
select count(*) ContinuousDays from (
select sum(UpDownTag) over (order by TradeDate) NoRisingDays from (
select TradeDate,case when Price>lag(price) over ( order by TradeDate) then 0 else 1 end UpDownTag from Stock ))
group by NoRisingDays )
自然思维是这样,按日期排序后开始计数,碰到涨了就加 1,跌了就清 0,看计数器最大计到几。但这个思路写不出 SQL,只能绕成这样多层嵌套的。
这个问题真地是当作应聘考题的,通过率不到 20%。
这么一个简单的例子就能暴露出 SQL 缺失的能力,SQL 缺失的内容还有更多,限于篇幅,这里就不再深入讨论了。
反正结果就是,SQL 实现查询时无法应用自然思路,经常需要绕路迂回,写得又长又难懂。
现实任务要远远比这些例子复杂,过程中会面临诸多大大小小的困难。这个问题绕一下,那个问题多几行,一个稍复杂的任务写出几百行多层嵌套的 SQL 也就不奇怪了,过两月自己也看不懂也不奇怪了。
事实上 SQL 一点也不容易。
下面是广告时间。
SQL 很难写怎么办?用 esProc SPL!
esProc SPL 是个 Java 写的开源软件,在这里github.com/SPLWare/esP…
SPL 在 SQL 已有的集合化基础上增加了离散性,从而获得了彻底的集合化和有序能力,上面的例子就 SPL 就可以延用自然思路写出来:
所有产品销售额都在前 10 名的销售员,按产品分组,取每个组的前 10 名再算交集;
T.group(product).(~.top(10;-amount)).isect()
SPL 支持集合的集合,top 也只是常规的聚合计算,有了这些基础,实现自然思路很容易。
一支股票最长连续上涨了多少天,只要按自然思路写就行了
cnt=0
Stock.sort(TradeDate).max(cnt=if(Price>Price[-1],cnt+1,0))
SPL 有强大的有序计算能力,即使实现和上面 SQL 同样的逻辑也非常轻松:
Stock.sort(TradeDate).group@i(Price<Price[-1]).max(~.len())
来源:juejin.cn/post/7441756894094491689
年,是团圆
大概是我刚上小学时候,村子里流行起来出门打工,每个家庭里面的壮年男人都会去湖北挖桩,组团去。他们过完年出发,先走路下山,坐车再坐船。
出门一段时间后,他们会寄钱回家,那时寄钱的方式我不清楚也还从没有问过,只记得家里女人们听到对门肉嗓子大喊“xxx,来拿汇票”时,她们是很开心的。
快过年,男人们从外面回家,背着或挑着大件的行李,行李中是破旧的棉穗,小孩的新衣与不常见的水果糖。
挖桩需要两个人配合,一人在井里往下挖,一人在上面接桶转移土石,隔一段时间换人。有想法的男人认为他可以一直待在井里,女人提桶倒土是完全没有问题的,如果与自己的女人组团,工钱不用与别人分,挣钱的速度便会快上一倍。
于是,到我上初中时候,出门打工的规模更加扩大,家中的男人女人一起出门,小孩交给父母带着。
男人女人外出打工的时间是一样的,过完年出门,快过年时回家。
男人女人年龄渐长,他们的小孩长成大人,成人都外出打工,过完年出门,快过年时回家。
男人女人与小孩的目的地并不相同。于是过年,成为一家团圆的日子。
家家张灯结彩,人人喜笑颜开。
初一的早餐,是可以很简单的,汤圆或是包面;也可以是复杂的,蒸米饭,炸鱼、牛肉干、猪耳朵、凉拌鸡脚等许多凉菜,昨天的猪脚炖鸡汤,再配几个新鲜炒菜,满满一桌。复杂与简单的差异,来自于当天行程的安排,马上要走亲戚就简单些,家中要来客人便复杂点。
午饭,必然是丰盛的,一锅必不可少的鲜炖的猪脚炖鸡汤(是的,猪脚与鸡年三十只炖四分之一,留着客人来后能炖新的猪脚汤),一盆小孩儿最是欢迎的洋芋片,一锅人人喜爱的猪头汤清炖萝卜,两盘肥腻多汁男人们热衷的辣椒炒三线儿,一盘香肠、两碗扣肉,再来一盘炸豆腐……凉菜有猪肝、猪耳朵、鸡脚、炸鱼、牛肉干、萝卜干儿、折耳根……炒一个青菜,或是再加一碗青菜猪血汤。
男人们大都是会喝点酒的,他们互相劝着,你敬我一口酒,我回你两句话:“这酒好甜啊!”女人们偶尔也喝些酒,但更多是和小孩一起喝饮料。主妇是最后上桌吃饭的,大家吃饭时,她为桌上换热汤,为客人盛热饭。
吃过午饭,晒晒太阳烤烤火,嗑嗑瓜子吹吹牛。客人准备回家,临出发告诫主人:“你们明天一定要来啊,都要来。”
初二初三初四初五初六,是初一的重复。
隔得近,午饭的重复便是晚饭。吃过晚饭,今日不用再做饭,男人女人们围着炉子说过去说现在,说去年说当下,那“哈哈”大笑声,是时常能传到地坝的。
地坝里,孩子们的游戏一个接一个的切换。老鹰抓小鸡、三个字(一个人追众人跑,快被抓到时喊出随便的三个字,被抓者定住等待救援,抓人者切换目标;待所有人都被定住,便问“桃花开了没?”)、跳绳、打沙包……四岁五岁的,大多数游戏还玩不太明白,但他们的参与感该是最强的,因为哥哥姐姐叔叔阿姨都护着他们。
地坝里的叽叽喳喳,盖过屋子里哈哈哈哈。
年,是团圆!
来源:juejin.cn/post/7337957655190847522
看完前端各种风骚操作,我眼睛被亮瞎了!
一、实现一个快速评分组件
const getRate = rate => "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
console.log(getRate(0)); // ☆☆☆☆☆
console.log(getRate(1)); // ★☆☆☆☆
console.log(getRate(2)); // ★★☆☆☆
console.log(getRate(3)); // ★★★☆☆
console.log(getRate(4)); // ★★★★☆
console.log(getRate(5)); // ★★★★★
这个都不用多解释了,简直写的太妙了!
二、巧用位运算
用位运算可以实现很多功能,比如乘2、除2(或者2的倍数),向下取整这些计算操作,而且性能很高!
let num = 3.14159;
console.log(~~ num); // 向下取整,输出3
console.log(2 >> 1); // >>表示右移运算符,除2,输出1
console.log(2 << 1); // <<表示左3移运算符,乘2,输出4
并且,利用~符
,即按位取反运算符(NOT operator)
,还可以和字符串的indeOf
方法配合使用。
const str = 'acdafadfa'
if (~str.indexOf('ac')) {
console.log('包含')
}
其实原理很简单,举几个例子大家就明白了:
~-1
的结果是0
。~0
的结果是-1
。~1
的结果是-2
,~2
的结果是-3
。
三、漂亮随机码
const str = Math.random().toString(36).substring(2, 10)
console.log(str); // 随机输出8位随机码
这个在要为每个用户生成一个随机码的时候特别好用,具体随机码多少位可以自己控制,如果要的随机码位数特别长,可以把这个函数多调用一次,然后把结果进行字符串拼接。
四、史上最NB的报错处理
try {
const str = '';
str.map(); // Uncaught TypeError: str.map is not a function
} catch(e) {
window.open("https://stackoverflow.com/search?q=js+" + e.message);
}
这应该是史上最NB的报错处理了,一般来说,抛出错误时应该打印日志并上报,这里直接带着报错信息给人家重定向到stackoverflow
去了,顺便stackoverflow
搜索了下这个报错,直接搜一波错误解决方案,而且这个网站是全英文的,顺便还能学一波英语,对于开发者来说,这简直太妙了!不过记得上线的时候,记得在跳转前加一个if(process.env.NODE_ENV === 'development')
,不然上到线上一旦报错可就惨了!
五、倒序排序的简写
const arr = [1, 2, 3, 4, 5];
for (let i = arr.length - 1; i >= 0; i--) {
console.log(arr[i]);
}
可简写为:
const arr = [1, 2, 3, 4, 5];
for(let i = arr.length; i--;) {
console.log(arr[i]);
}
代码解释:
先来回顾下for循环的书写结构,即for (初始化表达式; 条件表达式; 递增表达式)
,初始化表达式只会执行一次,而条件表达式和递增表达式在每次循环时都会执行一次,而正好这个倒序循环的终止执行条件为i==0
,所以就可以把条件表达式
和递增表达式
合而为一了,主打的就是一个简洁。
六、在控制台输出一个键盘图形
console.log((_=>[..."`1234567890-=~~QWERTYUIOP[]\\~ASDFGHJKL;'~~ZXCVBNM,./~"].map(x=>(o+=`/${b='_'.repeat(w=x<y?2:' 667699'[x=["BS","TAB","CAPS","ENTER"][p++]||'SHIFT',p])}\\|`,m+=y+(x+' ').slice(0,w)+y+y,n+=y+b+y+y,l+=' __'+b)[73]&&(k.push(l,m,n,o),l='',m=n=o=y),m=n=o=y='|',p=l=k=[])&&k.join`
`)())
这段代码会在浏览器控制台中打印出一个键盘图形,不得不说写出这段代码的人真的太有才了!
以上就是我总结的一些前端代码的风骚操作,大家有没有更风骚的操作呢?欢迎大家留言分享!
来源:juejin.cn/post/7453414571563925542
研发工作中的感悟
前两天工作上出现了纰漏,感兴趣的可以往前翻翻,大致情况是拼命干,结果干得越多错得越多,出了纰漏背了不少黑锅。
最近一直在调整工作状态,复盘以前错误的工作方式。
多和团队沟通解决方案
以前我都是一个人单打独斗,功能点从设计到逻辑到实现,都是我一个人。
这种情况首先一个人力量有限,有时候设计的不那么完美;其次是大家一起讨论出来的东西,做出来就算不好,也有人帮忙说话,避免了墙倒众人推的局面。
主动暴露自己遇到的问题
以前我也是闷头干,很多过程中的困难和问题,我都自己默默解决了。这就搞得别人以为这事没难度,还嫌我进度慢。
现在不管问题能不能解决,遇到卡点我都吱一声,能不能解决再说,能解决是我牛逼,不能解决就往上报。
不要被上线时间卡死
我们公司有个非常不好的风气,技术还不知道功能点,运营就定好了上线时间,一开始搞得很焦虑,匆匆忙忙赶着上线,结果出纰漏,开会一顿挨叼。
有些很大的需求,两三个人一两个月那种,即使前期做了评估,做着做着也会被临时需求插入呀、技术方案调整呀、三方调用阻碍等事拖慢进度。
现在我对这事看淡了,要做就做好,技术层面设计搞得清清楚楚,按规范的来,工时正常评估,需求 delay?无所谓的,直接报个直属领导,让领导之间去互撕。我周末来加天班表示下态度,其实一个人没人打扰专注写代码挺爽的。
配置化的东西让运营操作
我以前有个特别不好的习惯,配置的东西图快图简单,配在 redis 里或acos,每次运营跟我说我去改。
千万不要这么搞,不出问题还好,但也浪费开发时间,出了问题那更是开发背锅。
所以不如一开始就花点时间写个页面给运营自己配,别看一开始费了点时间,从此没了后患。
主要是看到论坛一个哥们自己配置的利息少了个 0 导致涨了 10 倍,事后自己背锅还是去修数据,也是惨的一批。
只做事不做决策
还是以前责任心太强了,到我这的事我必须完结掉,中间遇到的各种困难险阻我都干掉了。
现在中间遇到什么问题,尤其是需要做决策的地方,研发的就 @ 直属领导,业务的就 @ 运营和产品,让他们出解决方案,确定好了我再动工。
比如最近在接入京东小程序,用户注册的地方要不要对三方手机号做虚拟号拦截?我想个屁直接丢群里面问业务。比如一单一品下单缺商铺各种配置,我也是一个 @ 给到运营。商户订单转支付总觉得怪怪的,那就拉领导反复多次确定和讨论,果然架构都要改掉。
自从不做决策后,我感觉一身轻。首先决策本身很消耗精力,要想清楚不同决策的影响点,也需要一定的经验支持;其次决策做了是要担责的,万一做错了呢?锅这不就来了吗?况且我也没到那个位置,做那些有点越位了,不合适。
有意识的过滤杂事
晚上八点多的时候,群里反馈有人收不到登录验证码,运营疯狂 @ 我。
我试了下 5s 就收到了验证码,就这么杂碎的事,自己点一下系统就能判断的,也要 @ 我?用户手机欠费我还得去给他充点话费是吧?
纯粹是我以前太好欺负,来我这的什么脏活累活,我都没吭声的干完了,是开发的,不是开发的,反正到我这都能闭环,这么好用的人,不往死里用?
现在像这种 SB 场景,我看都不看,还把飞书提醒都关了,就留个加急消息通知。
还有些人老喜欢问我 xx 场景下 xx 逻辑是怎样的。我也是一脸黑人问号,这玩意不去问产品来找研发几个意思?
这些问题我也没怎么管了,已读不回就完事,结果大部分情况,没回也没再追问了。
脸皮厚起来
这话咋理解呢?就是细节的地方,要决策的地方,不要觉得这么简单的事也要抛出来,厚着脸问出来。
我以前就有这种不好的思想,总觉得事情很简单,决策影响面也不大,自己 hold 得住。结果就是解决得好没好处,解决不好就吊,出岔子还背锅。
现在我非常不要脸的把各种细节处的问题都抛出来,在群里面说的清清楚楚。
一来是逻辑大家都同步下,二来是决策相关的给领导。
就像这次京东小程序,需求评审会的时候运营、产品都说实名信息京东有接口,讨论都不让讨论直接过。结果对接的时候,京东直接懵逼了,说我没接口啊,我也傻眼了,信誓旦旦的接口没了,开发一半到这节点又要重新调整方案。
就这运营还私我让我搞清楚需求别问尴尬问题,我 TM 反手一个质疑三连,合着你给我搞个京东实名信息接口呗,我不问京东问你吗?
总结今日份把杂事脏活全甩了,不该背的锅全反弹,内心舒畅,心情大好。特意奖励自己鸭脖一根、鸭头一个、鸭肠一捆、薯片一袋、浪味仙一包、快乐水一瓶。
拒绝内耗,勇敢甩锅,快乐自己,不顾他人,从我做起,共勉。
来源:juejin.cn/post/7452665094193020939
30岁的程序媛,升值加薪与我无缘
前言
上篇讲述了一位老哥的10年搬砖历程《不容易,35岁的我还在小公司苟且偷生》,有位小姐姐看了之后比较有感触,希望我能将她的故事也讲讲,看看能否有共鸣的朋友。
程序媛的前半生
我今年30岁,无房无贷孑然一身。
出生在95年的沿海小镇(隶属八山一水一分田的省份),我四岁那年父母终于如愿以偿地迎来了弟弟,从此以后弟弟就是家里的中心。
高考填报自愿的时候,想到远点的地方上大学,最终上了四川的一所院校。坐了将近三十个小时的列车,也就是那会儿才真真体会到了书上说的:"我国地势西高东低,幅员辽阔"。
专业学的信息工程,大学那会班里男多女少,大家都挺照顾我的,性格也变得开朗了许多,谈了个男朋友,我们相约考个985院校的研究生。
那年春天,考研成绩出来,我没过线,他低空掠过,接受调剂到另一个省读研,然而正当我打算二战的时候,被分手了...
因为考研错过了秋招,春招也只剩了尾巴,也不打算二战了,家里人让我回去找工作,不过我还不太想回去,先找工作再说。幸好平时跟着班里的大牛们一起做过项目,项目虽小,但知其然也知其所以然,花了2个月终于找到一份前端的工作。
2018年入职成都北部郊县的一家小创业公司,公司主营业务是硬件,互联网软件是辅助,前端+后端也就6个人,没想到前端除了我还有另一个妹子。
那会工资比较低,自己也知道本身有几斤几两,为了节省开支,每天都带饭,当然每天也是按时下班,下班后先把第二天要带的饭菜准备好之后,再花一小时提升自己的技术(主要是看别人的轮子),最后边做面膜边刷美剧打发时间,直到窗外的喧闹声逐渐隐去,我也入睡啦。
平淡的日子真的淡如水,印象比较深的是有个晚上需要紧急修复Bug,要去公司对齐方案,尴尬的是,最终查出来不是我的问题。回家的路上,看着昏暗路灯下长长的影子,莫名的感觉害怕,越怕走的越快,越快影子越长,终于看到小区门口有个抽烟保安叔叔,赶紧飞奔过去。当进入小区的那刻,回过头来,却怎么也找不到影子了。从那时起,暗下决定,再也不加班了。
2020年7月,刚好在公司待了两年,周围的同事都换了一半的新面孔,不少同事去了南边,在小聚的时候他们说起了南边的机会比这多多了,让我考虑考虑。我也仔细思考了自己的处境,软件在这家并不是主营业务,想找一家纯互联网的公司,最主要的是这两年没有涨薪,新来的小伙伴工资都比我高,于是也暗中投递了简历。
运气不错,一个月内面了4、5家,最终选定了一家互联网公司,工资是上家的两倍,当时暗自感叹同一个城市差距咋那么明显。
新的公司,单单是前端的研发都有30+人,而这还只是成都分部的。刚开始很是珍惜这份机会,任劳任怨,偶尔也会加班表现一下。后面逐渐和同事们混熟了,才发现自己的工资就是垫底的存在,瞬间加班的动力跌至冰点。
在这公司接触了更多的业务,学习了很多技术知识,甚至知道有些同事大佬自己创造轮子。也许是我比较菜,或是我工资比较低,领导给我的任务都不会太难,我也没出过什么幺蛾子。每年都有涨薪的机会,当然每年都没我的份,在这块我是有心理预期的,因此也不会失落(或许是已经麻木了?)。
23年底开始,陆续有同事拿到了大礼包,甚至有一次我刚从厕所出来,对面的同事的工位就空了。问了才知道,谈妥了,立马走。
到了24年初,小组内陆续走了一半的同事,直属领导找我谈话问我想法,我说我比较懵,明明我在组内最菜,为啥走的不是我。领导说,相比而言你的性价比较高,我暗想:直说我薪资低呗~。
到了24年底,还暗自庆幸自己能挺过最艰难的一年,没曾想拿到了大礼包,没有任何挣扎,签协议走人。
休息了一个月,尝试投递简历,直到现在才有两次面试邀约,神奇的是这两家都会问我同一个问题:结婚了没,打算多久结?我:???
我决定了,以后进行自我介绍的时候先发制人,本人未婚未孕未育。
面试结果最终当然是不了了之了。
直到成为自由人才深刻体会到今年寒气之重,都结冰了。
还好申请了失业保险金,聊以慰藉吧。
经过了用人市场的洗礼,在三月份之前都不打算投递简历了,反正都是石沉大海。
刚好有时间多休息,多去看看以前没去的地方(虽然大部分是穷游),节后再做打算吧。
30岁的我,在人们眼中是个剩女。
30岁的我,阴差阳错学了技术。
30岁的我,没造过一个技术轮子。
30岁的我,远离家乡几千里。
30岁的我,在领失业保险金。
30岁的我,也许会离开这座城市。
30岁的我,祝朋友们所愿皆所得。
来源:juejin.cn/post/7461587929447776275
迟来的2024年终总结
这是一篇迟来的 2024年终总结,从来没正式的为自己做过总结。
一、工作
2023年底,其实当时经过了几面之后,本来已经拿到了 Gitee(开源中国) 的 Offer,然后因为杭州有朋友说有项目,于是思考许久之后,还是决定来了杭州。
选择杭州放弃了深圳,我觉得应该还是这几个原因:
- 杭州有朋友在,工作安排好之后,住宿的问题也一并解决了,当时到杭州之后基本就是直接拎包入住的。而且公司离住的地方走路也就十分钟。
- (因为老婆孩子在区县,市区家里目前也是空置的状态),如果杭州回老家,可以选择飞机、高铁,而且高铁能直达重庆家里,家里到火车站也就五分钟车程,来去方便。深圳的话,就只能飞机到重庆,再转动车到家里。我又是很怕麻烦的一个人。
- Gitee 的 Offer 是产品经理,纠结了一下之后,觉得如果转了的话,估计以后自己写代码会更少了
- 还没来过杭州。
参与的工作和项目
1.1 老系统的维护和迭代
本身有一套基于 PHP 的灵工财税系统在生产上跑,需要进行日常的维护、迭代一些新功能。
系统周边还有一套 支付宝小程序 也在线上运行着。
1.2 新系统的设计与开发
基于老系统的业务需求,重新架构设计和开发了一套新系统:
- 使用 Java17 / SpringBoot3 / MySQL8 / JPA / Redis / RocketMQ 等后端技术栈对后台服务做支持
- 使用 Vue3 / Vite / TypeScript / ElementPlus 等前端技术栈对前端页面做支持
新系统前前后后开发和测试花了大概三个月的时间,技术团队人员 2 个全栈,两个产品,两个测试。
1.3 MCN机构主播平台
设计开发了一个 MCN 机构的主播社区,技术栈和上面新系统基本一致,主要实现了一个后台服务、一个 Web 端的管理系统、一个基于 uniapp 的 App,上架了 App Store,Android 端倒是没有直接上商店,提供的是 H5 官网直接下载 APK.
1.4 一些小工具
也做了一些公司内部很多小工具的开发,例如基于 小爱同学 的业务语音通知服务、Web 叫号服务(类似在页面上输入信息,指定公司内各个部门的小爱同学进行通知的功能)
也不停折腾了公司的一些 VPN 网络架构 局域网服务器架构 等工作,例如基于 vmware vsphere
vcenter
vsan
的超融合架构等。
用大模型搭了一些好玩的服务,比如 ts.hamm.cn java.hamm.cn 等
1.5 其他项目
也客串了一个前端,参与了公司其他小组的社交类产品的管理后台开发。
因公司有一个 AI 出海项目需求,预研了一个 AI智能体项目,主要是一些角色扮演的场景服务 (此处有狗头)
。
二、开源
今年做了一些开源小项目,当然比去年的积极性要低了很多:
2.1 SPMS 智能生产管理
S-PMS (Smart Production Management System) 智能生产管理系统 ,是一个集成化、智能化的企业级应用软件,它集成了多个核心的生产管理模块,包括 制造执行系统 (MES)、仓库管理系统 (WMS)、企业资源计划系统 (ERP)、质量管理系统 (QMS) 以及 物联网管理系统 (IoTS) 等。
技术栈使用的也是和 1.1.2 中提到的一样。
其中完成了两个端的开发:
这个项目其实从 2013年底 就已经开始了,目前还在迭代中。
2.2 OllamaK
因为觉得其他的 Ollama iOS 客户端都不好用,然后自己花了几天时间写了个简单的 Ollama iOS 客户端。
Github: github.com/HammCn/Olla…
基于 Swift + SwiftUI 设计。
2.3 AirPower4T
AirPower4T 是一个基于 Vue3 TypeScript Element Plus Vite 的开发基础库,使用面向对象、装饰器、Hooks等开发模式,内置了数据模型转换、表格表单装饰器配置、加解密和编码解码、网络请求、权限管理等常见后台功能以及页面组件,助力后台类系统的前端开发效率,同时保障了优雅的代码质量。
Github: github.com/HammCn/AirP…
2.4 AirPower4J
AirPower4J是一个基于 Java17、SpringBoot3.x、JPA&MySQL 的后端开发脚手架,其中包含了一些 RBAC、请求验证、CURD封装、异常处理、多租户SaaS、加解密与安全、WebSocket等模块,以满足日常开发的快捷、稳健、标准化等要求。
Github: github.com/HammCn/AirP…
2.5 一些SDK包
2.5.1 WeComSDK
企业微信的 Java SDK
。目前是开发中,对 企业微信 的一些 OpenAPI
进行了封装。
Github: github.com/HammCn/WeCo…
2.5.2 WeComRobotSDK
一个很好用的企业微信机器人开发工具包SDK。也是发布到了 maven
仓库。
Github: github.com/HammCn/WeCo…
2.5.3 AirPowerJavaSdk
AirPower Java SDK 是基于 Java8 下用于快速对接 AirPower4J 项目中的开放应用的开发工具包,实现了与 AirPower4J 匹配的 AES / RSA 出入参加解密、参数签名、防止重返攻击、数据自动转换等功能,针对基于 AirPower4J 下的 Web 项目提供快速支持开放能力。
Github: github.com/HammCn/AirP…
三、写作
这一年免不了在掘金和其他社区摸了不少鱼。
3.1 掘金专栏
开了三个掘金的专栏:
3.1.1 《用TypeScript写前端》
本篇专栏主要讲解作者是如何大胆放肆的使用 TypeScript 面向对象思维来写前端的。
截止目前,共收录了 32篇 文章,订阅用户 500 人,希望能真正的帮到这 500 个订阅的朋友。
3.1.2 《来杯Java压压惊》
主要是分享一些用Java写后端的心得体会。
截止目前,共收录了 14篇 文章,订阅用户 6 人,因为是 11月 才创建的专栏,数据有些许惨淡。
3.1.3 《你好,全干工程师》
网络?运维?架构?产品?设计?可能这个专栏都有涉及到。
截止目前,共收录了 47篇 文章,订阅用户 21 人,数据也不是那么好看。
3.2 粉丝数据
截止目前:
3.2.1 掘金粉丝:800
3.2.2 Github粉丝:211 (@HammCn)
3.2.3 Gitee粉丝:887
3.2.4 公众号粉丝:3401 (@imHamm)
公众号的粉丝也不是很垂直,现在几乎不在公众号发布什么内容了。
3.2.5 微博粉丝:5000
(不垂直,已经不打算经营了,不再公开了)
3.3 阅读数据
截止目前,掘金阅读数据:
四、生活
杭州的生活很糟糕。特别是美食。
4.1 饮食问题
刚来的时候,还能维持每周两三次在家做饭炒菜,这几个月几乎没怎么在家做了,都选择了外卖或者在外面吃。
美团上拉黑了很多个商家了,实在是难吃。
4.2 日常出行
因为几个朋友都在一起,所以日常也基本都是在一块。一般也只在家、公司、附近商场、机场、火车站 这些地方。
日常没有什么出行的需求,但给老婆换掉了之前 我开的油车,换了 另外一辆油车。。。
(给家里添置了第二辆林肯了,蛮喜欢这个品牌的)
唯二在杭州较远的两三次出行:
4.2.1 灵隐寺
去过一次就不再想去第二次了。
4.2.2 乌镇
和重庆的磁器口差不多,没什么意思。
五、家庭
家庭是最重要的部分,所以选择放到最后说了。
5.1 儿子
儿子今年六月份三岁了,也上了幼儿园小班。
小子从小就聪明,情商也高。就是在学校不爱吃饭,还回家说学校的饭菜不好吃。
现在几乎能用英文从 1-100 读出来,一些颜色、水果、物体 也都能简单的表达了。
数学方面的话,10以内的加法没问题了,减法还不太会的样子。
5.2 老婆
家里最漂亮的女人。带孩子、上班都是她。
5.3 亲人
爸妈,岳父岳母依然是围着儿子在转,也慢慢的有了一些岁月老去的痕迹了。
依然是身体健康,这也就是最大的幸福。
5.4 离开了两个亲人
我这边的爷爷和外婆相继在今年离开了我们。希望他们在那边没有烦恼,快乐生活。
六、总结
这一年经历了太多,本文也是流水账的方式做了个年终的总结。
对2025年的期望,目前也还很迷茫。
先祝福吧:
希望儿子能健康快乐的成长,能学习到很多好玩的东西。
希望老婆依然是貌美如花,别被儿子整天的调皮折腾。
希望爸妈,岳父岳母,爷爷奶奶们身体健康,生活没有烦恼。
至于我自己,现在还没想好,但希望2025年工作上能有一些新的突破。
就这样,也祝所有幸福的人们,2025的愿望也都能实现。
来源:juejin.cn/post/7461207850456842303
✨Try-Catch✨竟然会影响性能
前言
一朋友问我Try-Catch写多了会不会让程序变慢,我不加思索的回答肯定不会,毕竟曾经研究过Java异常相关的字节码指令,只要被Try-Catch的代码不抛出异常,那么代码执行链路是不会加深的。
可事后我反复思考这个看似简单实则也不复杂的问题,我觉得顺着这个问题往下,还有一些东西可以思考,如果你感兴趣,那就跟随本文的视角一起来看下吧。
正文
首先郑重声明,单纯的针对一段代码添加Try-Catch,是 不会
影响性能的,我们可以通过下面的示例代码并结合字节码指令来看下。
示例代码如下所示。
public class TryCatchPerformance {
public Response execute(String state) {
return innerHandle(state);
}
public Response innerHandle(String state) {
// todo 暂无逻辑
return null;
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
我们依次执行如下语句为上述代码生成字节码指令。
# 编译Java文件
javac .\TryCatchPerformance.java
# 反汇编字节码文件
javap -c .\TryCatchPerformance.class
可以得到execute() 方法的字节码指令如下。
public com.lee.learn.exception.TryCatchPerformance$Response execute(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokevirtual #2 // Method innerHandle:(Ljava/lang/String;)Lcom/lee/learn/exception/TryCatchPerformance$Response;
5: areturn
现在对execute() 方法添加Try-Catch,如下所示。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
// todo 暂无逻辑
return null;
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
查看execute() 方法的字节码指令如下所示。
public com.lee.learn.exception.TryCatchPerformance$Response execute(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokevirtual #2 // Method innerHandle:(Ljava/lang/String;)Lcom/lee/learn/exception/TryCatchPerformance$Response;
5: areturn
6: astore_2
7: new #4 // class com/lee/learn/exception/TryCatchPerformance$Response
10: dup
11: sipush 500
14: invokespecial #5 // Method com/lee/learn/exception/TryCatchPerformance$Response."":(I)V
17: areturn
Exception table:
from to target type
0 5 6 Class java/lang/Exception
虽然添加Try-Catch后,字节码指令增加了很多条,但是通过Exception table(异常表)我们可知,只有指令0到5在执行过程中抛出了Exception,才会跳转到指令6开始执行,换言之只要不抛出异常,那么在执行完指令5后方法就结束了,此时和没添加Try-Catch时的代码执行链路是一样的,也就是不抛出异常时,Try-Catch不会影响程序性能。
我们添加Try-Catch,其实就是为了做异常处理,也就是我们天然的认为被Try-Catch的代码就是会抛出异常的,而异常一旦发生,此时程序性能就会受到一定程度的影响,表现在如下两个方面。
- 异常对象创建有性能开销。具体表现在异常对象创建时会去爬栈得到方法调用链路信息;
- Try-Catch捕获到异常后会让代码执行链路变深。
由此可见Try-Catch其实不会影响程序性能,但是异常的出现的的确确会影响,无论是JVM创建的异常,还是我们在代码中new出来的异常,都是会影响性能的。
所以现在我们来看看如下代码有什么可以优化的地方。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
if (state == null || state.isEmpty()) {
// 通过异常中断执行
throw new IllegalStateException();
} else if ("success".equals(state)) {
return new Response(200);
} else {
return new Response(400);
}
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
上述代码的问题出现在innerHandle() ,仗着调用方有Try-Catch做异常处理,就在入参非法时通过创建异常来中断执行,我相信在实际的工程开发中,很多时候大家都是这么干的,因为有统一异常处理,那么通过抛出异常来中断执行并在统一异常处理的地方返回响应,是一件再平常不过的事情了,但是通过前面的分析我们知道,创建异常有性能开销,捕获异常并处理也有性能开销,这些性能开销其实是可以避免的,例如下面这样。
public class TryCatchPerformance {
public Response execute(String state) {
try {
return innerHandle(state);
} catch (Exception e) {
return new Response(500);
}
}
public Response innerHandle(String state) {
if (state == null || state.isEmpty()) {
// 通过提前返回响应的方式中断执行
return new Response(500);
} else if ("success".equals(state)) {
return new Response(200);
} else {
return new Response(400);
}
}
public static class Response {
private int state;
public Response(int state) {
this.state = state;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
}
如果当某个分支执行到了,我们也确切的知道该分支下的响应是什么,此时直接返回响应,相较于抛出异常后在统一异常处理那里返回响应,性能会更好。
总结
Try-Catch其实不会影响程序性能,因为在没有异常发生时,代码执行链路不会加深。
但是如果出现异常,那么程序性能就会受到影响,表现在如下两个方面。
- 异常对象创建有性能开销。具体表现在异常对象创建时会去爬栈得到方法调用链路信息;
- Try-Catch捕获到异常后会让代码执行链路变深。
因此在日常开发中,可以适当增加防御性编程来防止JVM抛出异常,也建议尽量将主动的异常抛出替换为提前返回响应,总之就是尽量减少非必要的异常出现。
来源:juejin.cn/post/7458929387784077349
再见2024,秋枫春樱夏海与钱
也许一切都有个期限。
人生的前 7 年是童年,接着是 17 年的学生生涯(1 年复读)。在大学四年里,除了应付本身的学习外,一半的时间鼓捣音乐,一半的时间在炒股,对结束学生的生活充满了期待。
进入工作以后,来到今年,也17年了。
上班和上学真的很像的。上学时,小学中学大学一路打怪升级,上班时,小厂中厂再到大厂,也是一路打拼。每天两点一线,年复一年,日复一日。
因父亲是国企员工,自小对大厂的集体生活就有恐惧感。然而经过奋斗再进入大厂,享受到更好的福利待遇时,才发现大厂的生活更适合大众。
但是,经过前面几年的折腾,已经消耗过多的精力,进大厂之后缺乏了向前进的动力。在小厂里,你 3-5 年就可以成为小领导。在大厂里,3 年才刚刚对公司各方面有一些了解,才能协调资源去完成一个小项目。大厂里需要消耗更多的时间精力处理本身工作之外的事情。
在大厂时间一天天流逝,发现自己并没有得到真正的提升。
于是,凑巧也是 17 年,终于迎来了结束的一天。
因为给自己打了个 C,喜获炒鱿鱼一份,也正式离开了大部分人走的轨道。
大学的时候无所事事,那四年的确是个人最舒服的时间。现在也无所事事了,挺好。
旅行
没事就找点事吧。除了省内走走逛逛之外,也出省游了。
春天,到武汉赏樱花;秋天,到南京赏枫林。夏天则走了一趟海南环岛游。春夏之间,还去了宁波,与深圳同样的海滨城市。
中国的城市化的确很好,在任何城市生活都不会有很大的差别,只是各处的风景、人文又略有不同。
武汉,赏的是代表着中日友好的樱花,也是它的悠久历史。平原一望无际,楼都比山高,黄鹤楼与长江大桥夹在高楼间,并不显突兀。这里有春秋战国的遗物,有 800 年的楚国文化历史,也有与深圳一样新的光谷。
南京,除了心心念念的红枫林,还有六朝古都的风韵。枫叶看了,南朝四百八十寺看了,再去看看博物馆。作为在文化荒漠长大的广东人,博物馆看到第三天,脑里存储的历史知识已经用完。最后亦不能忘,要去中山陵园瞻仰近代伟人。
宁波像矮版的深圳,高楼大厦少了些,但是不缺发达的城市商业,不缺鲜甜可口的海鲜,也不缺舒适慵懒的海边。这里还有中国最大的私人藏书阁,这里也是徐霞客旅行的起点。
至于海南,熟悉又陌生。的确远离了大陆,除了冼夫人、苏东坡的痕迹外,没有太多先人的故事。在这里躺平也是一个选择。多少明白了全国各地的人都喜欢去海南的原因。
我心中是没游遍全国的计划的。作为一个中年人,开始向内求,其实不太需要外界刺激来放松自己,或者来达到什么样的目的。
明年会不会继续出发?当然是看钱行事。
钱
钱的问题才是主要的问题,尤其对一个失去了工资收入的人。
投资上,今年预判失败,提早清仓了纳斯达克指数 ETF,然后在 9 月底,参与了大 A 股的闪电牛市,但是没有全身而退。在股市上只能说小盈利。离职后,更是忘了“财不入急门”,跑去炒期货,最后在期货上亏了不少。
赢利比最高的反而是虚拟货币。这笔资金来源于参与了新发行的币。卖出后,交易了几笔,账户翻了倍。然而只有可怜的80刀。
所以整体下来,是亏损了。
终于发觉,自己作为 20 年老股民,一直没有很好地对待自己的投资。回想起来,自己有四点没有做好的:
- 在自己收入最好的时候,没有存到钱,没养成好的储蓄习惯。
- 买房时机还可以,但是没有尽早还房贷。
- 大部分钱都用于买房,只用很少的钱参与投资。
- 有自己的投资风格,却没有完善自己的风格,并不断地执行让雪球滚起来。
如果有时光机,我一定回去对自己说,“存钱,存钱,存钱!”
技术
今年,对规范、标准有了进一步了解。
在前端里,react 之前在高阶函数、服务端组件中的探索,成就了无头 UI,成就了shacn/ui;shacn/ui 丰富的下游组件、模板成就了 AI 编程工具 v0.dev。目前 v0.dev 还在继续接入 next.js 的模板。
另外,如果用了 RESTful 写的接口,使用 AI 编码可能 1 句话就解决了,如果不是,则需要对增删改查接口,每个都写不同的对话。
可以明确的是,越是标准、规范的技术/项目,越是容易接入 AI。
规范、标准是逐渐形成的,某些技术变成标准的时候,它后续的发展,比其他的技术都要顺利、要迅猛。
像现在国内的手机厂商,都在适配 iPhone 生态一样,即使 iPhone 是封闭的生态,但是其本身有非常丰富的开放的接口,这些接口就是 iPhone 定的标准,一直在稳定逐步推进,一步步地把用户锁在自己的生态中,你不加入永远就分不到蛋糕。(当然,小米也搭建了类似的生态,但影响力仅限国内)
我们在争论标准的时候,从未想过标准背后是什么,从未想过执行标准之后会带来的巨大好处。
国内 SAAS 还在垮台中,他们服务的一家家小公司,甚至是大公司,还在为一个流程要经过谁的审批争得死去活来。外国公司已经完成了阶段的飞跃,他们的数据早已为接入 AI 做好了准备。此时,国内还有些公司想着跳过信息化,直接进入 AI 时代……
说回 AI,如果 2023 是 AI 编程元年,今年就已经有初步成型的产品了,类似于 cursor 之类的编程 IDE。
用了 AI 编程再也回不去了,至少我是这样的。每月的 cursor 的费用并不低,但是它给我带来的收益是远大于此。从写demo,到逐步完成更复杂的内容,我把更多的事情交给了 AI。
总之,拥抱 AI, 依然是这几年的正确选择!
结束语
年底听了李楠在《脑放电波》的访谈,把近几十年关于去中心化的思潮与变革联系起来了。
世界的割裂正在进行,也许回不去那种互助互爱的地球村了,但也回不到联合国与大国控制的世界了。
在去中心化的所有技术都已经突破,成为现实。人们也开始接受虚拟货币,愿意把其变成一份份真的钱在社会上流通。社交媒体上,数字游民满世界的跑,宣扬着这一切的到来。
未来世界,也许真的如其所说,会由一个个的社区组成。你可以成为一个超级个体,社区根据你在链上的贡献给你奖励对应的数字货币,你通过数字货币获取自己所需的商品。
这一切不是不可能。
也许,未来已来!
新的一年继续努力吧,至少,继续为自己努力吧!
来源:juejin.cn/post/7454332481933295631
程序员焦虑症之「没用过」和「不知道」,码农的「拧螺丝」之道
许久没扯淡,今天就写点没营养的内容。
前几天和朋友聊天,其中一个话题很有意思,那就是「没用过」和「不知道」是他日常焦虑症的来源,因为他在一家传统企业做开发,技术栈一直很保守,很多框架代码可能一两年都不会升级改动,许多新东西都没用过,所以每次看到别人聊新技术的时候,都会觉得很焦虑,想“了解”却又“用不上”,想“上进”却又“学不进”,最后干脆选择“眼不见为净” 。
其实我过去也有类似的经历,每次接触到“新东西”时,内心潜移默化就会开始着急,仿佛再不跟进就要“挨打”,而“新东西”又层出不穷,结果就是东拼一脚西凑一下,最终像个无头苍蝇一样四处碰壁,不得而终。
为什么你懂这么多?相信这种情况对于「老 Androider 」多多少少应该都经历过,毕竟十年前的 Android 开发,「技术选型」可以说是日新月异,「你用过xxxx吗」和「你还在用xxxx」可以说是圈内话题的主旋律,举亿个例子:
- 你还在用 Eclipse ,现在都用 Android Studio 了
- 你还在本地 jar 包,现在都用 Gradle 远程依赖了
- xUtils 和 Afinal 听说过没?开发神器啊
- 你怎么还在用 ImageLoader ,知道 Picasso 吗? 用过 Fresco 没有?现在都推荐 Glide 了
- 你知道 GreenDao 吗?现在谷歌都推荐 Room 了,你用过 Realm 吗?
- 你还在用 ButterKnife?现在都用 DataBinding、ViewBinding 了
- 你怎么还用 Apache HttpClient,试试 Volley 呗?
- 现在都是 OKhttp 了,那你知道 Retrofit 吗?
- 你用过 gPRC 和 GraphQL 吗?
- 你还在用 MVC ,你知道 MVP 吗?我都用 MVVM、MVI 了
- 你用过 dynamic-load-apk、VirtualAPK、DroidPlugin、RePlugin、tinker 吗?
- 你知道 Dagger 吗?现在都 Dagger2 了
- 你还在用 Dagger ? 现在已经是 Hilt 了
- 你用过 EventBus 吗?
- 你知道 LeakCanary 吗?听过 BlockCanary 吗?
- 你还在用 Java 的 Dagger 啊,Kotlin 都用 Koin 了
- 你知道 Rxjava 吗?已经 Rxjava2 了
- 你用过 Couroutines 和 Flow 吗?
- 你知道 LiveData 吗?
- 用过 jadx 和 apktool 吗?
- 怎么还在用 Java ?Kotlin 都烂大街了
- 你知道 Jetpack 吗?用过 Lifecycle、Navigation、CameraX、Paging、Glance、Slice、Startup、Viewpager2、DateStore、WorkManager 吗?
- 你做过小程序吗?uni-app 听过吗?React Native 知道吗?Flutter 、KMP、Compose 了解不?
- 鸿蒙 Next 你适配了没?ArkTS 和 ArkUI 学了没?
- ····
有没有很熟悉的既视感?这还只是 Android 圈子的一角,如果你还做前端:那还有:
jQuery、AngularJS、Angular、Vue、React、Ember、Node、Express、Svelte、Nest、Nuxt、Deno、Solid····
就光说 React 更新带来的 JSX、Portals、Hook、Fiber、Concurrent、Suspense、Server Components、Transitions 就够一直玩下去····
但是在焦虑追新的同时,其实这里面一直有一个问题:会用这些框架,真的就是技术吗 ?你追的究竟是什么 ?
回答这个问题,刚好可以用到最近看到的一张图,因为我们大概率不是一个软件工程师:
- 高情商:蓝领科技者
- 低情商:工厂螺丝工
我们的工作就是使用别人「制作好的工具」,所以我们热衷追逐「新工具」,但是,大多数时候,我们又不了解工具是如何工作的,我们只是一直在“反复”的学会如何使用它们,并且焦虑于,我们还没全都学会。
我认为「拧螺丝」确实是一个很不错的比喻,各类框架就像是适配不同型号的螺丝刀,而市面上的螺丝头形态各异,我们就是不停的在学会「如何把某款型号的螺丝刀插入到对应螺丝头」,然后开始心满意足的拧一颗「新型号螺丝」。
拧熟了,大概还能解锁了多种姿势,拧起来更快更省力,不同型号的螺丝刀,对准的难度和发力的安全范围可能也会有些不同,不过没关系,多拧几次就熟悉了。
那么我们比的是谁认识的螺丝头多吗?
所以,拧螺丝是门槛吗?大概率不是的,因为学会拿螺丝刀就行。那问题又来了,既然都是拧螺丝,换个姿势,换个型号,你就不会拧了吗?不应该啊对吧 ?
回归到各式各样的框架上,有文档和有社区的情况下,换种语言,换种框架,难道就拧不动了?这又扯出另外一个问题:你的能力依赖于框架,是否又能超脱出框架?
所以我们在「追新」的时候追的是什么?是 1 - 2 - 3 这样的变化:
- 从 1 到 2 用户拧螺母需要准备的扳手数量减少了
- 从 2 到 3 扳手变得更加帅气有力,并且附带的“力道”也有所上升
那么开发的鄙视链就来了:
- 因为我用的是自动挡扳手 2 ,所以我看不起手动挡的扳手 1
- 因为我用的是全新工艺的扳手 3 ,所以比老工艺的扳手 2 牛逼
我想着这大概率是「追新」带来的「错觉」,牛逼的是扳手的制造者,而作为使用者,我们都是踩着别人的肩膀混口饭吃的工人。
回过头来看,在这种情况下,随着技术的发展,新生框架和技术会让开发变成更便捷,同时降低开发的门槛,从而方便后来者入坑,所以本质上就像开车,开「自动档」并没有比「手动挡」牛逼多少 。
就算是自动挡,也分很多换挡方式,那么你会用「怀挡」,真的就比用「直排挡」在技术上 NB 吗?难道比的不应该是,谁对「变速箱」的理解更深刻?尽管大部人其实都不会懂得如何造车,但在用车上,大家真正的差距在于:
- 车坏了你会不会修?
- 如何调节提升车的动力和操控
那么回过头来,所以作为老 Android 开发,在经历了开发项目需要准备“一堆扳手”的手动挡时代,如今在这个只要一个“扳手”就能干活的自动挡时代,怎么可能会拧不动螺母?
更多时候问题不在于我们学得不够多,知道的不够新,而是我们不知道框架实际上是如何工作,我们只是学会了使用一个叫做“React”的工具。
不是为了读源码而读源码,而是通过源码我知道了 CVT 和双离合的区别,知道了它们的实现原理和优劣,那么挡把是怀挡还是旋钮重要吗?
在不理解运作的原理,没有基础知识铺垫,当任务变成修理一个发动机时,当任务变成提高框架的性能瓶颈时,就会无从下手,就会回归到前面所说的:You are not a software engineer 。
我们不可能全知全能,也不可能认识所有螺丝头,也没机会熟练操控所有档位型号,但是我们的目的也不是为了认识所有汽车档把,我们的目的只是为了开车,然后进一步能力就是修车和调教,这对于大多数人来说,就很难能可贵了。
大多数人学习精力有限,但是理解能力和开车经验是可以精进的,比如最近看各个大厂的大佬们把 KMP 和 Compose 适配到鸿蒙上的分享,就能感受到老司机们的车技之滑溜。
而回到我们在焦虑「没用过」和「不知道」的时候,更多还是对于自身能力的不自信,就像是我们的尊严只能苟存于框架之下,多年的工作经验,只能寄希望于 XXX 不要凉。
所以才会对于 XXX 要火,XXX 要凉如此敏感,本质还是我们迷失在了工具上。
就像一些多年耕耘的老开发会告诉你,许多上层开发者「跟进」的基本都是:“ keeping up with all the bullshit that Google / Android dev community throws at you” ,远离所谓的 “architecture influencers” ,你需要的是自己的思维方式和选择能力,而不是臣服在框架之下,被社区推来推去:
当前,其实这也和当前很多工作岗位的设定有关系,公司当前只是需要一个螺丝钉,公司不需要你理解螺丝刀为什么十字和一字的区别,你拧就是了,标签化和细分化确实更好管理,而有人也愿意带着标签,这无可厚非。
而大佬们就不同,在和社区大佬的沟通中,我发现基本上他们的涉猎范围都很广泛,而且都很有深度,或者说:语言和框架都只是为了解决问题的工具。
总有人走在你前面,只是你是否还在路上。
好了,其实道理大家都懂,只是如何知行合一,那就见仁见智了,许多没写这种没营养的内容,毕竟纸上得来总觉浅,真要总结,大概就是:人和人体质不同不能一概而论。
来源:juejin.cn/post/7451964967165231104
又整新活,新版 IntelliJ IDEA 2024.1 有点东西!
就在上周,Jetbrains 又迎来了一波大版本更新,这也是 JetBrains 2024首个大动作!
JetBrains 为其多款 IDE 发布了 2024 年度首个大版本更新 (2024.1)。
作为旗下重要的产品之一,IntelliJ IDEA当然也不例外。这不,现如今 IntelliJ IDEA 也来到了 2024.1 大版本了!
据官方介绍,这次 2024.1 新版本进行了数十项改进。
下面就针对本次新版 IntelliJ IDEA 的一些主要更新和特性做一个梳理和介绍,希望能对大家有所帮助。
全行代码补全
IntelliJ IDEA Ultimate 2024.1 带有针对 Java 和 Kotlin 的全行代码补全。
该项功能由无缝集成到 IDE 中的高级深度学习模型来提供支持。它可以基于上下文分析预测和建议整行代码,以助于提高编码效率。
对 Java 22 的支持
IntelliJ IDEA 2024.1 提供了对 2024 年 3 月刚发布的 JDK 22 中的功能集的支持。
支持覆盖未命名变量与模式的最终迭代、字符串模板与隐式声明的类的第二个预览版,以及实例main方法。 此外,这次更新还引入了对super(...)
之前预览状态下的 new 语句支持。
新终端加持
IntelliJ IDEA 2024.1推出了重构后的新终端,具有可视化和功能增强,有助于简化命令行任务。
此更新为既有工具带来了全新的外观,命令被分为不同的块,扩展的功能集包括块间丝滑导航、命令补全和命令历史记录的轻松访问等。
编辑器中的粘性行
此次新版本更新在编辑器中引入了粘性行,旨在简化大文件的处理和新代码库的探索。滚动时,此功能会将类或方法的开头等关键结构元素固定到编辑器顶部。
这样一来作用域将始终保持在视野中,用户可以点击固定的行快速浏览代码。
AI Assistant 改进
在本次新版中,AI Assistant 获得了多项有价值的更新,包括改进的测试生成和云代码补全、提交消息的自定义提示语、从代码段创建文件的功能,以及更新的编辑器内代码生成。
不过需要注意的事,在这次 2024.1 版中,AI Assistant 已解绑,现在作为独立插件提供。这一改动是为了在使用 AI 赋能的技术方面提供更多的决策灵活度,让用户能够在工作环境中更好地控制偏好设置和要求。
索引编制期间 IDE 功能对 Java 和 Kotlin 的可用
这次新版本中,代码高亮显示和补全等基本 IDE 功能可在项目索引编制期间用于 Java 和 Kotlin,这将会增强用户项目的启动体验。
此外,用户可以在项目仍在加载时即使用 Go to class(转到类)和 Go to symbol(转到符号)来浏览代码。
更新的 New Project(新建项目)向导
为了减轻用户在配置新项目时的认知负担,新版微调了 New Project(新建项目)向导的布局。语言列表现在位于左上角,使最常用的选项更加醒目。
用于缩小整个 IDE 的选项
新版支持可以将 IDE 缩小到 90%、80% 或 70%,从而可以灵活地调整 IDE 元素的大小。
对Java支持的更新
- 字符串模板中的语言注入
IntelliJ IDEA 2024.1 引入了将语言注入字符串模板的功能。
用户既可以使用注解(注解会自动选择所需语言),也可以使用 Inject language or reference(注入语言或引用)来从列表中手动选择语言。
- 改进的日志工作流
由于日志记录是日常开发的重要环节,新版本引入了一系列更新来增强 IntelliJ IDEA 在日志方面的用户体验。
比如现在用户可以从控制台中的日志消息中轻松导航到生成它们的代码。
此外,IDE会在有需要的位置建议添加记录器,并简化插入记录器语句的操作,即便记录器实例不在作用域内。
- 新检查与快速修复
新版本为 Java 实现了新的检查和快速修复,帮助用户保持代码整洁无误。
比如,IDE 现在会检测可被替换为对 Long.hashCode() 或 Double.hashCode() 方法的调用的按位操作。
此外,新的快速修复也可以根据代码库的要求简化隐式和显式类声明之间的切换。
另一项新检查为匹配代码段建议使用现有 static 方法,使代码可以轻松重用,而无需引入额外 API。此外,IDE现在可以检测并报告永远不会执行的无法访问的代码。
- 重构的 Conflicts Detected(检测到冲突)对话框
这次版本 2024.1 重构了 Conflicts Detected(检测到冲突)对话框以提高可读性。
现在,对话框中的代码反映了编辑器中的内容,使用户可以更清楚地了解冲突,并且 IDE 会自动保存窗口大小调整以供将来使用。
另外,这次还更新了按钮及其行为以简化重构工作流,对话框现在可以完全通过键盘访问,用户可以使用快捷键和箭头键进行无缝交互。
- Rename(重命名)重构嵌入提示
为了使重命名流程更简单、更直观,新版推出了一个新的嵌入提示,在更改的代码元素上显示。要将代码库中的所有引用更新为新版本,点击此提示并确认更改即可。
版本控制系统改进
- 编辑器内的代码审查
IntelliJ IDEA 2024.1 为 GitHub 和 GitLab 用户引入了增强的代码审查体验。
该功能与编辑器集成,以促进作者与审查者直接互动。在检查拉取/合并请求分支时,审查模式会自动激活,并在装订区域中显示粉色标记,表明代码更改可供审查。
点击这些标记会弹出一个显示原始代码的弹出窗口,这样用户就能快速识别哪些代码已被更改。
装订区域图标可以帮助用户迅速发起新讨论,以及查看和隐藏现有讨论。另外这些图标还可以让用户更方便地访问评论,从而更轻松地完成查看、回复等功能。
- Log(日志)标签页中显示审查分支更改的选项
新版通过提供分支相关更改的集中视图来简化了代码审查工作流。
对于 GitHub、GitLab 和 Space,用户现在可以在 Git 工具窗口中的单独 Log(日志)标签页中查看具体分支中的更改。用户可以点击 Pull Requests(拉取请求)工具窗口中的分支名称,然后从菜单中选择 Show in Git Log(在 Git 日志中显示)。
- 对代码审查评论回应的支持
新版开始支持对 GitHub 拉取请求和 GitLab 合并请求的审查评论发表回复,目前已有一组表情符号可供选择。
- 从推送通知创建拉取/合并请求
成功将更改推送到版本控制系统后,新版IDE将会发布一条通知,提醒用户已成功推送并建议创建拉取/合并请求的操作。
- 防止大文件提交到仓库
为了帮助用户避免由于文件过大而导致版本控制拒绝,新版IDE现在包含预提交检查,以防止用户提交此类文件并通知用户该限制。
构建工具改进
- 针对 Maven 项目的打开速度提升
新版 IDEA 现在通过解析 pom.xml 文件构建项目模型。这使得有效项目结构可以在几秒钟内获得,具有所有依赖项的完整项目模型则同时在后台构建,这样一来用户就无需等待完全同步即可开始处理项目。
- 从快速文档弹出窗口直接访问源文件
快速文档弹出窗口现在提供了一种下载源代码的简单方式。
现在当用户需要查看库或依赖项的文档并需要访问其源代码时,按 F1 即可。
更新后的弹出窗口将提供一个直接链接,用户可以使用它来下载所需的源文件,以简化工作流。
- Maven 工具窗口中的 Maven 仓库
Maven 仓库列表及其索引编制状态现在直接显示在 Maven 工具窗口中,而不是以前 Maven 设置中的位置。
- Gradle 版本支持更新
从这个新版本开始,IntelliJ IDEA 将不再支持使用低于 Gradle 版本 4.5 的项目,并且 IDE 不会对带有不支持的 Gradle 版本的项目执行 Gradle 同步。
运行/调试更新
- 多语句的内联断点
新版IDEA为在包含 lambda 函数或 return 语句的行中的断点设置提供了更方便的工作流。
点击装订区域设置断点后,IDE会自动显示可在其中设置额外断点的内联标记。每个断点都可以独立配置,释放高级调试功能。
- 条件语句覆盖
2024.1 新版使 IntelliJ IDEA 距离实现全面测试覆盖又近了一步。该项更新的重点是确定测试未完全覆盖代码中的哪些条件语句。
现在,IntelliJ IDEA 既显示哪一行具有未覆盖的条件,还会指定未覆盖的条件分支或变量值。 这项功能默认启用。
框架和技术
- 针对 Spring 的改进 Bean 补全和自动装配
IntelliJ IDEA Ultimate 现在为应用程序上下文中的所有 Bean 提供自动补全,并自动装配 Bean。
如果 Bean 通过构造函数自动装配依赖项,则相关字段也会通过构造函数自动装配。 同样,如果依赖项是通过字段或 Lombok 的 @RequiredArgsConstructor 注解注入,则新 Bean 会自动通过字段装配。
- 增强的 Spring 图表
新版的 Spring 模型图表更易访问。用户可以使用 Bean 行标记或对 Spring 类使用意图操作 (⌥⏎) 进行调用。
同时新版为 Spring 图表引入了新的图标,增强了 Spring 原型(如组件、控制器、仓库和配置 Bean)的可视化。 此外,用户现在可以方便地切换库中 Bean 的可见性(默认隐藏)。
除此之外,其他包括像数据库工具、其他框架、语言和技术的支持等方面的更新和说明,大家也可参阅jetbrains.com/zh-cn/idea/whatsnew。
注:本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及程序员生活和感悟,欢迎star。
来源:juejin.cn/post/7355389990531907636
没想到学会这个canvas库,竟能做这么多项目
大家好,我是一名前端工程师,也是开源图片编辑器vue-fabric-editor项目的作者,2024年5月从北京辞职,我便开始了自己的轻创业之路,接触了不同的客户和业务场景,回顾这半年,没想到学会fabric.js
这个Canvas
库,竟能做这么多项目。
如果你打算学习一个Canvas
库或者做图片设计、定制设计相关的工具,我建议你学习一下fabric.js
这个库,它非常强大,可以做出很多有意思的项目,希望我的项目经历能给你的技术选型做一些参考。
项目经历
从北京回老家邯郸后,我陆续做了很多项目,包括正件照设计、锦旗/铭牌定制工具、Shopify定制插件、批量生成图片、手机版图片设计工具、服装设计、电商工具等,这些项目都离不开fabric.js
这个库。回顾这段经历,让我深刻体会到它的强大和广泛应用。
图片设计
图片设计是我接触的第一个主要应用领域。项目最初源于一个小红书成语卡片设计工具的构想,随后逐步扩展到更广泛的设计场景,包括小红书封面、公众号头图、营销海报以及电商图片等多种自媒体内容制作。
这类应用的核心功能在于自定义画布尺寸和元素排版,得益于fabric.js的原生支持,实现起来相对简单。我们主要工作是开发直观的属性编辑面板,使用户能够便捷地调整所选元素的文字和图片属性。
当然如果做的完善一些,还需要历史记录
、标尺
、辅助线对齐
、快捷键
等,这些功能fabric.js
并没有包含,需要我们自己实现,这些功能可以参考vue-fabric-editor 项目,它已经实现了这些功能。
还有很多细节的功能,比如组合保存、字体特效、图层拖拽、图片滤镜等,这些功能我们做的比较完善了。
定制设计工具
图片设计的场景相对通用,没有太多定制化的需求。而定制类的设计工具则需要针对特定场景深度开发,比如正件照、锦旗/铭牌设计、相册设计等,每个场景有不同的定制功能。
正件照设计工具的核心在于自动化的处理。主要工作量集中在尺寸的匹配,确保图片能自动调整到最佳大小。同时,需要提供人物图片的裁剪功能,让用户能便捷地进行换装、切换正件尺寸、更换背景等操作。
锦旗与铭牌设计则更注重文字内容的自动排版。系统需要根据用户输入的抬头、落款、赠言等内容,自动计算最优的文字间距和整体布局,确保作品的美观性。特别是铭牌设计,还需要实现曲线文字功能,让文字能够优雅地沿着弧形排布。
相册设计工具的重点是提供灵活的画布裁剪功能。用户可以使用各种预设的形状模板来裁剪图片,需要确保裁剪后的图片既美观又协调,最终生成精美的画册作品,交互上方便用户拖拽图片快速放入裁剪区域。
电商工具
电商场景比图片设计更垂直,除了普通的平面设计,例如店铺装修、商品主图、详情图的设计,另外还需要对商品进行换尺寸、抠图、换背景、去水印、涂抹消除、超清放大等操作,这些对图片处理的要求更高一些。
批量生成
批量算是一个比较刚需的功能,比如电商的主图,很多需要根据不同产品到图片和价格来批量加边框和文字,以及节庆价格折扣等,来生成商品主图,结合图片和表格可以快速生成,减少设计师的重复工作量。
另一部分是偏打印的场景,比如批量制作一些商品的二维码条形码,用在超市价签、电子价签、一物一码、服装标签等场景,根据数据表格来批量生成。
这种项目主要的工作量在交互上,如何将画布中的文字和图片元素与表格中的数据一一对应,并批量生成,另外会有一些细节,比如条形码的尺寸、图片的尺寸如何与画布中的尺寸比例进行匹配,这些细节需要我们自己实现。
上边的方式是通过表格来批量生成图片,还有一种是根据 API来批量生成图片,很多场景其实没有编辑页面,只希望能够通过一个 API,传入模板和数据,直接生成图片,fabric.js 支持在nodejs 中使用,我们要做的就是根据模板和数据拼接 JSON,然后通过fabric.js 在后端生成图片,然后返回给前端,性能很好,实际测试 2 核 2G 的机器,每张图片在 100ms 左右。
很多营销内容和知识卡片、证书、奖状也可以通过批量生成图片API来实现。
当然,还有一些更复杂的场景,比如不同的数据匹配不同的模板,不同的组件展示不同的形式等,包括错别字检测、翻译等,我们也为客户做了很多定制化的匹配规则。
服装/商品定制
服装/商品定制是让用户在设计平台上上传图片,然后将图片贴图到对应的商品模板上,实现让用户快速预览设计效果的需求。
这种场景一般会分为 2 类,一类是是针对 C 端用户,需要的是简单、直观,能够让用户上传一张图片,简单调整一下位置就能确认效果快速下单。
我在这篇文章里做了详细介绍:《fabric.js 实现服装/商品定制预览效果》。
另一类是针对小 B 端的用户,他们对设计细节有更高的要求,比如领子、口袋、袖子等,不同的位置进行不同的元素贴图,最后将这些元素组合成一个完整的服装效果图,最后需要生成预览图片,在电商平台售卖,完成设计后,还要将不同区域的图片进行存储,提供给生产厂家,厂家快速进行生产。
比如抱枕、手机壳、T恤、卫衣、帽子、鞋子、包包等,都可以通过类似服装设计的功能来实现。
很多开发者会提出疑问,是否需要介入 3D 的开发呢?
我们也和很多客户沟通过,从业务的角度看,他回答是:3D 的运营成本太高。他们做的都是小商品,SKU 很多很杂,如果每上一个商品就要进行 3D 建模,周期长并且成本高,他们更希望的是通过 2D 的图片来实现,而且 2D 完全能够满足让用户快速预览确认效果的需求,所以 2D 的服装设计工具就成为了他们的首选。
包装设计
包装设计是让用户在设计平台上,上传自己的图片,然后将图片贴图都包装模板上,主要的场景是生成定制场景,比如纸箱、纸袋、纸盒、纸杯、纸质包装等,这些场景需要根据不同的尺寸、形状、材质、颜色等进行定制化设计,最后生成预览图片。
因为设计到不同的形状和切面,而且大部分是大批量定制生产,所以对细节比较谨慎,另外包装规格相对比较固定,所有用3D模型来实现就比较符合。
另外,在确定设计效果后,需要导出刀版图,提供给生产厂家,厂家根据刀版图进行生产,所以需要将设计图导出为刀版图,这个功能 fabric.js 也支持,可以导出为 SVG 格式直接生产使用。
AI结合
在AI 大火的阶段,就不得不提 AI 的场景了,无论在自媒体内容、电商、商品、服装设计的场景,都有 AI 介入的影子,举个例子,通过 AI生成内容来批量生成营销内容图片,通过 AI 来对电商图片进行换背景和图片翻译,通过 AI 生成印花图案来制作服装,通过 AI 来生成纹理图来生成纸盒包装,太多太多的 AI 的应用场景,也是客户真金白银定制开发的功能。
展望2025
从图片设计的场景来看,我们的产品已经很成熟了,也算是主力产品了,未来会持续迭代和优化,让体验更好,功能更强大,把细节做的更完善,例如支持打印、视频生成等功能。
从定制设计工具的场景来看,我们积累了不同商品定制设计的经验,从技术和产品到角度看,我们还可以抽象出更好的解决方案,让客户能够更高效、低成本的接入,提供给他们的客户使用,快速实现设计生产的打通。
2024 到 2025 ,从在家办公一个人轻创业,搬到了我们的办公室,期待未来越来创造更多价值。
总结
半年的时间,这些项目的需求fabric.js
都帮我们实现了,所以如果你对Canvas
感兴趣,我的亲身经历告诉你,学习fabric.js
是一个不错的选择。
另外,对我来说更重要的是,客户教会了我们很多业务知识,这些才是宝贵的业务知识和行业经验,一定要心存敬畏,保持空杯,只有这样我们才能做好在线设计工具解决方案。
这篇文章也算是我从 2024年离职出来到现在的一个年终总结了,希望我们踩过的坑和积累的经验都变成有价值的服务,作为基石在2025年服务更多客户,文章内容供大家一些参考,期待你的批评指正,一起成长,祝大家 2025年大展宏图。
给我们的开源项目一个Star吧:github.com/ikuaitu/vue… 😄。
来源:juejin.cn/post/7459286862839054373
《真还传》续集来了,罗永浩J1助手能否创造“锤子”奇迹?
罗永浩备受期待的AI应用——J1 Assistant终于上线了,这款应用标志着他“最后一次创业”的正式启动。曾因锤子手机与SmartisanOS而广受关注的罗永浩,在经历过硬件领域的沉浮后,终于转向了AI技术的蓝海。J1 Assistant的上线,成为了他在AI领域的新起点,而这款产品也标志着罗永浩的“真还传”之路的回归。
目前,J1 Assistant已在Android平台推出Beta版,但只有三星Galaxy和谷歌Pixel的最新三代机型支持,且仅提供英文版,显然其首要市场定为海外而非国内。这一策略也印证了罗永浩的新目标——跨越国界,将AI技术推向全球。
与此同时,罗永浩的另一款 AI 硬件新品——JARVIS ONE也在路上,官网已有预告,预计将参加即将举行的CES 2025消费电子展。
请在此添加图片描述
锤子味的AI助手:重回初心,旧貌换新颜
从产品的设计来看,J1 Assistant显然延续了“锤子味”,无论是UI设计还是功能整合,都能看到过去SmartisanOS的影子。这款应用不仅仅是一个普通的AI助手,它可以视作待办清单、便签、AI聊天、即时通讯、搜索等多种功能的集合体。对于曾经使用过锤子手机的用户来说,这些熟悉的设计元素不仅带来了一种情感上的回归,也让人感受到一种怀旧的力量。
J1 Assistant通过五个Tab区分了五大核心功能:To Do(待办清单)、Notes(笔记)、AI Assistant(助手)、J1 Message(聊天)和Search(搜索) 。其中,Notes的设计延续了锤子便签的风格,但功能上略显简陋,缺乏排版工具和图片分享功能;To Do则是一个基础版待办清单,操作简便,却略显单一。
请在此添加图片描述
AI与信息管理的双重打击:技术与实用的结合
与传统的待办清单和笔记功能相比,J1 Assistant还融入了更多创新性功能——J1 Message和Search。J1 Message的设计灵感来源于已停运的“子弹短信”,用户通过注册后可进行即时聊天,然而在如今的即时通讯市场竞争如此激烈的情况下,这一功能能否得到用户的广泛接受仍有待观察。而Search功能,则类似于TNT的“发牌手”,支持多来源的搜索,用户可以根据自己的需求自定义最多五个来源进行查询。
在语音交互方面,J1 Assistant也做了突破,采用了“Ripple Touch(波纹触摸)”设计,用户按住语音图标进行语音输入时,可以灵活选择不同的操作方式(如保存为笔记、待办清单或直接发送给他人),这无疑增加了操作的便捷性和实用性。
请在此添加图片描述
AI价值的延伸:效率与便捷并重
J1 Assistant的最大亮点,不仅仅是传统的AI对话功能,而是它如何巧妙地将AI与信息管理结合,创造多重价值转化。举个例子,用户可以要求AI将它的回答直接保存为待办清单或笔记,从而将信息的价值最大化。如果你在CES 2025期间,想要记录重要的展会活动,AI助手就能帮助你快速整理信息,自动生成待办清单和笔记,从而大大提升工作效率。
尽管目前J1 Assistant还处于Beta版本,存在一些问题和bug,比如部分AI回答保存不完整,或者待办清单内容丢失等,但这些问题在更新后有望得到解决。正如任何一款刚上线的产品一样,J1 Assistant的潜力还有待发掘。
定位问题:这款APP真的能满足我们需求吗?
虽然J1 Assistant融合了多个创新功能,但它的市场定位仍显模糊。与其他成熟的AI助手相比,J1 Assistant的功能整合显得有些“乱炖”,缺乏独特的亮点和清晰的市场定位。尤其是在待办清单和笔记功能这一块,它是否能够真正吸引到那些有强烈需求的用户,仍然是一个未知数。
罗永浩的AI之路,还能走多远?
请在此添加图片描述
J1 Assistant是罗永浩进入AI领域的第一步,但要想在竞争激烈的市场中脱颖而出,它仍面临不小的挑战。从目前来看,J1 Assistant确实有着不少创新之处,尤其是在信息管理和AI对话的结合方面,但它能否真正解决用户的痛点,还需更多时间的验证。罗永浩的“真还传”之路,依然充满变数,他的AI应用能否走得更远,或许最终取决于它是否能够精准满足用户的实际需求,带来真正的价值。
从硬件到软件,罗永浩的创业之路仿佛进入了一个全新的阶段。这一次,他的“AI助手”会带给我们什么样的惊喜,值得每个人期待。
来源:juejin.cn/post/7457567841556693032
前支付宝工程师带你复盘支付宝P0故障
前支付宝工程师带你复盘支付宝P0故障
事故介绍
首先叠个甲,所有数据来自互联网公开数据,没有泄露任何老东家数据。
大家可能都听说了,但是我还是介绍一下。2025-1-16 14:40开始,一部分用户发现在支付宝内进行支付时,发现订单被优惠减免了20%的金额。意味着原本你买一个手抓饼可能100块,用支付宝直接省20。而且这不仅限于支付订单,转账订单也可以享受这个优惠(不知道有没有人开两个小号,反复转账薅羊毛的)。
支付宝方面很快反应,在14:45时完成了故障修复,并且在2025-1-17 1:00发出声明,称不会对享受到这个优惠的用户追回资金。(敢做敢认,点赞)
产生原因分析
在支付宝的通告里,我们看到,产生这个事故的原因是"某个常规营销活动后台配错了营模板",这句话我给大家解释一下。一般一个新的活动功能的上线,可能需要程序员开发新的功能,然后将功能里需要使用的规则,做成配置,配置在营销中心的管理后台上。当然对于一些比较成熟的活动,也可以直接复用以前的代码,只需增加配置即可。
但是在通告里,我们没办法判断这是一个新开发的活动还是复用以前开发出的老活动。针对需要开发的新活动和可以直接使用配置复用的老活动,这两者我们单独分析。
需要开发的新活动
一般新活动开发完毕后,正常是程序员会提前告诉运营在配置中心建好规则,并且将灰度人群置为0。然后程序员发布新代码,发布中因为没有人群命中活动规则,所以活动不生效。为了验证代码有效,会在发布的早期,让运营在营销中心配置一些测试用户,来测试看能否命中规则,并且验证活动在后续的收单,结算流程里是否正常。
举一个实际的例子,程序员小薰在服务demo order service里开发了新功能,需要发布上线,demo order service假设有1000台机器,小薰联系运营小丽在运营中心配置几个灰度账号,用来验证在线上发布后功能是否正常。在灰度发布阶段,会先选择几台机器做灰度,一般不超过10台,比如
- 第一批次 3台
- 第二批次 7台
- 第三批次 5%
- 第四批次 10%
- 第五批次 20%
- 第六批次 35%
- 第七批次 全量发布剩下所有的机器
根据支付宝解决故障只花了5分钟,我们可以推测出,应该是在第一批次时就发现了问题,并且采取了止血措施。那么收到影响的流量就是0.3%-1%,大家可能好奇为什么能确定说肯定是在第一批次就收到的影响,因为实际一次服务重启很耗时,一般都不止5min,而且服务发布后会有10-30min的观察期,再结合发生事故到解决事故总共没花费5min,我们可以推测出,第二批次应该还没发布。
但是流量并不代表受到影响的支付单量,因为要考虑有多少人命中了这个规则,这就取决于运营的配置了,如果运营只是把一些不应该开灰的用户加在了白名单里,影响还好,只会有一些固定的人员受影响。但如果运营是直接100%用户全量灰度,那就糟糕了。
我们得出在这种情况下,受到影响的单量范围为(0, 1%)
亏了多少钱
相信大家最感兴趣的一定是这次事故支付宝到底亏了多少钱,要回答这个问题我们首先要知道支付宝一天的交易流水是多少。当然这种数据官方一般是不会放出来的。但是我们可以大概算一下
23年移动支付555万亿,增速为11%。我们假设还是按照这个增速来预测24年的移动支付,当然还要考虑支付宝和微信在移动支付交易市场的份额,大概6/4开的比例。
那么24年的移动支付交易额
#以下单位亿元
5550000 * 1.11 = 6160500
#那么24年的平均日交易额
6160500/365 = 16878.08
#考虑到昨天事故发生的时间已经是25年,我们直接用24年的平均日支付交易额不合适,我们假设移动支付的日交易流水增长是线性的,我们再乘以一个增长速率得到12月的移动支付日交易额
16878.08 * 1.22 = 20561.26
#再乘以支付宝再移动支付市场的份额
20561.26 * 0.6 = 12336.76
那么我们假设支付宝一天的交易金额12336.76亿元,那么结合我们预估的影响流量范围(0, 1%),以及每一单20%的优惠力度,影响的时间5min,得出在事故事件内受影响的订单数量
# 单位亿元
12336.76 * 0.01 * 0.2 * 5 / 60 / 24 = 0.0857
即亏损金额不超过857W,实际上考虑到这种带有优惠的活动可能会有用户薅羊毛,重复下单,我们可以再把它影响范围乘以一个放大系数,比如1.5,即
#单位万元
857 * 1.5 = 1286
可以看到金额顶天也不会超过1286W,而且在实际的营销活动里配置的时候,一个营销活动的预算金额是会有上限的我们称为资金池,超过这个上限即使参加活动也不能享受到优惠,这个金额我们假设为2000W(一般活动不会这么多,一般几百万就算多了)。如果资金池小于1286W,比如资金池只有500W,资损的上限就只有500W了。
总的来说,我们还是认为本次事故的资损对于支付宝来说并不算多,大概1286W。
那些人会背锅
大家第二感兴趣的肯定就是那些人会背锅了,我们可以从整个流程上看那些人参与了这次事故。
- 运营 作为直接引发本次事故的责任人,没有认真检查配置,就上线,肯定要背大锅,主要责任跑不掉。(考虑到蚂蚁最近的降本增效,不知道这位运营是不是本部的也说不准)
- 开发功能的程序员 开发功能的程序员负责上线服务,并且把配置设计好交给运营人员去配置,虽然不是程序员配的,但是作为服务的owner,理论上应该要再发布前再去确认一下这个配置正不正确,所以开发跑不了(是不是感觉pua太凶了,放心吧,实际老板找你北固时说辞肯定比这更严重)
- 运营的老板,以及审核了运营配置的老板 所有配置都需要老板审核才能生效的,虽然老板一般不细看,但是不出事都好,出了事,背锅吧。
- 测试 测试老实说,责任不大,一般这种时候就是拉出来做替罪羊,和开发一样要带点连带责任。
- 程序员的老板 老板这种时候就还是连带责任
- 两位老板的顶头上司 这种级别的老板一般就是P9/P10了,分团队。责任大小也看事故的影响面,影响不大的话,处分大概就是罚酒三杯,大的话,被一撸到底边缘化也有可能
不需要开发的老活动
如果是不需要开发的老活动,运营直接在运营中心改配置即可让活动生效,这种情况下受到影响的流量范围就取决于运营的操作了,(0,100%]都有可能
亏了多少钱
这种情况下资损的计算方式还是类似上面,只不过在流量的影响范围变大了,我们可以直接用上面计算出的亏损金额乘以流量的影响倍率
#单位万元
1286 * 100 = 128600
看起来好像很夸张128600,以下干到13Y了,但是还是像我们上面说的,资损不会超过资金池配额,我们假设的资金池配额5000W,所以其实还好。实际的资金池配额我想会远远低于这个数。
那些人会背锅
- 运营
- 运营的老板
- 运营的老板的老板
背锅原因同上,不细说了
事故总结
从这次的事故,我们可以复盘一下再新功能上线时需要面临哪些问题以及对应的解决思路。当然支付宝内部也都有这些手段的成熟解决方案,但是实际落在执行上却是稀巴烂。
- 功能开发时要做好监控,能够监控出功能异常的流量并及时报警(本次事故的表现里得满分)
- 做好发布前的配置检查,配置上线一定要有审批,开发要和实际的配置操作人确认,这里的确认不仅仅是口头确认,要自己心里有数(本次事故得0分)
- 发布前要做好降级预案,必须要保证当功能出现异常时能降级(本次事故表现满分)
来源:juejin.cn/post/7460781036075761673
2024年终总结——未来该走向何处?
生活
年初,在老家办了答谢宴,也算是完成了父母的心愿,他们总说着等我结婚了,他们就可以退休了,虽然没有挣到钱,买的那个社保也领不了多少钱。但想到他们马上60了,人生难道就是打一辈子工吗?我支持他们回老家,无论怎样,有钱多花点,没钱少花点。
5.1 去了抚仙湖,昆明,抚仙湖之前去过一次,这次换了个位置,找了个山上的民宿,自由风还是很舒服。
现在想想,跳龙门,我是不是应该从上面翻过去,哈哈哈哈😂!!!!
7月份暑假期间,又去了一次三亚,住了一次海景房,贵的确实有道理。这次6个小伙伴一起,租了一辆车,沿着海南的东线,从三亚自驾到海口,第一次知道海南的高速不收费。
8月份去了贵州,赤水瀑布,赤水瀑布有充电桩,进去游玩的时间正好充电,算是第一次自驾游吧(海南算租车游😂);
当时,出去游玩全是大妈大爷,好羡慕他们呀,想原地退休!
去看了草原,和我想象的不一样,我一直以为草原就是公园的放大版,还是我见识少了,去了六盘水是真的凉快。
8月底,我老婆怀孕了,也是在5.1 开始备孕的,本来想的是到8月再怀不上,就等明年了,相当于我们在他不到一个月的时候,就带他去自驾游了,也是心大。
国庆节,因为怀孕,就没出远门,我自己回了趟老家,然后去定了月子中心,老婆结婚那会儿就说一定要住月子中心。知道怀孕后,就是产检,但基本都约的周内,我没有去,丈母娘陪着去的。
月子中心提供了孕期瑜伽,我还陪着她去参加了两次双人瑜伽。
12.22号去看了四维,有了他的第一张照片,也开始焦虑,该怎么教育小孩,都是全新的体验。期待着他的到来...
自媒体
技术方面,已经算是停止输出了。
今年尝试了很多小红书账号,我原本做苔藓的账号,靠ai生成的图,涨了几千粉,但没找到变现的路子,然后停了。
前端的账号,无意中接了一个面试招人的单子,挣了2000。然后觉得前端已死,技术方面不想做了,就注销了账号。
陆陆续续做了ai 壁纸,自媒体方面的,还找了一个做项目的人,花了300块钱,但是后面我也没做起来。12月份去考了普通话,我小学教资笔试面试都过了,就等普通话二甲,就可以领证了。然后就自己做了个普通话学习助手,又在小红书做起了,普通话赛道。
http://47.109.182.55/
对了,母婴赛道也起了个账号,现在主要都在记录一些孕期日常,看看后面小孩出生有没有什么可做的选题吧。
今天自媒体账号真的做了好多,肯定不少于6个。
还有抖音,也搬运了一些视频,没什么好的点子,算了吧。
前端&工作
还记得,当年刚入行的时候,写的第一篇文章《从学校到实习直至毕业,前端——我一直在路上》,还想着老了来回味,哈哈哈,感觉我的职业生涯也走到了末期了!!
因为我已经不看好做技术了,就业形势艰难,我的群里,今年失业的好多,还有人被裁了两三次,工资一降再降,都还是要干。也有认识的大学生,毕业了一直找不到工作,特别是双非大学生,马上过完年,新一批的大学生又要出来了,哎...
我也停止了在技术上进一步深耕,一直都在尝试自媒体,其他的实体创业也不敢辞职去干。
工作上呢,在这个公司快两年了,也没有做出什么成功的产品,我对公司的方法论也不认可了,一句话在这行疲了 —— 巨轮难掉头!
今年的精力大多花在了自媒体上,技术方面没学什么东西,公司今年新项目又用上了nextjs,现在我们是vue3, react, nextjs, nuxtjs 并行,好几个项目同时在做,感觉就技术来说,公司领导想要啥就用啥呗。
而且我们都用上了cursor,ai的发展让我更焦虑了,在ai的时代,我们应该扮演什么样的角色。首先我也认同ai不可能取代所有的程序员,但它提高了生产效率的同时,必然就用不了那么多人了。我之前看到一句话:“不要和ai比智力,不要和机器人比体力”,我很认同,有了cursor,我的工作变得更简单了,更快捷了。我也不觉得,我们再去学什么技术栈,能比他更快,特别是工作过几年的同学,语言都是相通的,有了ai的加持,切换技术栈就像喝水一样,可能有点夸张,但门槛已经很低了。
上面的普通话助手,我就是让cursor写的,我主要提供一下数据,做一些微调,我觉得它真的挺不错的,当然它就是不能背锅!😂
前端,我可能不能一直在路上了,对不起,我明年还得大力尝试做其他的突破,寻找下一个方向。
最后
各位,不破不立,愿新的一年,万事顺心!!!!
来源:juejin.cn/post/7451819556030758947
Timesheet.js - 轻松打造炫酷时间表
Timesheet.js - 轻松打造炫酷时间表
前言
在现代网页设计中,时间表是一个常见的元素,用于展示项目进度、历史事件、个人经历等信息。
然而,创建一个既美观又功能强大的时间表并非易事。
幸运的是,Timesheet.js
这款神奇的 JavaScript
开源时间表库为我们提供了一个简洁而强大的解决方案。
本文将详细介绍 Timesheet.js
的特点、使用方法,并通过一个真实的使用案例来展示其强大功能。
介绍
Timesheet.js
是一个轻量级的 JavaScript
库,专门用于创建基于 HTML5
和 CSS3
的时间表。
它无需依赖任何外部框架,如 jQuery
或 Angular.js
,即可快速生成美观的时间表布局。
Timesheet.js
的优势在于其简洁性和用户友好性,仅需几行 JavaScript
代码即可实现功能,同时提供了丰富的自定义选项,允许开发者根据需求进行样式调整。
核心特性
无依赖:不依赖任何外部 JavaScript
框架,减少了项目复杂性和加载时间。
易于使用:通过简单的 JavaScript
代码即可创建时间表,易于上手。
高度可定制:提供了丰富的 CSS
类,方便开发者自定义时间表的外观。
响应式设计:支持移动设备,确保在不同屏幕尺寸上都能良好显示。
官方资源
官网:sbstjn.github.io/timesheet.j…
GitHub 仓库:github.com/sbstjn/time…
使用案例
假设我们要为一个在线教育平台创建一个展示学生学习历程的时间表。
这个时间表将展示学生从入学到毕业的各个阶段,包括参加的课程、获得的证书等信息。
步骤 1:引入库文件
首先,在 HTML
文件中引入 Timesheet.js
的 CSS
和 JavaScript
文件。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/timesheet.js/dist/timesheet.min.css" />
<div id="student-timeline">div>
<script src="https://cdn.jsdelivr.net/npm/timesheet.js/dist/timesheet.min.js">script>
步骤 2:准备数据
接下来,准备时间表所需的数据。
在这个案例中,我们将展示一个学生从 2018 年入学到 2022 年毕业的学习历程。
const studentTimelineData = [
['09/2018', '06/2019', '入学 & 基础课程学习', 'default'],
['09/2019', '06/2020', '专业课程学习', 'ipsum'],
['07/2020', '01/2021', '暑期实习', 'dolor'],
['09/2020', '06/2021', '高级课程学习', 'lorem'],
['07/2021', '01/2022', '毕业设计', 'default'],
['06/2022', '09/2022', '毕业 & 就业', 'ipsum']
];
步骤 3:初始化 Timesheet.js
最后,使用 Timesheet.js
初始化时间表,并传入准备好的数据。
完整代码
将上述代码整合到一个 HTML
文件中,即可创建出一个展示学生学习历程的时间表。
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生学习历程时间表title>
<link rel="stylesheet" href="./timesheet.js/dist/timesheet.min.css" />
head>
<body>
<div id="student-timeline">div>
<script src="./timesheet.js/dist/timesheet.min.js">script>
<script>
const studentTimelineData = [
['09/2018', '06/2019', '入学 & 基础课程学习', 'default'],
['09/2019', '06/2020', '专业课程学习', 'ipsum'],
['07/2020', '01/2021', '暑期实习', 'dolor'],
['09/2020', '06/2021', '高级课程学习', 'lorem'],
['07/2021', '01/2022', '毕业设计', 'default'],
['06/2022', '09/2022', '毕业 & 就业', 'ipsum']
];
const timesheet = new Timesheet('student-timeline', 2018, 2022, studentTimelineData);
script>
body>
html>
效果如下
总结
Timesheet.js
是一个非常实用的 JavaScript
时间表库,它以简洁的代码和强大的功能为开发者提供了一个创建时间表的便捷工具。
通过本文的介绍和使用案例,相信你已经对 Timesheet.js
有了基础的了解。
无论是在个人项目还是企业应用中,Timesheet.js
都能帮助你快速创建出美观且功能强大的时间表,提升用户体验。
如果你对 Timesheet.js
感兴趣,不妨尝试在自己的项目中使用它,探索更多可能。
来源:juejin.cn/post/7461233603431890980
身份认证的尽头竟然是无密码 ?
概述
几乎所有的系统都会面临安全认证相关的问题,但是安全相关的问题是一个很麻烦的事情。因为它不产生直接的业务价值,而且处理起来复杂繁琐,所以很多时都容易被忽视。很多后期造成重大的安全隐患,往往都是前期的不重视造成的。但庆幸的是安全问题是普遍存在的,而且大家面临的问题几乎相同,所以可以制定行业标准来规范处理,甚至是可以抽出专门的基础设施(例如:AD、LDAP 等)来专门解决这类共性的问题。总之,关于安全问题非常复杂而且麻烦,对于大多数 99% 的系统来说,不要想着在安全问题领域上搞发明和创新,容易踩坑。而且行业的标准解决方案已经非常成熟了。经过长时间的检验。所以在安全领域,踏踏实实的遵循规范和标准就是最好的安全设计。
HTTP 认证
HTTP 认证协议的最初是在 HTTP/1.1标准中定义的,后续由 IETF 在 RFC 7235 中进行完善。HTTP 协议的主要涉及两种的认证机制。
基本认证
常见的叫法是 HTTP Basic,是一种对于安全性不高,以演示为目的的简单的认证机制(例如你家路由器的登录界面),客户端用户名和密码进行 Base64 编码(注意是编码,不是加密)后,放入 HTTP 请求的头中。服务器在接收到请求后,解码这个字段来验证用户的身份。示例:
GET /some-protected-resource HTTP/1.1
Host: example.com
Authorization: Basic dXNlcjpwYXNzd29yZA==
虽然这种方式简单,但并不安全,因为 base64
编码很容易被解码。建议仅在 HTTPS 协议下使用,以确保安全性。
摘要认证
主要是为了解决 HTTP Basic 的安全问题,但是相对也更复杂一些,摘要认证使用 MD5 哈希函数对用户的密码进行加密,并结合一些盐值(可选)生成一个摘要值,然后将这个值放入请求头中。即使在传输过程中被截获,攻击者也无法直接从摘要中还原出用户的密码。示例:
GET /dir/index.html HTTP/1.1
Host: example.com
Authorization: Digest username="user", realm="example.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", qop=auth, nc=00000001, cnonce="0a4f113b", response="6629fae49393a05397450978507c4ef1", opaque="5ccc069c403ebaf9f0171e9517f40e41"
**补充:**另在 RFC 7235 规范中还定义当用户没有认证访问服务资源时应返回 401 Unauthorized
状态码,示例:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Restricted Area"
这一规范目前应用在所有的身份认证流程中,并且沿用至今。
Web 认证
表单认证
虽然 HTTP 有标准的认证协议,但目前实际场景中大多应用都还是基于表单认证实现,具体步骤是:
- 前端通过表单收集用户的账号和密码
- 通过协商的方式发送服务端进行验证的方式。
常见的表单认证页面通常如下:
html>
<html>
<head>
<title>Login Pagetitle>
head>
<body>
<h2>Login Formh2>
<form action="/perform_login" method="post">
<div class="container">
<label for="username"><b>Usernameb>label>
<input type="text" placeholder="Enter Username" name="username" required>
<label for="password"><b>Passwordb>label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Loginbutton>
div>
form>
body>
html>
为什么表单认证会成为主流 ?主要有以下几点原因:
- 界面美化:开发者可以创建定制化的登录界面,可以与应用的整体设计风格保持一致。而 HTTP 认证通常会弹出一个很丑的模态对话框让用户输入凭证。
- 灵活性:可以在表单里面自定义更多的逻辑和流程,比如多因素认证、密码重置、记住我功能等。这些功能对于提高应用的安全性和便利性非常重要。
- 安全性:表单认证可以更容易地结合现代的安全实践,背后也有 OAuth 2 、Spring Security 等框架的主持。
表单认证传输内容和格式基本都是自定义本没啥规范可言。但是在 2019 年之后 web 认证开始发布标准的认证协议。
WebAuthn
WebAuthn 是一种彻底抛弃传统密码的认证,完全基于生物识别技术和实体密钥作为身份识别的凭证(有兴趣的小伙伴可以在 github 开启 Webauhtn 的 2FA 认证体验一下)。在 2019 年 3 月,W3C 正式发布了 WebAuthn 的第一版规范。
相比于传统的密码,WebAuthn 具有以下优势:
- 减少密码泄露:传统的用户名和密码登录容易受到钓鱼攻击和数据泄露的影响。WebAuthn,不依赖于密码,不存在密码丢失风险。
- 提高用户体验:用户不需要记住复杂的密码,通过使用生物识别等方式可以更快捷、更方便地登录。
- 多因素认证:WebAuthn 可以作为多因素认证过程中的一部分,进一步增强安全性。使用生物识别加上硬件密钥的方式进行认证,比短信验证码更安全。
总的来说,WebAuthn 是未来的身份认证方式,通过提供一个更安全、更方便的认证方式,目的是替代传统的基于密码的登录方法,从而解决了网络安全中的一些长期问题。WebAuthn 目前已经得到流程的浏览器厂商(Chrome、Firefox、Edge、Safari)、操作系统(WIndows、macOS、Linux)的广泛支持。
实现效果
当你的应用接入 WebAuthn 后,用户便可以通过生物识别设备进行认证,效果如下:
实现原理
WebAuthn 实现较为复杂,这里不做详细描述,具体可参看权威的官方文档,大概交互过程可以参考以下时序图:
登录流程大致可以分为以下步骤:
- 用户访问登录页面,填入用户名后即可点击登录按钮。
- 服务器返回随机字符串 Challenge、用户 UserID。
- 浏览器将 Challenge 和 UserID 转发给验证器。
- 验证器提示用户进行认证操作。
- 服务端接收到浏览器转发来的被私钥加密的 Challenge,以此前注册时存储的公钥进行解密,如果解密成功则宣告登录成功。
WebAuthn 采用非对称加密的公钥、私钥替代传统的密码,这是非常理想的认证方案,私钥是保密的,只有验证器需要知道它,连用户本人都不需要知道,也就没有人为泄漏的可能;
备注:你可以通过访问 webauthn.me 了解到更多消息的信息
文章不适合加入过多的演示代码,想要手上体验的可以参考 okta 官方给出基于 Java 17 和 Maven 构建的 webauthn 示例程序,如下:
来源:juejin.cn/post/7354632375446061083
前端同时联调多个后端
前言
最近公司项目有时需要和多个后端同时对接(😔小公司一对N),于是对公司项目基础配置文件做了些修改,最终达到能对于多个后端同时启动多个前端服务,而不是每次都需要和A同学对接完,代理地址再换成B同学然后重新启动项目
个人经验0.5年,菜鸡前端一枚,第一次写文章,只是对个人工作简要记录😂!!!
公司项目有vue脚手架搭建的也有vite搭建的,下面让我们分两种方式来修改配置文件
vue-cli方式【webpack】
1. 个人习惯把proxy单独抽离出来放到.env.development
# 启动端口号
VUE_PORT = 8000
# 代理配置
# A同学
VUE_PROXY_A = [["/api","http://localhost:3001"]]
# B同学
VUE_PROXY_B = [["/api","http://localhost:3002"]]
2. 使用cross-env来加载不同的代理
npm i -D cross-env
重新编写下script
3. 读取环境变量
vueCli内部dotenv已经加载到process.env,我们再做一层包裹,之前配置的proxy,这种其实是字符串,需要处理
const { VUE_PROXY, VUE_PORT } = require("./constant.js")
// Read all environment variable configuration files to process.env
function wrapperEnv(envConf) {
const ret = {}
const SERVER_NAME = process.env.NODE_ENV_PROXY || VUE_PROXY
for (const envName of Object.keys(envConf)) {
if (!envName.startsWith('VUE')) {
continue
}
let realName = envConf[envName].replace(/\\n/g, '\n')
realName = realName === 'true' ? true : realName === 'false' ? false : realName
if (envName === VUE_PORT) {
realName = Number(realName)
}
if (envName === SERVER_NAME && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'))
} catch (error) {
realName = ''
}
}
ret[envName === SERVER_NAME ? VUE_PROXY : envName] = realName
if (typeof realName === 'string') {
process.env[envName] = realName
} else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName)
}
}
return ret
}
module.exports = {
wrapperEnv
}
这样我们就可以拿到所有的环境变量,并且proxy是数组,而不是字符串
4. 生成proxy
/**
* Used to parse the .env.development proxy configuration
*/
const httpsRE = /^https:\/\//
/**
* Generate proxy
* @param list
*/
function createProxy(list = []) {
const ret = {}
for (const [prefix, target] of list) {
const isHttps = httpsRE.test(target)
// https://webpack.docschina.org/configuration/dev-server/#devserverproxy
ret[prefix] = {
target: target,
changeOrigin: true,
ws: true,
pathRewrite: { [`^${prefix}`]: '' },
// https is require secure=false
...(isHttps ? { secure: false } : {}),
}
}
return ret
}
module.exports = {
createProxy,
}
5. 修改vue.config.js
const { defineConfig } = require('@vue/cli-service')
const { wrapperEnv } = require('build/vue/util')
const { createProxy } = require('./build/vue/proxy')
const {
VUE_PORT,
VUE_PROXY
} = wrapperEnv(process.env)
module.exports = defineConfig({
transpileDependencies: true,
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: VUE_PORT,
open: false,
overlay: {
logging: 'info',
errors: true,
warnings: true
},
proxy: createProxy(VUE_PROXY),
disableHostCheck: true
},
})
6. 使用mock模拟两个后端服务
A同学使用3001端口
B同学使用3002端口
7. 测试是否达到效果
同样我们前端也起两个8000和8001
接下来看下8000端口请求
再看下8001请求
vite
结语
以上只写了webpack不过vite和这也差不多!!!
来源:juejin.cn/post/7456266020379541531
我在国企当合同工的那段日子
心血来潮
25号考完了,非常不理想,果然700页的东西不是一个月能搞完的。不对,我今儿写日志是为了纪念一下我的第一家公司,咋扯到别的了......言归正传,我在第一家公司待了仨年,可能是年纪到了(26岁咋还不退休啊),也可能是留了点感情在,离开前有些百感交集,思来想去还是写一个懒人日志吧,纪念一下我打工的三年光阴吧。
(:з」∠)
初说公司
先说一下俺的第一家公司,咱从学校出来就来这儿报道了,公司是国企控股,领导层全是国企员工,其他进公司的员工就是合同工,或者说是国企合同工,能吃公司东西,不是人力外包。
(:з」∠)
成都这边的开发都是围绕着云服务的,包括云操作系统、云桌面系统、云运维系统以及多云系统(我个人喜欢把他称为多个云集成系统),当然全是定制化项目。对,忘说了,公司主要业务是轨道交通行业,做云相关的产品是将轨道行业的运维放在云上面,算是相应国家的两化融合(信息化和工业化)。
对了,得说一下公司待遇,公司给的工资都在平均水平以下,尤其是对应届生而言,社保基数是工资八折(试用期)交的,公积金是12%,没有餐补但自带食堂以及饭卡补助,有些节假日有礼品,至少基础福利还好。
项目与业务
我所在的项目组就是多云系统,也算是我认为公司能拿得出手的项目。虽然是集成项目,但它只能集成。好像说了跟没说一样,那说具体点吧,比如说业主那边需要云,但怕私有云厂商垄断坐地起价,所以说一般配额划分为“7/3”、“4/3/3”、“6/4”,这样就有两套云系统,为了用起来顺心就需要一个集成系统,所以说我这个项目组的业务来源就是这样,至于你说的我们集成系统会不会垄断坐地起价,拜托,我们系统只会集成,没有底层设备控制权,坐地起价就直接禁用就行了,就不用这个系统呗,反正资源在另外的云操作系统中。
好了,话题回来,说说项目组开发相关的吧,项目开发受阻有三:与三方厂商沟通、项目代码老旧、随时随地变更的需求。
先说第一点吧,集成系统最大的麻烦就是跟三方厂商沟通,当然测试环境、测试数据获取这类的细节也算三方厂商沟通。因为地铁行业算是智能中国建设的一部分,所以说不光是我,连三方厂商的软件都必须是定制的。开发时候就要等着厂商环境稳定了,有数据了再联调,联调有bug了,再走一轮上面的流程,极大地增加了沟通成本以及开发成本。
在沟通,再沟通
其二就是和很多工业软件公司一样,软件项目时间跨度很大,里面东西不知道转手了好多次,缝缝补补式的开发,开发要考虑很多兼容性问题以及自己想办法写补丁。比如说node@6.x.x
不支持Object.entries
,你就要手动在webpack.base.conf.js
写的兼容,问我为啥不配置babel
呢,上次改babel
配置都是2016年的事儿了。代码要写兼容,久而久之就会忘记什么事封装、抽象,全部遗失在兼容的漩涡中。
我就改了一点点怎么崩了
其三就是随时随地变更的需求,这里我叠个甲,这个我不是甩锅给产品,虽然是产品改的需求,但产品不是想改就改,一定是业主/客户/上级/领导指示要改的。有需求变动谁都不会安逸,谁都烦,但请把炮火对准,不要误伤友军。频繁调整的需求会不断地消磨激情和热情,模糊项目方向,当然还有临时变卦导致的加班。
一直在变的需求
心态变化
三年工作时间虽然很短,但足以改变心态。原来有些迷茫到彻底迷茫;原来想要搞出一番事业到慢慢得过且过;原来想努力改变世界走到只想躺平加速世界毁灭。
公司的缝缝补补,工作的缝缝补补,项目的缝缝补补,这样的缝缝补补渐渐地缝补在人身伤,人心里。原来就算只有940的显卡也要努力熬夜玩游戏,现在用上3060ti后却只想打开直播看看,就只看看,重新上手玩太耗精力了。至于脱单嘛,自己都这么累了,为啥带着另一个一起累呢?
尾声
本来6月3号说写完的,忙着离职交接以及新公司入职,再加上拖延症又犯了,所以说一直到20号才写完,不过至少咱写完了,能发。
这篇算是自己里程记录,同时也是发牢骚,大家就当笑话看看吧。
来源:juejin.cn/post/7382121357608321059
太惨了,凌晨4 点替别人修复bug……
差点翻车
前两个月的某天凌晨,我司全新的一个营销工具,在全国如期上线。然而整个发布过程并非一帆风顺,在线上环境全量发布后,有同事观测到他所负责模块的监控曲线有异常!监控曲线在发布的时刻近乎于直线下跌。
经过初步排查,故障影响是:一部分新用户无法使用营销优惠~ 影响面非常大,所幸在凌晨的业务低峰期,实际影响有限,但是需要快速修复!不然等天亮用户请求量上来了,故障影响和定级就更大了!
目前接近凌晨4 点,时间很紧张!虽然这部分内容并非我负责,但我是当天的现场值班人,必须上!肝!
屎海无涯
我喝了一口红牛,打开电脑就扎进了陌生代码的汪洋大海中……
看着看着,我察觉到味道不对劲。我觉得这部分代码不是汪洋大海,而是一片屎海…… 代码堆砌如屎山,单个方法竟超过500行;嵌套的if else结构深不可测;日志更是完全缺失;职责不但不单一,反而极度混乱。总之,整个代码简直如同一团乱麻,排查难度极大。
四五个同事一起在排查代码,虽然他们负责过这部分代码,但是大家都十分挠头,找不到 bug 在哪。
当局者迷,旁观者清。经过了30分钟的细致分析,终于,我率先找到了 bug 原因。激动地心颤抖的手,我开了 5 分钟的 bug 发布会,通报了 bug 根因和修复方案。
破案了!
确定 bug 根因后,其他人默默去休息了……
接下来我负责修 bug、测试、打包、发版、验证…… 不知不觉,天空破晓,一直搞到早上 8 点多…… 在线上完成验证,监控曲线恢复正常!bug 修复完成!
bug根因
由于公司代码保密,所以我使用伪代码解释。
业务逻辑是遍历所有的优惠活动,若任意一个优惠活动需要限制新用户使用,那么就需要去查询当前用户是否新用户。
bug 代码如下! (实际的屎山代码,比这部分代码要复杂得多!)
boolean newUserCheckEnabled = false;
for ( Activity activity : activityList ) {
newUserCheckEnabled = activity.isLimitNewUser();
}
想必大家一眼就能看出问题所在!这样写代码, newUserCheckEnabled 等于最后一个活动的值,如果最后一个活动不限制新用户使用,那么 newUserCheckEnabled 就是 false,然而中间的活动可能需要限制新用户,于是 bug 产生了!
老板亲自指导写代码
正确的代码应该这样写,我按照如下方式修复了 bug,但是老板对代码不满意!
boolean newUserCheckEnabled = false;
for ( Activity activity : activityList ) {
if (activity.isLimitNewUser()) {
newUserCheckEnabled = true;
}
}
”一行代码就能解决的事,不需要使用 if “ ,老板看完我的代码后,说道。
他给出的代码示例如下,使用 || 表达式
boolean newUserCheckEnabled = false;
for ( Activity activity : activityList ) {
newUserCheckEnabled = newUserCheckEnabled || activity.isLimitNewUser();
}
if 代码被替换如下!
newUserCheckEnabled = newUserCheckEnabled || activity.isLimitNewUser();
"这能行吗”? 我的大脑飞速运转…… 这两段代码等价吗?似乎等价,但不是十分确定……
老板面前,不能暴露自己没跟上节奏,否则暴露智商。
我假装立刻明白,于是吹了一句,“卧槽,牛逼,这样写确实更加简洁吖!👍🏻”。(大家觉得应该怎么拍马屁,更合适?)
私底下,我还在心里嘀咕,两者真的等价吗?
现在我可以肯定:确实是等价的!
来源:juejin.cn/post/7425875126527918130
我以残躯入2025-以身入局胜天半子
我以残躯入2025-以身入局胜天半子
今天是 2025 年的第二天,我写下这篇文章,讲述我在 2024 年的经历和挑战,以及我如何克服困难,最终成功走出困境。本来是想昨天元旦第一天写的,但不凑巧的是元旦第一天我得甲流了,发烧了一天,所以今天才写这篇文章。
回顾2024
2024 年可以说是我经历的一个不平凡的一年,经历了股票亏损、事业不顺、年底被裁员、元旦得了甲流等诸多困难。
股票投资
我在 A 股其实也算一个老手了,虽然挣的少,但前 5 年基本上都是挣的,但比较悲催的是 24 年证券主席换了之后,市场行情波动极大。
先是我买的业绩不错的成长股持续下跌,后面换仓重仓买了国资背景的通信行业股票,想着上涨趋势但没想到 5 月直接 ST
了,直接导致了我今年股票大幅度下跌,最多时亏损达 50%+
。
同时也买了港股,港股也被踢出港深股通
了,股价至今还跌幅超过 80%
,基本上就是把我本金吃掉了。
因为股票跌幅比较大,每个交易日对我来说,都是折磨啊,我也很无奈。越追越跌、不追继续套牢,这种情况到 24 年 9 月才有一定的缓解。
事业不顺
23 年下半年,我手底下的俩个前端小伙伴被裁员了,导致从 23 年底到 24 年都非常的忙,被压的闯不过气来,也萌生了跳槽换个新的工作环境的想法,也出去找了工作,但一直找不到合适的工作,都要求降薪,那我就不能接受了。
然后,公司 9 月份搬家,我通勤时间增加了 20 分钟,每天来回就是 40 分钟,接近每天快 3 小时的通勤时间,然后公司还要求我们免费加班到 8 点,目前还在执行这个政策中,即晚 8 点才能下班,8 点之后才算加班调休,这点让我很头疼。
年底被裁员
到了年底 11 月公司开启了我入职以来的第三波裁员,这次的主题是降本增效
,在 11 月我们小部门裁掉了一个 BI(我们薪资最高的)、一个测试、产品、还给我们后端 JAVA 薪资最高的同学来了个降薪 3.5K
,据说我也在裁员名单上,我的薪资其实蛮高的,后来的开发基本上都比我低,但 11 月我没有收到对我裁员或降薪的通知,以为躲过了一次。
但到了 12 月上旬,有一天 CTO 让我晚点留下给我讲了合同到期不续的事情,至此我被正式裁员了,但因为提前一个月通知的,只给我赔偿 n,同时也不给我年终奖,前俩年我的年终奖其实挺高的,都是 3.5+ 的评分,有 2 个月。
虽然被裁了,但我也没有啥不开心的,因为公司的工作环境已经极度恶化了,年终下调、裁员、降薪、加班、同事内卷,已经不是我愿意为之付出努力的公司了。
元旦甲流
以为就这样熬过了 24 年,但危险悄然而至,12 月 31 号晚上,我开始发高烧,我的儿子从 30 号就开始高烧不退了,最后在元旦那天去医院确诊了甲流,也算是一个悲剧的开年,只能看着别人去逛商场跨年。
这也让我认识到身体健康其实是第一位的,其他都是其次。
走出困境
在投资方面,虽然目前我还是亏损的,但我有信心在 25 年收益回正,这一年的大幅下跌和波动,让我对如何研究股票和人性有了更深的体会,目前已经从亏损的 50% 回到了 20%,也让我有了更多的经历。
事业方面,年中开始我积极调整自己的个人状态,已经找回初心,开始了新的工作状态:积极写博客记录学习状态、持续进行技术分享、参加社区活动、积极的找新工作。通过持续的沉淀和学习,我逐渐找到了自己的兴趣和方向,那就是未来朝独立开发者的道路走,未来我可能是个销售、运营、产品、项目经理,但也会持续的进行产品开发,通过技术造福大众。
学习方面,我持续写了一些系列,大约有 60 多篇文章,也希望和大家一起学习,一起进步成长。
身体健康方面,后面我还是要减减肥、增强免疫力,让身体保持健康的状态。
祝福大家
写到这里就没啥写的了,祝福大家身体健康万事如意,以身入局胜天半子。
来源:juejin.cn/post/7454974103259824155
我的 CEO 觉得任何技术经理都是多余的
原文 QUESTIONABLE ADVICE: “MY BOSS SAYS WE DON’T NEED ANY ENGINEERING MANAGERS. IS HE RIGHT?”
我最近加入了一家初创公司,负责管理一个约 40 名工程师的团队,担任技术副总裁。然而,我与 CEO(之前是工程师)在是否需要雇佣专职技术经理的问题上产生了很大的冲突。目前,工程师们被分成了 3-4 人的小团队,每个团队有一个工程师头头,负责领导团队,但他们的主要职责仍然是编写代码和交付产品。
我有 HC 在未来一年雇佣更多的工程师,但没有经理的 HC。老板认为我们是初创公司,负担不起这种奢侈品。在我看来,我们显然需要技术经理,但在他看来,经理只是多余的开销,在我们的阶段所有人都应该全力编写代码。
我不知道该如何论证。在我看来这很显然,但实际上我很难用言语表达为什么我们需要技术经理。你能帮帮我吗?
—— 真的是多余的开销吗(?!)
这里有很多问题需要解答。
你的首席执行官不理解为什么需要经理,这并不奇怪,因为他似乎不明白为什么需要组织结构。🙈 他为什么要对你如何组织团队或你可以雇佣哪些角色进行微管理?他雇用了你来做这份工作,却不让你完成。他甚至不能解释为什么不让你做。这不是个好兆头。
但这个问题确实值得思考。我们假设他不是故意要刁难你。😒
我能想到两种论证雇用技术经理的方式:一种是相当复杂的,从第一性原理 (First Principle) 出发,另一种非常简单,但可能不太令人满意。
我个人对权威有一种强烈的反感;我讨厌被告知该做什么。直到最近,我才通过系统理论的视角,找到了一种对层级制度既健康又实用的理解。
为什么组织中存在层级制度?
层级制度确实带有很多负面包袱。我们许多人都有过在层级制度下与经理或整个组织打交道的不幸经历。在这些地方,层级制度被用作压迫的工具,人们通过垄断信息和玩弄权力游戏来提升地位,决策则是通过权力压制来做出。
在那种地方工作真的是一种折磨。谁愿意将自己的创造力和生命力投入到一个感觉像《呆伯特》漫画的地方,明知道自己的价值被极少认可或回报,而且这些价值会慢慢地但确实被压制掉?
但层级制度本质上并非是专制的。层级制度并不是人类为控制和支配彼此而发明的一种政治结构,它实际上是自组织系统的一种属性,是为了子系统的有效运作而出现的。事实上,层级制度对复杂系统的适应性、弹性和可扩展性至关重要。
让我们从一些关于系统的基本事实开始,为可能不熟悉的人介绍一下。
层级是自组织系统的一种属性
一个系统是「由相互依赖的组件组成的网络,这些组件共同工作以实现一个共同目标」(W. Edward Deming)。一堆沙子不是一个系统,但一辆车是一个系统;如果你把油箱取下来,车就无法运作。
子系统是一个在更大系统内有较小目标的元素集合。在一个系统中可以有很多层次的子系统,它们相互依存地运行。子系统总是为了支持更大系统的需求而工作;如果子系统只为自己的最佳利益优化,整个系统可能会挂掉(这就是「次优」(suboptimal)这个术语的由来 😄)。
如果一个系统能够通过多样化、适应和改进自身使自己变得更加复杂,那么它就是自组织的。随着系统自组织并增加其复杂性,它们往往会生成层级 —— 即系统和子系统的排列。在一个稳定、有弹性和高效的系统中,子系统在很大程度上可以自我管理、自我调节,并为更大系统的需求服务,而更大系统则负责协调子系统之间的关系并帮助它们更好地发挥作用。
层级最小化了协调成本,减少了系统中任何部分需要跟踪的信息量,防止信息过载。子系统内部的信息传递和关系比子系统之间的信息传递或关系要密集得多,延迟也少得多。
(对于任何软件工程师来说,这些应该都很熟悉。模块化,对吧?😍)
按照这个定义,我们可以说,经理的工作就是在团队之间进行协调并帮助他们的团队表现得更好。
对社会技术系统的二分是伪命题
你可能听过这个谬论:「工程师搞技术,经理搞人。」我讨厌这种说法。😊 我认为这完全误解了社会技术系统的本质。社会技术系统中的「社会」和「技术」并不是截然分开的,而是相互交织、相互依存的。事实上,很少有纯粹的技术工作或纯粹的人际工作;有大量涉及两种技能的粘合工作。
看看任何一个有效运作的工程组织除了编写代码之外还要做的一部分任务:
- 招聘、建立人脉、面试、培训面试官、汇总反馈、撰写职位描述和职业发展路径
- 每个项目或承诺的项目管理、优先级排序、管理利益相关者和解决冲突、估算规模和范围、进行回顾会议
- 召开团队会议、进行一对一交流、提供持续的成长反馈、撰写评审、代表团队的需求 架构设计、代码审查、重构;捕获 DORA 和生产力指标、管理警报量以防止倦怠
许多工作可以由工程师完成,而且通常也是如此。每家公司对这些任务的分配方式有所不同。这是一件好事!你不希望这些工作仅由经理来做。你希望个人贡献者共同创造组织,并参与其运行方式。几乎所有这些工作由有工程背景的人完成会更有效。
所以,你可以理解为什么有人会犹豫是否要把宝贵的人员编制花在技术经理上。为什么不希望技术部门的每个人的主要工作都是编写和交付代码呢?这不是从定义上说最大化生产力的最佳方式吗?
额……😉
技术经理是一层有用的抽象
理论上,你可以列出所有需要完成的协调任务,并让不同的人来负责每一项。但实际上,这是不切实际的,因为这样每个人都需要了解所有事情。记住,层级制度的主要好处之一是减少信息过载。团队内部的沟通应该是高效和快速的,而团队之间的沟通则可以少一些。
随着公司的扩展,你不能期望每个人都认识其他所有人;我们需要抽象的概念才能运作。经理是他们团队的联络点和代表,充当重要信息的路由器。
有时我把经理想象成公司的神经系统,将信息从一个部门传递到另一个部门,以协调行动。将许多或大部分功能集中到一个人身上,可以利用专业化的优势,因为经理会不断建立关系和背景知识,并在他们的角色中不断改进,这大大减少了其他人的上下文切换。
管理者 (Manager) 日程与创造者 (Maker) 日程
技术工作需要集中和专注。上下文切换的成本很高,过多的中断是挺要命的。而管理工作则是每小时左右进行一次上下文切换,并且一整天都要应对各种打断。这是两种完全不同的工作模式、思维方式和日程安排,无法很好地共存。
通常,你希望团队成员能够把大部分时间花在直接为他们负责的成果做出贡献的事情上。工程师只能做有限的粘合工作,否则他们的日程安排就会变得支离破碎,从而无法履行他们的承诺。而管理者的日程安排本身已经是支离破碎的,因此让他们承担更多的粘合工作通常不会带来太大干扰。
虽然并不是所有粘合工作都应该由管理者来完成,但管理者的职责是确保所有工作都能完成。管理者的职责是尽量让每个工程师都能从事有趣且具有挑战性的工作,但不能让他们感到过于负担重,还要确保不愉快的工作能公平分配。管理者还要确保,如果我们要求某人完成一项工作,就必须为其配备成功完成这项工作所需的资源,包括专注的时间。
管理是问责的工具
当你是工程师时,你对自己开发、部署和维护的软件负责。而作为经理,你则对团队和整个组织负责。
管理是一种让人们对特定结果(如构建具备正确技能、关系和流程的团队,以做出正确的决策并为公司创造价值)负责的方式,并为他们提供实现这些结果所需的资源(预算、工具和人员编制)。如果你不把组织建设作为某人的首要任务,那么这就不会成为任何人的首要任务,这意味着它可能不会得到很好地执行。那么,这该由谁负责呢,CEO 先生?
你对技术负责人、工程师或任何负责交付软件的人在「业余时间」能完成的任务有一个合理的上限。如果你试图让技术负责人负责构建健康的工程团队、工具和流程,那么你就是在要求他们在同一个日历里做两份时间不兼容的工作。最可能的情况是,他们会专注于自己觉得舒适的成果(技术成果),而在后台堆积组织债务。
在自然层级中,我们向上看是为了目标,向下看是为了功能。简而言之,这就是我们需要技术经理的复杂原因。
选择无趣的技术文化
更简单的论点是:大多数工程组织都有技术经理。这是默认设置。多年来,许多比你或我更聪明的人花了大量时间思考和调整组织结构,这就是我们得到的结果。
正如丹-麦金利(Dan McKinley)的名言,我们应该「选择无趣的技术」。无趣并不意味着不好,而是意味着它的能力和失败条件是众所周知的。你只能获得少数的创新点数,因此你应该明智地将这些点数用在能够成就或毁掉你业务的核心差异点上。文化也是如此。你真的想把你的点数用在组织结构上吗?为什么?
无论好坏,层级组织结构是众所周知的。市场上有很多人擅长管理或与管理者合作,你可以雇佣他们。你可以接受培训、指导,或者阅读大量的自助书籍。有各种各样的管理哲学可以围绕它们来凝聚团队或用来排除其他人。另一方面,我所知道的无经理实验(例如 Medium 和 GitHub 的全员自治,或 Linden Lab 的「选择你的工作」)都被悄然放弃或被颠覆了。在我的经验中,这并不是因为领导者疯狂追求权力,而是由于混乱、缺乏重点和执行不力。
当没有明确的结构或层级时,结果不是自由和平等,而是「非正式的、不被承认的和不负责任的领导」,正如《无结构的暴政》中详细描述的那样。事实上,这些团队往往是混乱、脆弱和令人沮丧的。我知道!我也很生气!😭
这个论点并不一定能证明你的 CEO 是错的,但我认为他的证明标准比你的要高得多。「我不想让我的任何工程师停止写代码」并不是一个有效的论点。但我也觉得我还没有完全解决生产力的核心问题,所以我们再来讨论一下这个问题。
更多代码行数 ≠ 更高生产力
简要回顾一下:我们在讨论一个有约 40 名工程师的组织,分成 10 个小组,每组有 3-4 名工程师,每组都有一个技术负责人。你的 CEO 认为,如果有人停止全职编程,这个减速将是你们无法承受的。
也许吧。但根据我的经验,由经验丰富的技术经理领导的几个较大团队,将远远优于这些小团队。这差距很明显。而且,他们可以以更高效、可持续和人性化的方式完成工作,而不是这种拼命的死命赶工。
系统思维告诉我们原因!更少的团队,但规模更大,你会有更少的整体管理开销,且大大减少了团队内慢且昂贵的协调。你可以在团队内部实现丰富、密集的知识传递,从而实现更大面积的共享。每组有7-9名工程师,你可以建立一个真正的值班轮换,这意味着更少的英雄主义和更少的倦怠。你需要进行的协调可以更具战略性,减少战术性,更具前瞻性。
五个大团队是否能比十个小团队编写更多的代码行数,即使有五名工程师成为经理并停止编写代码?可能会,但谁在乎呢?你的客户根本不关心你写了多少代码行数。他们关心的是你是否在构建正确的东西,是否在解决对他们重要的问题。关键是推动业务前进,而不是单纯地编写代码。不要忘记,单纯地编写代码会产生额外的成本和负面效应。
决定你速度的是你是否把时间花在了正确的事情上。学会正确决定构建什么是每个组织都必须自己解决的问题,而且这是一项持续不断的工作。技术经理不会做所有的工作或做出所有的决策,但根据我的经验,他们对于确保工作顺利进行并且做得很好,绝对至关重要。正如我在上篇文章中写到的,技术经理是系统用来学习和改进的反馈循环的载体。
管理人员是否会成为不必要的开销?
当然有可能。管理的核心是协调团队之间的工作并提升团队的运作效率,所以任何减少协调需求的方式也会减少对管理的需求。如果你是一家小公司,或者你的团队成员都是非常资深且习惯合作的,那么你就不需要太多的协调。另一个重要因素是变化的速度;如果你的公司在快速增长或者人员流动频繁,或者面临很多时间压力或频繁的战略调整,你对管理人员的需求就会增加。但也有许多较小的组织在没有太多正式管理的情况下运作得很好。
我不喜欢「开销」这个词,因为 a) 这有点粗鲁,b) 称管理人员为「开销」的人通常是不尊重或不重视管理这门技艺的人。
但管理实际上确实是开销😅。许多其他的粘合工作也是如此!这些工作很重要,但它们本身并不能推动业务向前发展;我们应该尽量只做那些绝对必要的工作。粘合工作的天然属性使得它很容易扩散,吞噬所有可用的时间和资源(甚至更多)。
限制是好的。感觉资源不足是好的,这应该成为常态。管理很容易变得臃肿,管理人员可能非常不愿意承认这一点,因为他们从来没有感到压力或紧张减少。(事实上,情况可能恰恰相反;臃肿的管理层可能会为管理人员带来更多工作,而精简的组织结构可能会让他们反而感到压力更小。官僚主义往往会自我发育。特别是当管理层过于关注晋升和自我时。这也是确保管理不应仅为升职或统治的又一个充分理由)
管理也很像运营工作,当它做得好的时候,是看不见的。评估管理人员的工作可能非常困难,尤其是在短期内,而决定何时创建或偿还组织债务是一个完全不同的复杂问题,远远超出了这篇文章的讨论范围。
但是,是的,管理人员绝对可以成为不必要的开销。
然而,如果你有 40 个工程师都向一个副总裁汇报,而没有其他人专门负责人员、团队和组织相关的工作,那么我可以相当肯定地说,这对你来说目前不是一个问题。
<3
💡 更多资讯,请关注 Bytebase 公号:Bytebase
来源:juejin.cn/post/7373226679730536458
2年前的今天,我决定了躺平退休
两年前的这个时候,突然觉得说话特别费劲,舌头不太听使唤,左手突然不听话,就像李雪健老师表演那个帕金森老头喝酒一样。
我心里一慌,请假去了医院,验血,CT,超声。然后医生给我列了长长一篇诊断书:高血脂,高血压,糖尿病,冠心病,还有最可怕的脑出血,还好只是渗血,虽然并不是很严重,但是位置不太好,影响了身体感官和左手。
平时身体非常好,也经常运动,为什么会突然得这么多病呢。毫无征兆的左手就不听使唤了。而且听力在这一段时间也非常差。通过大夫诊断,一部分是遗传因素,另一个是和我常年酗酒,熬夜有关,每天几乎只睡3-4小时。
是的,,,,,,我喜欢在家喝着啤酒写代码,甚至有时候在单位加班的时候也是喝啤酒写代码。和别人不太一样,别人喝酒爱睡觉,我喝啤酒失眠。因为接了很多项目,上班之余都是晚上和周末熬夜写代码做自己的项目。
其实听到这个消息我很失望,失望的并不是因为身体垮了,钱还没赚够,而是我还没有完成我的目标就是打造一个自己主导的产品。
那天从医院回家,我并没有去坐地铁,而是从中日友好医院徒步走回天通苑的出租屋。在路上,我反复的想,今后的路该如何走。
继续在互联网行业工作肯定是不行的,病情会进一步加重,到时候就真的成一个废人了,反而会拖累整个家庭。如果不继续“卷”那我也就无法实现自己来北京的目标了。不过好在经过这么多年的积累,已经存够足够养老的资本,并不需要为妻儿老小的生存发愁,但是也没有到财富自由的程度。
躺平,躺到儿子回老家上学就回老家退休
。这是一个并不那么困难的决定。但是却是一个非常无奈的决定,躺平就意味着自己来北京定下的目标没有完成,意味着北漂失败。
做好这个决定以后,我就开始彻底躺平,把手里的几个项目草草收尾,赔了大几十万。等于这一年白忙活。好在还有一份工作收入。同时也拒掉了2个新的Offer。在疫情最困难的时候,还能拿到两个涨薪offer。我还是蛮佩服我自己的。但是为了不影响我的额外收入,加上现在工作不是很喜欢,也就一直犹豫不决。但是这次生病彻底让我下定了决定 ---- 算了。
其实,经历这么多年,什么都看的很清楚,但是我的性格并不适合这个行业,我这个人最大的特点就是腰杆子硬,不喜欢向上管理,经常有人说我那么圆滑,肯定是老油条,而实际上,我整整18年的工作经历,只对领导说过一次违心的话,变相的夸了老板定制的开发模式,老板看着我笑了笑,也不知道他是不是听出来我这话是讽刺还是撒谎。
而其余都是和老板对着干,只有2任老板是我比较钦佩的,也是配合最舒服的。而且共同特点都是百度出身,我特别喜欢百度系的老板。特别务实,认认真真做业务。不搞虚头巴脑的事情,更不在工作中弄虚作假。一个是滴滴的梁老板,另一个就是在途家时候的黄老板。
当然,在我整个职业生涯有很多厉害的老板,有的在人脉厉害,有的人个人管理能力,有的在技术。但是由于我性格原因,我就是跟他们合不来,所以要么你把我开了,要么等我找好下家主动离开。
所以我的职业生涯很不稳定,就比如我见过的一个我认为在技术能力上最厉害的老板,也是我唯一在技术上佩服的人,就是在36kr期间认识的海波老师,听他讲的系统架构分享和一些技术方案,真的是豁然开朗,在Saas和Paas的方方面面,架构演化,架构升级所可能遇到的各种问题及面对产品高速迭代所需要解决的问题及方案都门清,而且他本身也是自己带头写代码,实际编码能力也是非常的牛,并不是那种“口嗨”型领导。但就是我跟他的性格合不来,最后我把他那套架构方案摸透了以后就跑路了,而从他那里学的那套技术方案,在我日后在lowcode和Paas以及活动运营平台的技术方案设计上帮助颇多。而他不久之后也离开了。据说去了字节。
混迹于形形色色的老板手底下,遇到过的事情非常多,也让我认清了一点,那就是,牛人是需要平台去成就的,平台提供了锻炼你的机会和让你成长的机会。所以你学到了,你就成了牛人。而不是你自己手头那点沾沾自喜的觉得别人没你了解的深入的技术点。所以平台非常重要,绝大多数情况下都是如此。
所以我这种人就不适合,因为我不喜欢违心。我顶多就是不说出来,不参与,不直接反对就已经是对老板最大的尊重了
。所以我能看透很多事情,但是也知道我不讨老板喜欢,而我的性格也不可能为了让老板喜欢而卑躬屈膝,所以,我早早就提前做好准备,就是拉项目,注意这不是做私活
。拉项目就是承包项目,然后找几个做私活的人给他们开发。这项收入有时候甚至一年下来比我的工资还要高。风险也是有的,那就是可能赔钱,十几万十几万的赔。所以也是一个风险与收益共存的事情。做项目的好处是,你可以不断的接触新的甲方,扩张自己的人脉,也就不断的有项目。
但是由于这次生病,我手头的3个项目都没有做好,都被清场了。所以为了弥补朋友的损失,我一个人扛下了所有。也同时意味着后面也就没项目可接了。身体不允许了。
躺平以后,为了等孩子回老家上学,本职工作上,也开始混,我最后一年多的时间里,写代码,都不运行直接就提测。是的。没错。。。。。。就是这样。但是功能是都好用的,基本的职业操守是要有的。虽然也会有更多的bug。但是一周我只干半天就可以完成一周的工作。这可能就是经验和业务理解的重要性。所以,我一直不太理解很多互联网企业精简人员的时候为什么精简的是一线开发人员,而留下的是那些只会指挥的小组长。这也是为什么各大互联网企业都在去肥增瘦,结果肥的一点也没减下去。
不是有那么一句话,P8找P7分一个需求,然后P7找P6喊来P5开发。互联网就是这样子,一群不了解实际业务和实际代码的人,在那里高谈阔论,聊方案,聊架构,聊产品,聊业务,聊客户,聊趋势,然后让那些一脸“懵逼”的人去开发。最后的结果可想而知,最后很多需求都是一地鸡毛,但是责任却都要一线执行去承担,而为了证明需求的正向收益,那就在指标口径上“合理”的动动手脚,所以我在我整个职业生涯说出了那么一次,也是唯一一次违心的恭维话。
所以我特别佩服一个网红叫“大圣老师”,是一个卖课的,虽然我看不上他做的割韭菜的事情,但是我很佩服他这个人,他也是很刚的人,就是看不惯老板pua和无意义的加班,人家就是不干了。成功开辟了第二职业曲线,而且也很不错。
另一个网红就是“神光”,虽然我也看不上他,但是我很佩服他,佩服他追求自我的勇气。
而反观那些在职场唯唯诺诺卑躬屈膝的人,现在过的如何呢?人啊。还是要有点个性。没个性的人下场都挺惨的。
峰回路转,人那,这一辈子就是命,有时候把,真的是你也不知道结果会是什么样,23年在我百无聊赖,闲的五脊六兽的时候,一周的工作基本上半天就干完了,所以一个机缘巧合,遇见了一群有意思的人。当时大模型正在风口浪尖。好多人都在大模型里面摸金,而有这么一群人,一群大学生,在海外对我们进行大模型技术封锁的时候,为了自己的初衷,建立了在问这个网站。
而作为起步比别人要晚,产品做的还很粗糙如何跟市场上的竞品竞争呢?而且不收费,更不打广告,完全靠赞助存活。但是这一切都是为了在国外封锁我国大模型技术背景下的那句话“让知识无界,智能触手可及”。站长原文
所以在同类起步更早,产品做的更精细的很多产品逐渐倒下去以后,zaiwen还活着。所以我觉得特别有意思,这种产品活下来的概率是非常低的,除非整个团队都是为爱发电,于是我也加入到这个团队。
事实上也确实这样,整个团队是有极少部分社会工作者和大部分在校大学生组成,而大家聚一起做这件事的初衷就是为了让知识无国界,让国内用户可以更方便的体验最先进的海外大模型技术。而目标用户,也都是学生,老师和科研工作者。
就这样在这里,我重新找回了自己的目标,虽然,由于资金问题,资源问题,以及我个人身体限制能做的事情很少,但是却发现,大家都做的非常有动力,产品也在不断的迭代不断的发展,并且还活的很好。团队的人在这里也干的很开心。
今天,正是两年前我诊断出脑出血的那天,心里没有低落,也没有失望,更没有懊悔,有的只是新的体验。人生啊,来这一世,就是来体验的,别难为自己。顺势而为,就像张朝阳说的那句话“年轻人挺不容易的,建议年轻人不要过度努力,太过拼搏的话(对身体)是有伤害的,年轻人得面对现实,这个世界是不公平的”
来源:juejin.cn/post/7416168750364540940
2024 年: 落考、车祸、失业, 没了!!!
引言
2024
没啥成长的, 净剩下焦虑、内耗、失意了! 随便写写总结, 诸君随便看看吧...
一、落考(软考)
如题, 今年报考了软考(高项), 选择题和论文没过 😅😅😅 明年继续吧!!
去年在掘金读了好多篇年终总结, 偶然了解到了杭州 E
类人才, 杭州 E
类人才无疑对我这等普通人来说是可以触及到的, 而且优待 福利较高
的一类人才了。
而对于我等普通人来说, 通过 软考 + 专利/软件著作
是最便捷可行的一种。这其中唯一有难度的其实就是 软考
了
软考是一种简称, 其全名是计算机技术与软件专业技术资格(水平)考试, 大家也称之为计算机软考、计算机软件资格考试等。软考又分为初级、中级、高级, 申请 E
类人才则需要高级资格证书, 这里直接报考高级就行(不需要从初级考起)。
如上图每个等级都有很多门专业, 今年我报考的是 信息系统项目管理师
简称 高项
, 因为据说这门比较简单, 都是介绍项目管理上的一些知识, 背的比较多, 对专业要求没那么高。
当然如题, 今年没准备好, 有两门挂了 😭😭😭, 总共要考三门分别是选择题、案例分析、论文, 每门总分都是 75
分, 需要每门都考及格(45
分), 才算通过考试。
经验教训:
- 一定要提前做好计划, 严格按照计划安排时间学习。 我自己就是中间有段时间公司活比较多、加上自己懈怠了, 后面就严重影响了进度
- 一定要多刷题, 特别是选择题, 以刷题为主。我这次基本没刷选择题 🤦
- 论文提前开始! 备好模版就开始写吧
二、车祸
是的, 不幸的是在 6
月底, 一天阴雨绵绵的傍晚, 在下班回家路上(家门口)骑着小毛驴的我和尊贵的宝马车主相撞了!!
被撞倒瞬间, 还是很刺激的! 整个大脑嗡嗡的, 思考 🤔 几秒, 原地蹦了起来!! 后面就是报警, 开具交通责任认定书(对方全责)。交警到场时看了下伤势, 说应该没事大事的。问我要不要去医院做个检查可以自己打车或者让司机送我去, 纠结了下还是打算去医院瞅瞅(后面回想起来也是后怕, 幸亏去了, 要不然可能半条命就没了...)!
到医院一通检查, 很不幸... 走不了了, 寰椎骨折得住院了 😱。医生看到片子后那一脸严肃的表情, 可把我吓坏了! 医生语气都变了, 让我赶紧坐下, 脖子不要乱动, 叫来了工作人员给我整了个颈托! 然后让我办理住院....
后面了解了下, 寰椎骨位于脑瓜子和脖子中间, 用于支持脖子的一个环形骨头, 骨头中间镂空的, 人的所有神经都是从这个环中间穿过到达身体各个地方, 所以这个位置骨折, 处理不好就可能会压迫到神经! 运气好的是我伤的位置比较好, 在脖子前侧, 如果是脖子后面可能就得开刀了!! 更庆幸的是, 来了医院做检查, 否则这条命可能就得交代了!!
后面住院 8
天, 过上了早 7
晚 10
的作息了, 每天就是看看电视、玩玩手机, 然后抽空就过道里溜达溜达, 提前过上了养老生活!!
出院后, 就开始居家办公咯! 中间还抽空搬了家, 然后还给自己整出了荨麻疹, 我也是醉了...
同时我的交通事故是发生在下班路上, 并且是对方全责, 所以还属于工伤! 故在受伤居家期间还申请了工伤。感慨下, 工伤流程也太麻烦了吧... 到目前为止还没整完 😤
最后友情提示, 出事不管怎样一定一定要报警(定责)、去医院做好检查...
三、祸不单行(失业)
9
月眼瞅着脖子马上要好了, 终于可以结束居家办公了! 接踵而来的是, 公司经营不善, 大规模裁员的消息!!! 所以自然的, 我又再次失业了!! 人生第二次失业了, 也没啥感觉, 该赔偿赔偿, 该滚蛋滚蛋!!
失业了, 三无人士, 一点也不带慌的, 该吃吃该喝喝! 这期间顺便处理了交通赔偿事宜, 虽然没多少! 伤好后, 去大西北溜达了一趟!
一下子没反应过来就到年底了... 只能怪今年太早过年了, 就这样办, 一切明年再议...
四、生活
今年重新找了住处, 顶楼, 再也不会被楼上邻居吵到了, 少了很多内耗, 就是夏天有点子热!! 同时房子也更大点了! 当然租金肯定也更贵了!
今年下厨的次数也明显多了起来, 出去吃大餐的次数少了挺多的! 上班也开始自己带饭咯, 当然省钱好像并没有省到, 自己煮饭量不好掌握, 一煮一大锅, 一吃一个不吱声! 同时大鱼大肉的, 一顿饭价格也不贵, 好在比外面吃健康点!
今年好像也没赞下什么大钱, 没有养成记账习惯, 所以一切靠感觉! 反正赞是攒了, 但是肯定没达到预期, 明年一定要养成记账的习惯
今年的韭菜长大了一点点, 感谢债基、感谢纳斯达克(明年能回本吗? 😵💫)
回顾下, 今年倒是去了不少地方, 一月迪士尼、三月苏州、六月昆明、十月武威张掖、十一月邯郸、十二月滑雪
家中新增一员, 名唤 二狗子
! 刚入门就命运多舛, 感冒、流鼻涕、咳嗽不断.... 但也不影响她可可爱爱!
五、卷? 不卷?
今年的代码全部贡献给了 昆仑虚, 但实际上也没干啥, 就将 昆仑虚 迁移到 NextJS
并引入 TS
, 代码量和去年比少得可伶!
内容创作这块也没啥成绩, 掘金输出 22
篇文章, 公众号「昆仑虚F2E」日常更新原创文章, 然鹅仅仅新增了 30
关注😓😓😓(这里求个关注)
内容创作收入: 掘金金石 +604
、公众号收入 +13
卷吗? 今年一顿摆烂, 和去年给自己定的目标差太多咯.....
对了, 今年还读了 5+
本书...
六、展望 2025
计划以及有了, 重点就三件事: 攒钱、卷、减肥。 更细节的就不列了, 直接看去年的吧, 基本差不多 !! 🤣🤣🤣🤣
来源:juejin.cn/post/7454508125772218395
旧Android手机改为个人服务器,不需要root
一、前言
随着手机更新换代的加速,每个人都有一些功能正常,但是闲置的手机,其实现在的手机都是ARM架构的,大多数手机内存还不小,相对于现在各大厂商提供的云服务器来讲,配置已经很不错了,所以这么好的资源能利用起来还是非常不错的~
二、工具介绍
目前能用的工具有很多,比如BusyBox、Linux Deploy、juice ssh、termux,但是很多都是需要手机能够root的,但是root并不是所有手机都能够简单获取到的,所以我这里选取Termux进行操作。
三、什么是Termux
Termux 是一款运行于 Android 系统的开源终端模拟器。提供了 Linux 环境,即使设备不具备 root 权限也可使用。通过自带的包管理器(Pacman、 APT),Termux 可以安装许多现代化的开发和系统维护工具,例如 zsh、Python、Ruby、NodeJS、MySQL 等软件。
四、开始改造
4.1 Termux安装
Termux下载:github.com/termux/term…
安装完成后,可以执行以下命令更新一下各软件包:
pkg update && pkg upgrade
4.2 安装openSSH
成功安装Termux之后,虽然手机是可以像服务器一样执行一些操作,但是毕竟手机管理配置起来没有PC方便,所以可以安装SSH服务,方便PC来远程操作。
# 安装openssh
pkg install openssh
# 默认端口为8022,修改端口
sshd -p 8888
# 启动ssh服务
sshd
4.3 远程连接SSH
要远程连接可以使用终端或者SSH客户端(如:PuTTY、Termius、XShell、MobaXterm等),使用以下命令连接到Termux服务。
ssh -p 8022 <username>@<device_ip>
username
在Android手机上使用Termux搭建服务器,并通过SSH让PC进行登录和操作时,**默认的用户名通常是u0_aXXX
,**可以通过以下方式获取到你的用户名是什么:
# 查询termux服务用户名
whoami
device_ip
通过以下命令获取手机的IP,这里的IP是局域网IP。
# 获取设备IP
ifconfig wlan0
连接时需要密码,由于termux服务默认密码为空,所以需要设置一个密码,具体方式如下:
# 切换管理员账户(如果有)
su
# 设置密码
passwd
五、注意点
5.1 保持服务在线
由于Termux是直接运行到Android手机上的,也是一个APP程序,所以需要注意Termux程序不要退出了。
5.2 内网服务
虽然经过上述方式已经实现了服务器的常规基础配置和操作功能,但是毕竟是在手机上的一个服务,也是受到网络环境限制的,因此如果要保证服务可用,需要保证手机和使用端在同意局域网内。
六、扩展
如果对手机作为网站服务器以及移动无线硬盘相关的内容,欢迎关注,后续会尽快分享相关方法。
来源:juejin.cn/post/7459816593230397494
老弟想自己做个微信,被我一个问题劝退了。。
大家好,我是程序员鱼皮。最近老弟小阿巴放暑假,想找点事情做,于是就来问我:老鲏,我想做个练手项目,有没有什么好的建议?
我说:练手项目的话,就做个自己感兴趣的呗,想加什么功能就加什么,做起来会更舒服~
小阿巴:Emm,我感兴趣的太多了,有没有推荐啊?
我说:那就想想自己经常使用的网站或 APP,选个对业务流程相对熟悉的。
小阿巴思考片刻,一拍脑袋:对啊,我天天用微信,那我就做个微信吧!说不定之后大家都在用我做的软件聊天呢?
我一听,不禁暗自惊叹,没想到小伙子年纪轻轻,野心很大啊!
我说:想法不错,但想做个微信这样的 IM(即时通讯)项目,可没有那么简单,你有什么实现思路么?说来听听?
小阿巴:微信的核心功能是收发消息,我可以把用户 A 发送的消息保存到数据库中,用户 B 进入聊天界面时,从数据库查询出发给他的消息就行。
我一听这个回答,就知道以小阿巴目前的水平,想做出微信是不太可能了。。。
我问:Emm,暂且不考虑用户体验和性能,我们就先实现基础功能吧,你会怎么让用户查看自己的历史消息呢?
小阿巴思考片刻,然后嘴角微微上扬,露出狡黠的笑容:你是不是以为我会说一次性把所有历史消息全部查出来?可惜啊老鲏,你把我想的太天真了,用户可能有成百上千条历史消息,全量加载会很慢,所以我必然会使用 分页
来查询!
我说:行,那你打算怎么分页呢?
小阿巴:这还真难不倒我,这几年我苦练增删改查,分页写得很溜的!纸笔呈上来,看我给你手写 SQL:
select * from message
where user = '鱼皮'
limit 0, 20;
我说:Emm,老弟啊,听我一句劝,咱先别想着做微信了,先实现一个消息管理系统吧。
小阿巴:怎么说?吾 SQL 不亦精乎?
其实这也是一道经典的场景题:即时通讯项目中怎么实现历史消息的下拉分页加载?
下面鱼皮给大家讲解一下。
如何实现下拉分页加载?
业务场景
一般在即时通讯项目(比如聊天室)中,我们会采用下拉分页的方式让用户加载历史消息记录。
区别于标准分页每次只展示当前页面的数据,下拉分页加载是 增量加载 的模式,每次下拉时会请求加载一小部分新数据,并放到已加载的数据列表中,从而形成无限滚动的效果,确保用户体验流畅。
比如用户有 10 条消息记录,以 5 条为单位进行分页,刚进入房间时只会加载最新的 5 条消息:
下拉后,会加载历史的第 6 - 10 条消息:
理解了业务场景后,再看下实现方案,为什么不建议使用传统分页实现下拉加载。
传统分页的问题
在传统分页中,数据通常是 基于页码或偏移量 进行加载的。如果数据在分页过程发生了变化,比如插入新数据、删除老数据,用户看到的分页数据可能会出现不一致,导致用户错过或重复某些数据。
举个例子,对于即时通讯项目,用户可能会持续收到新的消息。如果按照传统分页基于偏移量加载,第一页已经加载了第 1 - 5 行的数据,本来要查询的第二页数据是第 6 - 10 行(对应的 SQL 语句为 limit 5, 5),数据库记录如下:
结果在查询第二页前,突然用户又收到了 5 条新消息,数据库记录就变成了下面这样。原本的第一页,变成了当前的第二页!
这样就导致查询出的第二页数据,正好是之前已经查询出的第一页的数据,造成了消息重复加载。所以不建议采用这种方法。
推荐方案 - 游标分页
为了解决这种问题,可以使用游标分页。使用一个游标来跟踪分页位置,而不是基于页码,每次请求从上一次请求的游标开始加载数据。
一般我们会选择数据记录的唯一标识符(主键)、时间戳、或者具有排序能力的字段作为游标。比如即时通讯系统中的每个消息,通常都有一个唯一自增的 id,就可以作为游标。每次查询完当前页面的数据后,可以将最后一条消息记录的 id 作为游标值传递给前端(客户端)。
当要加载下一页时,前端携带游标值发起查询,后端操作数据库从 id 小于当前游标值的数据开始查询,这样查询结果就不会受到新增数据的影响。
对应的 SQL 语句为:
SELECT * FROM messages
WHERE id < :cursorId
ORDER BY id DESC
LIMIT 5;
扩展知识
其实游标分页是一种经典方案,它的应用场景很多,特别适用于增量数据加载、大数据量的高性能查询和处理。除了 IM 系统获取历史消息记录之外,常见场景还有社交媒体信息流、内容推荐系统、数据迁移备份等等。
最后
小阿巴听完,长叹道:唉,没想到光是这么一个小功能,就把我难住了。
我说:你可别这么想。。。难住你的,可不止这一个小功能啊!想做一个成熟的 IM 系统,除了最基础的消息发送和获取功能外,你得去学习 WebSocket 实时通讯、得考虑到消息收发的性能、得考虑到消息的顺序和一致性、得考虑到消息的存储成本和安全,等等等等。可没那么容易。
小阿巴:得,那我先去做消息管理系统了!🐶
来源:juejin.cn/post/7402517513932931122
原来微信小游戏用的技术就是web, 有想法的直接可以做
12月玩了2个微信小游戏, 发现都是在玩数值, 其实就是同一个游戏场景, 于是想自己写一个试试.
然后看了微信小游戏文档, 推荐 cocos creator, 学了下发现 web 开发者那是根本不用学.
自己写了2个demo, 于是分享给大家.
cocos creator
cocos creator 是个游戏引擎, 他推荐使用 vscode 和 chrome, 并且 ts 是唯一支持的脚本语言.
他的预览就是打开chrome的一个网页, 主体是个canvas, 这个场景下cc可能就是一系列资源的执行器.
重要的是他可以打包到微信小游戏, 也是微信小游戏推荐的框架.
也就是我可以用 ts 写小程序了.
其实也就是个 html5 的小游戏, 而 cc 包装了h5小游戏要手动写的requestAnimationFrame
执行器, 提供了更方便的编辑器, 包装了一些游戏开发要用到的概念.
网页开发和游戏开发的区别
显然网页开发和游戏开发是不同的, 来稍作分析.
游戏元素
网页元素一般由div布局, 终端的节点一般是文字, 或者输入框.
游戏元素看起来容易一些, 因为没有输入. 手机小游戏只有通过点击来传达一些指令.
游戏元素也有布局, 但没网页 bfc, flex 这些复杂的东西, 全部绝对定位, 也有z轴.
再细看游戏元素, 其实每个元素就是个图片.
简单总结, 游戏的所有元素就是图片, 通过设置x, y, z的数值来定位. 比网页开发容易得多.
游戏交互
网页的功能主要是2个部分: 输入和展示.
所以网页的交互也就是改变参数后刷新列表.
我们来分析游戏的交互, 也分为2个部分: 改变位置与结算.
随着游戏的开始和玩家的点击, 其实就是元素的位置发生改变而已.
我们只要通过脚本控制元素的位置. 这些位置和具体游戏场景相关, cc 也会提供常用工具库.
另外一个是结算, 判断分数高低, 或者数组比较, 最多通过位置计算碰撞, 来判断游戏结果.
可以看到这些计算都是在脚本中进行的, 也都是比较简单的数据结构或者数学公式.
在游戏场景外, 一些菜单, 设置的界面就和网页差不多了.
cc 系统介绍
我看了一个视频, 自己写了2个demo, 简单总结下 cc 的系统.
总的来说, cc 像是个低代码平台.
编辑器界面
编辑器就是典型的低代码.
- 场景界面. 就是把元素拖拖拽拽的地方.
- 资源列表. 放代码和图片的地方, 就是网页开发的
src
目录. 资源的类型值得下文展开. - 节点层级. 在编辑场景的时候, 场景通常是有多个节点的, 节点之间有层级关系便于维护, 所以有个界面展示.
- 节点属性. 在场景界面里选中节点, 肯定是可以编辑这个节点的属性的, 大小/位置什么的.
这些元素一看就是低代码了, 应该是低代码借鉴了这些游戏引擎的.
这些面板都是可以拖动位置, 或者合并成tab的, 很方便.
资源类型介绍
上面说到资源, 资源类型还挺多的. 这里介绍一点我用到的.
- ts文件, 图片文件.
脚本文件和图片文件都是用来拖到节点里, 和节点绑定的.
- 场景.
应该是 cc 的核心了. 从文件看来, 就是个 json. 所以拖拖拽拽的结果就是修改 json. 然后通过 json schema 执行渲染或打包.
场景是由节点组成的. 在场景里新建节点并嵌套, 来构建游戏场景.
节点的种类是很多的, 可以插入图片变为元素, 也可以绑定脚本, 作为一个"虚拟节点", 只是为了维护方便.
场景有必须的节点是 canvas 和 camera.
- prefab.
可以理解为"组件". 在场景中编辑了一些节点, 如果觉得可以复用, 直接把整个节点拖到资源列表里, 就会产生一个 prefab. 使用的时候拖动这个 prefab 到场景, 就会产生一个实例了.
更多的应该是用脚本批量创建.
- 动画.
其实和ts文件与图片文件一样, 是关联到节点上的. 但他是 cc 特有的, 可以在 cc 里编辑动画内容, 可以对各个属性做帧动画, 也可以导入动画软件做的动画.
开发流程
我写了2个算能跑的项目, 来说说开发的过程.
- 资源目录下新建一些文件夹: scripts, imgs, animation, scene.
- 主要开发就是编辑场景. 在场景里添加节点, 然后给节点贴图, 从"资源列表"把资源拖到"节点属性面板"就好了, 容易.
我的节点很简单, 就是玩家角色, 和背景.
- 建立个空节点, 写游戏逻辑. 具体操作是新建个 ts 文件, 然后拖到这个节点属性上.
- 游戏逻辑需要操作的内容, 包括动画, 都以"拖动"的方式关联到"节点属性面板"上.
这样就写好一个游戏了.
游戏逻辑开发是和 html5 游戏一样的, 最后一小节我再赘述下吧.
游戏逻辑编写
游戏逻辑在 ts 的脚本文件中编写.
所有新建的 ts 文件都会有一个初始模板. 内容是export class XXX extends cc.Component {}
.
这个类有2个生命周期方法. start()
和update()
.
update()
方法的参数deltatime
是离上一帧的时间, 不了解的去看下 h5 游戏的执行就好了.
游戏逻辑一定涉及到元素, 只要在脚本文件里声明一个属性, 就能在节点属性面板上看到一个属性.
把这个属性需要控制的元素拖过去就行.
然后元素节点也可以绑定脚本. 这个脚本可以通过this.node
提供的 api 来操作元素的位置.
元素节点一般会绑定动画, 也需要把动画声明在属性里, 然后从资源列表把动画拖动到自己的节点属性面板上, 就可以在脚本里调用动画了.
我现在理解的层级是这样的:
- 总脚本gameControl写在单独节点里. 写游戏逻辑与结算判断.
- 会动的元素, 自己绑定节点, 写一些方法供总脚本调用.
- 编辑一些动画, 供上一步"会动的元素"调用. 一般是和元素位置的移动同时调用的.
贴一些代码
这里分享个具体的demo代码. demo内容很简单, 按方向键角色就会在地图上走路.
走路的时候会播放一个帧动画, 是从微信表情里导出的20个png.
脚本文件只有2个. 一个是gameControl游戏控制, 只做了监听键盘事件, 并调用player脚本的对应方法.
另一个player脚本写了对应的方法, 改变一些参数, 在update()
方法根据参数来设置角色的位置.
gameControl.ts
import { _decorator, Component, Node, input, Input, EventKeyboard } from 'cc'
const { ccclass, property } = _decorator
import { player } from './player'
@ccclass('gameControl')
export class gameControl extends Component {
@property(player)
public player: player = null
start() {
input.on(Input.EventType.KEY_DOWN, (event) => {
switch (event.keyCode) {
case 37:
this.player.left()
break
case 38:
this.player.up()
break
case 39:
this.player.right()
break
case 40:
this.player.down()
break
}
})
}
update(deltaTime: number) {
}
}
player.ts
import { _decorator, Component, Node, Animation, tween, Vec3, math } from 'cc'
const { ccclass, property } = _decorator
@ccclass('player')
export class player extends Component {
@property(Animation)
anim: Animation = null
@property(Node)
lulu: Node = null
private direction = new Vec3(1, 0, 0)
private isMoving = false
private movePeriod = 0
start() {
}
update(deltaTime: number) {
if (this.isMoving) {
if (this.movePeriod < 1) {
let target = this.node.position
Vec3.add(target, this.node.position, this.direction)
this.node.setPosition(target)
this.movePeriod += deltaTime
} else {
this.isMoving = false
}
}
}
left() {
if (!this.isMoving) {
this.lulu.setRotationFromEuler(0, 0, 180)
this.direction = new Vec3(-1, 0, 0)
this.startMove()
}
}
right() {
if (!this.isMoving) {
this.lulu.setRotationFromEuler(0, 0, 0)
this.direction = new Vec3(1, 0, 0)
this.startMove()
}
}
up() {
if (!this.isMoving) {
this.lulu.setRotationFromEuler(0, 0, 90)
this.direction = new Vec3(0, 1, 0)
this.startMove()
}
}
down() {
if (!this.isMoving) {
this.lulu.setRotationFromEuler(0, 0, 270)
this.direction = new Vec3(0, -1, 0)
this.startMove()
}
}
startMove() {
this.anim.play()
this.isMoving = true
this.movePeriod = 0
}
}
另外做的demo是跟着cocos creator 文档的2d游戏做的. 有兴趣的也可以跟我一样, 先照着这个做一遍, 再自己新建个空项目自己操作.
最后
我认为 cocos creator 对 web 开发者来说真的是非常好上手了.
我认为小游戏的设计分为2个吧. 核心游戏场景, 与, 游戏运营.
其实核心游戏场景都不复杂的, 那怎么能让玩家一直玩呢.
其实就是策划运营, 操作一些数据, 让每次玩同一个场景, 看到不同的数字, 和不同的皮肤.
用户就会为了这些数字(pay for ability), 和皮肤(pay for love)来付费了.
我认为游戏脚本不难. 难在2点:
- 游戏的完整度, 需要美术和动画, 程序只能控制角色的位置, 加上动画才让人有操作角色的感觉. 精美的游戏场景也能让玩家觉得真实.
- 策划: 数值系统, 货币系统, 奖励系统, 活动这些. 让玩家重复玩同一个场景几百遍还觉得自己在成长, 真是牛逼.
来源:juejin.cn/post/7456805812045725734
海康摄像头 web 对接
真的烦躁,一个月拿着死工资,每天写着增删改查,不知道以后能做什么,有时候真的想离职,进广东电子厂....
这段时间,XXXX 要加一个海康监控,哎。
苦命开发
\webs\codebase 目录中有,第一个是插件,必须安装的, 后面两个JS文件是开发必要的。还要一个 JQ的,它内部使用了jq
初始化插件
引入了提供的jS后,就可以开始牛马了。。。。
首先注册插件,并检查更新
因为我这是4个摄像头,所以窗口是 2 * 2
WebVideoCtrl.I_InitPlugin
是用于初始化插件,成功回调是cbInitPluginComplete
WebVideoCtrl.I_CheckPluginVersion
用于检查更新。
在自动登录这里,我准备了数组,包含登录端口密码等信息,建议每一个之后都要等1秒。多个账号登录,插件只加载一次即可。
init() {
// 这里的代码会在文档完全加载后执行
WebVideoCtrl.I_InitPlugin({
iWndowType: 2, // 设置分屏类型为 2*2,显示 4 个窗口
bWndFull: true, // 支持单窗口双击全屏
bDebugMode: true, // 关闭调试模式
cbInitPluginComplete: async () => {
console.log("插件初始化完成")
try {
// 加载插件
await WebVideoCtrl.I_InsertOBJECTPlugin("divPlugin")
// 检查插件是否最新
const bFlag = await WebVideoCtrl.I_CheckPluginVersion()
if (bFlag) {
alert("检测到新的插件版本,双击开发包目录里的HCWebSDKPlugin.exe升级!")
}
for (const item of this.channel) {
// 自动登陆
this.clickLogin(item)
await new Promise(resolve => setTimeout(resolve, 1000))
}
} catch {
alert("插件初始化失败,请确认是否已安装插件;如果未安装,请双击开发包目录里的HCWebSDKPlugin.exe安装!")
}
},
iTopHeight: 0 // 插件窗口的最高高度
})
}
实现登录
WebVideoCtrl.I_Login 是登录接口
- 参数1:ip地址
- 参数2:1 是http,2 是https
- 参数3:端口
- 参数4:平台账户
- 参数5:平台密码
// 登陆
clickLogin(item) {
WebVideoCtrl.I_Login(item.ip, 1, item.port, 'admin', 'admin123', {
timeout: 3000,
success: () => {
console.log('登陆成功')
setTimeout(() => {
setTimeout(() => {
this.getChannelInfo(item)
}, 1000)
}, 10)
},
error: (oError) => {
if (this.ERROR_CODE_LOGIN_REPEATLOGIN === oError.errorCode) {
console.log('已登录过!')
} else {
console.log(" 登录失败!", oError.errorCode, oError.errorMsg)
}
}
})
}
获取通道信息
getChannelInfo 函数需要传递一个当前控制摄像头的信息对象。
模拟通道接口:WebVideoCtrl.I_GetAnalogChannelInfo
这里会使用 jq的一些方法,会对获取的xml元素进行遍历,并将获取的信息,加入到数组集合中,进行预览视频。
- id:获取的通道号是预览的必要字段。
- 数字通道:支持高清甚至超高清分辨率,如 1080P、2K、4K 等,但是对网络要求较高
- 零通道:无法播放,坏掉了。
- 模拟通道:成本小,实时性高。
// 初始化通道
getChannelInfo(item) {
// 模拟通道
WebVideoCtrl.I_GetAnalogChannelInfo(item.ip, {
success: (xmlDoc) => {
const oChannels = $(xmlDoc).find('VideoInputChannel')
$.each(oChannels, (i, channelObj) => {
let id = $(channelObj).find('id').eq(0).text(),
name = $(channelObj).find('name').eq(0).text()
if ("" === name) {
name = "Camera " + (i < 9 ? "0" + (i + 1) : (i + 1))
}
const ch = this.channel.find(arr => arr.ip === item.ip)
ch.channelId = id
ch.name = name
})
console.log(item.ip + '获取模拟通道成功!')
},
error: function (oError) {
console.log(ip + '获取模拟通道失败!', oError.errorCode, oError.errorMsg)
}
})
// 数字通道
WebVideoCtrl.I_GetDigitalChannelInfo(item.ip, {
success: function () {
// console.log(item.ip + '获取数字通道成功!')
},
error: function (oError) {
// console.log(item.ip + '获取数字通道失败!', oError.errorCode, oError.errorMsg)
}
})
// 零通道
WebVideoCtrl.I_GetZeroChannelInfo(item.ip, {
success: function () {
// console.log(item.ip + '获取零通道成功!')
},
error: function (oError) {
// console.log(item.ip + '获取零通道失败!', oError.errorCode, oError.errorMsg)
}
})
// 直接预览
this.clickStartRealPlay(item)
}
预览窗口
clickStartRealPlay 函数需要传递一个当前控制摄像头的信息对象。
WebVideoCtrl.I_GetWindowStatus
可以获取窗口的状态,比如传递 0 ,可以查看 第一个窗口的状态。返回值如果不是null,表示在播放了。
WebVideoCtrl.I_Stop
用于关闭当前播放的窗口,参数 iWndIndex 用于控制关闭的那个窗口,默认会根据当前选中的窗口。
WebVideoCtrl.I_StartRealPlay
预览视频
- 参数一:ip地址 + 下划线 + 端口,拼接的字符串,比如:'192.168.1.101_80'
- 参数二:是码流,1 主码流,2 子码流
- 参数三:是前面通过通道获取的通道ID
- 参数四:默认是false,表示是否播放零通道
- 参数五:RTSP端口号
// 预览窗口
clickStartRealPlay(item) {
const ips = item.ip + '_' + item.port
// 获取窗口的状态
const oWndInfo = WebVideoCtrl.I_GetWindowStatus(item.g_iWndIndex)
const iRtspPort = ''
const iChannelID = item.channelId
const bZeroChannel = item.zeroType
const szInfo = ''
const startRealPlay = function () {
WebVideoCtrl.I_StartRealPlay(ips, {
iWndIndex: item.g_iWndIndex,
iStreamType: 1,
iChannelID: iChannelID,
bZeroChannel: bZeroChannel,
iPort: iRtspPort,
success: function () {
console.log(ips + '开始预览成功!')
},
error: function (oError) {
console.log(ips + " 开始预览失败!", oError.errorCode, oError.errorMsg)
}
})
}
if (oWndInfo != null) { // 已经在播放了,先停止
WebVideoCtrl.I_Stop({
success: function () {
startRealPlay()
}
})
} else {
startRealPlay()
}
}
摄像头功能控制
接口:WebVideoCtrl.I_PTZControl
- 参数一:操作类型(1-上,2-下,3-左,4-右,5-左上,6-左下,7-右上,8-右下,9-自转,10-调焦+, 11-调焦-, 12-F聚焦+, 13-聚焦-, 14-光圈+, 15-光圈-
- 参数二:true 停止,false 启动
- 参数三:对象:iWndIndex 窗口号,默认为当前选中窗口,iPTZSpeed 云台速度,默认为4
<div class="jiu" :style="{display: isOpen ? 'flex': 'none'}">
<div class="remote-control">
<el-tooltip content="向左上转动" placement="top-start" effect="light">
<div class="button top-left" @mousedown="mouseDownPTZControl(5, false)"
@mouseup="mouseDownPTZControl(1, true)"></div>
</el-tooltip>
<el-tooltip content="向上转动" placement="top-start" effect="light">
<div class="button" @mousedown="mouseDownPTZControl(1, false)"
@mouseup="mouseDownPTZControl(1, true)">
<i class="iconfont icon-shangjiantou1"></i>
</div>
</el-tooltip>
<el-tooltip content="向右上转动" placement="top-start" effect="light">
<div class="button top-right" @mousedown="mouseDownPTZControl(7, false)"
@mouseup="mouseDownPTZControl(1, true)"></div>
</el-tooltip>
<el-tooltip content="向左转动" effect="light">
<div class="button" @mousedown="mouseDownPTZControl(3, false)"
@mouseup="mouseDownPTZControl(1, true)">
<i class="iconfont icon-zuojiantou"></i>
</div>
</el-tooltip>
<el-tooltip content="开启自动旋转" effect="light">
<div class="button center" @click="mouseDownPTZControl(9, false)">
<i class="iconfont icon-zidongxuanzhuan"></i>
</div>
</el-tooltip>
<el-tooltip content="向右转动" effect="light">
<div class="button" @mousedown="mouseDownPTZControl(4, false)"
@mouseup="mouseDownPTZControl(1, true)">
<i class="iconfont icon-youjiantou"></i>
</div>
</el-tooltip>
<el-tooltip content="向左下转动" effect="light">
<div class="button bottom-left" @mousedown="mouseDownPTZControl(6, false)"
@mouseup="mouseDownPTZControl(1, true)"></div>
</el-tooltip>
<el-tooltip content="向下转动" effect="light">
<div class="button" @mousedown="mouseDownPTZControl(2, false)"
@mouseup="mouseDownPTZControl(1, true)">
<i class="iconfont icon-xiajiantou1"></i>
</div>
</el-tooltip>
<el-tooltip content="向右下转动" effect="light">
<div class="button bottom-right" @mousedown="mouseDownPTZControl(8, false)"
@mouseup="mouseDownPTZControl(1, true)"></div>
</el-tooltip>
</div>
</div>
<!-- 下方操作按钮 -->
<div class="div-group" :style="{display: isOpen ? 'block': 'none'}">
<div style="display: flex; justify-content:space-around;">
<el-button-group>
<el-tooltip content="焦距变大" placement="top" effect="light">
<div class="btn-groups" @mousedown="mouseDownPTZControl(10, false)" @mouseup="mouseDownPTZControl(11, true)">
<i class="iconfont icon-fangdajing-jia"></i>
</div>
</el-tooltip>
<el-tooltip content="焦距变小" placement="top" effect="light">
<div class="btn-groups" @mousedown="mouseDownPTZControl(11, false)" @mouseup="mouseDownPTZControl(11, true)">
<i class="iconfont icon-fangdajing-jian"></i>
</div>
</el-tooltip>
</el-button-group>
<el-button-group>
<el-tooltip content="焦点前调" placement="top" effect="light">
<div class="btn-groups" @mousedown="mouseDownPTZControl(12, false)" @mouseup="mouseDownPTZControl(12, true)">
<i class="iconfont icon-jiaodianqiantiao"></i>
</div>
</el-tooltip>
<el-tooltip content="焦点后调" placement="top" effect="light">
<div class="btn-groups" @mousedown="mouseDownPTZControl(13, false)" @mouseup="mouseDownPTZControl(12, true)">
<i class="iconfont icon-jiaodianhoutiao"></i>
</div>
</el-tooltip>
</el-button-group>
<!-- <el-button-group>
<el-tooltip content="光圈扩大" placement="top" effect="light">
<el-button>
<i class="iconfont icon-guangquankuoda"></i>
</el-button>
</el-tooltip>
<el-tooltip content="光圈缩小" placement="top" effect="light">
<el-button>
<i class="iconfont icon-guangquansuoxiao"></i>
</el-button>
</el-tooltip> -->
</el-button-group>
</div>
</div>
mouseDownPTZControl(iPTZIndex, selection) {
// 获取窗口状态
const oWndInfo = WebVideoCtrl.I_GetWindowStatus(this.item.g_iWndIndex)
if (oWndInfo == null) {
return
}
// 如果是零通道,直接返回
if (this.item.zeroType) {
return
}
let iPTZSpeed = selection ? 0 : 4
// 表示开启了自动
if (9 === iPTZIndex && this.g_bPTZAuto) {
// 将速度置为 0
iPTZSpeed = 0
} else {
this.g_bPTZAuto = false
}
// 控制云平台
WebVideoCtrl.I_PTZControl(iPTZIndex, selection, {
iWndIndex: this.item.g_iWndIndex, iPTZSpeed,
success: (xmlDoc) => {
if (9 == iPTZIndex) {
this.g_bPTZAuto = !this.g_bPTZAuto
}
},
error: function (oError) {
console.log(oWndInfo.szDeviceIdentify + " 开启云台失败!", oError.errorCode, oError.errorMsg)
}
})
}
到此就结束了,海康这个还不错,就是没有vue webpack的包,在webpack 的环境下,是会报错的。
来源:juejin.cn/post/7449644683330240549
我:偷偷告诉你,我们项目里的进度条,全都是假的!🤣 产品:???😲
扯皮
最近接到了一个需求:前端点击按钮触发某个任务并开启轮询获取任务进度,直至 100% 任务完成后给予用户提示
这个业务场景还挺常见的,但是突然上周后端联系到我说现在的效果有点差,之前都是小任务那进度条展示还挺不错的,现在有了一些大任务且会存在排队阻塞的情况,就导致视图上经常卡 0% 排队,用户体验太差了,问能不能在刚开始的时候做个假进度先让进度条跑起来😮
因此就有了这篇文章,简单做一下技术调研以及在项目中的应用
正文
其实假进度条也不难做,无非是轮询的时候我们自己做一个随机的自增,让它卡到 99% 等待后端真实进度完成后再结束
只不过还是想调研一下看看市面上有没有一些成熟的方案并去扒一下它们的源码🤓
NProgress
首先当我听到这里的需求后第一时间想到的就是它:rstacruz/nprogress: For slim progress bars like on YouTube, Medium, etc
记得大学期间做的一些中后台系统基本都少不了路由跳转时的顶部进度条加载,那时候就有了解到 NProgress,它的使用方式也很简单,完全手控:NProgress: slim progress bars in JavaScript,去文档里玩一下就知道了
视图呈现的效果就是如果你不手动结束那它就会一直缓慢前进卡死 99% ,挺符合我们这里的需求,可以去扒一下它内部进度计算相关的逻辑
NProgress 的内容实际上比较少,源码拉下来可以看到主要都在这一个 JS 文件里了:
需要注意的是我们看的是这个版本:rstacruz/nprogress at v0.2.0,master 分支与 npm 安装的 0.2.0 内部实现还是有些差别的
我们这里不关注它的样式相关计算,主要来看看对进度的控制,直奔 start 方法:
还是比较清晰的,这里的 status
就是内部维护的进度值,默认为 null,所以会执行 NProgress.set
,我们再来看看 set 方法:
set 方法里有一大堆设置动画样式逻辑都被我剪掉了,关于进度相关的只有这些。相当于利用 clamp 来做一个夹层,因为初始进来的 n 为 null,所以经过处理后进度变为 0.08
再回到 start 的逻辑,其中 work
就是内部轮询控制进度自增的方法了,初始配置 trickle
为 true 代表自动开启进度自增,由于进度条在 set 方法中已经设置为 0.08,所以走到后面的 NProgress.trickle
逻辑
看来这里就是进度控制的核心逻辑了, trickle
里主要调用了 inc
,在 trickle
中给 inc
传递了一个参数:Math.random() * Settings.trickleRate
,显然这里范围是:0 <= n < 0.02
而在 inc
中,如果传递的 amount 有值的话那就每次以该值进行自增,同时又使用 clamp 将最大进度卡在 0.994
最后再调用 set
方法,set 里才是更新进度和视图进度条的方法,涉及到进度更新时都需要回到这里
当然 NProgress.inc
也可以手动调用,还对未传参做了兼容处理:
amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95)
即根据当前进度 n 计算剩余进度,再随机生成自增值
再来看 done
方法,它就比较诡异了:
按理来说直接将进度设置为 1 就行,但它以链式调用 inc
再调用 set
,相当于调用了两次 set
而这里 inc
传参又没什么规律性,推测是为了 set
中的样式处理,感兴趣的可以去看看那部分逻辑,还挺多的...😶
一句话总结一下 NProgress 的进度计算逻辑:随机值自增,最大值限制
但是因为 NProgress 与进度条样式强绑定,我们肯定是没法直接用的
fake-progress
至于 fake-progress 则是我在调研期间直接搜关键词搜出来的😶:piercus/fake-progress: Fake a progress bar using an exponential progress function
很明显看介绍就是干这个事的,而且还十分专业,引入数学函数展示假进度条效果,具有说服力:
所以我们项目中其实就是用的这个包,只不过和 NProgress 类似,两个包都比较老了,瞟一眼源码发现都是老 ES5 了🤐
因为我们项目中用的是 React,这里给出 React 的 demo 吧,为了编写方便使用了几个 ahooks 里的 hook:
其实使用方法上与 NProgress 都类似,不过两者都属于通用的工具库不依赖其他框架,所以像视图渲染都需要自己手动来做
注意实例化中的传参 timeConstant
,某种意义上来讲这个值就相当于“进度增长的速率”,但也不完全等价,我们来看看源码
因为不涉及到样式,fake-progress 源码更简单,核心就在这里:
下方的数学公式就是介绍图中展示的,只能说刚看到这部分内容是真的是死去的数学只是突然又开始攻击我😅,写了那么多函数,数学函数是啥都快忘了
我们来简单分析一下这个函数 1 - Math.exp(-1 * x)
,exp(x)= , 的图像长这样,高中的时候见的太多了:
那假如这里改成 exp(-x) 呢?有那味了,以前应该是有一个类似的公式 与 图像效果是关于 y 轴对称,好像是有些特殊的不符合这个规律?🤔反正大部分都是满足的
OK,那我们继续进行转换,看看 -exp(-x) 的效果
同样有个公式 与 图像效果是关于 x 轴对称:
初见端倪,不知道你们有没有注意 -exp(-x) 最终呈现的图像是无限接近于 x 轴的,也就是 0:
那有了🤓,假如我再给它加个 1 呢?它不就无限接近于 1 了,即 -exp(-x) + 1,这其实就是 fake-progress 里公式的由来:
但你会发现如果 x 按 1 递增就很快进度就接近于 1 了,所以有了 timeConstant
配置项来控制 x 的增长,回看这个公式:1 - Math.exp(-1 * this._time / this.timeConstant)
this._time
是一直在增长的,而 this.timeConstant
作为分母如果被设置为一个较大的值,那可想而知进度增长会巨慢
所以 fake-progress 的核心原理是借助数学函数,以函数值无限接近于 1 来实现假进度条,但是这种实现有一个 bug,可以看我提的这个 issues,不过看这个包的更新时间感觉作者也不会管了😅:
bug: progress may reach 100% · Issue #7 · piercus/fake-progress
useFakeProgress
虽然我们现在项目中使用的是 fake-progress,但是个人感觉用起来十分鸡肋,而且上面的 bug 也需要自己手动兼容,因此萌生出自己封装一个 hook 的想法,让它更符合业务场景
首先我们确定一下进度计算方案,这里我毫不犹豫选择的是 NProgress 随机值增长方案,为什么?因为方便用户自定义
而且 NProgress 相比于 fake-progress 有一个巨大优势:手动 set 进度后仍然保持进度正常自动递增
这点在 fake-progress 中实现是比较困难的,因为你无法保证手动 set 的进度是在这个函数曲线上,相当于给出函数 y 值反推 x 值,根据反推的 x 值再进行递增,想想都麻烦
确定好方案后我们来看下入参吧,参考 NProgress 我定义了这几个配置项:
这里我简单解释一下 rerender 和 amount 配置:
实际上在封装这个 hook 的时候我一直在纠结这里的 progress 到底是 state 还是 ref,因为大多数场景下 hook 内部通过轮询定时器更新进度,而真实业务代码中也会开启定时器去轮询监听业务接口的
所以如果写死为 state,那这个场景 hook 内部的每次更新 render 是没必要的,但是假如用户又想只是使用假进度展示,没有后端业务接口呢?
思来想去其实完全可以放权给用户进行配置,因为 state = ref + update,统一使用 ref,用户配置 rerender 时我们在每次更新时 update 即可
至于 amount 我是希望放权给用户进行自定义递增值,你可以配置成一个固定值也可以配置成随机值,更可以像 NProgress master 分支下这样根据当前进度来控制自增,反正以函数参数的形式能够拿到当前的 progress:
至于实现细节就不再讲述了,实际上就是轮询定时器没什么复杂的东西,直接上源码了:
import { useRef, useState } from "react";
interface Options {
minimun?: number;
maximum?: number;
speed?: number;
rerender?: boolean;
amount?: (progress: number) => number;
formatter?: (progress: number) => string;
onProgress?: (progress: number) => void;
onFinish?: () => void;
}
export function useFakeProgress(options?: Options): [
{ current: string },
{
inc: (amount?: number) => void;
set: (progress: number) => void;
start: () => void;
stop: () => void;
done: () => void;
reset: () => void;
get: () => number;
}
] {
const {
minimun = 0.08,
maximum = 0.99,
speed = 800,
rerender = false,
amount = (p: number) => (1 - p) * clamp(Math.random() * p, minimun, maximum),
formatter = (p: number) => `${p}`,
onProgress,
onFinish,
} = options || {};
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const progressRef = useRef(0);
const progressDataRef = useRef(""); // formatter 后结果
const [, update] = useState({});
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
const setProgress = (p: number) => {
progressRef.current = p;
progressDataRef.current = formatter(p);
onProgress?.(p);
if (rerender) update({});
};
const work = () => {
const p = clamp(progressRef.current + amount(progressRef.current), minimun, maximum);
setProgress(p);
};
const start = () => {
function pollingWork() {
work();
timerRef.current = setTimeout(pollingWork, speed);
}
if (!timerRef.current) pollingWork();
};
const set = (p: number) => {
setProgress(clamp(p, minimun, maximum));
};
const inc = (add?: number) => {
set(progressRef.current + (add || amount(progressRef.current)));
};
const stop = () => {
if (timerRef.current) clearInterval(timerRef.current);
timerRef.current = null;
};
const reset = () => {
stop();
setProgress(0);
};
const done = () => {
stop();
setProgress(1);
onFinish?.();
};
return [
progressDataRef,
{
start,
stop,
set,
inc,
done,
reset,
get: () => progressRef.current,
},
];
}
这里需要补充一个细节,在返回值里使用的是 progressDataRef 是 formatter 后的结果为 string 类型,如果用户想要获取原 number 的 progress,可以使用最下面提供的 get 方法拿 progressRef 值
一个 demo 看看效果,感觉还可以:
当然由于直接返回了 ref,为了防止用户篡改可以再上一层代理劫持,我们就省略了
这也算一个工具偏业务的 hook,可以根据自己的业务来进行定制,这里很多细节都没有补充只是一个示例罢了🤪
End
以上就是这篇文章的内容,记得上班之前还在想哪有那么多业务场景需要封装自定义 hook,现在发现真的是各种奇葩需求都可以封装,也算是丰富自己武器库了...
来源:juejin.cn/post/7449307011710894080
差生文具多,这么些年,为了写代码我花了多少钱?
背景
转眼写代码有10多年了,林林总总花费了很多的钱,现在主要按照4大件来盘点下我都买了啥。
电脑
acer 4741g ¥4500+
这是我入门时的一款电脑,整体配置在当时还是属于中等的。
当时用的编辑器还是notepad++,在这个配置下,还是可以愉快的编码的。
mac air 2013 ¥8800+
当时被苹果的放进信封的广告创意所折服,这也是我的第一台apple,在之后就一直用苹果了。到手后的感觉是,薄,确实薄,大概只有我宏基的1/3-1/4厚。
当时apple的简洁,快速,很少的配置,让我在环境变量上苦苦挣扎的心酸得以释放。以后也不用比较各种笔记本参数了
mac book pro13 2015 ¥9000+
当时买这台的原因是因为air进水了,经常死机,修了2次后,又坏了。一怒之下,直接买了一台。
换了新的retina屏之后,色彩质量和效果都提升了不少,对比原来的air性能也是拉升了超级多。但是因为是16上半年买的,所以没体验到toch bar,到现在都没体验过。。。
这是我真正意义上的第一台十分满意的电脑,大概是当时的理想型了。
公司电脑
2016年下半年进了一家创业公司,公司配置了mac book pro,比我的配置还高,所以之后一直就是用公司的。
2021年换新公司,公司配了thinkpad,又一次开始用win。然后又被win各种打败,有时又有了换回mac的想法。
当前–mac book pro14 2021 ¥21999
主要入手的原因是公司的电脑我觉得太慢了。当时开发小程序,脚手架是公司自己的,每次打包都是全量的,没有缓存。所以每次打包短则7,8分钟,长则10多分钟。加上切分支/安装依赖(如果两个分支依赖版本不同,需要强制更新),导致我每天花费大量的时间等待上。
同事早于我入手了M1,反馈巨好,于是我也买了,想着配置拉的满点,但是还是高估了自己的钱包,低估了苹果的价格,只能退而求其次的选择了中档配置。
每次看着低低的负载,都是满满的安全感。
另外m1是支持stable diffusion的,所以偶尔我也会炼丹
显示器
dell U2424H ¥1384
其实在写代码之前也买过几台显示器,但是以程序员视角来说,第一台是这台。原因是当时公司也是这个型号,主要是能旋转,谁能拒绝一台自由旋转的显示器呢?
而且dell的质量和做工都不错,在当时是十分喜欢的。
小米 Redmi 27 ¥789.9
dell那台显示器是放在家里的,公司也需要显示器,而且自带设备每个月可以补贴100,所以就入手了这款,原因无他:便宜,也够大。
但是用久了,发现也有些问题。例如失真等,但是真的便宜,
厂家送寄,但因为合作内容没谈拢,本周寄回
键盘
当前-cherry G80-3000 ¥689
一把真正可以用到包浆的键盘,大多数看到这个键盘的感觉应该都是黄色,而不是原本的白色,不知道是不是材质的问题,极其容易变黄。同时由于键帽又不变黄,所以呈现了诡异的脏脏的颜色。
因为本身机械键盘的高度,所以建议加个手托比较好。各种轴也齐全,任君选择。
目前这个键盘在家里游戏了,毕竟是个全键盘
当前–京造C2 ¥253
选择这个键盘的原因嘛,同事有了,并且是一个带灯的键盘。手感比cherry硬一些,但还属于是能接受的程度,整体延迟比较低(也可能是因为有线的原因)。目前是在办公室使用的一款,当前这篇文章就是用这个敲出来的。
鼠标
总览
鼠标其实留在手边的不太多,大多数都是消耗品了,这么些年,各种有用过。大概用了不下10个鼠标,我只挑2个重点的说吧。
微软ie 3.0 ¥359
这是我用过最好的鼠标,没有之一。握感极佳,用久了也不累,比其他的鼠标都舒服万分。
当前–apple magic trapad ¥899
mac用户的最终归属就是板子,如果你刚开始用mac,那么建议直接用板子吧。支持原生手势操作,各种mac本身触控板的事情都完美适用,真正的跟你的电脑和为一体。
欢迎评论区留言你的设备
如上所述,我这年的大头是电脑,消耗品是鼠标、,那么你都花了多少钱呢?
来源:juejin.cn/post/7395473411651682343
2025年,前端开发为什么一定要学习Rust?
引言
Rust语言是一门现代系统编程语言,由Mozilla Research于2009年开始开发,Mozilla Research 是 Mozilla 基金会旗下的一个研究部门,专注于推动开放网络和创新技术的发展,Rust语言正是在 Mozilla Research 中孕育并发展的。
Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目,在2006年开始了Rust语言的初步设计,Mozilla 随后投入资源,支持Rust的发展,并最终于2010年公开这个项目,2015年发布1.0版本。
以下引用自Rust 语言圣经
大家可能疑惑 Rust 为啥用了这么久才到 1.0 版本?与之相比,Go 语言 2009 年发布,却在 2012 年仅用 3 年就发布了 1.0 版本[^1]。
● 首先,Rust 语言特性较为复杂,所以需要全盘考虑的问题非常多;
● 其次,Rust 当时的参与者太多,七嘴八舌的声音很多,众口难调,而 Rust 开发团队又非常重视社区的意见;
● 最后,一旦 1.0 快速发布,那绝大部分语言特性就无法再被修改,对于有完美强迫症的 Rust 开发者团队来说,某种程度上的不完美是不可接受的。
因此,Rust 语言用了足足 6 年时间,才发布了尽善尽美的 1.0 版本。
大家知道 Rust 的作者到底因为何事才痛下决心开发一门新的语言吗?
说来挺有趣,在 2006 年的某天,作者工作到精疲力尽后,本想回公寓享受下生活,结果发现电梯的程序出 Bug 崩溃了,要知道在国外,修理工可不像在中国那样随时待岗,还要知道,他家在 20 多楼!
最后,他选择了妥协,去酒店待几天等待电梯的修理。
当然,一般人可能就这样算了,毕竟忍几天就过去了嘛。但是这名伟大的程序员显然也不是一般人,他面对害他流离失所的电梯拿起了屠龙宝刀 - Rust。
自此,劈开一个全新的编程世界。
Rust语言是一门现代系统编程语言,由Mozilla Research于2009年开始开发,Mozilla Research 是 Mozilla 基金会旗下的一个研究部门,专注于推动开放网络和创新技术的发展,Rust语言正是在 Mozilla Research 中孕育并发展的。
Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目,在2006年开始了Rust语言的初步设计,Mozilla 随后投入资源,支持Rust的发展,并最终于2010年公开这个项目,2015年发布1.0版本。
以下引用自Rust 语言圣经
大家可能疑惑 Rust 为啥用了这么久才到 1.0 版本?与之相比,Go 语言 2009 年发布,却在 2012 年仅用 3 年就发布了 1.0 版本[^1]。
● 首先,Rust 语言特性较为复杂,所以需要全盘考虑的问题非常多;
● 其次,Rust 当时的参与者太多,七嘴八舌的声音很多,众口难调,而 Rust 开发团队又非常重视社区的意见;
● 最后,一旦 1.0 快速发布,那绝大部分语言特性就无法再被修改,对于有完美强迫症的 Rust 开发者团队来说,某种程度上的不完美是不可接受的。
因此,Rust 语言用了足足 6 年时间,才发布了尽善尽美的 1.0 版本。
大家知道 Rust 的作者到底因为何事才痛下决心开发一门新的语言吗?
说来挺有趣,在 2006 年的某天,作者工作到精疲力尽后,本想回公寓享受下生活,结果发现电梯的程序出 Bug 崩溃了,要知道在国外,修理工可不像在中国那样随时待岗,还要知道,他家在 20 多楼!
最后,他选择了妥协,去酒店待几天等待电梯的修理。
当然,一般人可能就这样算了,毕竟忍几天就过去了嘛。但是这名伟大的程序员显然也不是一般人,他面对害他流离失所的电梯拿起了屠龙宝刀 - Rust。
自此,劈开一个全新的编程世界。
深入了解Rust
为什么要创建Rust这门语言?
在 Rust 出现之前,系统级编程领域主要由 C 和 C++ 统治。虽然这两种语言在性能方面表现出色,但它们也存在一些固有的缺陷,促使了 Rust 的诞生。
什么是系统级编程语言?
简单来说,系统级编程语言用于开发操作系统、驱动程序、嵌入式系统、游戏引擎、数据库等对性能和硬件控制要求极高的软件。
有以下特性:
- 硬件访问: 系统级语言需要能够直接访问硬件资源,直接操作硬件
- 高性能: 系统级程序通常需要直接操作硬件,对性能要求非常高。因此,系统级语言通常具有高效的内存管理机制和优化的编译器,以生成高效的机器码。
- 较强的类型系统和编译时检查:为了尽早发现潜在的错误,系统级语言通常具有较强的类型系统和编译时检查机制,以提高代码的可靠性和安全性。
- 并发和并行: 现代计算机系统通常具有多核处理器,系统级程序需要能够有效地利用多核资源,实现并发和并行执行,以提高性能。
- 内存控制: 系统级编程需要对内存进行精细的控制,包括内存分配、释放、布局等。一些系统级语言允许开发者直接操作内存地址,以实现更高的灵活性和效率
有哪些系统级编程语言?
- C/C++
无GC,性能高,内存不安全
- Rust
无GC,性能高,内存安全
- Go
有GC,性能不如Rust,安全性不如Rust。
- Assembly Language(汇编语言)
性能高,开发效率低
- zig
无GC,性能高,安全性不如Rust,发展初期
C/C++ 的缺陷
- 内存安全问题: C/C++ 允许开发者手动管理内存,这虽然提供了灵活性,但也容易导致各种内存安全问题,如:
- 空指针(Null Pointers): 访问未初始化的指针或空指针会导致程序崩溃。
- 野指针(Wild Pointers): 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。。
- 悬垂指针(Dangling Pointers): 指针指向曾经存在的对象,但该对象已经被释放,再次访问该指针会导致未定义行为,悬垂指针是野指针的一种。
- 双重释放(Double Free): 释放同一块内存两次,导致崩溃或不可预测的行为。
- 内存泄漏(Memory Leaks): 分配的内存没有被及时释放,导致内存占用不断增加,最终可能导致系统崩溃。
- 缓冲区溢出(Buffer Overflows): 向缓冲区写入超出其容量的数据,可能覆盖相邻的内存区域,导致程序崩溃或安全漏洞。
- 并发安全问题: C/C++ 的并发编程容易引入数据竞争(Data Races)等问题,导致程序行为不确定,难以调试和维护。
- 缺乏现代化的语言特性: C/C++ 的语法相对陈旧,缺乏一些现代化的语言特性,如模式匹配、类型推断等,使得代码编写和维护相对繁琐。
Rust 的创建正是为了解决 C/C++ 等语言的这些不足,同时保留其高性能的优点。具体来说,Rust 的设计目标是:
- 解决内存安全问题: Rust 通过所有权系统、借用检查器等机制,在编译时就杜绝了空指针、野指针、数据竞争等内存安全问题。
- 提供安全的并发编程: Rust 的所有权系统和类型系统也对并发安全提供了保障,使得开发者可以更容易地编写安全的并发程序。
- 提供现代化的语言特性: Rust 引入了模式匹配、类型推断、trait 等现代化的语言特性,提高了代码的简洁性、可读性和可维护性。
- 保持高性能: Rust 的设计理念是“零成本抽象”,即提供高级的抽象能力,但不会带来额外的运行时开销,且无需垃圾回收器等运行时机制,从而避免了额外的性能开销,媲美 C/C++。
简而言之,因为还缺一门无 GC 且无需手动内存管理、性能高、工程性强、语言级安全性、广泛适用性的语言
,而 Rust 就是这样的语言。
在 Rust 出现之前,系统级编程领域主要由 C 和 C++ 统治。虽然这两种语言在性能方面表现出色,但它们也存在一些固有的缺陷,促使了 Rust 的诞生。
什么是系统级编程语言?
简单来说,系统级编程语言用于开发操作系统、驱动程序、嵌入式系统、游戏引擎、数据库等对性能和硬件控制要求极高的软件。
有以下特性:
- 硬件访问: 系统级语言需要能够直接访问硬件资源,直接操作硬件
- 高性能: 系统级程序通常需要直接操作硬件,对性能要求非常高。因此,系统级语言通常具有高效的内存管理机制和优化的编译器,以生成高效的机器码。
- 较强的类型系统和编译时检查:为了尽早发现潜在的错误,系统级语言通常具有较强的类型系统和编译时检查机制,以提高代码的可靠性和安全性。
- 并发和并行: 现代计算机系统通常具有多核处理器,系统级程序需要能够有效地利用多核资源,实现并发和并行执行,以提高性能。
- 内存控制: 系统级编程需要对内存进行精细的控制,包括内存分配、释放、布局等。一些系统级语言允许开发者直接操作内存地址,以实现更高的灵活性和效率
有哪些系统级编程语言?
- C/C++
无GC,性能高,内存不安全
- Rust
无GC,性能高,内存安全
- Go
有GC,性能不如Rust,安全性不如Rust。
- Assembly Language(汇编语言)
性能高,开发效率低
- zig
无GC,性能高,安全性不如Rust,发展初期
C/C++ 的缺陷
- 内存安全问题: C/C++ 允许开发者手动管理内存,这虽然提供了灵活性,但也容易导致各种内存安全问题,如:
- 空指针(Null Pointers): 访问未初始化的指针或空指针会导致程序崩溃。
- 野指针(Wild Pointers): 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。。
- 悬垂指针(Dangling Pointers): 指针指向曾经存在的对象,但该对象已经被释放,再次访问该指针会导致未定义行为,悬垂指针是野指针的一种。
- 双重释放(Double Free): 释放同一块内存两次,导致崩溃或不可预测的行为。
- 内存泄漏(Memory Leaks): 分配的内存没有被及时释放,导致内存占用不断增加,最终可能导致系统崩溃。
- 缓冲区溢出(Buffer Overflows): 向缓冲区写入超出其容量的数据,可能覆盖相邻的内存区域,导致程序崩溃或安全漏洞。
- 并发安全问题: C/C++ 的并发编程容易引入数据竞争(Data Races)等问题,导致程序行为不确定,难以调试和维护。
- 缺乏现代化的语言特性: C/C++ 的语法相对陈旧,缺乏一些现代化的语言特性,如模式匹配、类型推断等,使得代码编写和维护相对繁琐。
Rust 的创建正是为了解决 C/C++ 等语言的这些不足,同时保留其高性能的优点。具体来说,Rust 的设计目标是:
- 解决内存安全问题: Rust 通过所有权系统、借用检查器等机制,在编译时就杜绝了空指针、野指针、数据竞争等内存安全问题。
- 提供安全的并发编程: Rust 的所有权系统和类型系统也对并发安全提供了保障,使得开发者可以更容易地编写安全的并发程序。
- 提供现代化的语言特性: Rust 引入了模式匹配、类型推断、trait 等现代化的语言特性,提高了代码的简洁性、可读性和可维护性。
- 保持高性能: Rust 的设计理念是“零成本抽象”,即提供高级的抽象能力,但不会带来额外的运行时开销,且无需垃圾回收器等运行时机制,从而避免了额外的性能开销,媲美 C/C++。
简而言之,因为还缺一门无 GC 且无需手动内存管理、性能高、工程性强、语言级安全性、广泛适用性的语言
,而 Rust 就是这样的语言。
为什么选择Rust语言?
- 保证安全、内存占用小的同时能提供和C/C++ 一样的性能
- 广泛的适用性,系统编程、网络服务、命令行工具、WebAssembly等场景都能应用
- 生态渐渐完善,有大量的库和框架,完整的工程化开发工具链,强大的包管理
- 社区非常活跃和友好,文档全面
Rust不是闭门造车的语言,能看出来设计者是做过大量不同语言的项目的人,Rust从解决实际问题出发,借鉴和融合其他语言的优点,又能够创新地提出所有权和生命周期,这个强大的能力带来了0开销的内存安全和线程安全。
凡事有利必有弊,Rust是站在前人的肩膀上,它集百家之长,借鉴了其他语言的许多优秀特性,如npm的包管理、go的channel来进行并发通信、Haskell的trait(类似java的接口),还有元组、泛型、枚举,闭包、智能指针这些特性并非rust原创,但Rust确实把这些优点全部吸收了进来,而没有做过度的设计和发散,让有一些其他语言基础的人还能够减轻一些上手成本。
即使这样,Rust依然是在一众语言里学习曲线最陡峭的语言之一,此外,Rust为了提高运行时性能必然是会牺牲一些编译时的效率的。
但是,这丝毫不会影响Rust成为一门伟大的语言,如果有一门语言可以改变你的编程思维方式,倒逼你进行更好的代码设计,让你在初学过程中连连发出“原来是这样啊”的感叹,那么它一定是Rust。
- 保证安全、内存占用小的同时能提供和C/C++ 一样的性能
- 广泛的适用性,系统编程、网络服务、命令行工具、WebAssembly等场景都能应用
- 生态渐渐完善,有大量的库和框架,完整的工程化开发工具链,强大的包管理
- 社区非常活跃和友好,文档全面
Rust不是闭门造车的语言,能看出来设计者是做过大量不同语言的项目的人,Rust从解决实际问题出发,借鉴和融合其他语言的优点,又能够创新地提出所有权和生命周期,这个强大的能力带来了0开销的内存安全和线程安全。
凡事有利必有弊,Rust是站在前人的肩膀上,它集百家之长,借鉴了其他语言的许多优秀特性,如npm的包管理、go的channel来进行并发通信、Haskell的trait(类似java的接口),还有元组、泛型、枚举,闭包、智能指针这些特性并非rust原创,但Rust确实把这些优点全部吸收了进来,而没有做过度的设计和发散,让有一些其他语言基础的人还能够减轻一些上手成本。
即使这样,Rust依然是在一众语言里学习曲线最陡峭的语言之一,此外,Rust为了提高运行时性能必然是会牺牲一些编译时的效率的。
但是,这丝毫不会影响Rust成为一门伟大的语言,如果有一门语言可以改变你的编程思维方式,倒逼你进行更好的代码设计,让你在初学过程中连连发出“原来是这样啊”的感叹,那么它一定是Rust。
Rust 核心设计理念对前端有哪些启示
Rust的很多设计理念都可以在前端领域中或多或少地找到影子,让你明白前端某些技术为什么要这么设计,以及为什么不那么设计。
Rust的很多设计理念都可以在前端领域中或多或少地找到影子,让你明白前端某些技术为什么要这么设计,以及为什么不那么设计。
安全性设计
1. 类型安全
- 静态类型检查:
Rust 提供了一个非常强大的类型系统,确保了类型安全。在编译时,Rust 会强制要求所有变量和函数都有明确的类型声明。这使得很多潜在的错误能够在编译时被捕获,避免了运行时出现类型错误。
反观JavaScript,作为js开发者,那是太有发言权了,由于js是动态类型语言,所以很多错误只能在运行时被发现,跑着跑着可能就出一个线上bug,这是多少前端开发者的痛啊。
当在写了一段时间Rust后,我们就会明白TypeScript为什么会火了,以及TS为什么是必要的,TS不能解决所有问题,但能解决大部分低级问题。
如果你不用TS,提高代码健壮性也是有方法的,只不过心智负担更重。参照文章接口一异常你的页面就直接崩溃了?
- 不可变性和可变性:
在 Rust 中,变量默认是不可变的,只有显式声明为可变 (mut) 才能修改。这种设计减少了错误发生的概率,因为不可变数据是线程安全的,不会在多个地方被修改。
在JS中也有类似的设计,联想到ES6的const和let,const 只能保证变量引用不可变,但如果引用的是对象或数组,内容依然可以改变。可谓是相当鸡肋。
- 静态类型检查:
Rust 提供了一个非常强大的类型系统,确保了类型安全。在编译时,Rust 会强制要求所有变量和函数都有明确的类型声明。这使得很多潜在的错误能够在编译时被捕获,避免了运行时出现类型错误。
反观JavaScript,作为js开发者,那是太有发言权了,由于js是动态类型语言,所以很多错误只能在运行时被发现,跑着跑着可能就出一个线上bug,这是多少前端开发者的痛啊。
当在写了一段时间Rust后,我们就会明白TypeScript为什么会火了,以及TS为什么是必要的,TS不能解决所有问题,但能解决大部分低级问题。
如果你不用TS,提高代码健壮性也是有方法的,只不过心智负担更重。参照文章接口一异常你的页面就直接崩溃了?
- 不可变性和可变性:
在 Rust 中,变量默认是不可变的,只有显式声明为可变 (mut) 才能修改。这种设计减少了错误发生的概率,因为不可变数据是线程安全的,不会在多个地方被修改。
在JS中也有类似的设计,联想到ES6的const和let,const 只能保证变量引用不可变,但如果引用的是对象或数组,内容依然可以改变。可谓是相当鸡肋。
2. 内存安全
Rust 提供了一种独特的所有权系统来自动管理内存,避免了许多传统语言中常见的内存错误,如内存泄漏、悬垂指针和双重释放。
Rust 的内存安全设计包括以下几个方面:
- 所有权系统:Rust 中的所有权系统确保每个资源只有一个所有者,而所有权可以转移。一旦所有权转移,原所有者无法再访问或修改该资源,资源离开作用域时会自动释放,这就避免了双重释放和内存泄漏的问题。
- 借用检查器:Rust 的借用检查器确保在同一时间只能存在不可变借用或一个可变借用。这避免了并发情况下的内存冲突。
- 生命周期:Rust 的生命周期系统确保引用的有效性,在编译时检查引用的生命周期与持有它的资源的生命周期是否匹配,从而防止悬空引用和野指针。
反观JavaScript 中的内存问题:
- 内存泄漏(Memory Leak): 内存泄漏是指程序无法释放不再使用的内存,导致内存资源被浪费。在 JavaScript 中,由于垃圾回收机制,内存泄漏通常发生在以下几种情况:
全局变量:
全局变量是在全局作用域中声明的,它们在程序执行期间存在,直到程序结束时才会被销毁。因此,无论这些全局变量是否仍在使用,它们都将保持存在,无法被垃圾回收器回收。
// 全局变量
var globalVar = { name: 'example' };
// 该对象即使没有被引用,仍然会存在,直到页面关闭
在浏览器中,全局变量被视为 window 对象的属性,在 Node.js 中,则是 global 对象。
垃圾回收器一般不会回收全局变量,原因之一是全局变量的清理通常意味着整个应用程序的关闭或重载。如果要强制回收全局变量,会导致额外的复杂性和性能开销。因此,大多数 JavaScript 引擎(如 V8)选择让全局变量一直存活。
全局变量不仅会导致内存泄漏还有容易被意外覆盖的风险,尽量使用模块化、闭包等方式来避免将变量暴露到全局作用域中。
同理,全局变量也会导致无法准确的进行treeshaking优化,因为全局变量是有副作用的。
闭包(Closures)
:闭包可能会保持对外部函数作用域变量的引用,从而防止这些变量被回收。
function createClosure() {
let largeObject = new Array(1000000).fill('Memory leak');
// 返回一个函数,访问 largeObject
return function() {
console.log(largeObject[0]);
};
}
const closure = createClosure();
// 使用完闭包后,显式清除引用
closure = null; // 删除对闭包的引用,垃圾回收器可以回收 largeObject
largeObject 是一个占用大量内存的对象。当我们调用 createClosure 时,它返回一个内部函数 closure,这个内部函数会引用 largeObject。尽管 largeObject 的生命周期在 createClosure 执行完之后结束,但由于 closure 仍然持有对 largeObject 的引用,这个对象就无法被垃圾回收器回收,从而导致内存泄漏。
闭包本身不会引起内存泄漏,但如果闭包捕获了外部函数的引用,且这些引用长时间未清除,就可能导致内存泄漏。
事件监听器
:如果没有正确移除事件监听器,可能导致无法释放关联的内存。
如果我们为 DOM 元素注册了事件监听器,但没有在适当的时候移除它们,尤其是在元素被删除或不再需要时,事件监听器会一直保持对 DOM 元素的引用,从而防止垃圾回收。
"my-element">Click me!
<script>
let element = document.getElementById('my-element');
// 给 DOM 元素添加事件监听器
function handleClick() {
console.log('Element clicked');
}
element.addEventListener('click', handleClick);
// 假设我们从 DOM 中移除了该元素
document.body.removeChild(element);
// 但是我们没有移除事件监听器,事件监听器仍然持有对该元素的引用
// 因此该元素无法被垃圾回收
script>
需要手动清除事件监听器
element.removeEventListener('click', handleClick); // 移除事件监听器
element = null; // 清除对 DOM 元素的引用
DOM 元素引用
:如果 DOM 元素的引用在不再需要时没有清除,垃圾回收机制也无法回收它们。
当我们通过 DOM 操作获取并引用一个 DOM 元素时,如果该元素的引用没有及时清除,即使该元素已经被移除或不再需要,它也不会被垃圾回收,从而导致内存泄漏。
"my-element">Hello, World!
<script>
// 获取 DOM 元素并保存引用
let element = document.getElementById('my-element');
// 动态移除该元素
document.body.removeChild(element);
// 但是我们没有清除 element 引用
// 这个引用仍然指向已经从 DOM 树中移除的元素
// 此时垃圾回收器无法回收这个元素,因为引用仍然存在
script>
可以使用 element = null
来清除引用,但这个操作需要手动执行,容易忘记。
Rust 提供了一种独特的所有权系统来自动管理内存,避免了许多传统语言中常见的内存错误,如内存泄漏、悬垂指针和双重释放。
Rust 的内存安全设计包括以下几个方面:
- 所有权系统:Rust 中的所有权系统确保每个资源只有一个所有者,而所有权可以转移。一旦所有权转移,原所有者无法再访问或修改该资源,资源离开作用域时会自动释放,这就避免了双重释放和内存泄漏的问题。
- 借用检查器:Rust 的借用检查器确保在同一时间只能存在不可变借用或一个可变借用。这避免了并发情况下的内存冲突。
- 生命周期:Rust 的生命周期系统确保引用的有效性,在编译时检查引用的生命周期与持有它的资源的生命周期是否匹配,从而防止悬空引用和野指针。
反观JavaScript 中的内存问题:
- 内存泄漏(Memory Leak): 内存泄漏是指程序无法释放不再使用的内存,导致内存资源被浪费。在 JavaScript 中,由于垃圾回收机制,内存泄漏通常发生在以下几种情况:
全局变量:
全局变量是在全局作用域中声明的,它们在程序执行期间存在,直到程序结束时才会被销毁。因此,无论这些全局变量是否仍在使用,它们都将保持存在,无法被垃圾回收器回收。
// 全局变量
var globalVar = { name: 'example' };
// 该对象即使没有被引用,仍然会存在,直到页面关闭
在浏览器中,全局变量被视为 window 对象的属性,在 Node.js 中,则是 global 对象。
垃圾回收器一般不会回收全局变量,原因之一是全局变量的清理通常意味着整个应用程序的关闭或重载。如果要强制回收全局变量,会导致额外的复杂性和性能开销。因此,大多数 JavaScript 引擎(如 V8)选择让全局变量一直存活。
全局变量不仅会导致内存泄漏还有容易被意外覆盖的风险,尽量使用模块化、闭包等方式来避免将变量暴露到全局作用域中。
同理,全局变量也会导致无法准确的进行treeshaking优化,因为全局变量是有副作用的。
闭包(Closures)
:闭包可能会保持对外部函数作用域变量的引用,从而防止这些变量被回收。
function createClosure() {
let largeObject = new Array(1000000).fill('Memory leak');
// 返回一个函数,访问 largeObject
return function() {
console.log(largeObject[0]);
};
}
const closure = createClosure();
// 使用完闭包后,显式清除引用
closure = null; // 删除对闭包的引用,垃圾回收器可以回收 largeObject
largeObject 是一个占用大量内存的对象。当我们调用 createClosure 时,它返回一个内部函数 closure,这个内部函数会引用 largeObject。尽管 largeObject 的生命周期在 createClosure 执行完之后结束,但由于 closure 仍然持有对 largeObject 的引用,这个对象就无法被垃圾回收器回收,从而导致内存泄漏。
闭包本身不会引起内存泄漏,但如果闭包捕获了外部函数的引用,且这些引用长时间未清除,就可能导致内存泄漏。
事件监听器
:如果没有正确移除事件监听器,可能导致无法释放关联的内存。
如果我们为 DOM 元素注册了事件监听器,但没有在适当的时候移除它们,尤其是在元素被删除或不再需要时,事件监听器会一直保持对 DOM 元素的引用,从而防止垃圾回收。
"my-element">Click me!
<script>
let element = document.getElementById('my-element');
// 给 DOM 元素添加事件监听器
function handleClick() {
console.log('Element clicked');
}
element.addEventListener('click', handleClick);
// 假设我们从 DOM 中移除了该元素
document.body.removeChild(element);
// 但是我们没有移除事件监听器,事件监听器仍然持有对该元素的引用
// 因此该元素无法被垃圾回收
script>
需要手动清除事件监听器
element.removeEventListener('click', handleClick); // 移除事件监听器
element = null; // 清除对 DOM 元素的引用
DOM 元素引用
:如果 DOM 元素的引用在不再需要时没有清除,垃圾回收机制也无法回收它们。
当我们通过 DOM 操作获取并引用一个 DOM 元素时,如果该元素的引用没有及时清除,即使该元素已经被移除或不再需要,它也不会被垃圾回收,从而导致内存泄漏。
"my-element">Hello, World!
<script>
// 获取 DOM 元素并保存引用
let element = document.getElementById('my-element');
// 动态移除该元素
document.body.removeChild(element);
// 但是我们没有清除 element 引用
// 这个引用仍然指向已经从 DOM 树中移除的元素
// 此时垃圾回收器无法回收这个元素,因为引用仍然存在
script>
可以使用
element = null
来清除引用,但这个操作需要手动执行,容易忘记。
v8的垃圾回收器
V8 中的GC采用标记清除法进行垃圾回收。主要流程如下:
- 标记:从根对象开始,遍历所有的对象引用,被引用的对象标记为活动对象,没有被引用的对象(待清理)标记为垃圾数据。
- 垃圾清理:将所有垃圾数据清理掉
在我们的开发过程中,如果我们想要让垃圾回收器回收某一对象,就将对象的引用直接设置为 null
let a = {}; // {} 可访问,a 是其引用
a = null; // 引用设置为 null
// {} 将会被从内存里清理出去
但如果一个对象被多次引用时,例如作为另一对象的键、值或子元素时,将该对象引用设置为 null 时,该对象是不会被回收的
let a = {};
let arr = [a];
a = null;
console.log(arr)
// [{}]
因为a被arr引用,即使a不被使用了,也不会被释放,除非arr也被设置为null。
JS也考虑到了这一点,在ES6中推出了: WeakMap和WeakSet 。它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)。
let a = {};
let arr = new WeakSet();
arr.add(a);
a = null;
console.log(arr.has(a))
// false
即 arr 对a的引用是弱引用,如果a不用了,不会阻止垃圾回收。
以上代码可以在控制台自行尝试一下
即便JS给出了可以避免特定场景的内存泄漏的方案,但依然无法避免所有场景的内存泄漏,而且就算你熟谙内存泄漏的各种场景以及对应解决方案,百密也终有一疏,更何况实际开发中代码能跑起来我们就几乎不会考虑啥内存问题,而Rust则强制你一定要考虑内存安全,否则编译都不过。
V8 中的GC采用标记清除法进行垃圾回收。主要流程如下:
- 标记:从根对象开始,遍历所有的对象引用,被引用的对象标记为活动对象,没有被引用的对象(待清理)标记为垃圾数据。
- 垃圾清理:将所有垃圾数据清理掉
在我们的开发过程中,如果我们想要让垃圾回收器回收某一对象,就将对象的引用直接设置为 null
let a = {}; // {} 可访问,a 是其引用
a = null; // 引用设置为 null
// {} 将会被从内存里清理出去
但如果一个对象被多次引用时,例如作为另一对象的键、值或子元素时,将该对象引用设置为 null 时,该对象是不会被回收的
let a = {};
let arr = [a];
a = null;
console.log(arr)
// [{}]
因为a被arr引用,即使a不被使用了,也不会被释放,除非arr也被设置为null。
JS也考虑到了这一点,在ES6中推出了: WeakMap和WeakSet 。它对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)。
let a = {};
let arr = new WeakSet();
arr.add(a);
a = null;
console.log(arr.has(a))
// false
即 arr 对a的引用是弱引用,如果a不用了,不会阻止垃圾回收。
以上代码可以在控制台自行尝试一下
即便JS给出了可以避免特定场景的内存泄漏的方案,但依然无法避免所有场景的内存泄漏,而且就算你熟谙内存泄漏的各种场景以及对应解决方案,百密也终有一疏,更何况实际开发中代码能跑起来我们就几乎不会考虑啥内存问题,而Rust则强制你一定要考虑内存安全,否则编译都不过。
3. 并发安全
Rust 的并发模型通过其所有权和借用规则确保了并发编程中的安全性。Rust 中的并发安全设计包括:
- 数据竞争防止:Rust 中,数据竞争在编译时就能被发现。Rust 通过所有权规则确保要么有多个不可变借用,要么有一个可变借用,从而避免了并发时对共享数据的非法访问。
- 线程安全:Rust 使用 Send 和 Sync 特性来标识哪些类型可以在线程之间传递或共享。Send 允许数据在不同线程之间传递,Sync 允许多个线程共享数据。
- 锁机制:Rust 提供了 Mutex 和 RwLock 等机制来确保在多线程环境下对共享资源的安全访问。
JS是单线程,但是JS的单线程是基于事件循环的非阻塞的,所以可以通过异步来实现伪并发,竞态条件、数据竞争、数据共享等问题在JS中是很难发现的,甚至别人的代码修改了你的数据你都不知道,没有一定的开发经验积累,去排查由此产生地莫名其妙的bug是相当折磨人的。
当学习了Rust之后,你就会下意识地去考虑你所定义的数据的安全性,有没有不确定的执行顺序引发的问题?有没有可能被非预期的共享和修改?我改了这个对象会不会影响到其他部分的功能表现等等?从而去想办法将可能会发生的问题扼杀在摇篮里。例如在处理组件状态时,采用不可变数据结构和函数式编程模式可以减少出错的机会。实际上对于前端开发者来说,这类bug是相当常见的。
JS其实也是在不断努力解决其本身存在的各种问题的,例如不断升级的ES新特性,使用 use stric 开启严格模式,还有函数式编程范式的流行,以及各类框架都支持的状态管理等等,这些措施都是为了让JS代码能够更加健壮,弥补JS本身的一些不足。
不可变数据结构: 在React中,使用useState和useReducer来管理状态,避免直接修改状态对象。以及redux等状态管理库,还有像Immer这样的库来简化不可变数据的操作。 函数式编程: 在JavaScript中,封装有明确输入和输出的函数,或使用高阶函数(如map、filter、reduce)来处理数据,避免修改原始数据,从而保持代码的清晰性和可测试性。
无论是函数式编程,还是状态管理,都是为了减少每个动作的副作用,有明确的数据流,让代码更安全更加可维护,低耦合高内聚不是一句空话,是业界大佬们真正在不断去实践的。只是我们自己没有感知,而实际上JS这门语言自身的缺陷真的很多,用JS去开发很容易,但是用JS去开发出健壮又高性能的代码是很难的,这可能也是为什么前端框架和库百花齐放而又前仆后继的原因。
Rust 的并发模型通过其所有权和借用规则确保了并发编程中的安全性。Rust 中的并发安全设计包括:
- 数据竞争防止:Rust 中,数据竞争在编译时就能被发现。Rust 通过所有权规则确保要么有多个不可变借用,要么有一个可变借用,从而避免了并发时对共享数据的非法访问。
- 线程安全:Rust 使用 Send 和 Sync 特性来标识哪些类型可以在线程之间传递或共享。Send 允许数据在不同线程之间传递,Sync 允许多个线程共享数据。
- 锁机制:Rust 提供了 Mutex 和 RwLock 等机制来确保在多线程环境下对共享资源的安全访问。
JS是单线程,但是JS的单线程是基于事件循环的非阻塞的,所以可以通过异步来实现伪并发,竞态条件、数据竞争、数据共享等问题在JS中是很难发现的,甚至别人的代码修改了你的数据你都不知道,没有一定的开发经验积累,去排查由此产生地莫名其妙的bug是相当折磨人的。
当学习了Rust之后,你就会下意识地去考虑你所定义的数据的安全性,有没有不确定的执行顺序引发的问题?有没有可能被非预期的共享和修改?我改了这个对象会不会影响到其他部分的功能表现等等?从而去想办法将可能会发生的问题扼杀在摇篮里。例如在处理组件状态时,采用不可变数据结构和函数式编程模式可以减少出错的机会。实际上对于前端开发者来说,这类bug是相当常见的。
JS其实也是在不断努力解决其本身存在的各种问题的,例如不断升级的ES新特性,使用 use stric 开启严格模式,还有函数式编程范式的流行,以及各类框架都支持的状态管理等等,这些措施都是为了让JS代码能够更加健壮,弥补JS本身的一些不足。
不可变数据结构: 在React中,使用useState和useReducer来管理状态,避免直接修改状态对象。以及redux等状态管理库,还有像Immer这样的库来简化不可变数据的操作。 函数式编程: 在JavaScript中,封装有明确输入和输出的函数,或使用高阶函数(如map、filter、reduce)来处理数据,避免修改原始数据,从而保持代码的清晰性和可测试性。
无论是函数式编程,还是状态管理,都是为了减少每个动作的副作用,有明确的数据流,让代码更安全更加可维护,低耦合高内聚不是一句空话,是业界大佬们真正在不断去实践的。只是我们自己没有感知,而实际上JS这门语言自身的缺陷真的很多,用JS去开发很容易,但是用JS去开发出健壮又高性能的代码是很难的,这可能也是为什么前端框架和库百花齐放而又前仆后继的原因。
4. 错误处理
Rust没有传统意义上的异常机制。在许多编程语言中,错误通常会通过运行时抛出异常来传递,而Rust采用了一种完全不同的方式来处理错误。
Rust通过Result类型和Option类型来明确地处理错误和空值,Rust的错误处理是编译时检查的,必须显式处理Result或Option,如果忽略了错误处理,编译器会报错,确保错误处理不被遗漏。这种做法可以避免程序出现未处理的异常,增强程序的健壮性。
Result 类型:Rust使用Result类型来显式表示一个函数可能返回的两种状态:成功(Ok(T))或失败(Err(E))。这种方式要求函数调用者在编译时就明确考虑到错误的处理,而不是依赖于运行时的异常机制。
Result是一个枚举类型,定义如下:
enum Result {
Ok(T),
Err(E),
}
Option 类型:在处理可能的空值时,Rust使用Option类型,它表示一个值可能存在(Some(T))或不存在(),避免了空指针异常的问题。
enum Option {
Some(T),
,
}
在前端开发中,JavaScript和TypeScript也可以借鉴Rust的错误处理机制,明确地处理每一种错误情况,尤其是空值问题,没有一个前端开发能躲过 undefined 的摧残。
Rust没有传统意义上的异常机制。在许多编程语言中,错误通常会通过运行时抛出异常来传递,而Rust采用了一种完全不同的方式来处理错误。
Rust通过Result类型和Option类型来明确地处理错误和空值,Rust的错误处理是编译时检查的,必须显式处理Result或Option,如果忽略了错误处理,编译器会报错,确保错误处理不被遗漏。这种做法可以避免程序出现未处理的异常,增强程序的健壮性。
Result 类型:Rust使用Result
Result是一个枚举类型,定义如下:
enum Result {
Ok(T),
Err(E),
}
Option 类型:在处理可能的空值时,Rust使用Option类型,它表示一个值可能存在(Some(T))或不存在(),避免了空指针异常的问题。
enum Option {
Some(T),
,
}
在前端开发中,JavaScript和TypeScript也可以借鉴Rust的错误处理机制,明确地处理每一种错误情况,尤其是空值问题,没有一个前端开发能躲过 undefined 的摧残。
高性能设计
1. 零成本抽象
零成本抽象是指使用高级编程语言的抽象(如函数式编程的高阶函数、泛型、闭包等)时,不会引入额外的性能开销或运行时成本。换句话说,编写高抽象层的代码并不会影响程序的性能,编译器能够将抽象代码转化为与低级代码相同的高效机器码。
在 Rust 中,“零成本抽象”特别重要,因为 Rust 旨在提供与 C 和 C++ 等低级语言相似的性能,同时保持高层次的代码抽象和安全性。通过静态分析和优化,Rust 能够在编译时消除大多数抽象层的开销。
零成本抽象的三个原则:
- 没有全局成本(No global cost): 一个零成本抽象不应该对不使用该功能的程序的性能产生负面影响。
换句话说,零成本抽象应该只在使用时产生影响,在未使用时不会引入任何额外的开销。
- 最佳性能(Optimal performance): 一个零成本的抽象应该编译成相当于底层指令编写的最佳实现。意味着它在使用时会以尽可能接近底层代码的方式运行,即它的性能应当与手写的低级实现相当。
可以理解为,如果你想要用rust抽象某个高级能力,那么抽象完成的性能不能比用更原始写法实现的性能差,如果你想要抽象前端框架,那么就不能比直接操作DOM的JS原生写法性能差。
- 改善开发者体验(Improves developer experience): 抽象的意义在于提供新的工具,由底层组件组装而成,让开发者更容易写出他们想要的代码,提高开发效率和代码可读性。
举几个例子
- Rust 的所有权系统(ownership system)和生命周期(lifetimes)。
当你写一个简单的程序,没有使用所有权系统的特性时,编译器会对这些特性进行优化,使得它们对程序的性能没有任何影响。只有当你使用这些特性时,编译器才会引入相关的检查和优化。
- 迭代器(Iterators) Rust 的迭代器是一种高效的抽象
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().map(|x| x * 2).sum();
println!("Sum: {}", sum);
}
1. 零成本抽象
零成本抽象是指使用高级编程语言的抽象(如函数式编程的高阶函数、泛型、闭包等)时,不会引入额外的性能开销或运行时成本。换句话说,编写高抽象层的代码并不会影响程序的性能,编译器能够将抽象代码转化为与低级代码相同的高效机器码。
在 Rust 中,“零成本抽象”特别重要,因为 Rust 旨在提供与 C 和 C++ 等低级语言相似的性能,同时保持高层次的代码抽象和安全性。通过静态分析和优化,Rust 能够在编译时消除大多数抽象层的开销。
零成本抽象的三个原则:
- 没有全局成本(No global cost): 一个零成本抽象不应该对不使用该功能的程序的性能产生负面影响。
换句话说,零成本抽象应该只在使用时产生影响,在未使用时不会引入任何额外的开销。
- 最佳性能(Optimal performance): 一个零成本的抽象应该编译成相当于底层指令编写的最佳实现。意味着它在使用时会以尽可能接近底层代码的方式运行,即它的性能应当与手写的低级实现相当。
可以理解为,如果你想要用rust抽象某个高级能力,那么抽象完成的性能不能比用更原始写法实现的性能差,如果你想要抽象前端框架,那么就不能比直接操作DOM的JS原生写法性能差。
- 改善开发者体验(Improves developer experience): 抽象的意义在于提供新的工具,由底层组件组装而成,让开发者更容易写出他们想要的代码,提高开发效率和代码可读性。
举几个例子
- Rust 的所有权系统(ownership system)和生命周期(lifetimes)。
当你写一个简单的程序,没有使用所有权系统的特性时,编译器会对这些特性进行优化,使得它们对程序的性能没有任何影响。只有当你使用这些特性时,编译器才会引入相关的检查和优化。
- 迭代器(Iterators) Rust 的迭代器是一种高效的抽象
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().map(|x| x * 2).sum();
println!("Sum: {}", sum);
}
在这个例子中,iter 返回一个迭代器,map 是一个高阶函数,它将一个闭包应用到每个元素。尽管你使用了高阶函数和迭代器,Rust 会在编译时优化这段代码,确保它与手写的 for 循环代码在性能上等效。
Rust 的迭代器通过惰性求值来避免不必要的中间计算。当你调用 .map() 和 .sum() 时,Rust 会合并这些操作为一个高效的迭代过程,不会在内存中创建不必要的临时集合,最终生成与手动实现同样高效的机器码。
而在JS中,几种高阶函数的使用则是有成本的,如map、foreach 等,在进行一些极端的数据量测试时,性能差异相比 for 循环就比较明显了,但是js引擎实际上也是做了很多的优化的,这点不用太过于担心,放心用就好了。
- 泛型(Generics) Rust 中的泛型是一个典型的零成本抽象。
使用泛型时,Rust 在编译时会根据类型替换(monomorphization单态化)生成具体的代码,在运行时没有任何额外的开销。
fn add(a: T, b: T) -> T
where
T: std::ops::Add
在这个例子中,add 函数是一个泛型函数。Rust 编译器在编译时会为每种特定类型(例如 i32、f64 等)生成专门的机器码。
如果你调用 add(1, 2),Rust 会为 i32 类型生成专门的代码。同样,如果你调用 add(1.5, 2.5),Rust 会为 f64 类型生成专门的代码。泛型不会引入运行时的性能损失。编译后的代码与手写的具体类型版本在性能上是等价的。
- 错误处理
包括Rust的错误处理,由于Rust不使用异常机制,程序的控制流更加清晰,避免了异常捕获时的运行时性能开销。
对前端的启示
- 抽象高级能力提升开发效率和体验: 前端框架(如 React 和 Vue)、TypeScript等都是属于抽象的高级能力。例如,React 的优化算法通过虚拟 DOM 的比对、最小化 DOM 更新,已实现类似的零成本抽象。除此之外还有很多事可以做。
- 优化代码:减少抽象代码中不必要的计算,必要时惰性引入(前端叫懒加载)或计算,减少对不相关部分的隐形影响(前端叫副作用),利用好模块化开发和treeshaking等。
- WebAssembly (Wasm): Rust 本身可以编译为 WebAssembly,这为前端性能优化开辟了新天地。借鉴 Rust 的这一特点,前端可以通过将性能瓶颈部分的代码(如图像处理、数据加密等)使用 Rust 编写并编译为 WebAssembly 运行,从而提升性能。
总结
社区很多人并不看好Rust甚至很激进地开喷,人确实是会有自己的舒适区的,当用熟了一样语言后,便不那么容易接受某一个自己不熟悉的语言更好,但是,尝试走出舒适区,真正地去接触Rust,一定会也能够感受到Rust的设计光辉。
要学习Rust,你需要先深入理解内存、堆栈、引用、变量作用域等这些其它高级语言往往不会深入接触的内容,Rust 会通过语法、编译器和 clippy 这些静态检查工具半帮助半强迫的让你成为更优秀的程序员,写出更好的代码。
Rust 程序只要能跑起来,那代码质量其实就是相当不错的,甚至不需要调试,能编译基本就没bug(当然是避免不了逻辑bug的)。正因为较高的质量下限,我们维护别人的代码时心智负担也比较小,能编译通过基本能保证不新增bug,把精力完全放在业务逻辑上就可以了。
而如果用javascript写程序,我们必须一边写一边调试,虽然写出能跑的程序极为简单,但要想没有bug,心智负担极高,js的程序员上限不封顶,但下限极低。而且review代码成本也很高,1000个人有1000种写法,review完改一改可能又不小心改出bug了。
JS对开发者要求较低,也是时代变了,条件好了,搁以前几百兆内存、单核cpu的时候,那时候的JS开发该有多痛苦啊。
社区流传着一个很奇怪的论调,“通过学习Rust,你能写出更好的xx语言的代码”。
学习Rust后,会潜移默化地影响你写其他语言代码时的思维方式,最直观的变化就是,对javascript中各类容易造成不安全不稳定的情况会更加敏感,所以,某种程度来看,Rust的价值可能并不在于用它写出多么优秀的代码,更重要的是它带给你的全面的方法论层面的提升。
我使用Rust做了比较丰富的尝试,包括用Rust写命令行工具、用Rust写 postcss 插件、用Rust写vite 插件、用Rust写WebAssembly在前端页面中使用,整体体验和效果还是非常棒的,WebAssembly的尝试可以查看文章Rust + wasm-pack + WebAssembly 实现Gitlab 代码统计,比JS快太多了,其他实践后面会陆续和大家分享,感兴趣的小伙伴可以关注收藏插个眼~
附:
Rust在前端领域的应用
- SWC: 基于 Rust 的前端构建工具,可以理解为 Rust 版本的 Babel,但是性能有 10 倍提升。目前被 Next.js、Deno , Rspack等使用。
- Tauri:Tauri 是目前最流行的 Electron 替代方案,通过使用 Rust 和 Webview2 成功解决了 Electron 的包体积大和内存占用高的问题。Atom 团队也是看到了 Tauri 的成功,才决定基于 Rust 去做 Zed 编辑器。
- Parcel2:零配置构建工具,特点是快速编译和不需要配置,和 Vite、Webpack等打包比起来更加简单,而且是基于 Rust 开发
- Biome: 旨在取代许多现有的 JavaScript 工具,集代码检测、打包、编译、测试等功能于一身。
- Rspack: 基于 Rust 的高性能 Web 构建工具, 对标 Webpack, 兼容大部分Webpack api
- Rocket: 可以帮助开发人员轻松编写安全的Web应用程序, 对标 Expressjs,性能卓越,具体参考 Web Frameworks Benchmark
- Yew : 使用 Rust 开发 h5 页面,支持类 jsx 的语法,和 React 类似开发前端网页,打包产物是 wasm,挺有趣。
- Napi-rs: 用 Rust 和 N-API 开发高性能 Node.js 扩展,可以替代之前用 C++ 开发的 Node.js 扩展,许多基于 Rust 语言开发的前端应用都结合这个库进行使用。
- Rolldown: 基于 Rust 的 Rollup 的替代品。
- 美国国防部准备征求一个把所有C代码翻译成Rust的软件。
来源:juejin.cn/post/7450021642377199643
我的2024:裁员,退市,协和,副业,剑来;全赶上了!!
一点都不夸张,就是这么惊悚!这么刺激!
裁员,退市
公司在2023年10月份,来了一次全球大裁员。整个过程从官宣,到结束,也就一周时间,不长。但后续影响的阴影,笼罩了2024年,整整一年。
对于被裁员的人,是残忍的。收拾一下,告别一段经历,一群好友,慢慢开启下一段旅程。当找到下一份工作后,往往很快就告别了这一段低落的情绪。很多人再联系时,问他们,当时的感觉如何,很有一些人给出的答复是:塞翁失马,焉知非福!因为公司给的补偿方案,都还是不错的,N+2, N+3的人,也是有的。
对于我们这些暂时还在的人,反而更加折磨。被阴影笼罩,看不到希望。熟悉的人不在了,组织结构也变了,未来充满了不确定性,焦虑一下子包围了过来,把你的世界塞得满满当当。内心世界一下子就沉寂,阴暗了起来。对于像我这样已经35+的人来说,实在是有些窒息。
不出所料,2024年又来了一波。不过和之前不一样的是,大家似乎淡定了许多。也许是柔软的心也长出了厚厚的茧,同样的打击,反而来得更轻松了些。
裁员的压力,其实来自于上市公司的财报要求。全球经济下行,市场不景气,作为上市公司,需要对全球投资人负责。而裁员是短期见效最快的方案。因为科技公司,最贵的其实就是人力成本。但很明显,这并不是长期最有效的方法。最终,公司做出了退市的决定。其实,综合来看,并不算坏。
协和
医院是离生死最近的地方,再次面对时,本觉得可以洒脱些,然而,不然!真的真的是再也不想去医院了!
35。是一个普通的数字。上有老,下有小。是对幸福的一种描述。
可把它们放在一起:35+,上有老,下有小。就变成了巨大的压力,还是喘不过气的那种。
这无形的压力,很容易让你自我怀疑,焦虑万分:
- 我真是一点用都没有,找工作都怕找不到。
- 我真是一点用都没有,不能给家人最好的医疗环境。在生命面前,尊严是那么的奢侈,活着就已经不错了。
- …
不想再列举更多,也不想制造焦虑。因为这玩意儿,现在到处都是,廉价的很。
副业
因为一直喜欢读源码,并且喜欢写一些源码阅读笔记 - 大家感兴趣也可以翻翻我之前的文章,多少和源码有一些关联。
前些年读到Hugo源码的时候,就有一个想法:反正源码也读了,笔记也记了,索性整理成一本书,名字我都想好了,就叫《深入理解Hugo之源码精读》。
读着读着,就有了一些困惑和想法,就跑去Hugo官方论坛上,寻求帮助,和验证想法。在2023年9月份的一天,发了一篇Hugo Headless CMS的贴子,问官方有没有计划,开发这样一个版本,就能把Hugo的服务,通过API暴露出来,那应用的场景一下子就能开阔起来。我也就可以用Hugo来帮我生成我书的站点,还能帮我生成书的电子版,我就可以发布到各大在线书商平台了。当然最后得到的答复是,并没有这样的计划。就都还好,坏就坏在,我一时抽抽,夸下海口。说我也是软件工程师,不行我来写一个吧...
后面找了半天,怎么删帖... 没找着...
想着咱也不能丢咱们中国程序员的脸啊,这要是丢起来,可丢到国外了。别人丢我不管,咱可不能够丢这个脸!一咬牙,一跺脚,还有什么好说的,那就撸一个吧。就这样,在阴暗的2024里,有一出,没一出的,慢慢的,还真给我实现了这么个PoC(验证了可行性)。
也是因为这个项目,后来演变成了我现在的SaaS服务 - Friday。一个可以将Markdown笔记,转换成Hugo站点的服务。目前还在持续稳步迭代中。自己也给自己立了个Flag,先干个20年再说,要走,就走个完整的商业闭环。
可能,这就是大家说的,当上帝给你关了一扇门的时候,也会给你开一扇窗。正因为每天有坚持去看看,去写写,做一些具体的事情,不让自己有太多时间胡思乱想,反而也就没那么焦虑了!# 剑来
最近,国漫《剑来》特别火,火得理所当然,火得恰逢其时!
特别能理解,大家为什么能对齐先生,敬爱有佳,热泪盈眶。在强大的生活面前,谁还不是陈平安,谁还不想有齐先生这样的引路人!
2025
最后,回过头来看:
还好,我还有工作。
还好,大家都好好的。
还好,能做自己喜欢的事情。
2025,欢迎你,再一起加把劲吧。
最后,把我喜欢的剑来台词送给大家:
愿大家,永远不要对生活失去希望!
岁岁平,岁岁安,岁岁平安!
写在圣诞
2024年12月25号
来源:juejin.cn/post/7452280790791258162
我这🤡般的7年开发生涯
前两天线上出了个漏洞,导致线上业务被薅了 2w 多块钱。几天晚上没咋睡,问 ChatGPT,查了几晚资料,复盘工作这么久来犯下的错误。
我在公司做的大部分是探索性、创新性的需求,行内人都知道这些活都是那种脏活累活,需求变化大,经常一句话;需求功能多,看着简单一细想全是漏洞;需求又紧急,今天不上线业务就要没。
所以第一个建议就是大家远离这些需求,否则你会和我一样变得不幸。
但是👴🐂🍺啊,接下来也就算了,还全干完了。正常评估一个月的需求,我 tm 半个月干完上线;你给我一句话,我干完一整条链路上的事;你说必须今天上线,那就加班加点干上线。
就这样干了几年,黄了很多,也有做起来的。但是不管业务怎么发展,这样做时间长了会出现很多致命问题。
开发忙成狗
一句话需求太多,到最后只有开发最了解业务,所有人所有事都来找开发,开发也是人,开发还要写代码呢。最先遇到的问题就是时间严重不够,产品跟个摆设一样,什么忙都帮不上,我成了产品开发结合体。
bug 来了
开发一忙,节奏就乱了,乱则生 bug,再加上原本需求上逻辑不完整的深坑,坑上叠坑,出 bug 是迟早的事。
形象崩塌
一旦出现 bug,人设就毁了。记住一句话,没人会感谢你把原本一个月的需求只用半个月上线,大家都觉得这玩意本来就半个月工时。慢慢的开始以半个月的工时要求你。
那些 bug 自己回头,慢慢做都是可以避免的,就像考试的时候做完了卷子复查一遍,很多问题回头看一下都能发现,结果因为前期赶工,没时间回看,而且有很多图快的写法,后期都是容易出问题的。
形象崩塌在职场中是最恐怖的,正所谓好事不出门,坏事传千里。
一旦出了问题,团队、领导、所有人对你的体感,那都是直线下降,你之前做的所有好事,就跟消失了一样,别人对你的印象,一提起来说的都是,这不是当时写出 xxx bug 的人吗?这还怎么在职场生存?脸都没了,项目好处也跟自己没关系了。
我 tm 真是愣头青啊蠢的💊💩,从入职开始都想的是多学点多干点,结果干的越多错的越多,现在心态干崩了,身体干垮了,钱还没混子多,还背了一身骂名和黑锅。
之前我看同事写代码贼慢,鼠标点来点去,打字也慢一拍,我忍不住说他你这写代码速度太慢了,可以用 xxx 快捷键等等,现在回想起来,我说他不懂代码,其实是我不懂职场。
我真是个纯纯的可悲🤡。
提桶跑路
bug 积累到一定程度,尤其是像我这样出现点资金的问题,那也差不多离走人不远了,我感觉我快到这个阶段了,即使不走,扣钱扣绩效也是在所难免的,综合算下来,还没那些混子赚的多。
我亲自接触的联调一哥们儿,一杯茶,一包烟,一个 bug 修一天。是真真正正的修了一天,从早到晚。那天我要上线那个需求,我不停的催他,后来指着代码说着逻辑让他写,最终半夜转点上线。我累的半死不活,我工资和他差不多,出了问题我还要背锅。
我现在听到 bug 都 PTSD 了,尤其是资金相关的,整个人就那种呆住,大脑空白,心脏像被揪住,我怀疑我有点心理问题了都。
为什么别人可以那么安心的摸鱼?为什么我要如此累死累活还不讨好?我分析出几点我的性格问题。
责任心过强
什么事都觉得跟自己有关系,看着别人做的不好,我就自己上手。
到后期产品真 tm 一句话啊,逻辑也不想,全等着我出开发方案,产品流程图,我再告诉她哪里要改动。不是哥们?合着我自己给出需求文档再自己写代码?
为人老实
不懂拒绝,不懂叫板。
运营的需求,来什么做什么,说什么时候上线就什么时候上线。不是哥们?我都还不知道要做什么,你们把上线时间都定了?就 tm 两字,卑微。
用力过猛
十分力恨不得使出十一分,再加一分吃奶的劲儿。一开始就领导很高的期望,后面活越来越多,而且也没什么晋升机会了,一来的门槛就太高了知道吧,再想提升就很难了。
先总结这么多吧,我现在心情激荡的很,希望给各位和我性格差不多一点提醒,别像我这样愣头青,吃力不讨好,还要遭人骂。后面再写写改进办法。
来源:juejin.cn/post/7450047052804161576
从前端的角度出发,目前最具性价比的全栈路线是啥❓❓❓
今年大部分时间都是在编码上和写文章上,但是也不知道自己都学到了啥,那就写篇文章来盘点一下目前的技术栈吧,也作为下一年的参考目标,方便知道每一年都学了些啥。
我的技术栈
首先我先来对整体的技术做一个简单的介绍吧,然后后面再对当前的一些技术进行细分吧。
React、Typescript、React Native、mysql、prisma、NestJs、Redis、前端工程化。
React
React 这个框架我花的时间应该是比较多的了,在校期间已经读了一遍源码了,对这些原理已经基本了解了。在随着技术的继续深入,今年毕业后又重新开始阅读了一遍源码,对之前的认知有了更深一步的了解。
也写了比较多跟 React 相关的文章,包括设计模式,原理,配套生态的使用等等都有一些涉及。
在状态管理方面,redux,zustand 我都用过,尤其在 Zustand 的使用上,我特别喜欢 Zustand,它使得我能够快速实现全局状态管理,同时避免了传统 Redux 中繁琐的样板代码,且性能更优。也对 Zustand 有比较深入的了解,也对其源码有过研究。
NextJs
Next.js 是一个基于 React 的现代 Web 开发框架,它为开发者提供了一系列强大的功能和工具,旨在优化应用的性能、提高开发效率,并简化部署流程。Next.js 支持多种渲染模式,包括服务器端渲染(SSR)、静态生成(SSG)和增量静态生成(ISR),使得开发者可以根据不同的需求选择合适的渲染方式,从而在提升页面加载速度的同时优化 SEO。
在路由管理方面,Next.js 采用了基于文件系统的路由机制,这意味着开发者只需通过创建文件和文件夹来自动生成页面路由,无需手动配置。这种约定优于配置的方式让路由管理变得直观且高效。此外,Next.js 提供了动态路由支持,使得开发者可以轻松实现复杂的 URL 结构和参数化路径。
Next.js 还内置了 API 路由,允许开发者在同一个项目中编写后端 API,而无需独立配置服务器。通过这种方式,前后端开发可以在同一个代码库中协作,大大简化了全栈开发流程。同时,Next.js 对 TypeScript 提供了原生支持,帮助开发者提高代码的可维护性和可靠性。
Typescript
今年所有的项目都是在用 ts 写了,真的要频繁修改的项目就知道用 ts 好处了,有时候用 js 写的函数修改了都不知道怎么回事,而用了 ts 之后,哪里引用到的都报红了,修改真的非常方便。
今年花了一点时间深入学习了一下 Ts 类型,对一些高级类型以及其实现原理也基本知道了,明年还是多花点时间在类型体操上,除了算法之外,感觉类型体操也可以算得上是前端程序员的内功心法了。
React Native
不得不说,React Native 不愧是接活神器啊,刚学完之后就来了个安卓和 ios 的私活,虽然没有谈成。
React Native 和 Expo 是构建跨平台移动应用的两大热门工具,它们都基于 React,但在功能、开发体验和配置方式上存在一些差异。React Native 是一个开放源代码的框架,允许开发者使用 JavaScript 和 React 来构建 iOS 和 Android 原生应用。Expo 则是一个构建在 React Native 之上的开发平台,它提供了一套工具和服务,旨在简化 React Native 开发过程。
React Native 的核心优势在于其高效的跨平台开发能力。通过使用 React 语法和组件,开发者能够一次编写应用的 UI 和逻辑,然后部署到 iOS 和 Android 平台。React Native 提供了对原生模块的访问,使开发者能够使用原生 API 来扩展应用的功能,确保性能和用户体验能够接近原生应用。
Expo 在此基础上进一步简化了开发流程。作为一个开发工具,Expo 提供了许多内置的 API 和组件,使得开发者无需在项目中进行繁琐的原生模块配置,就能够快速实现设备的硬件访问功能(如摄像头、位置、推送通知等)。Expo 还内置了一个开发客户端,使得开发者可以实时预览应用,无需每次都进行完整的构建和部署。
另外,Expo 提供了一个完全托管的构建服务,开发者只需将应用推送到 Expo 服务器,Expo 就会自动处理 iOS 和 Android 应用的构建和发布。这大大简化了应用的构建和发布流程,尤其适合不想处理复杂原生配置的开发者。
然而,React Native 和 Expo 也有各自的局限性。React Native 提供更大的灵活性和自由度,开发者可以更自由地集成原生代码或使用第三方原生库,但这也意味着需要更多的配置和维护。Expo 则封装了很多功能,简化了开发,但在需要使用某些特定原生功能时,开发者可能需要“弹出”Expo 的托管环境,进行额外的原生开发。
样式方案的话我使用的是 twrnc,大部分组件都是手撸,因为有 cursor 和 chatgpt 的加持,开发效果还是杠杠的。
rn 原理也争取明年能多花点时间去研究研究,不然对着盲盒开发还是不好玩。
Nestjs
NestJs 的话没啥好说的,之前也都写过很多篇文章了,感兴趣的可以直接观看:
对 Nodejs 的底层也有了比较深的理解了:
Prisma & mysql
Prisma 是一个现代化的 ORM(对象关系映射)工具,旨在简化数据库操作并提高开发效率。它支持 MySQL 等关系型数据库,并为 Node.js 提供了类型安全的数据库客户端。在 NestJS 中使用 Prisma,可以让开发者轻松定义数据库模型,并通过自动生成的 Prisma Client 执行类型安全的查询操作。与 MySQL 配合时,Prisma 提供了一种简单、直观的方式来操作数据库,而无需手动编写复杂的 SQL 查询。
Prisma 的核心优势在于其强大的类型安全功能,所有的数据库操作都能通过 Prisma Client 提供的自动生成的类型来进行,这大大减少了代码中的错误,提升了开发的效率。它还包含数据库迁移工具 Prisma Migrate,能够帮助开发者方便地管理数据库结构的变化。此外,Prisma Client 的查询 API 具有很好的性能,能够高效地执行复杂的数据库查询,支持包括关系查询、聚合查询等高级功能。
与传统的 ORM 相比,Prisma 使得数据库交互更加简洁且高效,减少了配置和手动操作的复杂性,特别适合在 NestJS 项目中使用,能够与 NestJS 提供的依赖注入和模块化架构很好地结合,提升整体开发体验。
Redis
Redis 和 mysql 都仅仅是会用的阶段,目前都是直接在 NestJs 项目中使用,都是已经封装好了的,直接传参调用就好了:
import { Injectable, Inject, OnModuleDestroy, Logger } from "@nestjs/common";
import Redis, { ClientContext, Result } from "ioredis";
import { ObjectType } from "../types";
import { isObject } from "@/utils";
@Injectable()
export class RedisService implements OnModuleDestroy {
private readonly logger = new Logger(RedisService.name);
constructor(@Inject("REDIS_CLIENT") private readonly redisClient: Redis) {}
onModuleDestroy(): void {
this.redisClient.disconnect();
}
/**
* @Description: 设置值到redis中
* @param {string} key
* @param {any} value
* @return {*}
*/
public async set(
key: string,
value: unknown,
second?: number
): Promise<Result<"OK", ClientContext> | null> {
try {
const formattedValue = isObject(value)
? JSON.stringify(value)
: String(value);
if (!second) {
return await this.redisClient.set(key, formattedValue);
} else {
return await this.redisClient.set(key, formattedValue, "EX", second);
}
} catch (error) {
this.logger.error(`Error setting key ${key} in Redis`, error);
return null;
}
}
/**
* @Description: 获取redis缓存中的值
* @param key {String}
*/
public async get(key: string): Promise<string | null> {
try {
const data = await this.redisClient.get(key);
return data ? data : null;
} catch (error) {
this.logger.error(`Error getting key ${key} from Redis`, error);
return null;
}
}
/**
* @Description: 设置自动 +1
* @param {string} key
* @return {*}
*/
public async incr(
key: string
): Promise<Result<number, ClientContext> | null> {
try {
return await this.redisClient.incr(key);
} catch (error) {
this.logger.error(`Error incrementing key ${key} in Redis`, error);
return null;
}
}
/**
* @Description: 删除redis缓存数据
* @param {string} key
* @return {*}
*/
public async del(key: string): Promise<Result<number, ClientContext> | null> {
try {
return await this.redisClient.del(key);
} catch (error) {
this.logger.error(`Error deleting key ${key} from Redis`, error);
return null;
}
}
/**
* @Description: 设置hash结构
* @param {string} key
* @param {ObjectType} field
* @return {*}
*/
public async hset(
key: string,
field: ObjectType
): Promise<Result<number, ClientContext> | null> {
try {
return await this.redisClient.hset(key, field);
} catch (error) {
this.logger.error(`Error setting hash for key ${key} in Redis`, error);
return null;
}
}
/**
* @Description: 获取单个hash值
* @param {string} key
* @param {string} field
* @return {*}
*/
public async hget(key: string, field: string): Promise<string | null> {
try {
return await this.redisClient.hget(key, field);
} catch (error) {
this.logger.error(
`Error getting hash field ${field} from key ${key} in Redis`,
error
);
return null;
}
}
/**
* @Description: 获取所有hash值
* @param {string} key
* @return {*}
*/
public async hgetall(key: string): Promise<Record<string, string> | null> {
try {
return await this.redisClient.hgetall(key);
} catch (error) {
this.logger.error(
`Error getting all hash fields from key ${key} in Redis`,
error
);
return null;
}
}
/**
* @Description: 清空redis缓存
* @return {*}
*/
public async flushall(): Promise<Result<"OK", ClientContext> | null> {
try {
return await this.redisClient.flushall();
} catch (error) {
this.logger.error("Error flushing all Redis data", error);
return null;
}
}
/**
* @Description: 保存离线通知
* @param {string} userId
* @param {any} notification
*/
public async saveOfflineNotification(
userId: string,
notification: any
): Promise<void> {
try {
await this.redisClient.lpush(
`offline_notifications:${userId}`,
JSON.stringify(notification)
);
} catch (error) {
this.logger.error(
`Error saving offline notification for user ${userId}`,
error
);
}
}
/**
* @Description: 获取离线通知
* @param {string} userId
* @return {*}
*/
public async getOfflineNotifications(userId: string): Promise<any[]> {
try {
const notifications = await this.redisClient.lrange(
`offline_notifications:${userId}`,
0,
-1
);
await this.redisClient.del(`offline_notifications:${userId}`);
return notifications.map((notification) => JSON.parse(notification));
} catch (error) {
this.logger.error(
`Error getting offline notifications for user ${userId}`,
error
);
return [];
}
}
/**
* 获取指定 key 的剩余生存时间
* @param key Redis key
* @returns 剩余生存时间(秒)
*/
public async getTTL(key: string): Promise<number> {
return await this.redisClient.ttl(key);
}
}
前端工程化
前端工程化这块花了很多信息在 eslint、prettier、husky、commitlint、github action 上,现在很多项目都是直接复制之前写好的过来就直接用。
后续应该是投入更多的时间在性能优化、埋点、自动化部署上了,如果有机会的也去研究一下 k8s 了。
全栈性价比最高的一套技术
最近刷到一个帖子,讲到了
我目前也算是一个小全栈了吧,我也来分享一下我的技术吧:
- NextJs
- React Native
- prisma
- NestJs
- taro (目前还不会,如果有需求就会去学)
剩下的描述也是和他下面那句话一样了(毕业后对技术态度的转变就是什么能让我投入最小,让我最快赚到钱的就是好技术)
总结
学无止境,任重道远。
来源:juejin.cn/post/7451483063568154639
一半员工净资产过亿,英伟达中国员工自爆工资单
英伟达
据风险投资人 Ruben D 透露:芯片巨头英伟达 78% 的员工已成为百万美元富翁,有一半人的净资产甚至达到 2500 万美元(约合人民币 1.83 亿元)。
英伟达的薪资很高,但光靠现金薪资不足让一半人资产过亿,这里面的主要原因,是英伟达 🚀 一般的涨幅,在过去五年翻了 20 倍。
英伟达作为"卖铲子"的人,总能精准踩中每个风口。
工业设计软件、3A游戏、虚拟货币、AI,带来了海量订单的同时,也把"濒死"的英伟达一度拉到「全球市值第一」的位置。
要知道,如今让全球科技公司都"高攀不起"的英伟达 CEO 黄仁勋,十年前还只能蹭刚起步的小米发布会来推销芯片。
当年,雷军还不是现在的"雷神",小米手机也才出到第三代。但即使是这般初创品牌的客户,也足以让黄仁勋毕恭毕敬。
十年河东十年河西,如今英伟达的市值,接近 50 个小米,不少有着股权激励的员工,身家也得以水涨船高。
这几年行情不景气,网上晒工资的人少了许多(或被限流),但仍然找到了一份 2021 年英伟达员工收入的资料:
该员工在 2021 年底共有 3 笔收入:股权激励 900W+、全年一次性奖金 16W+,月工资 17W+。
知道英伟达员工收入高,但却是我想象不到的高 🤣🤣🤣
而且,这还是 2021 年的英伟达,当时一股英伟达 30 不到,如今 130+(涨幅 433%),这两年的股权激励会去到多少,我不敢想 🤣🤣🤣
但巨额财富背后是高强度的工作,据英伟达员工爆料,他们基本每周工作 7 天,加班到凌晨 2 点也是常态,还要面临密集的会议安排和严格的时间管理要求,大家几乎没有多少时间陪伴家人,甚至开始考虑"半退休"状态,以缓解工作带来的负面影响。
对此,你怎么看?说实话,你是不是也想体验几年加班到 2 点的日子?欢迎评论区交流。
...
回归主题。
来一道「HOT 100」级别算法题。
题目描述
平台:LeetCode
题号:450
给定一个二叉搜索树的根节点 root
和一个值 key
,删除二叉搜索树中的 key
对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0
输出: []
提示:
- 节点数的范围
- 节点值唯一
- root 是合法的二叉搜索树
进阶: 要求算法时间复杂度为 , 为树的高度。
递归
利用题目本身的函数签名的含义,也就是「在以 root
为根的子树中,删除值为 key
的节点,并返回删除节点后的树的根节点」,我们可以用「递归」来做。
起始先对边界情况进行处理,当 root
为空(可能起始传入的 root
为空,也可能是递归过程中没有找到值为 key
的节点时,导致的 root
为空),我们无须进行任何删除,直接返回 null
即可。
根据当前 root.val
与 key
的大小关系,进行分情况讨论:
- 若有 ,说明待删除的节点必然不是当前节点,以及不在当前节点的左子树中,我们将删除动作「递归」到当前节点的右子树,并将删除(可能进行)之后的新的右子树根节点,重新赋值给
root.right
,即有root.right = deleteNode(root.right, key)
; - 若有 ,说明待删除的节点必然不是当前节点,以及不在当前节点的右子树,我们将删除节点「递归」到当前节点的左子树,并将删除(可能进行)之后的新的左子树根节点,重新赋值给
root.left
,即有root.left = deleteNode(root.left, key)
; - 若有 ,此时找到了待删除的节点,我们根据左右子树的情况,进行进一步分情况讨论:
- 若左/右子树为空,我们直接返回右/左子树节点即可(含义为直接将右/左子树节点搬到当前节点的位置)如图所示:
- 若左右子树均不为空,我们有两种选择:
- 从「当前节点的左子树」中选择「值最大」的节点替代
root
的位置,确保替代后仍满足BST
特性; - 从「当前节点的右子树」中选择「值最小」的节点替代
root
的位置,确保替代后仍满足BST
特性;
我们以「从当前节点的左子树中选择值最大的节点」为例子,我们通过树的遍历,找到其位于「最右边」的节点,记为 ( 作为最右节点,必然有
t.right = null
),利用原本的root
也是合法BST
,原本的root.right
子树的所有及节点,必然满足大于t.val
,我们可以直接将root.right
接在t.right
上,并返回我们重接后的根节点,也就是root.left
。
而「从当前节点的右子树中选择值最小的节点」,同理(代码见 )。
- 从「当前节点的左子树」中选择「值最大」的节点替代
- 若左/右子树为空,我们直接返回右/左子树节点即可(含义为直接将右/左子树节点搬到当前节点的位置)如图所示:
Java 代码(P1):
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
if (root.left == null) return root.right;
if (root.right == null) return root.left;
TreeNode t = root.left;
while (t.right != null) t = t.right;
t.right = root.right;
return root.left;
} else if (root.val < key) root.right = deleteNode(root.right, key);
else root.left = deleteNode(root.left, key);
return root;
}
}
Java 代码(P2):
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
if (root.left == null) return root.right;
if (root.right == null) return root.left;
TreeNode t = root.right;
while (t.left != null) t = t.left;
t.left = root.left;
return root.right;
} else if (root.val < key) root.right = deleteNode(root.right, key);
else root.left = deleteNode(root.left, key);
return root;
}
}
C++ 代码(P1):
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return nullptr;
if (root->val == key) {
if (!root->left) return root->right;
if (!root->right) return root->left;
TreeNode* t = root->left;
while (t->right) t = t->right;
t->right = root->right;
return root->left;
} else if (root->val < key) {
root->right = deleteNode(root->right, key);
} else {
root->left = deleteNode(root->left, key);
}
return root;
}
};
Python 代码(P1):
class Solution:
def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
if not root:
return
if root.val == key:
if not root.left:
return root.right
if not root.right:
return root.left
t = root.left
while t.right:
t = t.right
t.right = root.right
return root.left
elif root.val < key:
root.right = self.deleteNode(root.right, key)
else:
root.left = self.deleteNode(root.left, key)
return root
TypeScript 代码(P1):
function deleteNode(root: TreeNode | null, key: number): TreeNode | null {
if (!root) return null;
if (root.val === key) {
if (!root.left) return root.right;
if (!root.right) return root.left;
let t: TreeNode | null = root.left;
while (t!.right) t = t!.right;
t!.right = root.right;
return root.left;
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else {
root.left = deleteNode(root.left, key);
}
return root;
};
- 时间复杂度:,其中 为树的深度
- 空间复杂度:忽略递归带来的额外空间消耗,复杂度为
来源:juejin.cn/post/7459953815325327412
支付宝事故这事儿,凭什么又是程序员背锅?有没有可能是这样的...
你好呀,我是歪歪。
昨天支付宝那事儿你听说了吧?
网传支付宝 14:40-14:45 所有的支付订单都按国补减免了 20%。
从网上铺天盖地的截图来看,非常多类型的交易都被“减免了 20%”。
说实话,歪师傅纵横互联网多年,什么千奇百怪的事情没见过?
比如这种还带款有政府补贴的,我觉得还说得过去,毕竟有时候确实有政策扶持,你要强行往这个上面圆谎,遇到外行也是能糊弄过去的:
但是个人转账都能有政府补贴的,这个还真没见过:
还真是猪八戒吃人参果,第一遭。
又好比大姑娘坐花轿,头一回。
我第一反应甚至是:我靠,现在的诈骗的套路都玩得这么深吗?我甚至都一眼看不穿它。
针对支付宝这个“真百亿补贴”行为,很多网友纷纷猜测这波又是程序员干的。
有这种可能,但是我觉得还有另外一种可能,在这种可能性中,这个问题和程序员毫无关系。
我个人猜测这可能是一次运营配置事件。
我觉得是这样的。
根据官方信息,过两天,就是 1 月 20 日,国补就要正式开始了:
支付宝的程序员在这之前接到了一个国补相关的需求,需求可能是要求在创建支付订单的时候,根据订单对应的产品来判断该产品是否符合国补条件,从而决定是否进行对应的金额减免。
那么哪些产品符合国补条件,这个逻辑肯定不是写死在代码里面的。
应该是作为一个运营配置项,可做实时配置、灵活调整,而这个配置项又可能相对复杂。
比如同时可以配置支持 A 大类下的 B 小类产品、不支持 C 大类的 D 小类产品,类似这种条件组合吧。
具体的运营人员在做参数配置时,不知道出于什么原因,配置出了一个条件组合之后,这个组合的最终效果按照程序逻辑解析是所有订单都可以参与国补。
这种配置肯定是要一审二审,层层审批的。
但是巧了,审核人员就是没看出来这个配置是有问题的,给通过了。
然后就出现了这个“真百亿补贴”行为。
这种行为,换个场景就更好理解了。
就类似于营销发优惠券。
本来设计的营销活动是给指定客群的用户发一个满减的无门槛优惠券。
结果运营人员在选客群的时候操作失误,给所有的用户都发了优惠券。
你说这个场景,和程序员有什么关系?
本来就存在给所有的用户发优惠券的需求和场景,使用的人用错了,你怪我开发的时候为什么不想着拦截一下?
还有天理吗?
但是这个真实的场景又比优惠券惨烈的多,毕竟优惠券客户不一定真的去用,但是这次“补贴”是真金白银的给出去了呀。
而且还是直接给到了无数个 C 端用户,追都不好追。
哎,惨啊,真的惨。
我再强调一次啊,以上是我个人的猜测,没有任何依据。
我作为一个程序员,屁股当然得歪一下,当然是希望这个问题和程序员无关了。
只有苦一苦运营的兄弟了。(手动狗头
如果真的是因为程序员编码问题导致的,朋友,不管你看不看得到,挺住,没啥大不了的,我入行第一天就查过了,程序员因为非主观 BUG 导致公司重大损失的,不需要承担法律责任。
但是,话又说回来,这里面就没有程序员的事儿了吗?
肯定有呀!
分析数据范围,捞问题数据,做数据修复等等这些后续的事情,肯定还是程序员来做的。
昨天晚上一定有一波相关人员没睡觉,整晚整晚的都在想这个事情到底是什么了。
另外,我昨天还看到了这个短信:
我猜这个短信是假的,因为短信内容太过随意了。
简单来说,“BUG”这个词,就不应该出现在短信里面,不可能是官方用语。
你能确保收到短信的每个人都知道“BUG”是什么意思吗?
注意信息茧房的存在,你认为的常识不一定是每个人的常识。
我就在现实生活中遇到过不知道“BUG”是什么意思的,我还要解释一番。
另外,都说到短信了,顺便给大家分享一个小技巧。
这个截图暴露了短信发送方的前 8 位数字。
这 8 位数字就是短信号段,这里面是大有文章。
简单来说,106 短信码号码是由工信部或通信管理局颁发,接入移动、联通、电信三大基础运营商并进行统一管理,共分为1062、1063、1065、1066、1068 和 1069,用于不同的服务目的。
其中,1066、1068/1069 号段的短信需要企业在工信部进行申请,1062、1063 号段则是向各省通信管理局进行申请。
国家也有一个号段查询网站,前八位一输就知道这个短信是哪一家公司发的了。
比如上面截图中的 10680503:
这个公司看起来和支付宝没有任何关系,这个也正常。你用真的支付宝发送的短信去查,查到的也不是支付宝,因为这只是对接的众多短信通道中的一个而已。
但是如果以后收到骚扰短信,可以按照这个去投诉,最终这个投诉会附带着一点点惩罚,到最初的短信发送方的。
一个小技巧,送给大家,祝大家春节前的倒数第二个周末愉快。
最后,不管和程序是否相关,保命箴言再背一次总是没错的:可监控、可灰度、可回滚。
---- 2025.01.17 01:22 更新 ----
上面的内容 16 号晚上就写好了,想着早上起来就发。 17 号凌晨被起来上厕所的时候看到官方声明了:
看起来确实是运营活动配置问题,看来歪师傅的猜测还是比较靠谱的。
看起来没有一个程序员在本次事件中受伤。
但是看起来又像是一个边界值的问题,可能存在程序校验不到位的情况。
然而这些都不重要了,重要的是我怎么开始起夜了???
一定是白天水喝多了,嗯,一定是这样的。
最后,欢迎关注公众号:why技术。好玩儿,来玩儿。
来源:juejin.cn/post/7460449861403951113
外行转码农,焦虑到躺平
介绍自己
本人女,16年本科毕业,学的机械自动化专业,和大部分人一样,选专业的时候是拍大腿决定的。
恍恍惚惚度过大学四年,考研时心比天高选了本专业top5学校,考研失败,又不愿调剂,然后就参加校招大军。可能外貌+绩点优势,很顺利拿到了很多工厂offer,然后欢欢喜喜拖箱带桶进厂。
每天两点一线生活,住宿吃饭娱乐全在厂区,工资很低但是也没啥消费,住宿吃饭免费、四套厂服覆盖春夏秋冬。
我的岗位是 inplan软件维护 岗位,属于生产资料处理部门,在我来之前6年该岗位一直只有我师傅一个人,岗位主要是二次开发一款外购的软件,软件提供的api是基于perl语言,现在很少有人听过这个perl吧。该岗位可能是无数人眼里的神仙岗位吧,我在这呆了快两年,硬是没写过一段代码...
inplan软件维护 岗位的诞生就是我的师傅开创的,他原本只是负责生产资料处理,当大家只顾着用软件时,他翻到了说明书上的API一栏,然后写了一段代码,将大家每日手工一顿操作的事情用一个脚本解决了,此后更是停不下来,将部门各种excel数据处理也写成了脚本,引起了部门经理的注意,然后就设定了该岗位。
然而,将我一个对部门工作都不了解的新人丢在这个岗位,可想我的迷茫。开始半年师傅给我一本厚厚的《perl入门到精通》英文书籍,让我先学会 perl 语言。(ps:当时公司网络不连外网,而我也没有上网查资料的习惯,甚至那时候对电脑操作都不熟练...泪目)
师傅还是心地很善良很单纯的人,他隔一段时间会检查我的学习进度,然而当他激情澎拜给我讲着代码时,我竟控制不住打起了瞌睡,然后他就不管我了~~此后我便成了部门透明人物,要是一直透明下去就好了。我懒散的工作态度引起了部门主管的关注,于是我成了他重点关注的对象,我的工位更是移到了他身后~~这便是我的噩梦,一不小心神游时,主管的脸不知啥时凑到了我的电脑屏幕上~~~😱
偶然发现我的师傅在学习 php+html+css+js,他打算给部门构建一个网站,传统的脚本语言还是太简陋了。我在网上翻到了 w3scool离线文档 ,这一下子打开了我的 代码人生。后面我的师傅跳槽了,我在厂里呆了两年觉得什么都没学到,也考虑跳槽了。
后面的经历也很魔幻,误打误撞成为了一名前端开发工程师。此时是2018年,算是前端的鼎盛之年吧,各种新框架 vue/react/angular 都火起来了,各种网站/手机端应用如雨后春笋。我的前端之路还算顺利吧,下面讲讲我的经验吧
如何入门
对于外行转码农还是有一定成本的,省心的方式就是报班吧,但是个人觉得不省钱呀。培训班快则3个月,多的几年,不仅要交上万的培训费用,这段时间0收入,对于家境一般的同学,个人不建议报班。
但是现在市场环境不好,企业对你的容忍度不像之前那么高。之前几年行业缺人,身边很多只懂皮毛的人都可以进入,很多人在岗位半年也只能写出简单的页面,逻辑复杂一点就搞不定~~即使被裁了,也可以快速找到下家。这样的日子应该一去不复返了,所以我们还是要具备的实力,企业不是做慈善的,我们入职后还是要对的起自己的一份工资。
讲讲具体怎么入门吧
看视频:
b站上有很多很多免费的视频,空闲之余少刷点段子,去看看这些视频。不要问我看哪个,点击量大的就进去看看,看看过来人的经验,看看对这个行业的介绍。提高你的信息量,普通人的差距最大就在信息量的多少
还是看视频:
找一个系统的课程,系统的学习 html+css+js+vue/react,我们要动手写一些demo出来。可以找一些优秀的项目,自己先根据它的效果自己实现,但后对着源码看看自己的局限,去提升。
做笔记:
对于新人来说,就是看了视频感觉自己会了,但是写起来很是费力。为啥呢?因为你不知道也记不住有哪些api,所以我们在看视频学习中,有不知道的语法就记下来。
我之前的经验就是手动抄写,最初几年抄了8个笔记本,但是后面觉得不是很方便,因为笔记没有归纳,后续整理笔记困难,所以我们完全可以用电子档的形式,这方便后面的归纳修改。
嘿嘿,这里给大家推荐一下我的笔记 前端自检清单,这是我对我的笔记的总结,现在看来含金量不是很大,这些文章基本就copy总结别人的文章,很少有自己的思想,我更多是将它当成一个手册吧,我自己也经常遗忘一些API,所以时不时会去翻翻。
回顾:
我们的笔记做了就要经常的翻阅,温故而知新,经常翻阅我们的笔记,经常去总结,突然有一天你的思维就上升了一个高度。
- 慢慢你发现写代码就是不停调用api的过程
- 慢慢你会发现程序里的美感,一个设计模式、一种新思维。我身边很多人都曾经深深沉迷过写代码,那种成就感带来的心流,这是物质享受带来不了的
输出:
就是写文章啦,写文章让我们总结回顾知识点,发现知识的盲区,在这个过程中进行了深度思考。更重要的是,对于不严谨的同学来说,研究一个知识点很容易浅尝则止,写文章驱动自己去更深层系统挖掘。不管对于刚入行的还是资深人士,我觉得输出都是很重要的。
推荐大家去看神说要有光大神的文章 为什么我能坚持?因为写技术文章给我的太多了呀!,这时我最近很喜欢的一个大神,他的文章我觉得很有深度广度(ps:不是打广告呀,真心觉得受益了)。
持续提升
先谈谈学历歧视吧,现在很多大厂招聘基本条件就是211、985,对此很是无奈,但是我内心还是认可这种要求的,我对身边的本科985是由衷的佩服的。我觉得他们高考能考上985,身上都是有过人之处的,学习能力差不了。
见过很多工作多年的程序员,但是他们的编码能力无法描述,不管是逻辑能力、代码习惯、责任感都是很差的,写代码完全是应付式的,他们开发的代码如同屎山。额,但是我们也不要一味贬低他人,后面我也学会了尊重每一个人,每个人擅长的东西不一样,他可能不擅长写代码,但是可能他乐观的心态是很多人不及的、可能他十分擅长交际...
但是可能的话,我们还是要不断提高代码素养
- 广度:我们实践中,很多场景没遇到,但是我们要提前去了解,不要等需要用、出了问题才去研究。我们要具备一定的知识面覆盖,机会是给有准备的人的。
- 深度:对于现在面试动不动问源码的情况,很多人是深恶痛绝的,曾经我也是,但是当我沉下心去研究的时候,才发现这是有道理的。阅读源码不仅挺高知识的广度,更多让我们了解代码的美感
具体咋做呢,我觉得几下几点吧。(ps:我自己也做的不好,道理都懂,很难做到优秀呀~~~)
- 扩展广度:抽空多看看别人的文章,留意行业前沿技术。对于我们前端同学,我觉得对整个web开发的架构都要了解,后端同学的mvc/高并发/数据库调优啥的,运维同学的服务器/容器/流水线啥的都要有一定的了解,这样可以方便的与他们协作
- 提升深度:首先半路出家的同学,前几年不要松懈,计算机相关知识《操作系统》《计算机网络》《计算机组成原理》《数据结构》《编译原理》还是要恶补一下,这是最基础的。然后我们列出自己想要深入研究的知识点,比如vue/react源码、编译器、低代码、前端调试啥啥的,然后就沉下心去研究吧。
职业规划
现在整个大环境不好了,程序员行业亦是如此,身边很多人曾经的模式就是不停的卷,卷去大厂,跳一跳年薪涨50%不是梦,然而现在不同了。寒风凌凌,大家只想保住自己的饭碗(ps:不同层次情况不同呀,很多大厂的同学身边的同事还是整天打了鸡血一般)
曾经我满心只有工作,不停的卷,背面经刷算法。22年下半年市场明显冷下来,大厂面试机会都没有了,年过30,对大厂的执念慢慢放下。
我慢慢承认并接受了自己的平庸,然后慢慢意识到,工作只是生活的一部分。不一定要担任ceo,才算走上人生巅峰。最近几年,我爱上了读书,以前只觉得学理工科还是实用的,后面慢慢发现每个行业有它的美感~
最后引用最近的读书笔记结尾吧,大家好好体会一下论语的“知天命”一词,想通了就不容易焦虑了~~~
自由就是 坦然面对生活,看清了世界的真相依然热爱生活。宠辱不惊,闲看庭前花开花落。去留无意,漫随天外云卷云舒。
来源:juejin.cn/post/7343138429860347945
小毛驴 40km 通勤上班:不一样的工作日!
从到公司上班之后因为距离变远了,也不能像之前一样小毛驴上下班了。
所以通勤方案就变成了:
上班:
小毛驴 15min ----- 地铁 40min ----- 公交OR共享单车 12min + 步行 5min
下班:
公交 12min ----- 地铁 40min ----- 小毛驴 15min
通勤费用: 小毛驴一块钱充电可以开两天。地铁 + 公交 来回 12块。
这半年下来地铁已经坐够够了。🤦♂️ 有的时候实在是不想坐了。就动了开小毛驴的心思。
但是百度地图看从家到公司的距离是 34km。之前公司到家的百度距离是 18km,其实等于翻翻了。
而且之前的路况很好么有什么红绿灯而且路上的人也很少。所以基本没有什么时间浪费18km大概半个小时左右就到了。
本来是想直接买一个新电瓶车来通勤用的,但是碰到那个什么新国标要去考摩托车驾-照就耽搁了。
然后正好这两天天气还行不冷不热。我就想要买今天就开小毛驴去公司得了。正好熟悉下路况。
早上还是按照正常出门的时间 7.25 出门。然后按照百度导航直接走。因为第一次开,路况不熟悉。按照百度走的路线全是走的人多的地方。早上正好又是上班高峰期。非机动车道上全部都是人。而且路上的红绿灯贼多。基本遇到一个红绿灯就要停下来。
前半程车的电量充足速度可以很快,但是路况太差了。路上人太多,而且有占着超车道一直慢悠悠的。开的血压飙升。所以就导致速度起不来。然后到了后半程的时候全是大路。而且没有什么红绿灯也没啥人,但是电量下去了,速度又上不来。脑壳痛!
最后到公司楼下的时候是 8.42。百度地图显示 34km 需要 2 小时零五分。实际电瓶车里程显示 40km ,耗时一小时 20 分。
其实 1 小时开车的时间是感知不到的。前半程因为都是人所以精神高度集中。
另外路上的风景也是不错的。可以走之前没有走到的地方。可以愉快的画图。
下面早上的时候拍的,因为第一次。怕时间不够。就随便瞎拍了两张记录了一下。
等会晚上回去的时候看看能不能走另外一条路会不会快点。
来源:juejin.cn/post/7362729128476524563
小米正式官宣开源!杀疯了!
最近,和往常一样在刷 GitHub Trending 热榜时,突然看到又一个开源项目冲上了 Trending 榜单。
一天之内就狂揽数千 star,仅仅用两三天时间,star 数就迅速破万,增长曲线都快干垂直了!
出于好奇,点进去看了看。
好家伙,这居然还是小米开源的项目,相信不少小伙伴也刷到了。
这个项目名为:ha_xiaomi_home。
全称:Xiaomi Home Integration for Home Assistant。
原来这就是小米开源的 Home Assistant 米家集成,一个由小米官方提供支持的 Home Assistant 集成组件,它可以让用户在 Home Assistant 平台中使用和管理小米 IoT 智能设备。
Home Assistant 大家知道,这是一款开源的家庭自动化智能家居平台,以其开放性和兼容性著称,其允许用户将家中的智能设备集成到一个统一的系统中进行管理和控制,同时支持多种协议和平台。
通过 Home Assistant,用户可以轻松地实现智能家居的自动化控制,如智能灯光、智能安防、智能温控等,所以是不少智能家居爱好者的选择。
另外通过安装集成(Integration),用户可以在 Home Assistant 上实现家居设备的自动化场景创建,并且还提供了丰富的自定义功能,所以一直比较受 DIY 爱好者们的喜爱。
大家知道,小米在智能家居领域的战略布局一直还挺大的,IoT 平台的连接设备更是数以亿记,大到各种家电、电器,小到各种摄像头、灯光、开关、传感器,产品面铺得非常广。
那这次小米开源的这个所谓的米家集成组件,讲白了就是给 Home Assistant 提供官方角度的支持。
而这对于很多喜欢折腾智能家居或者 IoT 物联网设备的小伙伴来说,无疑也算是一个不错的消息。
ha_xiaomi_home 的安装方法有好几种,包括直接 clone 安装,借助 HACS 安装,或者通过 Samba 或 FTPS 来手动安装等。
但是官方是推荐直接使用 git clone 命令来下载并安装。
cd config
git clone https://github.com/XiaoMi/ha_xiaomi_home.git
cd ha_xiaomi_home
./install.sh /config
原因是,这样一来当用户想要更新至特定版本时,只需要切换相应 Tag 即可,这样会比较方便。
比如,想要更新米家集成版本至 v1.0.0,只需要如下操作即可。
cd config/ha_xiaomi_home
git checkout v1.0.0
./install.sh /config
安装完成之后就可以去 Home Assistant 的设置里面去添加集成了,然后使用小米账号登录即可。
其实在这次小米官方推出 Home Assistant 米家集成之前,市面上也有一些第三方的米家设备集成,但是多多少少会有一些不完美的地方,典型的比如设备状态响应延时,所以导致体验并不是最佳。
与这些第三方集成相比,小米这次新推出的官方米家集成无论是性能还是安全性都可以更期待一下。
如官方所言,Home Assistant 米家集成提供了官方的 OAuth 2.0 登录方式,并不会在 Home Assistant 中保存用户的账号密码,同时账号密码也不再需提供给第三方,因此也就避免了账号密码泄露的风险。
但是这里面仍然有一个问题需要注意,项目官方也说得很明确:虽说 Home Assistant 米家集成提供了 OAuth 的登录方式,但由于 Home Assistant 平台的限制,登录成功后,用户的小米用户信息(包括设备信息、证书、 token 等)会明文保存在 Home Assistant 的配置文件中。因此用户需要保管好自己的 Home Assistant 配置文件,确保不要泄露。
这个项目开源之后,在网上还是相当受欢迎的,当然讨论的声音也有很多。
小米作为一家商业公司,既然专门搞了这样一个开源项目来做 HA 米家集成,这对于他们来说不管是商业还是产品,肯定都是有利的。
不过话说回来,有了这样一个由官方推出的开源集成组件,不论是用户体验还是可玩性都会有所提升,这对于用户来说也未尝不是一件好事。
那关于这次小米官方开源的 Home Assistant 米家集成项目,大家怎么看呢?
来源:juejin.cn/post/7454170332712386572
2025:白手起家,两娃的爸准备创业
前言
2024年陆陆续续听到老东家几个同事被裁的消息,倒有些后悔2023年自己主动提出离职,结束北漂回老家。算了下,损失个小几十万。
2024年6月份来了一场彻彻底底的自我反思,找不到明确目标,于是稀里糊涂定了两个计划:
- 每天读书,围绕技术、文学、创业类。
- 粉丝数涨到150。
工作方面,实话说呆的有些憋屈,但也算尽职尽责,年底拿了个优秀员工,满足了个人虚荣心。
创业必定是成功率低、过程艰巨的事,有这方面想法的小伙伴可+V微信进群相互取暖:wxiaomimap
。
2024年总结
掘金涨粉情况
说到定涨粉目标,有些低估自己的实力。当时只有30个粉丝,对标了下掘金的优秀创作者,感觉自己和他们差距挺大,于是定了个保守的目标:达到150个粉。
中间调整了两次目标,分别是300、500,最终赶在12月前把年度目标给完成了。写作过程中也得到官方、掘友、编辑社、三方的认可。
官方的:
掘友的:
三方的:
关于读书
为什么读书,看文学类书主要和老板吹牛用,看技术、创业类书算是个人储备。
截止目前已连续阅读209天,读完33本,做了3432条笔记。读书时间集中在上下班路上以及周末。
如果像读小说一样把书读完,作用真心不大,所以读完的书强迫自己写读后感,到目前已为27本书写过读后感。
2025年:两娃的爸,创业筹备
2024年12月初完成了年度目标,但也没闲着,开始着手考虑2025年我能干什么。为什么是创业筹备?基于两个原因。
按目前的大环境,工作的尽头就是被裁。前两天看到"前端欧阳"也面临失业(就他的实力,值得一个好工作),当公司遇到困难,技术牛人也仅是牛马。
卷孩子不如卷自己!现在的小孩压力太大,1月11日刚放寒假,1月11日-1月17日早上8:30-12:00,下午13:30-15:30已经排好了兴趣班、补习。有人说小孩的压力还不是大人给的,我只能说懂得都懂。所以,为了孩子轻松些,还是卷自己吧。
基于以上两个原因,经过12月份的深思熟虑,决定2025年开始个人产品筹备,把职业生涯紧紧把握在自己手里。目前有哪些进展?
产品定位、市场调研
作为技术人员,一定要分清开源项目和商业产品的区别。开源项目大多是自我价值体现,一个优秀的开源项目能在同圈提升个人影响力。一个商业产品的目的是盈利,当没考虑清楚产品最终能落地的受益群体,不能盲目启动。十个创业九个跨,有人会说即使失败,也会从失败中学习,但你付出的时间成本、金钱成本远大于你从失败中收获的经验。
目前,一边读创业类书籍学习前人经验,一边调研竞品市场。
已读完的创业类书籍:《财富自由从0到1》、《幕后产品》、《新手开公司》、《精益创业》。
之前有考虑做一款地图产品,当了解了市场同类产品exping,无法商业变现,2024年11月刚停止服务,我也就放弃做同类产品。
当前是个人IP的互联网时代,强如企业家雷军、周鸿祎、余承东、何小鹏等都得亲自下场运营各大视频号。甚至部分个人IP都能耗动一个公司的存亡。 微信公众号、微信视频号、抖音、小红书、快手、B站都是最小成本的产品运营渠道。
个人产品进展
目前已确认产品面向的差异化市场还存在比较大的空间。产品前期主要 to C,后期可根据影响力扩展to B。
团队组建过程也是检验产品能否落地的手段之一,如果没人愿意和你一起实施,那你得思考产品是否还值得继续做下去!差不多每个人都聊了2小时起步, 目前已有了可实施产品的初期团队,共5个人,包含前端、后端、UI、产品。
申请个人商标,当产品名称确认清楚了就得尽早申请商标。这不前段时间小米的Yu 7商标闹了乌龙,被别人占用。商标申请得有个人营业执照或企业执照。
创建了运营产品的公众号、视频号,虽然还没发什么内容,但得先想到运营手段。全网同名:绘个球
。
一个人走的快,一群人走的更远
2025年还得靠公司养活,在公司工作得干好。余下的时间并不多,产品、运营、技术、计划一个不落地都得考虑,一个产品能面向市场并开花结果,得有一群志同道合的伙伴一起使劲,2025在路上。。。。。。
产品开发在技术方面也会面临很多挑战,2025年文章输出集中在产品实现的技术挑战方面。
创业必定是成功率低、过程艰巨的事,有这方面想法的小伙伴可加我微信进群聊:wxiaomimap
。
我是
前端下饭菜
,原创不易,各位看官动动手,帮忙关注、点赞、收藏、评论!
来源:juejin.cn/post/7458931012854562842
不要让认知困死自己
7 年前,我被培训机构 8k 高薪的幌子,骗着带款了 2w 块钱,签完合同的后,我才觉察到自己被骗了。
但是 Java 好像挺有意思的,我学得不错,班里有 40 多个人,最后只有 3 个人找到了工作,我就是其中之一。
于是我入行了 Java,我的第一份工作在去哪儿,很感谢当年的领导给我面试机会,要知道当时我只有高中文凭,HR 反复确认要我来面试吗?我的领导说来试试吧。
为了那次面试,我推掉了所有其他面试,我对自己说一定要拿下这个 offer,在去面试的地铁上,我还在准备,我甚至准备了万一别人不要我,我可以做些什么来挽回。
后来面试通过了。
我珍惜这来之不易的机会,努力奋斗,晚上 10 点多下班是常态,有次冬天加到 12 点,我走在路上看着满大街的雪,反射出白光照向天空,亮亮的一点也不像晚上,我也一点都不冷。
那真是段美好的回忆,但是一年半后,压力太大,我主动提出了离职。
我跳槽到一家私企,工资翻了一倍,而且工作内容相比较之前,轻松的要死。除了基础代码开发,我顺手把服务器、Jenkins 部署、发布脚本、gitlab、redis、测试和线上环境全搭了。
但公司业务没做起来,部门解散了。
我接着找工作,年底通过了 thoughworks 的面试,和 HR 约定好了开年就发 offer 去上班,接到电话的时候我在图书馆,真是开心死了,现在回忆起来也不经咧开了嘴,爷也是能去外企的人了哈哈。
但是造化弄人,过年期间疫情来了,我的 offer 没了。
后来找到了一家创业公司,我和 boss 聊的很合拍,入职后工作了一年多,他和福禄建立了深度合作,把我带了过去。
截止至今,我在福禄工作了四年多,现在我面临着和 6 年前一样的问题,我对工作有了不一样的看法。
人们把工作当成一个赚钱的方式,出卖自己的时间换取经济价值。当然这没有问题,但我相信我的 boss,我感谢他知遇之恩,这些年我一直尽力多做一些事情。
只不过我的能力和认知,没有到那个境界,做的是很多,但也做错了很多,方法不对,成长也不够。
直到最近一次线上事故,我被击垮了。这是我五年来,第一次滋生离职的念头。
放下了对 boss “报恩” 的想法,我开始再次思考,工作的意义、生活的意义、人生的意义。
这不是我第一次思考,虽然我到现在也没找到答案。
曾经我的生活一眼望得到头,事业上,35 岁前努力工作存钱,35 后有了一定风险,但应该还能再工作几年,只要任劳任怨,万一真没公司要,再想别的出路,什么出路,我也不知道,生命总会找到出路。
生活中,20 多岁结婚,过几年生个娃,然后赚钱养家,天天为孩子奔波,把孩子养大,尽量给他好的环境。
身边的同事、朋友都是这样的,他们也劝我这样,不要想那些有的没的。
但是我忍不住会想,那我呢?我把时间给了工作、家庭、孩子,我在哪里?
有多少人真的了解自己?我不了解,我不知道自己喜欢什么,擅长什么,未来想做什么,想成为什么样的人。
我只是随大流,别人做什么,我也跟着做。买房、买车、结婚、生娃,这些人生重要节点,我做的那么随意。
痛苦让我成长,让我反思。如果回到 10 年前,我会对那时的自己这么说。
人生的重大决策,一定要仔细思考
买房
不要买房,不要背 30 年带款。这会把人压死,让人不敢尝试,不敢探索,失去勇气。
有个房贷压在头顶,那种窒息感和压力,无时无刻不在消耗自己。
做任何经济上有关的决策,都会忍不住想到我还有房贷呢。
会错失很多机会,也会让操作变形。
买车
车也是个消耗品,买车要钱,养车也要钱。停车费、过路费、油费、保养、车险,每年怎么也要大几千上万。
如果车带来的价值不如车的支出,我建议直接把房子租在公司附近,走路上下班,平时有事打车。
结婚
我之前从未思考过结婚意味着什么,也是随大流的和一个女孩子谈恋爱,谈了几年差不多了,就结婚了。
结婚意味着,和一个人共度余生。
这种影响未来几十年的决定,我甚至没认真思考过一天。
所以请一定要认真思考,可能即使我怎么说也不能理解,第一次结婚都没什么经验。
具体点说就是不要因为父母催婚去结婚;想清楚两个人之间的大方向上能不能统一;想清楚自己想从婚姻中获得什么,能提供什么价值;花点钱去找专业的人咨询,别找自己身边的案例,和自身一个 level 的人不会有很深的思考;不要因为牛牛充血一时冲动。
好在我运气不错,老婆挺好的,我们没什么摩擦,除了她不是很理解我的一些想法,剩下就是我跟个唐僧一样喜欢逼逼叨。
生娃
生娃那简直是比结婚更要命的存在。
以我现在的认知,我真的不理解为什么要生娃。
知乎上各种各样的答案,没一个能说服我,什么觉得生活没意思造个娃;夫妻生活不和谐靠娃调节等。
如果觉得生活没意思,夫妻没感情,那是自己的问题,生娃只是转移了问题,而不是解决问题。
所以我的态度是在没有想清楚为什么生娃之前,不生娃。
买车、买房、生娃,就算决定要做,我也建议晚点做,趁年轻先把事业打顺。
事业
刚进社会,懵懵懂懂啥都不知道,哪家公司给的钱多就去哪家。这是对的。
然后努力工作,不要躺平,多赚点钱,多存点钱。
工作了几年以后,兜里有点积蓄了,能覆盖两三年的支出,就要开始思考了。
这份工作有前途吗?我喜欢吗?对我个人有什么帮助?能学到什么知识?可不可以试试别的工作?
选择什么工作,入什么行业。这也是影响未来几十年的决策。
我觉得不要把工作看成出卖时间换取收入,那样会觉得自己在给别人打工,在给别人做事,心很累,做的事也不咋地。
应该把工作看成能力训练场、大型实验基地、资源交换中心;通过工作提升自己能力,让自己更值钱,通过公司验证个人的方法,学习经验,整理方法论。
把技能和经验学到一定程度,就可以结合手头积累的资源,自己创业了。
以学会赚钱为目的,为自己打工。
现在新能源和 AI 是公认的有前景的行业,往这两个方向靠。
现在我处在有点积蓄,准备尝试新的方向,去和钱比较近的岗位,学习赚钱能力,为以后创业赚钱打打基础。
寻找人生意义
我想有钱了再思考这个问题,被生活压的喘不过气的人,天天为生活奔波,哪有时间想这些呢?
在没有找到答案前,认真生活,对自己负责。
空闲时间刷抖音,打游戏,到处玩,偶尔放松可以理解,一直这样不行,这不叫认真生活。
当然这不怪当事人,我之前也是那样的,下班和周末看直播,刷短视频,搞学习什么,不存在的。
直到近期的变故,我深入思考这些问题,到处找课找人学习,认知有了提升,做这些事变得理所当然。
这种转变就像之前是强迫自己每天必须写篇文章,做复盘写总结,用意志力坚持,很痛苦,坚持不了多久。
现在是就是想写了,有感悟,想找个地方记录,主动的写,认真的写。
希望我的经历可以给各位参考,尽快提升认知,趁年轻,还有机会。
不要等有了车贷房贷,还有娃,但被裁了,或还没被裁但被当牛做马使劲压榨,而自身却没了任何反抗资本,才幡然醒悟,那样太残忍了些。
加油,共勉。
来源:juejin.cn/post/7458954918590988328
50个月年终奖?看完内部贴,我释怀的笑了
xhs
这两天,一篇爆料《xhs 最高 50 个月年终奖》的帖子火了。
从帖子的内容来看:绩效 3.75 可以拿到 8 个月年终奖;绩效 4 可以拿到 20 个月的年终奖;绩效 5 可以拿到 20 个月年终奖,外加 30 个月的期权。
综合下来,今年 xhs 最高有 50 个月的年终奖,就这帖子,还是带有「xhs 职业认证」(至少通过了企业邮箱认证)的网友发的 🤣🤣🤣
看完后,我直接沉默了。
前几天才讲了 蔚来最高 1 个月年终奖,还划分出多个等级,今天就有「xhs 年终奖 8 个月起步,最高 50 个月」的新故事。
现在简中网的黑话越来越多,要不是资深冲浪选手,有时候都真分不清楚网友发的内容,是在描述事实,还是在反串黑。
一般对于这些"惊天"好消息,如果是真,那么各大 App 早就预定热搜,但事实上并没有。
再进一步,深入到「仅 xhs 员工可见的同事圈」里,发现并不是"普天同庆,一片热烈"的内部氛围,反而更像是维权集中地。
有真诚反问,上半年的高绩效奖励到底还有没有,什么时候通知:
还有距离期权归属(真正到手)还剩两个月,就被"关账号"走人的吐槽贴:
一圈看下来,离职的在吐槽、低绩效的在吐槽、高绩效的也在吐槽 🤣🤣🤣
然后再回想刚开始的「50 个月年终奖」,我释怀的笑了,感觉还是太保守了,毕竟通知了也不一定什么时候发放,发放了也不一定能熬到期权归属,应该直接喊它 100 个月。
对此,你怎么看?你司的年终奖多少个月起步?
...
回归主题。
来一道和「校招」相关的算法题。
题目描述
平台:LeetCode
题号:926
如果一个二进制字符串,是以一些 (可能没有 )后面跟着一些 (也可能没有 )的形式组成的,那么该字符串是单调递增的。
给你一个二进制字符串 s
,你可以将任何 翻转为 或者将 翻转为 。
返回使 s
单调递增的最小翻转次数。
示例 1:
输入:s = "00110"
输出:1
解释:翻转最后一位得到 00111.
示例 2:
输入:s = "010110"
输出:2
解释:翻转得到 011111,或者是 000111。
示例 3:
输入:s = "00011000"
输出:2
解释:翻转得到 00000000。
提示:
s[i]
为'0'
或'1'
LIS 问题贪心解
根据题意,不难想到将原题进行等价转换:令 s
长度为 ,原问题等价于在 s
中找到最长不下降子序列,设其长度为 ,那么对应的 即是答案。
由于数据范围为 ,因此我们需要使用 LIS
问题的贪心求解方式:使用 g
数组记录每个长度的最小结尾元素,即 g[len] = x
含义为长度为 的最长不下降子序列的结尾元素为 ,然后在从前往后处理每个 时,由于是求解「最长不下降子序列」,等价于找「满足大于 的最小下标」,这可以运用「二分」进行求解。
不了解
LIS
问题或者不清楚LIS
问题贪心解法的同学可以看前置 🧀 : LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明,里面详细讲解了LIS
贪心解的正确性证明,以及LCS
和LIS
在特定条件下存在的内在联系。
Java 代码:
class Solution {
public int minFlipsMonoIncr(String s) {
char[] cs = s.toCharArray();
int n = cs.length, ans = 0;
int[] g = new int[n + 10];
Arrays.fill(g, n + 10);
for (int i = 0; i < n; i++) {
int t = s.charAt(i) - '0';
int l = 1, r = i + 1;
while (l < r) {
int mid = l + r >> 1;
if (g[mid] > t) r = mid;
else l = mid + 1;
}
g[r] = t;
ans = Math.max(ans, r);
}
return n - ans;
}
}
C++ 代码:
class Solution {
public:
int minFlipsMonoIncr(string s) {
int n = s.length(), ans = 0;
vector<int> g(n + 10, n + 10);
for (int i = 0; i < n; i++) {
int t = s[i] - '0';
int l = 1, r = i + 1;
while (l < r) {
int mid = l + r >> 1;
if (g[mid] > t) r = mid;
else l = mid + 1;
}
g[r] = t;
ans = max(ans, r);
}
return n - ans;
}
};
Python 代码:
class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
n, ans = len(s), 0
g = [n + 10] * (n + 10)
for i in range(n):
t = int(s[i])
l, r = 1, i + 1
while l < r:
mid = l + r >> 1
if g[mid] > t:
r = mid
else:
l = mid + 1
g[r] = t
ans = max(ans, r)
return n - ans
TypeScript 代码:
function minFlipsMonoIncr(s: string): number {
let n = s.length, ans = 0;
const g = new Array(n + 10).fill(n + 10);
for (let i = 0; i < n; i++) {
const t = parseInt(s[i]);
let l = 1, r = i + 1;
while (l < r) {
const mid = l + r >> 1;
if (g[mid] > t) r = mid;
else l = mid + 1;
}
g[r] = t;
ans = Math.max(ans, r);
}
return n - ans;
};
- 时间复杂度:
- 空间复杂度:
前缀和 + 枚举
更进一步,利用 s
只存在 和 两种数值,我们知道最后的目标序列形如 000...000
、000...111
或 111...111
的形式。
因此我们可以枚举目标序列的 和 分割点位置 (分割点是 是 都可以,不消耗改变次数)。
于是问题转换为:分割点 左边有多少个 (目标序列中分割点左边均为 ,因此 的个数为左边的改变次数),分割点 的右边有多少个 (目标序列中分割点右边均为 ,因此 的个数为右边的改变次数),两者之和即是分割点为 时的总变化次数,所有 的总变化次数最小值即是答案。
而求解某个点左边或者右边有多少 和 可通过「前缀和」进行优化。
Java 代码:
class Solution {
public int minFlipsMonoIncr(String s) {
char[] cs = s.toCharArray();
int n = cs.length, ans = n;
int[] sum = new int[n + 10];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (cs[i - 1] - '0');
for (int i = 1; i <= n; i++) {
int l = sum[i - 1], r = (n - i) - (sum[n] - sum[i]);
ans = Math.min(ans, l + r);
}
return ans;
}
}
C++ 代码:
class Solution {
public:
int minFlipsMonoIncr(string s) {
int n = s.length(), ans = n;
vector<int> sumv(n + 10, 0);
for (int i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + (s[i - 1] - '0');
for (int i = 1; i <= n; i++) {
int l = sumv[i - 1], r = (n - i) - (sumv[n] - sumv[i]);
ans = min(ans, l + r);
}
return ans;
}
};
Python 代码:
class Solution:
def minFlipsMonoIncr(self, s: str) -> int:
n, ans = len(s), len(s)
sumv = [0] * (n + 10)
for i in range(1, n + 1):
sumv[i] = sumv[i - 1] + int(s[i - 1])
for i in range(1, n + 1):
l, r = sumv[i - 1], (n - i) - (sumv[n] - sumv[i])
ans = min(ans, l + r)
return ans
TypeScript 代码:
function minFlipsMonoIncr(s: string): number {
let n = s.length, ans = n;
const sumv = new Array(n + 10).fill(0);
for (let i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + parseInt(s[i - 1]);
for (let i = 1; i <= n; i++) {
const l = sumv[i - 1], r = (n - i) - (sumv[n] - sumv[i]);
ans = Math.min(ans, l + r);
}
return ans;
};
- 时间复杂度:
- 空间复杂度:
来源:juejin.cn/post/7458226274029797402
人总得裸辞一次(🎉裸辞|离沪|进京|入职|恋爱|买房|2024)
2024马上就要过去了,本命年呀,这一年真的太快太快了,发生了很多事情,记录一下,多年以后或许能够看到有一点点记录就足够了。
还记得2023年跨年的时候来北京跨年的,2024就真的到了北京工作,这一年算是集中发生了很多事情,比23年22年都精彩一些。
有一些错误的决定,也有一些正确的决定。总得来说差强人意,但是依旧悲观。
裸辞
上家公司是在上海,从本科毕业后就在这家公司工作,工作了两年吧,在这家公司认识了很多玩的很好很好的朋友,现在也时不时的会聚一下,群里总是会有各种话题
大家一起抱怨生活,一起玩乐,能在工作后认识这么要好的朋友实属幸运。
我们在上海的时候经常去唱歌,钓鱼,公园,晚上睡不着了在群里吆喝一声 喝点? 下一分钟就到了酒桌上,大家很合得来,都是性情中人,有时候会喝的多一点,然后第二天都请假了哈哈哈。
大家经常约出去玩,我们去过宁波、苏州、乌镇、无锡、扬州等上海周边的城市玩了个遍,现在想想那时候确实挺开心的,大家单身的居多,都是没有什么羁绊,玩得很高兴,压力大了偶尔打打麻将,去酒吧喝喝酒,然后接着周一上班,妥妥打工人生活实录,就靠周末过日子呐
但是公司效益不好,开始陆续裁员,除了我之外都回去了老家所在地工作,之后在上海就不好玩了,没有朋友,也没有钱,在这家公司两年了没有涨过工资,工作也是没有什么乐趣,那时候离职的心到达了顶峰,得知公司今年也不会涨薪后,我选择了裸辞不要裸辞!辞职后压力很大
离开
提了离职后,和项目上的同事们一起吃了一个散伙饭,然后在上海最后3个玩的比较好的朋友吃了顿饭,差不多我就留了一周时间在上海再待一周,看看上海,以后估计来的就比较少了。
那一周是真的舒服,没有工作压力,那时候也不想找工作,一周就是放松,不想任何事情,辞职的事情没有告诉父母,所以没有任何羁绊,天天吃了玩,玩了睡,差不多持续了一周后
我收拾行李开始北漂了,收拾行李的时候回想到刚毕业到上海漂泊,孤身一人,谁也不认识,搬家3次,发烧生病不及其次,其实就是加班太多了,压力太大导致的
当时和我非常要好的哥们苏总,我俩都阳了好几次,发烧好几次,什么病流行,我俩就得什么病,你看我们多时髦,在上海的两年还是收获了很多很多,但是已经不适合现阶段的我了,那就走!下一站北京!
进京
还好有一个非常要好的好哥们在北京实习,租的房子有一个月的空窗期,没有人住,所以我就可以白嫖一个月的住宿了,这一个月的白嫖住宿对我来讲还是很重要的,给我提供了一个月的找工作面试学习的时间。
非常感谢我这位朋友的照顾,才使得我在偌大的北京有一个可以休息的地方。感谢飞哥
从上海坐高铁到北京,自己拖着行李,正好那天晚上还在下雨,我见到了飞哥,飞哥请我吃了一顿饭后,我到了他的出租屋,那天晚上迟迟睡不着觉,想了很多事情,也担心找不到工作之后怎么办,回老家?回老家干什么工作呐?这或许是当代年轻人的痛吧
第二天睡醒后,我就开始找工作之旅了,没有接着休息了
现在的工作是真的不好找,主要还是自己核心竞争力不够导致的,经济形势不容乐观。到北京后也没有怎么玩,直接开始复习找工作,疯狂面试投简历,那段时间还是很规律的每天都在面试学习。
经过小一个月的备战,拿到了几个offer,最终选择了现在的这家公司
入职
拿到offer后很快就入职了,准备入职体检,体检tmd尿酸高点,我一般体检指标都是正常的,这次是第一次有指标不正常,不禁感慨,工作两年后确实伤身体
约好入职时间后,就休息了一两天,然后入职了现在这家公司,挺喜欢现在这家公司的,氛围很好,是一个创业公司,也不加班
在这家公司学习了很多,未来打算学习一下外语
这家公司有美国员工,正好练习一下口语
恋爱
啊哈哈哈,来北京一段时间后,就谈恋爱了,这里就简单说一下,留一点个人隐私
买房
在北京半个月稳定了后,我告诉了父母,我换工作到了北京,父母很开心,老家是邯郸的,距离北京不算远,基本上可以每周末都回家,经过考虑后,决定在邯郸买一个房子
在北京是不太可能了,未来大概率是要离开北京的,或早或晚吧,具体时间随缘吧
我拿了30%的首付钱,爸妈拿了70%的首付钱,我在邯郸买了一个三居室,在大学对面,我很喜欢这个地段,以后没事了大学里面看看腿也挺好的
这里也不细讲了,留点隐私
你呐?
你们呐,过的好吗?过的开心吗?
祝大家2025,天天开心
2024-12-12 于北京出租屋
来源:juejin.cn/post/7450878328036982819