如何从零开始学一门程序语言?
今天一大早排队挂号给孩子看病,耽搁了,现在才发。
说实话,『能花钱的,就不要花时间』是篇即兴之作,本该随着时间被有限的听众渐渐淡忘。但没想到前有 @老赵 微博转发,后有知乎日报鼎力提携,这篇文章火得一塌糊涂,一下子把我推到了风口浪尖。我其实想委婉地表达一个意思:程序员的时间很宝贵,只要有能用合理价格买来的用来提高效率的产品和服务,就不要花时间找免费或廉价的替代品。但显然还是被不少人误读。不少人质疑的焦点放在了:『你是有钱了,可我们是屌丝,买不起正版软件,正版图书,盗版不也一样用么?』我没想到随手举的一个买正版软件,kindle图书的例子引起这么大反响,可这争论似乎偏离了我文章的主旨 —— 正版盗版之争不是该文讨论的重点。其实每个人心里应该都有一杆天平,左边放着『合理的价格』,右边是你的时间。天平怎么调整,在乎你,不在乎我的几行文字。
程序君再次重申,『程序人生』传递的是作者个人当下的思想,你可以不信,但切不可全信。:)
善者不辩。程序君修行还不够啊。:(
言归正传。
最近的留言中,『如何从零开始学一门程序语言?』这个问题问到的频次最多。我本不该回答,因为我不在行 —— 我现在已经回想不起十几年前究竟是怎么学第一门(有实际意义的)语言的。而我现在学习一门新语言的经验对入门者来说也许并不合适。
仔细想了想,还是决定赶鸭子上架,说说看。Again,绝对个人意见,望能抛砖引玉。首先,『兴趣是最好的老师』这样类似的话我先晾到一边,你自己补。这些东西一讲你都知道,看完你除了能点个赞之外,似乎还是对学程序语言没感觉。而且这种话说给学习任何东西似乎都有效。
如何选择语言?
我的第一门实用型的语言是Visual Basic。看了一段时间的windows编程,在各种句柄,消息循环以及繁复的MFC中迷失后,VB让我见识到了简单,直接了当,以及文档(MSDN)的重要性。学习的门槛是个大问题,有些语言(及其背后的框架)没有一定的基础最好不要碰。
当然,语言总是在发展,现在在dotnet框架下的VB已经失去了那种简单直接的美。
第一门语言最好选择带如下属性的语言:
- 1. 语法简单,结构明晰,写出来的代码相对简洁明了
- 2. 有完善的生态系统,尤其是文档
- 3. 语言最好提供REPL(Read-Evaluate-Print-Loop)和introspection
- 4. 开源社区(如github)有足够多的样本代码
- 5. ...
以python为例,稍微解释一下第三条。python的shell(python或ipython)提供了一个语法和库函数的试验场,对初学这门语言来说帮助很大。想弄通python的数据结构,在shell下练习半小时基本就懂了。另外,在shell里随时可以对任何函数,类库做introspection(内省)—— 通过 help
,dir
,type
,inspect(库)
等深入到被调者内部了解其行为,这就进一步降低了初学者的门槛。写C代码的时候,遇到不太明白怎么使用的函数时,要么看其源码,要么找文档,但在python里,方便的内省工具可以随时随地帮助你。
补充一句,这两个语言没有可比性,我仅仅拿来举个例子。也许有人会说我可以用gdb做内省啊,但那已经不是一个层面的解决之道,也不是初学者入门时能掌握的。
第一条不用太解释,没人一开始就喜欢复杂吧?第二条和第四条当你学习时,有什么参考资料,及当你遇到问题时,可以寻求到什么帮助。你可以从某语言的官网上了解其社区文档的完备性,从stackoverflow和github上了解其生态系统和社区力量的强弱。
如果非要让我很主观地推荐,第一门语言我推荐python,它满足上面的所有要求(ruby,scala等也都可以考虑)。
看到这,也许你会问,有REPL岂不是不把大多数static typing的语言给排除在外了?对!
因为标题是从零开始学,学的过程中的互动(和shell),学习者不断构建信心很重要。compiler介入只会给这个阶段的学习带来不必要的负担。
如何学习?
学一门语言,要从语言的历史和思想入手。
从语言的历史中,我们可以了解到语言诞生时的各种因素,以及在之后发展过程中的种种选择。除此之外,你要了解大概该语言的社区中在时间轴上都出过什么样的产品。这些都会加深你对语言的理解和认同感。
学习语言的思想(哲学)很重要,这体现在语言的设计,语法,甚至整个社区的行为。将一个数字转化成字符串,在python里你大概会这么做:
In [1]: str(10)
Out[1]: '10'
但在ruby里你应该这么做:
irb(main):001:0> 10.to_s
=> "10"
如果你将其视为语法而死记硬背,那事倍功半。ruby的哲学是,纯粹的OO,告诉object做什么事,而非对object做什么事。
对比ruby和python两种语言很有意思。ruby作者从smalltalk和perl的影响很大,所以ruby里一切都是对象(smalltalk),做一件事可以有多种方法(perl)。ruby的作者赋予了ruby灵活的控制权,让你可以改变系统的行为(比如可以open一个类库中的class修订),又有点像lisp。
python从Abc这门教学语言(Algol)中繁衍出来,对纯粹的OO并不感冒,因此代码可以混用面向过程和面向对象。也许是教学语言的基因,python强调做一件事用一种最清晰的方式完成("There should be one - and preferably only one - obvious way to do it."),所以社区里有人说某个实现是"pythonic",说明这个实现清晰,简洁,符合python之道。当然python还吸收了很多其它语言的优点,如list comprehension(haskell),applying function to list(lisp)等等。
了解了历史和思想,你会对语言的行为有一个比较合理的解释,学习起来也比较容易举一反三。当然作为第一门语言,你肯定不知道那么多其它语言的名字,很多我也不知道,但可以wiki一下,当拓展/延伸阅读了。
之后就是看文档书籍,看各种网上的视频教程来学习语言的语法和各种库。这个阶段比较枯燥(REPL能稍稍降低这种枯燥)。不要看教科书学语言,教科书上的习题大多和生活中的问题无关,不要试图去写什么鸽笼问题,八皇后的代码。你在学程序语言,不是在做思维训练或是数学。
注意选择书籍的时候尽量选择该语言作者(或者名家)的书,比如ruby该看『松本行弘的程序世界』,python的 "Programming Python"(python作者做的序),erlang看 "Programming erlang - software for a concurreny world",等等,他们会将很多知识和思想汇入书中。
另外还有一本无关语言,但关于编程要义的书非常值得一看:『冒号课堂』。国内的作者多写点这样的书,少做点语法介绍、类库介绍的大砖头,将是中文图书的幸事。
过了语法关之后,有两个学习方法:
- 以练代学
- 和社区互动
- 以教代学
以练代学是找个有意思的项目,甩开膀子边写代码边学。驾校里永远无法让你真正学会开车,但你上路后的一周内,你就基本掌握了驾驶的全部技巧;每天都在各种复杂路况上开一个月,你就是老手了。学语言也是这个理。
学习的过程中总有困惑,多在google groups和stackoverflow里面寻找答案。如果实在找不到,用符合社区标准的方式发问。这也是一个累积经验的过程。
以练代学的基础上,最好教一个不会但又想学的人这门语言,或者找一些志同道合的人一起学。有时候我们的思维会受定势的影响,过滤好多头脑中的『傻』问题。别人一个看上去很简单的问题也许会重新触动你的神经,让你好好思索那些被自己视为『简单』的问题的答案。
不过,教别人某门语言的机会不常见,但在社区里看看别人有什么问题,尝试回答,也实践了以教代学。
如何进阶?
当你基本能比较随手用某个语言写出简单的应用后,你该考虑回过头来补补那些之前忽略的环节,重新审视这门语言:
- 它的类型系统有什么特点?
- 内存管理模型是什么样的?
- 语言和库分别有什么并发的手段?
- 对范型的支持?
- 异常处理的机制和社区约定俗成的方式是什么?
- 对OOP都有哪些支持?
- 对FP都有哪些支持?
- 如何进行元编程?
- 与其他语言的互操作(比如C)是怎么样的?
- 语言有什么天然的限制?
- ...
在这个审视的过程中,不断把基础知识补齐 —— 这些都是快速掌握下一门语言的基础。
你也许还应该找些比较有意思的源代码,看看别的程序员都是怎么写代码的。读优秀的代码就像读一本好书,仔细咂吧,其乐无穷。
代码写累了,看看名家的书,修炼一下思想。『黑客与画家』,『松本行弘的程序世界』,『程序员修炼之道』等一众书都可看看。
比如说程序员修炼之道里写到:
You shouldn't be wedded to any particular technology, but have a broad enough background and experience base to allow you to choose good solutions in particular situations.
我觉得这就是做软件应该有的态度 —— 没有最完美的语言,只有最合适的工具。
结语
说了这么多,似乎跟没说一样。学习语言本就是一个因人而异的东西,没有放之四海皆准的教条。还是那句话:修行靠个人!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- spring redis java.lang.IllegalStateException: Cannot load configuration class: redis.RedisTemplet报错
- Elasticsearch: Index template
- 基于rest风格对Neo4j进行调用访问之———httpClient
- spring boot + mybatis 访问 neo4j
- window下通过ELK框架进行大型日志线下的可视化分析
- 一句话解释ThreadLocal类
- ElasticsSearch 之 倒排索引
- tomcat返回cookie有双引号问题
- 利用辗转相除法求最大公约数以及最小公倍数-Java
- 网络爬虫的风险
- git 使用小结大全
- Elasticsearch:search template
- 模仿echo命令学习BIO——Java实现
- 模仿echo命令学习NIO——Java实现
- Java 7 种阻塞队列详解