迟到的2014年总结

本来这篇总结早该写了, 只是最近一直在纠结网络编程问题没能静下心来去总结, 今天终于有时间就仔细总结一下以勉2015. 说是2014年总结, 其实应该算是毕业之后的总结, 因为13年才工作半年实在没什么好可以总结的, 因此拖到14年一并总结了.

13年7月毕业后, 我装着MFC 2K行的项目经验和几个玩具的MCU代码以及二千块钱便和同学一块来到了上海. 刚到没两天, 火车上的觉都没补够就被昂贵的消费打醒了. 房租隔断间都要700多(因为住隔断间我同学把笔记本也丢了), 每顿伙食费也要12块以上. 更要命的是找工作总是需要简历和坐地铁, 可是简历一张打印至少1块钱, 一趟地铁也至少要3块. 于是托师兄到他们公司偷偷打了两张简历, 后来又怕不够就又请还没来上海的同学帮忙打了一叠拿过来. 比较悲催的是我的简历才用了6章, 我的资金链就快断了. 在无奈的情况下, 只能在面试的六家公司中比较了一下, 挑了一家稍微正式点的公司, 也就是我现在所呆的公司.

在面试时说的是做Firmware然后偶尔可能需要用汇编开发(在毕业之初, 我其实是想做Firmware工程师的), 但是入职第一天, 给了我一份自定义汇编的英文文档、一个示波器和一个m25p16说这是试用期考核项目. 我一下子就傻眼了, MCU中C去驱动SPI_FLASH很随意, 甚至于说用FPGA都是小意思. 但是给我一个自定义汇编的文档, 然后就让我自己用示波器边写边调是我万万没想到的, 在上学时只用过电子束的示波器, 哪见过这么高端的数字示波器. 后来阿汤哥见到我一筹莫展的样子好心教了我示波器怎么用, 于是终于开始了我人生工作中的第一次代码经历. 经过一周多之后我终于可以用那自定义汇编操作M25P16了, 自以为万事大吉了. 原来还不算完, 还有要MFC dll, 还好有MFC功底, 搞定.

我以为原来这些工作熟悉之后是如此的随意, 等到正式分配给我的第一个芯片后才发现没我想的那么简单, 原来之前的考核项目中少了第一个步骤, 画原理图并layout. 原本画板对于我来说也不算什么, 但我只是会画数字电路PCB而已, 对于模拟电路和电源部分根本连了解都算不上, 于是我的第一关就卡死在电源上, 最后以学会一个新名词”纹波”(用于形容电压的稳定度)为代价解决了这个问题. 第二关就是协议,那颗芯片的调试接口几乎没有文档, 最后终于在某角落里找到一个中文文档后来还发现有错误, 过了这两关我总算是成功支持了一颗芯片, 再回头已经是一个月后的事了.

做完这颗芯片之后我心里很矛盾, 工作内容与我想的完全不一样, 这哪里是Fimrware工程师, 这其实是杀死脑细胞而又没收获的苦力. 之后就一直在想辞职的事, 恰好这时候又分给我一颗很变态的芯片, 半个月后发现自已依然如上一次一样处处碰坎, 别人一周做完的到我这就得一个月, 总觉得自己实在不适合做这个工作. 于是打定主意第二天找老板辞职. 每每回想起来, 都不得不感叹这是天意, 在回去路上碰到了一个熟人, 我与他在地铁上一直讨论到下地铁, 然后就决定熬到年底再说.

在第三芯片毫无起色时, 公司正在准备做新架构, 他们又出重新制定了一套汇编语言, 汇编器已经完成, 但是他们没有调试器UI. 这里要再次感谢阿汤哥的帮助, 他们当时都有人在忙, 然后看我芯片支持做的毫无起色,在阿汤哥的提议下这个任务终于放到我头上了, 当时高兴了老半天了. 他们让我估时间, 我不想失去这个难得的机会, 于是直接说一周内搞定. 后来才发现一周内搞定简直是天方夜谈, 就凭我那半调子的MFC基础, 周一到周日每天都加班, 最后也才出了个bug百出的简化版.

后来公司出了一件不大不小的事, 两位软件工程师先后离职, 公司正准备开始做新架构软件, 但是又找不出做软件的人. 于是我就如愿以偿的顶了过去, 当时想终于跟芯片支持说拜拜啦.

然后我就开始做新架构软件demo啦, 最初开始做时只是做了一个MFC程序, 然后多任务模拟进度条而已. 然而一切都没有我想象的那么简单, 到这时我才发现我的软件知识弱爆了. 第一步就碰到了一个很奇怪的问题, 我开了8个线程去跑8个进度条, 这8个假进度条函数是完全一样的, 但是他们的进度却完全不同步, 于是我第一次知道了在多线程中其实sleep(0)也是有用的, 虽然windows号称是抢占式操作系统, 但是还是需要我们在恰当的时机调用sleep(0)让出cpu的使用权来让程序跑的更均匀. 在后来总结多线程时总觉得这么多线程怕cpu会跑死, 于是就有了使用状态机去模拟多线芯程的做法, 当然其实也只是想想, 虽然可以做到但是很麻烦. 直到前不久才发现其实coroutine这种工作模式应该比状态机模拟多线程要好的多, 据说在windows下可以使用fiber来实现, 在linux下可以用swapcontext函数来实现, 具体还没有深入研究.

demo UI做完之后, 老板提出新的设想, 他觉得把数据处理部分和UI逻辑处理部分混合做在一起不好, 他觉得整个软件应该分成两块, 一个只是处理用户逻辑, 一个用于处理所有数据作业. 提到这种架构,我不得不说一句, 虽然老板是搞FPGA的, 但是他这一观点真的很不错, 与大名鼎鼎的MVC模式有不谋而合的味道, 后来我在做UI时采用了类似的方法. 然后我们就把逻辑处理部分定义为后台, 也就是TCP端的server端. 但是Server一词被引入后老板就开始有点胡思乱想了, 他觉得要把软件做成UI可远程控制的模式, 于是我们花大力气去做远程文件浏览器等很多不必要的复杂设计. 现在软件开发已经快结束了, 我可以很负责任的说这完全是过度设计, 为了所谓远程操作功能, 我们不得不在设计上牺牲很多其他功能以保证这个功能的实现, 而且至目前为止这种设计可能还残留着一个大坑.

在demo做完之后, server刚做了个开头, 公司招来了一位有多年经验的软件工程师, 所以最初socket通信模式是他来实现的, 但是他也只实现了socket通信那一块就离职了. 他走之后我来维护他那部分socket代码时总是很容易搞错, 于是把socket部分给重写了. 在实现socket命令处理之初我在把socket命令放在每一个模块的顶层去调用, 但是没多久我就发现这种实现的坏处了, socket相关的操作到处都是, 完全不便于移值和维护. 于是花了一周时间将所有socket命令集中实现为一个模块,也可以称为中间件, 这样以后不管是socket, CLI还是PIPE, 甚至于将TLV的通信协议换成文本通信都不会产生太大的问题, 我要做的只是换掉这个中间件而已, 这也是我第一次在软件开发中引用了分层的概念.

当我将socket单独实现为一个模块之后, 其实之前就存在但是不明显的问题现在就很明显了. 一些structure, Server要用, Client要用, socket通信要用, 为了满足高内聚低耦合, 在实现之初我都是定义三个structure, struct server, struct protocol, struct client, 然后在在模块与模块交互处一个字段一个字段的去转换, 这个问题一直困扰到我今年下半年, 后来才被我想通. 其实软件开发除了设计上的智慧外可能还需要一个度的取舍. 比如如上需求, 有些structure几乎是与整个软件相关的, 使用这些structure的模块其实是为这些structure来服务的, 那么这些structure就应该单独抽出来时做成全局头文件, 让各模块包含. 当然这也是需要自己去衡量哪些structure定义为全局最好,哪些structure定义为局部分有利于高内聚低耦合.

软件的重中之重就是数据解析部分, 设计之初只有一个数据解析模块, 后来随着需求的增加我发现不同的模式下其实数据解析除了接口一样之外, 其UI逻辑和解析逻辑完全不同, 而且不能保证对于数据解析模块的需求总是固定不变的. 在一次讨论上我提议根据open-close原则, 将数据解析模块拆分成三个具有相同接口的DLL, 然后根据ini文件来配置何时使用哪个数据解析模块, 以减少不断变更需求时更改代码对于其他数据解析模块误修改的风险. 由于数据解析模块也需要UI, 所以每一个数据解析模块被拆分成两部分, UI逻辑部分和数据解析部分. UI逻辑部分和数据解析部分中间采用tcp协议进行通信. 由于不同的数据解析模块的UI完全不一样, 所以更不要想定义一个结构体去通用所有数据解析模块的UI配置信息了, 更何况有可能还有第四个数据解析模块的出现. 于是我们在软件中首次采用了透明的概念, 即在UI的DLL接口中出来的是unsigned char数据和实际size, 在server端的DLL接口处进去的是unsigned char数据和实际的size, 然后在各自的DLL中转为相应的结构体.

这个软件其实严格来说算是一个平台, 中间还需要一些工程师写DLL插件. 当基础功能实现差不多时, 平台也总算初具雏形, 终于迎来了换架构热潮. 在一波又一波的问题之下, 最终发现其实那些DLL插件原本可以更简单, 于是我用实现了一个DLL导出基类, 然后给写DLL的插件的工程师用. 这个DLL插件和插件基类DLL是在Client下使用的, 正如前面所说由于不得不考虑Client和Server可能不运行于同一台PC上, 最终我提出使用将DLL通过socket复制到Client当前目录, 然后再使用LoadLibrary进行本地加载.

平台的基本功能该有的都差不多了, 随着写DLL插件的工程师编写插件的增加, 每天依然会有很多问题, 但是有些问题其实只是关于代码的实现问题. 当时下班后在观摩nignx, 恰好看到服务端的所有程序都是有log的, 而且log都分级的. 为了不影响量产使用时程序的执行效率, 我改写了log模块, 为log分级, 然后在debug级别打印了许多代码的执行过程以便其他工程师可以方便debug, 如果需要很底层的log信息, 则去Server的配置文件去定义log级别为DEBUG即可.

在软件基本功能还没完成时就提出了uniquekey功能, 结果这个功能反而是最后一个实现的. 这个模块的功能就是用来生成一个惟一序号, 供其他线程使用, 典型的生产者消费者模式. 最坏情况是1个线程生产, 64个线程消费. 以前对锁的效率测试过, 所以对于这种线程极多的情况下我是很忌讳用锁的. 在研究过DisruptorDisruptor框架中的队列实现方式之后, 仿写了一无锁队列出来, 使生产者消费者通过队列来消费, 由于无锁队列的数据拷贝上是可以并行的, 因此会快上不少.

在软件使用过程中, 开始有人报怨server启动慢的问题. 最后发现有一部分原因是软件使用xml来充当数据库的角色(大概几十M), 而且采用了非常规的定义方式(xml中几乎全是属性, 没有值的存在)导至了普通的xml解析器速度不是非常理想. 为此我专门针对这种特殊xml的用法重写了个xml解析器.

在软件不断添加小功能和改bug的过程中, 我一直在反思软件的模块化到底应该怎么进行. 虽我尽了最大努力, 但是在不断维护过程中, 发现我的代码组织方式依然不够理想. 为此我先是使用代码模块化(一)的方式去重构代码, 使代码依赖关系变得清晰, 最后却发现我的代码模块成图状完全没有层次的概念. 经过一个月的再次反思, 我借用了少许面向对象的思想, 采用代码模块化(二)的方式重构了代码. 重构完成之后发现代码像是树形了, 而且只要接口定义好之后结构就会非常清晰. 遗憾的是在重构完之后由于其他非技术原因没有使用上.

总得来说这一年半, 从无到有的开发一款软件还是感触颇多, 长了很多在书本上不能体会的知识. 有些东西只有做过再去看书上写的, 才会有共鸣. 不去经历永远也不会知道, 度的权衡永远也不会提高. 最近越来越觉得并发和网络是两大流行趋势, 可惜windows在此都不占优势, 于是准备转向linux平台, 在1月1日格了win8装上Debian 7.7也算有象征意义吧.



评论

  1. […] 自从写了第一个假server之后, 我就一直在想真正的处理高并发的server是如何做的。然后我就研究了skynet, nginx, Node.js这些服务器程序框架。 […]

  2. […] 终于还是求知欲战胜了懒惰。因为我发现如果再干着同样的工作,我的技术不太可能再提高。所以最终我还是选择了辞职,由于上一份工作的原因,相比前端程序来讲,我对于后端的兴趣更大。因此在找工作方面,其实我更倾向于服务器编程。因此后来就找了一份游戏服务器工作. […]

发表评论