独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废

在写文章前容许我啰嗦一下:对于软件开发,我走了不少弯路,有时觉得自己作为API侠,无所不能,有时又觉得自己很多LeetCode题写不出来,无能为力。我有一个博客,但是写满了自己的絮絮叨叨,真正有本领的东西九牛一毛。 我甚至没有自己的“代表作”,因为我是一个急性子,想马上得到结果(事实上计算机真能马上给出结果,但开发过程不行)。我经常在“造自己的轮子”和“用别人的轮子”之间徘徊,“造自己的轮子”比较有成就感,但是难度其实很大,需要踩很多别人踩过的坑;“用别人的轮子”则没有什么成就感,做出来也不像是自己做的。而平时工作中,每天都在用别人的轮子,自己真正从比较底层开始实现的情况是非常少的,导致我在闲暇时间也不想用别人的轮子。 啰嗦一下自己的经历: 2004年,我家买了电脑,我也接触了很多电脑上的游戏。但是也许是我心浮气躁或者没有游戏天赋,我总是在游戏里输,总是追不上邻居小伙伴; 2008年,我萌生了“自己写一个什么东西”的想法。 2009年夏,小学毕业的我开始摆弄魔兽地图编辑器,虽然一点脚本都没写,做出来的游戏也粗制滥造。也开始查找“如何创建一个个人网站”。 2010年,在和初中新同学混熟之后,我们打算做一个班级网站,我找了一圈发现5566和phpwindy有免费的论坛可以注册,我们注册了一个论坛,并且想把每天的作业更新发布上去。但事实上那位同学仅发布过一次作业。 2011年,我利用爸妈给我的打游戏的时间,将网页保存下来,咬着牙研读里面的html标签,然后给我当时加入的一个魔兽争霸群做了一个纯html的“官网”。在某免费空间上注册了一个只支持asp和基础web的免费空间,用8uFTP把网站上传上去。 2014年,在学校的图书室,我昧着良心在寻找Dreamweaver的教程书,还真给我找到了。但是我根本没有自己的电脑,只能对着书发呆脑补。 2015年夏,我高中毕业,填志愿的时候几乎把“软件工程”和“计算机科学与技术”都填的满满的。我查了资料发现“软件工程”比“计算机科学与技术”学费更贵,在询问了父母的意见之后,我把“软件工程”填到了第一位,然后和父母说,一定会考上。最后我考上了一个普通的本科,开始学习软件工程专业。 2015年暑假,还有一件事情,我自己注册了(已经失效了的)域名,购买了阿里云的虚拟主机,配置了WordPress服务器,在上面写博客文章。仅仅是备份就花了一周,然后终于拿到了自己的备案号。我永远不会忘记第一次用自己的域名打开自己的网站那一刻的快乐。 2016年春,我推开软件学院大楼某间实验室的大门,然后就我大学一年级就和学长们一起做项目,当时团队里缺人写html页面,而我正好已经算是html css入门了(完全不会js),开始仿写各大网站。然后想自己写一个象棋游戏,结果找资料就找了一个下午,最后不了了之。 2017年夏,我跟着课程开始研究蚁群算法,最后实现了论文的内容。现在回想起来,对着论文依葫芦画瓢其实没什么技术含量,当打包运行并且在台上汇报之后,我真的觉得很快乐。不过,我并没有学到什么“真才实学”。 2017年秋,在Java课上,我带领团队(其实开发就我一个人)用Bmob作为后端,安卓作为前端,开发了一个“今日特价” app。我对着github上的一个高仿微博的项目,拼命的抄各种代码。而最后我觉得写的太差,而把代码都删除了,真是太可惜了。 2018年,我有了很多很多想法,但是都没有执行下去。 我想自己从零搭建一个博客,用新学的SpringMVC,替换掉WordPress。 我想做一个网站,叫做“预言”,简单来说就是让用户发表一些对未来的猜测,并且定一个时间,然后系统到了那个时间会提醒用户确认是否预言成功 我想做一个安卓app,像三国志11一样,将所有三国的人物,历史事件,城池信息,战役,单挑,舌战都完整地汇总在里面,然后再像游戏一样,将“官渡之战”、“赤壁之战”、“夷陵之战”都像三国志11一样活灵活现地展示在地图上。 当时我觉得做网页做系统已经很没有意思了,所以我决定做机器学习,深度学习。 我想考研,因为不太满意自己的双非学历。 那时候LPL竞猜挺火的,我和朋友商量写一个统计各队伍各选手的评分的系统。当时我先用jQuery写了前端,拼命的append html标签,导致我自己写完的时候都不知道自己在写什么。 2019年春,考研没考上,但是数学二确实也让我复习了高数和线代。借助这点基础,我选了一个“手写汉字识别的研究”的题目,然后开始学习吴恩达的机器学习入门,学习深度学习Tensorflow,学习卷积神经网络等等,然后由于自己的电脑的显卡是1050ti,有点不够用,我就花钱去租专门用于训练的服务器,印象中要6块1小时。那时候为了一个好的结果,,花了不少家里的钱。 2019年春夏之交,我的论文提交给了一个不太懂机器学习的老师,老师一看就觉得我在抄别人的论文。给我的建议是,我要么一意孤行继续这样答辩,但可能延毕;要么就把手写汉字识别改成某个系统,走需求分析,UML设计,数据库设计,开发,测试,结论的框架。当时我感到了深刻的绝望,自信心几乎被击垮,支离破碎。仗着自己之前也曾一个人咬着牙实现过不少系统,我决定把这个算法的研究都丢掉,转用百度提供的手写汉字识别api,包装成一个笔记系统,手写的笔记拍个照就转储成汉字。 2019年秋,我入职某软件公司,我终于会写Vue了。在之前我总是写jQuery。我甚至以为jQuery是最好的解决方案。也许2015年前是,2015年后就不是了。 2020年,我虽然是Java开发工程师,但是做了很多很多前端的内容。我被迫花很多时间去调CSS,去抄别人的js来完成移动端的h5网页的展示效果。我感到非常的厌恶,觉得自己的能力被封锁了。领导想弄区块链,想提供API接口给其他公司使用,但现在看来根本没有任何优势,仅仅是困兽之斗。另外,为了能把智能客服的问答预料快速查找,我学习了ElasticSearch数据库的用法。为了爬取一些信息,我学习了Python开发。 2020年秋,我被“发配”北京,每天都过得很不开心,回家遥遥无期。本来说好的出差一个月,到了地方之后却要求长期驻地开发。而那套系统的代码很乱,找到机会我马上就离职了。 2021年,我来到上海,接触了游戏服务器开发。我第一次发现服务器是如此的庞大。 我学习了新的架构:Actor架构。 我受到网上的信息“蛊惑”,觉得hibernate已经过时,但在游戏这个场景里,hibernate比mybatis更适合。 我以前不理解Zookeeper到底用来做什么,他们总是只教我存和取,现在我知道可以配置系统信息; 我自学过netty,但是不理解为什么要用netty,现在我知道了,封包解包,NIO这些情况用netty确实快人一步。 我以前觉得mysql能力有限,但是在mysql前加一层缓存的话,速度就会成倍提高。还有索引和主键的配置也是有考究的,慢查询也是可以分析的。并不能武断的认为mysql能力不行。 还有一些完全没听过的,比如protobuf,akka,集群,ticker,在我之前的认知里只有json是唯一通信的结构。 2022年,我回过头来发现,我好像是从2021年之后,才真正打破“啥都会,啥都不会”的尴尬局面。在此之前,我好像什么都会: Web前端:html,css,js基础语法,jQuery的用法,简单的WebPack打包,简单的Vue Java后端:SpringMVC过渡到SpringBoot,MyBatis 移动端:基础的Android开发 数据库:使用过关系型的Oracle,mysql,也使用过非关系型的mongodb和ElasticSearch Python:爬虫相关 服务器:只会简单的java -jar部署,netstat -nlp看端口,然后给我所有的电脑都装好双系统。 又啥都不会: Web前端不会最有技术含量的React Java后端不会hibernate,HashMap的底层逻辑,Java虚拟机的垃圾回收机制都是用背的。 移动端:不会用kotlin写安卓,也不知道最新的安卓有什么内容 数据库:从没有仔细学习mysql背后的存储优化原理,也没有上手实操过 服务器:以为服务器只是java -jar,而没有研究过docker,zookeeper,jenkins等更优秀的工具和中间件。装双系统也就装的那一刻用一下,真要做事还是用虚拟机多开…… 没有研究过消息队列。 总的来说就是,都是浅尝辄止。当跑完hello world,把环境搭起来的时候,我就觉得自己行了,牛了,然后关掉编译器去玩了。 不过有意思的是,2021年我在10年工作经验的主程面前点开8uFTP的时候,他惊讶地问:“woc你怎么在用这个东西?太老了这个,可以换一下Filezila”。而我会心一笑。后来团队发生了变动,换了个新主程。在闲暇时间我和他攀谈“网页三剑客”,向他展示我2015年就搭建起来的博客,和他开玩笑说,我和他是同一个软件开发时代的人,我是Young OG! 但也仅此而已了。也许我真的是Young OG,但是我总是缺了点什么很关键的东西。以前我觉得自己不懂坚持,总是半途而废,而现在,在上海工作了一年多之后,我发现我的能力并没有任何问题,我的问题在于不知道怎么将工作切分,不知道怎么保存自己的体力,不知道自己的目标是什么。 不管是写LeetCode,写项目,还是研究某个新框架,都需要安排时间,都需要统筹自己拥有的资源。特别是写项目,写项目有一点像写小说。只有将小说里每个章节都完成并且有所关联(长篇小说),才能是一部优秀的小说。长篇小说家也不是一朝一夕能写完一部大作,我们软件开发人员自然也不能一朝一夕写好一个系统,更何况我们还需要测试自己的代码。 所以我们需要对任务进行切分。 任务切分的方法和意义 最近两三个月,在工作中,我开始使用画图工具来画一些流程图或者架构图。这些图不是标准的UML图,但是是我自己能看懂的图。 简单说一下本次系统做了什么。 我平常会看LPL比赛,尤其是季后赛或者国际赛事。这些赛事一般是BO5(五局三胜制),一局比赛一般30分钟,加上休息的10分钟,如果打满5局大概4个小时多。如果中途出了点差错,还有暂停的时间。 而最好看的比赛往往不是前两三局,而是第四局或者第五局。因为如果第三局就结束这场BO5,只有可能是3-0碾压,没什么意思。而如果打到第四局,必然是2-1,这时领先方再加把劲就赢下整场比赛,而落后方已经站到悬崖边,这时候的比赛是最好看的。 但要打完3局,至少也要两个多小时了。如果我手头上有一件其他的事情要做,那么我就需要经常点开某个APP检查比赛是否到了2-1?又或者我对两队的实力很信任,我相信他们一定能打到2-2进决胜局,那我就很可能需要在三个小时内不断的抽时间查看比赛进度。这会影响我手上的事的效率。 所以,不如写一个系统,每隔好几分钟去请求一下LPL的接口,和数据库的预定比赛信息比对,如果到达了我预定的比赛比分,或者到不了我预定的比分,就发邮件提醒我。我只需要开着QQ,等着右下角提示就好了。 技术选型 回到上一节说的“造自己的轮子”和“用别人的轮子”的讨论,我平时的工作用Java进行开发,如果用SpringBoot,在路由,操作数据库逻辑,发送邮件那几块的内容,在数据库设计好了的情况下,我会写的很快。 在这个情况下,“用别人的轮子”就是用SpringBoot,“造自己的轮子”就是用某个自己没学过的语言或者框架,边做边学。 本次我选用的是Go语言和Gin,对标Java和SpringBoot。...

四月 5, 2022 · JohnathanLin

使用Python实现简单UDP Ping

套接字编程作业2:UDP ping 程序 在本实验中,您将学习使用Python进行UDP套接字编程的基础知识。您将学习如何使用UDP套接字发送和接收数据报,以及如何设置适当的套接字超时。在实验中,您将熟悉Ping应用程序及其在计算统计信息(如丢包率)中的作用。 您首先需要研究一个用Python编写的简单的ping服务器程序,并实现对应的客户端程序。这些程序提供的功能类似于现代操作系统中可用的标准ping程序功能。然而,我们的程序使用更简单的UDP协议,而不是标准互联网控制消息协议(ICMP)来进行通信。 ping协议允许客户端机器发送一个数据包到远程机器,并使远程机器将数据包返回到客户(称为回显)的操作。另外,ping协议允许主机计算它到其他机器的往返时间。 以下是Ping服务器程序的完整代码。你的任务是写出Ping客户端程序。 服务器代码 以下代码完整实现了一个ping服务器。您需要在运行客户端程序之前编译并运行此代码。而且您不需要修改此代码。 在这个服务器代码中,30%的客户端的数据包会被模拟丢失。你应该仔细研究这个代码,它将帮助你编写ping客户端。 # UDPPingerServer.py # We will need the following module to generate randomized lost packets import random from socket import * import random # Create a UDP socket # Notice the use of SOCK_DGRAM for UDP packets serverSocket = socket(AF_INET, SOCK_DGRAM) # Assign IP address and port number to socket serverSocket.bind(('', 12000)) while True: # Generate random number in the range of 0 to 10 rand = random....

十一月 13, 2021 · JohnathanLin

使用Python开发一个简单的web服务器

来自书籍《计算机网络-自顶向下方法-第6版(课本)》第120页,第二章应用层的课后题第一题。 基础题目 题目:在这个编程作业中,你将用Python语言开发一个简单的Web服务器,它仅能处理一个请求。具体而言,你的Web服务器将:(1)当一个客户(浏览器)联系时创建一个连接套接字;(2)从这个连接接收HTTP请求;(3)解释该请求以确定所请求的特定文件;(4)从服务器的文件系统获得请求的文件;(5)创建一个由请求的文件组成的HTTP响应报文,报文前面有首部行;(6)经TCP连接向请求的浏览器发送响应。如果浏览器请求一个在该服务器中不存在的文件,服务器应当返回一个“404 Not Found”差错报文。 参考或者说学习了github上moranzcw的仓库:Computer-Networking-A-Top-Down-Approach-NOTES的内容,完成了这道题。 代码https://github.com/moranzcw如下: # import socket module from socket import * serverSocket = socket(AF_INET, SOCK_STREAM) # Prepare a sever socket # Fill in start # 定义ip和端口号,然后bind绑定socket host = '' port = 6789 serverSocket.bind((host, port)) serverSocket.listen(1) # Fill in end while True: # Establish the connection print('Ready to serve...') connectionSocket, addr = serverSocket.accept() # Fill in start #Fill in end try: message = connectionSocket.recv(1024) # Fill in start #Fill in end filename = message....

十一月 7, 2021 · JohnathanLin

Kotlin手动实现一个最简单的哈希表

参考的是《数据结构(C语言版)》上256页左右的哈希表的介绍,用了最简单的直接寻址法 + 链地址法。 用的是Kotlin。 package main.kotlin /** * 手动实现简单的hash表 * 简单的数组 +链表 (无红黑树) * 要求哈希函数可配置(被自我否决,太复杂了啦),这次就先做比较简单的 直接定址法 + 链地址法 * * @Date 2021-10-16. * @author Johnathan Lin */ data class Node( val key: Int, //key var value: Int, //value var next: Node? //如果hash值重复了,则用头插法放进去 ) fun main() { // hash表,这次可为空 val size = 100 val hashArr: Array<Node?> = Array(size) { null } //插入 假设插入key 8 value 24 println("插入key 8 value 24") set(hashArr, size, 8, 24) { k, s -> k % s } println("插入key 108 value 32") set(hashArr, size, 108, 32) { k, s -> k % s } var v = get(hashArr, size, 108) { k, s -> k % s } println("读取key为108: $v") println("删除key 108") remove(hashArr, size, 108) { k, s -> k % s } v = get(hashArr, size, 108) { k, s -> k % s } println("读取key为108: $v") v = get(hashArr, size, 8) { k, s -> k % s } println("读取key为8: $v") } /** * @param hashFunc 哈希函数 param1:key param2:size */ fun get(hashArr: Array<Node?...

十月 16, 2021 · JohnathanLin

Kotlin实现二叉堆、大顶堆、优先级队列

参考了 https://www.bilibili.com/video/BV11t4y1r79L https://blog.csdn.net/qq_19782019/article/details/78301832 他们已经写的足够好了。我最近都在用Kotlin编程开发,我尝试用Kotlin实现了大顶堆,并且作为手动实现的优先级队列,通过了Leetcode 347。 package main.kotlin /** * 二叉堆(大顶堆、优先级队列) * * @Date 2021-10-14. * @author Johnathan Lin */ data class MaxHeap<T : Comparable<T>>( val arr: Array<T>, var size: Int ) { //将无序的数组构建一个二叉堆 fun makeHeap() { for (i in (size - 1) downTo 0) { heapDown(i) } } //向二叉堆中加入元素 fun addItem(value: T) { //TODO 添加的边界还未考虑 val newIndex = size++ arr[newIndex] = value heapUp(newIndex) } //移除堆顶元素 fun removeItem(): T { //TODO 删除的边界还未考虑 val removeValue = arr[0] val lastValue = arr[size - 1] // println("lastValue:$lastValue") arr[0] = lastValue size-- heapDown(0) return removeValue } fun printHeap() { for (i in 0....

十月 14, 2021 · JohnathanLin