2020 年注定是充满着悲情与不平凡的一年,如果世界上有像游戏那样的「重新游戏」功能,那我相信绝大多数人都会想重启 2020 年。
年底将至,我回望从去年毕业到现在个人的成长,从一个最初代码都敲不利索且毛发旺盛的大学生,到现在毛发依旧旺盛只是代码敲得更利索的 Coder 一年的时间里,我没想到自己能够有这么快的成长速度。如果能重来,那么我应该会比现在有着更明确的成长方向,也会少走不少弯路。
在工作中我的定位是什么呢?大数据分析师、后端工程师、运维工程师、数据挖掘工程师等⋯⋯通常来说,只要是体量较大的公司,往往分工会十分明确,每个人都只要「拧好自己的螺丝」即可;但在新开辟的部门或者小公司里,可能就会像我一样有着多种角色或身份,职能并不明确且模糊。
但我的这些角色身份都有一个共同之处,那就是跟代码息息相关。
所以为了行文方便,我也会以「程序员」这既通用又充满刻板印象的角色为视角说一说我是如何成为一个合格的初级程序员。
Hello, World
提起「程序员」一词,你会想到什么?
格子衫?随身带着傻大黑电脑的理工男?不善言辞但又思维敏捷?直男般的说话方式?
可能我对于程序员这一职业存在的刻板印象描述并不完全,但这些话术确实是和程序员人群的职业特征在某些方面存在着重合或者相似性。
我在毕业之前,从来也不曾想过有天我会成为一名程序员。那时的我虽然只是对敲代码感兴趣,但做的通常都是处理数据或做和数据分析有关的事情。程序员一词在当时的我看来就是像黑客那样有着高超技术的一类人或职业,并且想要成为程序员的基础就是必须得是计算机类专业这样科班出身才行。
而我只是一名社科专业的文科生,这种看着和计算机类专业相比就是南辕北辙的学科背景似乎让我从一开始好像就和程序员的这一词沾不上边。
但事实上,你的学科背景并不会限制你成为一名程序员。(当然如果你有计算机基础或是科班出身那将会是更好的)
举个最著名的例子就是,JavaScript 领域里 Vue 这一知名框架的作者尤雨溪(知乎:尤雨溪)本人就不是计算机专业出身的,根据 freeCodeCamp 的这篇 访谈 可以得知,尤大本科读的艺术史,研究生阶段读的设计与技术(the Master of Fine Arts for Design and Technology)。但他创造的这一框架却在国内外都被众多公司使用(包括少数派)。
所以至今你也会看到很多非计算机专业的非科班程序员的身影,在成为程序员之前他们的学科背景可能都是来自于生物信息、通信或是像我一样的社会科学等。
但也并不意味着所有人都能成为一名程序员,抛开代码、编程语言来说,想成为程序员应该具备最基础的东西是:
- 逻辑。无论是你写的代码,还是跟产品经理或者客户解释,都离不开逻辑。这种逻辑并不是指逻辑学这门课程里的逻辑,而是指把一件事情或东西讲清楚、从头到尾理顺的能力,这不仅体现在你的口头表达中,还体现在你的代码中。
- 找/解决问题的能力。代码是人写的,但只要是人写的必然会存在 Bug 或是问题。一个优秀的程序员或者工程师不在于它会用什么框架、会哪些技巧,而是能够在问题发生时快速定位到问题、甚至解决问题。所以不要以为国内的搜索引擎能帮到你所有,你需要的是「另外一个世界」;同时还能从源码或者别人的给出的代码中,找出或者创造出自己的解决方案。
- 手动实践的能力。我曾跟同事聊天时就说了我一个文科生对于程序员这门职业的一个直观感受:手艺人。和木匠、厨师、演员等这类职业一样,程序员也都需要不断地练习、实践。从来没有谁能够说自己一行代码不写就可以构造出一个完美的程序。无论是在工作还是学习时,程序员都要能够多动手实践、代码敲一敲看看是否能够真正运行一个样例程序,并对这个程序进行二次或多次加工、改造。
非科班的程序员相比于科班程序员来说的优势在于多了跨领域、跨专业的知识经验,但劣势就在于计算机基础、编程和技术等方面的不足。
所以一旦成为了非科班的程序员,就意味着我们是属于「先上车后补票」的那批人,那么我们能做的就只有:恶补,疯狂地补课计算机相关的东西。这也预示我们会完全进入到了一个由数字和代码所构成的领域。
欢迎来到程序世界:Hello, World!
熟能生巧:先做 API 工程师和调包侠
写代码这件事和烹饪其实很类似,在这件事上我这个经常做饭的厨子深有感触。
我想起在我刚学着做饭时,无论是使用菜刀切菜还是起火掌勺无一不是蹑手蹑脚,而随着做饭的次数的增多,我也由最初蹑手蹑脚的模样转变成为一个熟练、信手拈来的「干饭人」。不管是刀具、锅铲还是调味料,都能被我游刃有余地使用。
因此一个合格的厨师如果连刀具、锅铲和调味料都用不好,那这个人还能算是个合格的厨师吗?所以答案是显而易见的。
所以,在学习编程的过程中,根据基础的教程或课程入门之后并不代表着我们就是一个初级且合格的程序员了。我们首先就应该成为一个 API 工程师和调包侠。
所谓的 API(Application Programming Interface)就是你所使用的编程语言里都内置了哪些基本的函数、方法;而调包(或库,Library)即调用别人写好的包或库来编程之意。如果你有经常看各种初级的编程书籍或者教程就会发现,当中的大部分内容不是在讲原理和实现,而是在讲如何用这些基本的 API 或者常用库!
因为无论是初级、中级还是高级的工程师或程序员,对于各种基本的 API 和常用的内置库或第三方库的使用都是信手拈来,这些东西就好比是刀具、锅铲和调味料,它是我们构建一个程序的起点。能将这些基本的 API 和库用熟练或操作了,那就说明用来创造的工具已经玩熟了,剩下的就需要我们在实际项目中像乐高积木一样发挥想象力来将它们组合在一块。
在知乎上有看到别人回答如何入门或者学习一门编程语言之类的问题时,一上来就抛出各种计算机基础、编译原理、内存管理(除 C 或 C++ 之外)时,相信没有多少人能够真正实践下去(这些是内功,但不适用先上车后补票的非科班人群)。这就好比说你做菜时别人先跟你讲各种菜系的演进、配方的配比一样让人心神乏味。
因此无论是学习一门编程语言也好,还是想成为一个合格的工程师、程序员也好,都不要忽视对基本 API 和常用的内置库或第三方库熟悉或使用。因为这部分没有什么难度,这就和高考里面的「送分题」一样,无论是学霸还是学渣都通过死记硬背的方式拿到分数,如果别人都做到了自己却没做到,那么自己就应该好好反思一下了。
对于这些东西我们不需要死记硬背。一方面,大部分东西会因为我们经常使用而形成肌肉记忆(换而言之就算记住了一旦用得少了也就忘掉了);另一方面,我们可以通过像 Dash (只有 macOS 版本,Windows 可以用 Zeal)这样的文档查阅 APP 或是直接到官方文档中去查阅相关用法即可。
代码审美:该开始学会「捯饬」自己的代码了!
「捯饬」(dáo chi)一词属于北方地区的用词,大意就是修饰、梳理、整理之意。
当学完基本的 API 时,就应该说明你已经是个会用工具做事的家伙了,但不意味着你的代码是好的代码。合格的程序员,甚至是高级的程序员或工程师都更容易写出好的代码。
说到好的代码我们可能脑海里已经浮现出那么几个描述:
- 能够稳定运行
- 高级、简洁的处理技巧
- 强大且有着丰富的功能
- ⋯⋯
这些描述确实是属于好的代码的范畴,但这却不是初级程序员首当其冲要考虑的。首当其冲的是什么?
格式化你的代码,遵循编程规范。
人是感官动物,视觉是我们能够快速获取信息的第一途径。因此伴随着这样的生理特性人也在千百年进化中衍生出了对「审美」的要求。
在对程序员的众多刻板印象中,「缺乏审美」可能也是其中板上钉钉的一条。
虽然这指的是程序员的穿着打扮或是设计的东西,但我需要为程序员正名的是,程序员也是有审美的。除了将自己使用的 IDE(集成开发环境 Integrated Development Environment)用不同主题装扮得赏心悦目之外,还能体现审美的地方在于代码本身。
对一个程序员来说,衡量一段代码是否是好代码,映入眼帘的第一眼就是看这段代码是否规范、工整。这也和我们每个人作为消费者和用户来说是一致的,如果有一件衣服奇丑无比,但它却是用料上乘,作为消费者来说我们单从外观上来说是无法将其和「好」字挂钩的,更不会有买它的欲望。
就好比下面这两段代码(代码源于 black 格式化工具官方文档):
# Bad
def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, 'w') as f:
...
# Good
def very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = True,
debug: bool = False,
):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
相比于第一段代码的函数定义那种所有东西都堆叠在一起的风格来说,第二种会更受程序员喜欢,因为它有所换行、缩进,可读性强且排版整齐。
所谓的格式化代码就是让你的代码以整齐规范的形式呈现,因为代码是人写的,也是需要人来阅读的。形象理解格式化代码就是我们在做试卷时的老师所强调的「卷面整洁,写字工整」,上面的代码示例从外观就能很好诠释。
而遵循编程规范就是遵照某个语言的代码形式和规范来写代码,通常这个规范不同的编程语言会有不同的规范(如 Python 的 PEP8、谷歌的《Java Style Guide》等),这些规范可能是程序语言社区的官方提案,也可能是某个公司或大厂自己推行的(如阿里巴巴的《Java 开发手册》)。
因为每个人的编码方式是风格迥异的,如果不对编码的方式进行规范和统一,那么有可能会让程序出现问题或是一个人离职后接手的人很难读懂这个人遗留的代码,同时也可能增加多人协作的成本。所以这些规范出现的目的在于对使用该语言的程序员进行约束,希望程序员能够共同遵守。
形象理解编程规范就和我们语文老师教的「标点符号」规范一样:对话内容时要用引号、一句话结尾要使用句号、提到书名、作品名等要使用书名号等。编程规范同样也是会有很多细节,包括变量的大小写命名、代码缩进、注释说明等。
当然这些东西都是和鸡毛蒜皮一样的琐碎的部分,通常来说我们都不需要去死记硬背,更多时候我们可以通过别人编写或开发好的格式化工具就能将我们的代码排布成即符合编程规范又规范工整的漂亮代码(如:Go 语言的 gofmt、Python 的 autopep8、black 等)
可尽管有自动化工具来帮我们做这些事,可至今在我的工作中,经常性碰到协作的同事不遵循规范的代码,甚至有的同事根本就不知道还有能够格式化代码这一回事。
有着良好的编码规范或者编程风格不一定能保证你能找到多好的工作,但一定能保证的是这会给人留有很好的第一印象,也是你能够写出好代码的第一步!甚至在一些招聘信息当中我们可能还会看到这么一条「有着良好的编码规范或编程风格」的要求描述,如果你养成了这些良好的编程习惯,没准可能会是加分项(至少在试用期我考核别人的时候会是这样)。
模仿游戏:框架、Demo 与模仿
学习基本的 API 和调包调库这些的基础已经达到能找工作地步的 30% 了,剩下的 70% 中:
- 40% 是需要能够掌握并熟练运用各种框架
- 20% 是涉及到其它技能,如能够操作 Linux 系统、有 Java 基础或前端基础等
- 5% 是涉及到你个人的项目经验(如果是刚毕业那就看校园经历)
- 5% 是关于你个人在面试过程中所表现的一些语言表达能力、反应能力、智力测试表现等
以上的划分不一定准确,具体还要看不同招聘岗位的具体要求。但有一个是能确定的就是,招程序员或者工程师就是招一个能来干活的人。说白了就是你的一些基础知识过关并能将框架玩熟练了,那你就是一个合适干活的打工人任选了。
所谓的框架(framework)其实也就是别的优秀的程序员设计或编写的 API 和库的集合。这形象的理解就是等价于我们写一篇文章的「开头-内容-结尾」的三段式结构,当中每个部分都有很多可供你使用、玩耍或支撑起整篇文章的东西,或者你也可以更进一步理解为「好词好句的文章模板」。
每个框架的诞生都是为了解放一些存在于程序员项目中的重复性代码或痛点,也正是因为有了框架的存在才能让干活的程序员快速投入到实际需求的开发中,而不是将工时都耗在底层实现或封装中。有效做到敏捷开发、响应式开发。
框架为你预先配置了一部分东西,也留有让你能够自由发挥的部分。在整个框架的架子下发挥你作为程序员应有的想象力和对实际需求的理解,最后呈现一个具有使用价值的产品(乐高玩具)。
比如那些用 Python 来进行一些 Web 开发的程序员,必然是会经过对 Django、Flask、Tornado 这类知名的框架学习,否则即便你只懂基本的 API 调用和调包调库,投入到真正的实际开发中也很难如鱼得水。
同理,做 Java Web 开发的也必须要了解 Spring 全家桶框架、做前端开发必须要了解并能熟练使用 React、Vue 和 Angular 这三大框架当中的一个或多个才行。
每个框架都会有官方对应的配套文档(即说明书,告诉你怎么用),我们能通过官方文档以及给出的示例进行初步的学习。
但学习的最好方式以及途径就是「学以致用」,所以除了官方文档给出的 Demo 以外,我们还需要能够自己做一个结合自己所学的 Demo,比如自己实现了一套 Web 服务的接口(CURD,即增删改查)又或是模仿框架示例做出了一个类似的网页展示等等。
模仿是一种很好的学习方式,在模仿的过程中我们可能思维会受到已有文档或示例的影响固化了,但这对于一个想要成为合格或初级的程序员或工程师来说这并不重要。重要的是我们能在「依葫芦画瓢」的过程中把程序运行起来,说明我们已经能够保持和框架一致的步调了;剩下能够让我们提升的地方不在于官方文档,而是在于使用框架时出现的各种问题或 Bug,能够将它们及时定位并解决才标志着我们才是真正地掌握了框架。
当我们对框架的运用到了一定程度或者会当中的实现原理有了深刻的认识时,就会发现框架不是万能的,也不是完美的;在使用的过程中框架存在的问题会逐渐暴露出来,新的痛点或者问题也会随之而来。
这时有思想、有能力的工程师就会想方设法地重新打造一套框架来解决这些问题或痛点,于是乎「造轮子」的行为就产生了。这里的轮子不是指车辆上的车轮,而是可以理解为像框架、编程语言这样类似东西的一个统称。
程序员们对于造轮子这件事本身也是乐此不疲。轮子造得好,那将会吸引其它志同道合的程序员们来一起共同建设,甚至久而久之在被多人使用的情况下形成自己的社区;轮子造的不好也没关系,在造轮子的这个过程中也提高自己的编程、思维、架构等能力。
但无论是学习框架还是造轮子,值得肯定的是我们都是在模仿别人的游戏中成长着,我们都有着美好的未来。
晦涩难懂:元编程
对于所有「先上车后补票」的非科班程序员来说,元编程(Metaprogramming)可以说是最难的一部分。
如果说学会使用内置的 API 和常用的内置库或第三方库就好比是学会武侠小说里的武功招式,那么了解并学习元编程的东西就好比是武侠小说里的内功。有了内功你不仅学习武功招式会更加得心应手,并且用起来还能威力大增。
但读过武侠小说的都知道学一招一式易,但学内功难。一方面,元编程的知识纯粹就是和计算机基础或者编程语言 API 底层实现原理相关的;另一方面是元编程涉及到的东西偏向于抽象且晦涩,如果是非理科专业的文科生在理解上或许会更加痛苦。
在新手入门初期,不涉及元编程的原因就和我们在上高中时的扩展题一样,只需要了解但并不需要深入,因为当时的那个时期或者知识水平下就很少用得到。但如果想要提高或者向更高的层次发展,那么元编程的东西,甚至前面提到的计算机基础、编译原理、内存原理等等特别多计算机的内容都需要回过头来真正的「补课」。
元编程的东西其实特别多,但有一个东西是特别重要,也是最为基础的部分,那就是面向对象编程的 OOP(Object Oriented Programming)思想。这久负盛名的且影响深远的编程范式之一,理解了这一思想去学习不同的编程语言就会发现大部分内容都是大同小异的,并且在类和对象的机制上也都是异曲同工。
毫不夸张的说,有很大一批程序员对于 OOP 的思想甚至就根本没有一点认识,在编程的时候甚至都不会通过类来封装方法并创建对象,而是不断重复制造多个写法相似的函数或是共享同一个变量。
在最初学习 Python 生态里的 Pandas 这一知名的第三方库时,我是经常性地碰上一个 API 就会单独学习它的用法,但却没有搞清楚它实质上是对象下的一个方法。直到我真的对 OOP 思想有着思考并实践后我才发现学习 Pandas 库真的十分容易,因为我们只需要知道我们此时正操作的是 DataFrame
、Series
或是 Index
、GroupBy
等对象,就调用这个对象下所具有的方法就足够了。于是就在那一刹时间里,我脑子里感受到一种豁然开朗的感觉,这种感觉就和在中学时弄懂一道数学题的解题原理一样类似。
所以自从那之后,无论是在学习框架或看框架配套的文档,还是阅读相关源码时,我发现最初那种抗拒的心理已经不能将我束缚,我也更容易去上手、使用一门框架及对应的方法。然而一切的一切都要归功于我真正花了有效的时间和精力对 OOP 思想进行理解。
当然,理解的一个事物或者原理最好的方式就是实践,Python 的 OOP 语法并不纯粹,所以我就开始接触 Java 这一门年龄跟我一样大的编程语言,试图从中获得更好的理解。Java 比 Python 有着更为庞大的 OOP 体系,并且由于这么多年的积累,元编程相关的内容也会比 Python 更为丰富。
当我学习一段时间之后再回来看 Python 里关于 OOP 的东西时会发现,似乎某些困扰自己思维的东西又被打通了,这时我才真正体会到元编程真正的魅力所在。
推倒重来:重构与模块化
正如我在本文开头所提到的:如果世界上有像游戏那样的「重新游戏」功能,那我相信绝大多数人都会想重启 2020 年。
我所说的「重新游戏」的功能其实是现实世界并不存在,但在虚拟世界中是实实在在可以存在的。当我们在玩游戏保存到某一进度时,如果通关难度较高,那么存档之后我们可能有不少时候都是在「重新游戏」。
代码也不是现实世界里的东西,它也能够重来,简单来说就是重新写一遍。这样的行为有一个更为标准的名词——重构(restructure)
要知道,每个程序员并不是一开始就能写出很好的代码(这里的代码相比于我们上一节来说延伸到了描述那部分)。
不同时间节点、不同阶段同一个人可能对相同的一段代码会有不同的写法。所以如果在很早以前我们写了一版差劲到自己都用不下去(这里已经连审美都抛弃了)的代码,那么随着对经验的积累和对新知识的学习,当我们将自己上一阶段的代码全部重新组织、编写时,重构就已经开始了。
重构从哲学的角度上来说是新事物代替旧事物的过程,而不是旧事物的螺旋上升。旧代码中的思想、写法都会被重构的代码所借鉴或者使用,最终达到对早期烂代码的优化一种最优解。
在写这篇文章时,我也一直在进行着我去年接手的负责项目(基于 Flask 的算法后端接口)的重构工作。正如我前面所说的那样,去年无论是经验、编程技巧还是功能设计上都十分蹩脚的我所写的代码,现在看已经是差劲到自己都用不下去了,所以趁着整个项目的代码体量还没有过于膨胀时,并且项目技术上由我一个人把控时(是的你没看错,因为没有技术的 Leader 或技术负责人)我果断选择了「踩刹车」并着手第二版的重构工作。
重构这件事情如果是在大厂或者大公司,通常不会轻易开展,因为在大厂或者大公司一个项目的代码需要通过层层把关才会最终上线,还有一个原因就是稳定。(画外音:只要能赚钱能跑不崩,我管你们这帮干活程序员写的代码有多烂)。
但是在小公司或者新开辟的部门里,在缺少技术负责人的情况下,你一个人就是一个团队,甚至你就是技术负责人(画外音:上线出了问题你就别想跑)
当然这重构的第二版还处于开发阶段,这也就意味着第一版的祖传代码将会在其它同事以及已经上线的生产环境中继续运行着,并被有着不同编码风格以及水平参差不齐的同事们不断「添砖加瓦」(这里指贬义)。
但重构还是很有意义的,在重构的过程中你会不由自主地避开或优化掉以前所遇到的「坑」,并且会把你积累下来的经验和所学知识发挥得淋漓尽致。在这一过程中你会开始不由自主的脱离代码层面以一种架构师(理解为程序版的设计师即可)的视角去审视自己在每个功能上的代码设计、甚至为以后如何扩展埋下预留位置。
因此在这个过程中 模块化(Modularization) 的思想就很重要了,模块化顾名思义就是将每个功能或者代码设置成一个模块,通过不同模块的组合来实现一个完整的产品,之后只需要每次对不同的模块进行局部小修小补即可实现功能的更新迭代,而不是伤筋动骨。
倘若你有看过别人的开源项目或者源码,就会发现大多数都是以模块化的方式来设计。大模块可以由多个小模块构成,而小模块又能接着由多个小部分组成,这在我看来其实就是「大事化小小事化了」思想的一种诠释。
用一句更为专业的设计模式的话来概括就是:低耦合,高内聚。
参与亲手重构项目的我对于重构这件事真的是乐此不疲。原因前面也说了,因为受限于不同时间节点和阶段个人的经验与知识,所以此时的自己比彼时的自己的水平是只增不减,于是乎新一轮的重构循环似乎又开始了,甚至都想替其它同事重构(画外音:给你们看看什么叫做优雅、漂亮的代码)⋯⋯
- 参考阅读:《重构:改善既有代码的设计》
追根溯源:源码里有你想知道的一切
Talk is cheap, show me the code.(少跟我扯淡,给我看代码)——Linus Torvalds
正如 Linus Torvalds(Linux、Git 之父)这一句经常被程序员奉为圭臬或是引用的话所透露的思想一样,跟程序员交流最好的方式就是通过代码。
尽管人们常说在内容呈现上「字不如表,表不如图」,但对于程序员来说字、表、图都远不如代码来得熟悉。
所以很多时候我在跟程序员同事沟通时,往往都会通过代码,甚至是伪代码的形式能够让他们能了解我的意图或者让我搞懂他们需要哪些东西是我提供的。但如果是不懂技术产品经理或者是项目经理,在和程序员沟通时往往需要花费大量的篇幅去让他们尽量了解到每个部分或者功能模块的逻辑是什么。
这个过程通过口头交流或者会议传达的时间可能需要 10 分钟及以上,但我通过代码的形式只需要 5 分钟甚至是更少。
所以对于程序员间的交流来说,源码毫无疑问是一种更为直接且明了的方式。通过源码及当中的一些注释或写法,我们或多或少能够搞清楚写这段代码的程序员所要表现的是什么或是用来做什么。
作为一个合格的程序员,也必须要具备读源码的本事。读源码本质上就是相当于读别人写的文章一样,从逐字逐句中获取到别人文章所要表达的意思、有哪些伏笔、有哪些情节等⋯⋯这经常性地发生在我们在调用第三方库或想了解别人 API 的底层实现时才会去读源码。
可以说读源码的重要性和难度都是仅次于元编程的。源码可能不会像元编程那样枯燥,但它混杂了一个或多个程序员模块化或架构的思想、代码技巧、API 的代码实现、用例思路等等多方面的东西。对于一个小项目来说,它的源码可能不会很复杂,模块就那么些,可能一天之内就能了解到大概的实现;但对于一个复杂且庞大的项目来说,可能底层的源码就好比是一本大部头,你可能需要花费数日甚至更久的时间才能去完全摸透。
读源码的目的不是为了读而读,而是为了从中学到或搞懂别人是如何实现而读。在这里我们需要借助 IDE 来实现不同模块之间的跳转,如有必要还要自己进行记录。源码的益处你无法从不同的教程或课程中体会,而是当你开始一个新的项目或者在实际工作中才会发现你所读过的源码里的精华都会内化成你自己的东西,最终都体现在你所写过代码的字里行间。
这里我还是以我学习 Pandas 为例,通过 Pandas 库的底层源码并结合所学的 OOP 思想,我才明白在 Pandas 实现链式调用(Method Chaining)的基本思路:每个 DataFrame 或 Series 对象的方法在调用时每次返回的是一个 DataFrame 或 Series 类对象。正因为方法属于对象的一部分,而每次返回的又是对象,所以就能循环往复地调用所有从属对象的方法。
# Not Method Chaining
on_hill = went_up(jack_jill, 'hill')
with_water = fetch(on_hill, 'water')
fallen = fell_down(with_water, 'jack')
broken = broke(fallen, 'jack')
after = tmple_after(broken, 'jill')
# Method Chaining
jack_jill = JackAndJill()
(jack_jill.went_up('hill')
.fetch('water')
.fell_down('jack')
.broke('crown')
.tumble_after('jill')
)
Pandas 的官方文档只告诉你调用之后会返回 DataFrame 或 Series 对象,但却没告诉你为什么能够这样循环往复的像链子一样将所有方法串在一起,所以我们只能从源码上找答案,也正是因为我带着这个疑问去翻看了源码之后才真正的贯通。
这一些内容如果不是我有分享,或许和我协作的一些 Python 程序员同事永远也理解不了我的代码里为什么会这么写,因为他们本身没有尝试过去了解当中的原理以及阅读过底层源码。
因此,当你已经能熟练使用大部分 API、库和框架之后,就可以从源码开始了你的「内功」修炼了。能够将一个库或框架的源码聊熟于胸,不仅能够帮助你在程序出现 Bug 时迅速定位并解决问题,减少加班缓解秃头;也可能会成为你在将来找工作或跳槽的面试中赢得面试官的青睐(在一些招聘中如果有熟悉与工作相关的库或框架的源码,会是一个加分项)
n→1:知识经验的碎片化与体系化
在自我恶补的过程中能够获取到很多相关的知识或内容是一件大有裨益的事情,因为这已经是不同于你在校园课堂里老师主动灌输给你知识而你被动吸收的方式,而是一种自我驱动、自我主动去学习的过程。在这个过程中学习的效率往往都会比你在课堂上学习的效率高出一大截。
在我自行补课的过程中,我除了会收藏关于各种同我感兴趣的或疑问的点有关的内容;并且在每次每个阶段性学习完后我会再回过头来重新梳理、回顾所学的东西,最后构建出自己的对这一部分内容的系统性框架,让我能够更好地透视每个主要的知识点。
做一个碎片化知识的收集者
每个人或多或少在学习的时候都有一点「收集癖」,各种相关的资源都会丢到收藏夹里,我也不例外。虽然我现在很少用 Notion 来做相关的笔记(毕竟编辑 Markdown 没有我直接在 VS Code 里来的舒服),但最初或者是现在我通常都会用它来进行学习资源的收集与归类。
但「收藏了」并不意味着「学会了」。所以我在闲暇时候总会自己翻一翻或者是认真地将资源收藏的 Github 项目或者相应教程认认真真地看一遍、学一遍,虽然有「假勤奋」之嫌,但我感谢我从去年到现在一直都在干这样假勤奋的事情,让我在一定程度上做到了量的积累。
与此同时,Trello 也曾是我收集碎片化知识的好帮手,只是因为由于网络原因,它的访问一直都不太稳定给我造成困扰。但它核心的「看板」功能让我养成了将问题拆分或者简单化后再发散、处理并完成的这么一个习惯。这其实在某些方面与 GTD 的思想不谋而合。
我在学习 Pandas 的相关用法时,会将自己遇上的自己不懂的 API 都放在 Trello 中,配合其 Markdown 的语法会对该 API 方法写下用法以及自己的理解或发散。
通过 Notion 和 Trello 的配合,实现了对一些碎片化知识的收集与整合,在某些时刻它们甚至会成为我技术文档或文章中的某些素材。
碎片化的知识虽然有写下自己的想法或是别人的观点和回答,但仍然需要被进一步整理。碎片化的知识也会因不断地整理最终升华到系统化的高度。
Notion 和 Trello 在这个过程中不是必须的,你可以换成任何你熟悉的 APP 或者是工具。虽然有些时刻我打算通过不同的工具或软件来丰富自己的学习工作流,但实际上当我游离于各种工具或软件间进行切换时,它们所给我带来的收益并不是 1+1>2 的,甚至一度是「正正得负」的效果。
所以至今我很少像少数派其它的作者那样写关于不同软件或者 APP 使用体验或者介绍,因为我一直都希望能保持一种纯粹、极简的学习过程,让自己专注于要学的东西本身,而不是如何通过各种软件来留下自己学习的证明。
系统化知识
私以为,一个人如果能够系统总结自己所学的东西,并能将其很快地以思维导图形式铺开(当然也不一定是实物,脑海里的视图也算),那么能说明这个人的学习和总结能力肯定不差。然而,在工作了这一年多的时间里,我发现身边的大多数同事都不具备系统化自己知识的能力,似乎对自己所学的东西都了解那么一二,但是能够很清晰、渐进地将知识体系化的人只能说是寥寥无几。
我在五月份到国庆前这段出差驻点的时间里,因为和不同技术栈的同事一起共事,当然也就对前后端的技术有了一些兴趣,于是问了一个前端同事,想了解一下 JavaScript 怎么入门。这位同事只是零碎的跟我说了一先学 JavaScript 或者 TypeScript,再学一下 HTML、CSS 和 Vue 基本上就差不多了。
当然,我知道这种问题就和论文题目一上来就取《论 XXX》这类题目一样宏大,同事的回答也是泛泛而谈。但是当我总结自己这一年多以来的成长历程,我反而会觉得,像这种基础性的内容或者知识,对于一个有经验的老鸟来说应该是特别明晰且透彻的。
虽然我自己本身并非科班出身,但我觉得我自己具备了这种系统化知识和所学内容的能力。
《Python 自学手册》教程的提纲我只用了 5 分钟左右就在纸上迅速列出,然后再用 5 分钟左右的时间通过 Markdown 对章节进行了排布。我想这与我在入门 Python 阶段所看的书籍知识的总结、反思和对内容安排的疑问有着密不可分的联系。
如果现在有人问我如何入门 Python,那我肯定是很快啊,啪一下就能一口气列出从初级到中级的学习路线,毕竟我早已是个有备而来的初级工程师了。
好记性不如烂笔头
系统化的过程离不开前面碎片化收集的前提。当然对于程序员来说,积累自己的所学的笔记的最好方式就是将其以文档或博客的形式记录下来。这样在后续整理的过程中体系化、系统化的学习路线就慢慢浮现在眼前了。
所以目前只要是关于写文章或进行笔记记录的内容,我都会毫不犹豫的以 Markdown 形式进行记录,并最后保存在我的 Github 私人仓库中。
这是我学习的见证,也是自己在未来用以写作的素材来源。
技术栈:点亮其它技能树
我曾在少数派的《Python 自学手册》教程里的序言中提到的那样,单单只会一门 Python 并不能让你找到工作。
虽然现在的社会是一个分工极其明确的社会,但只要你随便打开一个招聘信息都会发现上面列出来的招聘要求可不单一,往往希望你具有多种复合型技能,换而言之要的却是复合型人才。这在程序员这一行业里尤为明显,因为如果你只懂一种技术,那你就是我们中学时候里所说的偏科生,而大家都知道咱们考试看的却是综合成绩。所以从人力成本来说,招聘一个人当然希望这个人能会很多,这样对于特定技能的用人需求就会减少,从而也减少了人力成本。
因此对于那些非科班的程序员,并不会因为你不是科班出身而降低对你的要求,甚至要求你和其它科班出身的程序员有一样的知识面或技能。所以咱们除了恶补的基础知识外,都还应该拓宽自己的技术栈。
从去年到今年一年的时间里,我学过的东西大致有:
- Linux 系统操作、Shell
- 数据科学相关:数据分析、机器学习等
- 数据库相关:MySQL / PostgreSQL / MongoDB 等数据库知识及操作
- Python 及框架 Flask、Django、FastAPI 等
- 爬虫
- 其它其它编程语言:Go、Java、R 语言
- 前端:JavaScript、HTML5、CSS3
- 其它技能:Vim、Git
- ⋯⋯
从中可以看到我学的东西并不少,甚至又多又杂。一方面是因为我存在着焦虑,害怕我确实不能在工作做到游刃有余而要每天辛苦的加班;另一方面是我对这些同是计算机领域内的知识有着浓厚的兴趣,并且随着学习的深入以及对元编程的内容的理解和领悟,让我能更加快速地上手一门语言(触类旁通)。
而且在出差的时间里,除了要应付来自于客户的需求之外还要面临着环境部署、Bug 等各种问题,也迫使我在这当中不得不加强了对上述当中某些技能的应用。所以我虽然是个分析师,但我依旧和前后端的同事们有着可以聊得话题。
所以作为一个程序员,会一门能够谋生的编程语言是必须的,但除了这门语言之外还应该会得更多才能让你更好地涨工资、有着更好的发展空间。无论你是怎样的程序员,会得越多你越能真正做到「游刃有余」。你选择的余地就会愈加宽广。
在学习不同技术栈的过程中,你会体会到不同编程语言或技术之间的相似性以及差异性,能更好地把握不同技术的适用面;同时在实际过程中你能更好地做到对协作同事的兼顾,以及整个程序或项目的大局把控、架构等。
像这种前后端都会或者会得很多的程序员往往都叫「全栈工程师」(全干工程师),可一个人的精力是有限的,不可能每项技能都做到突出。虽然也不乏有大佬确实是对不同的编程语言都达到精通的地步,但那只是少数人。对于想要成为合格的程序员、甚至高级程序员或工程师的人来说,横向拓宽自己的技术栈是必须的,但并不一定要达到精通的地步,能做到了解或稍微熟悉就已经很不错了。剩下的只需要在自己更为擅长的方面纵向专精那就已经是在某方面可以称为专家了。
代码不是一切:聊聊代码之外的东西
对于程序员来说「Everything is code」,但从某些层面来说「Code isn't everything」。
技术其实并没有我们想象得那么高大上
仅工作一年多,我对「技术」的信仰就已经从狂热趋近于平淡。这和我们「经常点一家好吃的外卖,之后就每次点都感觉没有最初的味道」这种耐受性不同。
趋于平淡的原因是因为,褪去宏大抽象的包装外衣之后,技术似乎就并没有那么高大上了。更多时候技术仅仅只是依附于业务、经理或者是领导们的一种获取价值和利润的方式而已,无论技术含量高低与否,都是一视同仁。
大多数的非技术出身的经理和领导不会在乎你使用了怎样的数据库技术、哪种编程语言、前端采用哪种动效能提升用户交互、将延迟从 5s 秒减少到 1s⋯⋯在乎的是如何将「能做到的技术」以抽象难懂或华丽的概念辅以某种新时代下的意义打包成所谓的「产品」进行出售或向目标客户推销。
但在产品外表下,各种组合的技术栈大部分都是枯燥、乏味没有太多技术含量的东西。
今年尽管忙忙碌碌了大半年,我也一直在想,那堆组合在一起的代码对于程序员或者工程师来说意味着什么?这仅意味着我们完成了对得起工资的工作量?还是意味着我们仅仅只是做到了一件能够自我满足的技术实现?亦或是意味着仅仅只是完成了年度 KPI 中的一小部分跨越?
就代码本身来说,它是一个程序员或工程师的全部;但在对人的价值和意义面前,它却又是渺小且卑微的。
谈一谈「996」、时间价值与自我提高
「996」一词也是去年开始火热起来,但我在没工作之前对此并没有很深刻的感触,直到我开始进行了人生第一次长时期的加班。
虽然我工作的年限并不长,但在今年出差的时间里,我和同事们需要经常性地会在工位上除了要赶进度之外,还需要额外为了满足客户的需求多变去做临时的事情,时不时还要解决程序出现的 Bug 或环境部署问题,可以说尽可能把一天的工作时间都排满了。可尽管如此加班依旧是常有的事,到现在调休还剩一大堆。
在没出差之前我还欣喜说每天能有大约 80 元的补贴,每天早晨也不需要挤地铁,去汇报时打车也会报销,这样能省下一笔不小的开支并且还留有富余。
但驻点的工位的环境不算很好,无论是桌椅还是设备(内网环境无法使用自己的设备)。长时间干到晚上时发现我开始经常性地眼睛刺痛、流泪,不得不购置眼药水并时刻走动并放松眼睛。受限于桌椅的高度与面积,更多时候身体不能保持在合理且健康的姿势下。颈椎、腰部都开始频频发出不舒服的警报。
在此期间,在和一位后端的同事聊天时聊到关于出差的话题,大意是每天出差有补贴可以拿,早餐钱、交通费也都不需要自己出,打车还能报销,出差不也很舒服?在我没有出差之前,我认为这是美好的,但自从出差结束之后我就改观了。
有补贴拿、没有多余的交通开支这对我来说算好吗?有一笔额外的收入是很好,但其实在扣除对应的房租、伙食费之后其实也所剩无几,但关键的是个人要经常性地为需求和工期让步(当然加班没有补贴,只有调休)。在没有加班之前,我每天都会在下班之后的时间里锻炼、阅读或是学习了解一些技术方面的东西以充实自己,但加班之后回去却是难以挤出多余的时间来做这些事。
所以相比于个人的时间价值和自我提高来说,同事口中的这些「福利」完全就是不值一提。尽管我依然只是个事业起步的打工人,但个人的时间价值和自我提高我永远是摆在第一位的。也正是在我个人的时间里,我扩展了自己的技术栈、有时间写文章写教程、能够在闲暇之余做做饭、锻炼⋯⋯这都是加班和那些微薄的福利无法给予的。
所以如果有两份工作摆在我眼前,一份工资少但不 996 或少加班,一份工资高但工作强度高且经常性 996,那毫无疑问我会选择前者。
可能有人会说「趁着年轻为什么不奋斗一把?」
没错,年轻应该勤奋上进,但在没有加班或 996 的影响下,自己能在个人时间里自我提升就不算是奋斗了吗?我无法苟同奋斗就一定要和 996、很辛苦的加班划上等号。
金钱物质可能是一个人生活基础的保证,但却不一定是一个人幸福、人生意义和价值的保证。
EOF
这是我这一年多以来作为一个分析师、程序员和工程师的成长之路。
我由一个懵懵懂懂、不谙世事的大学生逐渐成长了一个对自我有追求、有挑战的初级程序员。或许接下来的路程更为难走,要求学习的东西也会越来越多、越来越庞杂,但我相信作为一个成年人,主动选择的决定总是比从众而行的跟风更有毅力,既然选择了远方,便只顾风雨兼程。