您的当前位置:首页正文

【非技术】说说2024年我都干了些啥

2025-01-29 来源:个人技术集锦

今天是除夕,老周不打算聊编程的事,毕竟大伙当码农也不容易,一年又一年地啃代码,既然都要过年了,说点轻松、随便的话,你看着不焦虑,老周写着也不费神。

先解一下有些大伙伴们的疑惑,老周这半年去哪儿了?是不是被坑到缅东去了?当然不是了,不管缅北或是缅东,这些地方,只要相当聪明的人才会被骗去,老周生来平平,不太聪明,根本不可能被骗去那些地方。这半年左右,事情比较杂,也有好些项目,有个别项目还弄得不太愉快(烂尾)。水文标题都说了2024老周都干了啥鸟事,所以,我找几件有代表性的说说。

24、25年的经济形势大伙都懂,不必多言。对于咱们这一行,烂尾巴烂脑袋的项目也不会少。年初,大伙可能听过一个词,叫“共享员工”,或称“通用派遣”,就是你同时拥有几个“老板”,帮N个单位“打工”。24年年初,老周很是荣幸地与潮流“接轨”。

先说第一个,这个玩意儿很简单,说白了是个极简化的进销存系统,水果店的。他们的鲜水果会有专门的物流(送货专用的,不是我们常见的那些快递)送上门,他们只需要下订单就行。订单输入(店员负责)后是“待定”状态,老板确定要订货后状态变为“确认”,然后联系供货的。未收到货的订单会显示为蓝色,收到货并检验没问题了,操作员会输入两次口令确认,订单变成白色,同时更新“仓储”记录。

售卖时,店员会用扫描枪扫条码,输入数量(这个是专买的数字键盘,不是一般的PC键盘,用串口的,按下键后直接串口读到键码),算好价格,并存入记录表,同时“仓储”表也会更新。至于扫描枪怎么读数,相信伙伴们都很熟了。一般是两种:1、当键盘用,扫码完毕自动追加个 \r\n;2、串口通信,也是在扫完后追加 \r\n,如果同时扫到多个条码,会用分号(;)隔开。

其他功能,没了,平时也就三个操作员,特简单,老板一口价 <= 650 大洋,凑个 700 整头他都不愿意。

根据他们的要求:程序是按电脑主机授权,即只能一台机器用,所以,就绑定了电脑的硬盘序列号、网卡MAC、CPU序列号,外加购买客户的姓名(拼音)转化为 SHA1 值。注册的算法是老周自己设计的,也不用太高级,这种软件又不是面向大众的,也不会有吃饱了撑着的黑客去破解,工厂里那些家伙也不会破解。算法如下:

2、把客户的姓的拼音插入上面字符串的头部,把客户的名的拼音插入到上面字符串的末尾;

3、把 2 中得到的字符串做三次 SHA256 运算,得到 byte 数组;

4、在 3 得到的 byte 数组中,索引 0、4、8、12、16、20、24、28 处分别插入固定的字节,得到 40 个元素的 byte 数组;

5、再把上面的 byte 数组变为 base64 字符串,就是注册码。

注册验证时也是按上面的过程算一遍,看结果是否相等。注册文件用一个自己生成的证书加密,连同证书一起存放在程序所在目录就 OK 了。

五月份的时候,有同学找到老周,说他的朋友有个三人小团队,有段代码他们一直搞不明白错在哪,运行就报错,网上查不到结果,想找人帮忙看看。报错的那段代码,大概的逻辑是这样的:

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*-------------------- 定义的新类型 ---------------*/
typedef struct
{
    uint16_t            spi_freq;
    uint8_t                __multiple;
    struct {
        uint8_t            _msb : 1;
        uint8_t            _port : 1;
        uint8_t            _filter_on : 1;
        uint8_t            _lazy_wr : 1;
    } flags;
} sot_dev_info_t;
/*---------------------------------------------------------------------------*/

void sot_factory_api_default_parm(void* buffer, sot_dev_info_t* _out)
{
    buffer;            /* 其他代码 */

    sot_dev_info_t* d;
    d = (sot_dev_info_t *)malloc(sizeof(sot_dev_info_t));
    // 内存默认全写0
    memset(d, 0, sizeof(sot_dev_info_t));
    d->spi_freq = 40 * 1000;
    d->__multiple = 12;
    _out = d;
    /* 其他代码 */
}

int main(void)
{
    sot_dev_info_t* pInfo = NULL;
    uint8_t data[127] = { 0 };
    // 调用函数
    sot_factory_api_default_parm((void *)data,  pInfo);

    printf("spi-freq = %u, multi = %u\n", pInfo->spi_freq, pInfo->__multiple);
}

看,这算个龟的难题。我相信有大伙伴们早就发现问题了。老周知道他们的意图:在 sot_factory_api_default_parm 函数内给 sot_dev_info_t 指针类型的变量(_out 参数传入)分配内存,并给字段赋默认值。然而,代码的执行结果是,调用完函数后,sot_dev_info_t 指针变量依然指向的是 NULL(空指针)。没想到吧,伙计,这么简单的问题居然让这个三人团队全军覆灭。

老周觉得,他们可能理解为以下情形:

void modify_value(int* val)
{
    *val = 300;
}

int main(void)
{
    int number = 99;
    printf("调用 modify_value 函数前,变量 number 的值:%d\n", number);
    modify_value(&number);
    printf("调用 modify_value 函数后,变量 number 的值:%d\n", number);
}

回到第一个例子

sot_dev_info_t* pInfo = NULL;

那么,这个错误怎么改呢?很 Easy,让 _out 参数变成指向指针的指针就行了,套娃吗,非也,看

void sot_factory_api_default_parm(void* buffer, sot_dev_info_t** _out)
{
    buffer;            /* 其他代码 */

    sot_dev_info_t* d;
    d = (sot_dev_info_t *)malloc(sizeof(sot_dev_info_t));
    // 内存默认全写0
    memset(d, 0, sizeof(sot_dev_info_t));
    d->spi_freq = 40 * 1000;
    d->__multiple = 12;
    *_out = d;
    /* 其他代码 */
}

故正确的代码是这样的:

void sot_factory_api_default_parm(void* buffer, sot_dev_info_t** _out)
{
    ……
}

int main(void)
{
    sot_dev_info_t* pInfo = NULL;
    uint8_t data[127] = { 0 };
    // 调用函数
    sot_factory_api_default_parm((void *)data,  &pInfo);

    printf("spi-freq = %u, multi = %u\n", pInfo->spi_freq, pInfo->__multiple);
}

有小伙伴还发现,老周,你这代码还有错,没有 return 0; ,人家 main 函数是返回 int 的。其实,只要你返回0的,你可以不写 return 语句,编译器会帮你写。

也有小伙伴会说:这个团队太瞎装逼了,干脆用 Go、Rust 就行了。是的,很多人会这么想的,Rust 的作者肯定也是这么想的,不然他们不会把 C/C++ 喷得一无是处。但是,老周明确告诉你:

1、你这种想法是错的。如果啥都能用 .NET 来做,老周还打算学 .NET 到养老多轻松。实际上呢,许多时候,用 C 语言是最合适的。Go、Rust 只是解决了菜鸟问题,并不能解决现实问题,更解决不了逻辑错误和 bug,这些问题才是最头痛的。这就好比现在用 AI 找代码,可以解决搜索的整合性问题,但解决不了准确性和逻辑问题(多数情况下答非所问),你敢让AI写代码,就等着撞墙吧。机器永远是机器,就算发展到像奥特曼里面那么高科技,也不能离开人工操作。老周最喜欢 Bing 搜索的整合方式,在搜索结果中突出显示结果(一般就几个字),我看到结果关键词后就会决定看不看原文,这样搜索效率才高。最反感那些生成一大段一大段对话的,不仅反应慢了N秒,还主次不分,我让你说那么多废话了吗。

2、其实,至今为止出现的编程语言已经够用了(要啥有啥),再推出新的语言并没什么J用。现在做项目首先是从现有语言中选择合适的,然后重心都放在功能实现上。

好了,这个事情纯粹是那个破团队基础知识没学好,不算什么项目。不过,说句实话,也不能全怪他们,现在很多大学的 C 语言教材根本学不好基础,很多关键东西书上都不说,只顾着扯。

----------------------------------------------------------------------------------------------------------------------------------------------------

不知道现在的人是自我营销培训课程听多了,还是营销号视频看多了,吹起牛皮来是真的一套一套的,真眼都敢瞎说话。但是啊,这人啊,包装自己是没有错,可你真的得有那个实力才可以,不然人家拿根针一扎,你就破了,吹得再完美也没用。

老周主要负责对付 .NET 和 C++ 方面的,有时候会有另一个人(跟我一样,也是外面请来的)负责数据库( 其实就是SQL 方面的)。小公司比较零散,Y集团这些大规模的,面试的会比较集中,毕竟他们要招 40 多个开发人员。为了避免不必要的纠纷,下面讲案例时老周一律不使用真实的公司名和人名。操作流程是这样的:

1、要招聘的公司会把应聘者的工作经历相关信息,以及简历做成 PDF 文件,提前发给老周,老周好准问什么问题;

2、在线面试时,公司负责人一名 + 老周 + 应聘者,三人语音群聊;

3、公司负责人先问他们公司特有的问题;

4、老周提问并考核应聘者的水平;

5、交回给公司负责人,让应聘者自由提问,他答疑(非编程);

6、通知应聘者复试时间,会议结束。

初试结束后,公司那边会与老周单独聊,交换对刚才应聘者的评价。

上面的内容可能太严肃了,下面说说不严肃的。老周分享几个印象比较深刻的应聘者,至于是正面教材还是反面教材,那你自己判断了,这个不好下定论。

选手A:这位仁兄最特别,所以先说他。他做的简历上,到处都是初音未来。看得出来,这位仁兄绝对是真爱粉。老周并不反对这种爱好,毕竟他境界再高也比不了近藤显彦先生——有几个敢步他后尘?其实老周卧室里也放了两个,当然,不是初音,是小樱和奈叶。但是,把这个爱好弄到简历上(而且面试的是码农职位),尽管没有错,但终究不太适合的。最要命的是,虽说这位仁兄简历做得挺用心挺个性,可回答提问时没几个能答对的。最后连公司那边的人都看不下去了,直接叫他回去重新规划一下自己到底想找什么样的工作。所以说啊,个性张扬的你必须要有实力做保证,不然,这样的个性仅仅是空虚而已

选手B:这个人光看简历的话很厉害,有十三年电子商务平台开发经验,而且还参与过国内互联网巨头(在深圳的,你懂的)的项目。就凭这个架势,老周都自愧不如。问的一些基础问题他都能答上,不能说特强大,至少比其他的应聘者强。所以老周给了他很高的分,Y集团也安排了复试。然而,后来老周得到的反馈是,Y集团那边的技术一面试就把他 K 掉了。老周是真不明白为何,难道跟他们的需求不符。后来,那边的开发部 W 经理跟我说,那个人面试时,第一道题就挂了。做了十几年电子商务系统,居然不知道HTTP状态存储有 Session、Cookies 这些。哦,碰巧老周问他的问题里没有这个。老周每次问的问题都不一样,是随机挑选的,刚好那天没问到他有关会话和 Cookies 的。唉,真的可惜。大公司的流程也确实严格。

选手C:这个人是树莓派狂热者,大草莓所有型号他都有,玩得不亦乐乎。遗憾的是他的 .NET 玩得不怎么样,可是他呢,一心想做 .NET 开发。面试完后,老周单独联系他,跟他说既然 Linux 玩得很 6,那么 C++ 应该可以的,我是兼职帮人招聘,有公司找 C 方面的,你可以把简历改一下投过去,成功率较高。结果他是“语不惊人死不休”,他居然 C/C++ 玩得更烂。而且他坚持要做 .NET 开发。那老周只好建议他先不找工作,抓紧时间补,既要补基础知识,也要补实战。这个绝对比你在学生时代要艰难很多,学生时代学编程就没几个人会去想实战的事。但,找工作,你必须这样做。

选手D:这个人老周是最无语的,因为招聘公司那边的负责人是女的(主要是这妹子说话的声音确实好听),这小子面试的时候老是远程调戏人家。老周提醒过他两次,注意场合,打断他的“骚话”并提问面试内容,这小子直接无视我的存在,一直跟那女的聊。就差没把她的生辰八字问出来。当时我在想,码农团队确确实实,98%是罗汉敢死队、和尚特务营,所以,公司里哪怕有个妹子,他们都会很兴奋。不过要是哪个公司的团队有一个像这小子的,估计这家公司是永远招不到女员工。更离谱的是这家伙还是应届生,看来他在大学期间是一星期换七个女朋友的那种。

以上选手是老周印象深刻的,其他的就是跟咱们差不多的“平凡人”,老周就不多扯了。

有一家公司在复试的时候,问的一个问题难倒了很多人,这里老周提一下。WPF 有个 Dispatcher 类,负责 UI 线程的调度,问题就源自官方的这个示例:

public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
    ((DispatcherFrame)f).Continue = false;
   
    return null;
}

为什么设置 DispatcherFrame 的 Continue 属性为 false 可以推动 UI 线程的消息循环,不再阻塞呢?准确讲应该是每隔一段时间 PUSH 一个这样的 Frame,并把 Continue 属性设置为 false,UI 线程就不会卡了。老周曾经写过相关的博文的。向调度器里推入一帧,相当于嵌套一层消息循环,但循环中如果 Continue 为 false,就会结束循环,调度器会从消息队列里提取下一条消息处理,所以这样做等于让消息循环在卡住时可以动一下,消息队列中的消息就能被处理。

七月、八月份的时候,老周被派遣到另一家公司上班,帮他们修“八阿哥”,这个老周曾在博文中提过。

中秋节前,有一个要先过电子秤记录物品重量,然后开启包装机自动打包的项目。这个项目你别听着玄,简单得要跳楼,老周可以说说原理:

1、电子秤模块,串口直接输出数据,读取就完事了;

2、自动包装怎么搞?你别想复杂,其实就是控制一个继电器罢了。物品在电子秤上读到重量后,先 POST 进数据库服务器保存;

3、接着等待 800 毫秒左右,传送带的滚轮会动,当物品到达挂着包装袋的地方,产线上某个口会输出高电平;

4、收到高电平后,往连接袋子闸门的电机控制器写一个高电平,保持 100 微秒以上再拉低电平,闸口就会打开,物品会掉进袋子里。这个东西设计得挺巧,但用起来还不完善,偶尔会出现掉不下去的,需人工给点外力;

5、袋子连同物品一起被传送到加热封口的压条上,此时产线会复位;

6、上位程序收到复位信号后,向继电器送一个低电平,电源接通,开始加热,上位程序把 IO 口转为读模式,等待高电平;

7、封完口后会自动让继电器断电,恢复高电平,上位程序读到高电平后,封口完毕,结束,进入下一个循环,等待新的物品过来。

关于高压气缸的控制,这个的作用主要用高气压推动横刀,切断袋子的尾部(袋子是一卷的,一个个是连着的),这个刀比较危险,老周都不敢靠近它,怕被它打到。这个玩意儿是一个 Stm32 主控,IO 口连着很多个气缸,一条产线一个。其实还是继电器的原理,这些玩意好像是 MOS 管控制开关的。需要哪个气缸推刀子,直接给它个高电平就行,500 ms 后写回低电平就行了。如果不写回低电平,那个气缸就会不断地充气,放气,看着很恐怖的样子,放气时声音很大。

十月份开始有个纱线机主控程序的项目,到了十二月中旬老周主动放弃了,不做了,一分钱也没拿到。这个项目用的是高配板的 Stm32—— H7R7xxxx,底板实际上是正点原子的,有个七寸 RGB 屏,因为需要 UI 交互,就移植了 LVGL。移植后也没啥问题,输入法也不需要,只要数字键盘就行,都是修改一些电机参数。

又麻烦又难搞就是几个无刷电,大电机A,小电机有两个:B、C。当设置比率后,B、C 的转速必须是 A 的 1/N。假设用户在界面上设置了跟随 3 ,那么,B、C 的转速是 A 的 1/3。

摇摆电机这个是最难搞的,复位时它必须位于正中间,然后先向左摆120度,再回到中位,再向右摆 120 度;再回到中位,再向左摆 120 度,再回到中位,再向右摆 120 度……就这样一直循环,每摆过 60 度要停一下(小于1秒)。这个东西特难搞,复位时要处于绝对中位这个就头大,反 A、B 相脉冲是不行的,因为这个只能知道相对角度,不是绝对的。后来加了 MT6701 磁编码器,读绝对角度。传感器是调好了,可这两个破电机的转轴末端无法安装磁铁,MT6701就无法读角度。他们说产品定型了,电机都选好型,不能换。NN的,那没办法了,把电机后盖打穿,把磁铁直接用胶水粘在转轴上,再打四个孔安装铜柱,用来固定 MT6701 模块。

总算折腾完了,然后他们又说,不用 485 了,想改用 Ether-Cat 总线。我去,这货可麻烦,又挑网卡又难配置,绝对没有 CAN 、IIC、串口这些好弄。老周就跟他们说,要用硬件级别,只能买欧洲货,湾湾生产是软件模拟的,然后老周发了一款最贵的链接给他们看。果然,高成本把他们吓住了,于是他们放弃了改 E-Cat,就用回串口。

没几天他们又说,这样弄还是没竞争优势,现在要上云,要物联网,还得能远控管理、监测,哪个模块坏了也能主动向主机报告。老周心想:你上个鬼火云,我为了这玩意儿买开发板,买传感器,找人做电路都花了 1400 多大洋了,还有一台1千多元的示波器,180元的直流电源(电机是从捡破烂那里弄来的,成本就15元),你们一分钱都不出,还要等产品正式上线后,卖出去再按 15% 提成。我去,要是卖不出去呢,我不是白折腾了?再说了,这么智能的设备,你居然把市场定位在家庭小作坊。小作坊通常就一台机,多的也就两台机器,人家闲着没事干,用这么高级的功能吗?你以为上千台设备的车间啊。

所以,这项目老周最后跟他们说,放弃了,不做了,搞不下去。

另外,还有一个 ASP.NET Core 的项目,给 PDA 设备用的。本来没什么难度,可厂里安卓 2.3 到 安卓 11 的设备都有,为了兼容性,只好纯 JS 手写 + Web API 的方式搞,什么前端框架,什么 Blazor,想都别想,没法用。原本老周是想,如果他们厂里的设备都是 7.0 以上的,就直接用 MAUI 做客户端 App,用 ASP.NET Core 做 Web API 服务器。但结果,还是手打JS,没办法。

 

好了,写着写着,就快到大年初一了,不能再写了,再写就过了除夕了,只能就此结尾。

 

显示全文