hi,大家周末好,很多人觉得编程很难,尤其是做算法题,很多时候无从下手,明明知道怎么做,就是很难实现出来,今天分享一篇文章,希望让你明白人类编程背后的思维模式,只有看清问题的本质,才能快速解决问题!
编程的难分为两类,一种是工程上的难,一种是算法上的难。
工程
我先回答一下工程上的难:
我们做一个比较, 就是开发一个大型软件, 和设计并建造一栋摩天大楼, 究竟哪个更难, 为什么?
实际上这个比较一旦抛出, 软件开发的"难"就立马显现出来了。
摩天大楼一旦盖起,将不再,也不可能“更新版本”,更不可能在建筑结构上进行“重构”,比如一个一百层的摩天大楼,不可能说过两个月发现地方不够住,然后再加五层,又过了两个月发现某个房间的设计满足不了人们日益增长的需求,将整个房间扩大两倍,究其原因,就是因为“建筑”这个东西太不灵活了,你如果想“重构”这个摩天大楼,几乎只有一个办法:炸毁,推倒重来。
而对于软件来讲,它本身的一个灵活性,导致了它可能在不破坏原有的大部分功能的前提下进行一定的功能性改造,在改造的过程中,原有的一些功能模块的更改,可能导致之前的一些文件或环境, 或应用程序在这个新的版本下出现不兼容的现象,所以它要保证旧的东西在新的东西下能成功运行。
一个东西越是牵扯的东西多,越和环境耦合,和“用户”耦合越深,和“自身”耦合,和“历史”耦合越深,它的“更新”就越难,而“重构”是多次更新之后的“必然结果”。
这就是为什么摩天大楼几乎无法进行功能性更新,因为建筑这个东西本来就是一个“超级耦合体”,你改变墙壁的厚度,会减轻它的重量,但于此同时因为墙厚度降低,它本身的抗压刚度和抗扭刚度也发生了降低,所以后果可能还需要重新计算,墙里面的电线会不会因为墙的厚度改变而需要重新布线?而且不仅如此,墙的厚度还会改变房间的大小,这是它和其自身的高度耦合。
它和用户的耦合性也很深,因为这个房间的一些地方有可能早已住了人,原有的位置放置了很多用户的东西,比如一些很重的家具等,你对墙重新施工,就意味着你要强迫用户更改它们原有的使用习惯,甚至可能让用户原有的一些家具无法在新的版本中放进去,这是用户的耦合性。那么类比软件,比如我们都知道的excel,这个软件开发了二十多年,你在2003版的excel里编辑的,十几年历史的excel文件,你觉得可以在2018版的excel中打开吗?这听起来很不容易,但是微软做到了,这就是为什么说现在除了微软几乎没有公司有能力开发出这样的软件,原因之一就是它变态的兼容性,长达十几年的时间跨度,版本跨度,保证文件的兼容性,这本就很了不起。
所以向前兼容是软件开发的主要难点之一。
有时候不仅是某个功能模块的更新,随着一个”服务型“的应用不断发展,它要提供服务的用户量就会不断提升,一个典型的例子是淘宝,学过数据结构的我们知道,这世界上很多东西的复杂性并不是线性增长的,简单的例子就是排序,最优的时间复杂度也是nlog(n),理想世界如此,现实世界怎么可能更简单!
十年前php可以撑起淘宝的流量,而今天呢?php的性能问题恐怕让其无法再承担淘宝的亿级流量,所以只能用java重写,而这就是重构,重构其实就可以认为是推倒重来,代价很大,需要更换技术栈,但必要时不得不做。
有人说服务一万人用一台服务器,那么服务一亿人用一万台服务器就解决了,哪来那么多事,而这也正是我刚才说的,陷入了”线性增长思维陷阱“。
一万人的时候,可能有时候可以让系统停掉,进行一到两小时的服务器维护,这一万人也不会因为你停了服务器就有多大的损失,那一亿人呢?你的服务器集群敢断电吗?敢让网站挂掉进行网站维护吗?
所以既然要让网站每时每刻不断运行,就首先要做到持续集成,持续部署,对应用进行完善的版本控制,为了降低系统bug风险要进行完善的发布前测试,而测试又分单元测试和集成测试,缺一不可。为了防止某个地区的服务器因为突发事故,比如地震,火灾,大面积停电等事故发生宕机甚至损毁,我们需要进行服务器的异地容灾,那么另一个地区马上就能无缝根据原有服务器的中断镜像进行服务重建,做到服务端这边“山崩海啸”,用户端那边还能一如既往的从容淡定,感觉什么事都没发生一样。
所以,扯了这么多,我用一句话总结编程的”工程性难点”就是,你如何在这错综复杂的耦合中,在复杂性不断增加的过程中,如何让软件有序,规范地”进化“,而不是让其肆意疯长,最后成为一坨无人能懂,无人敢碰的”屎山“。
算法
算法的难主要集中在两点, 一种是算法的构思和提出, 一种是提出算法之后, 用code实现出来。
个人认为算法的构思和提出最为困难,因为这部分经常要求我们将我们眼中“显然”的东西,转换成计算机语言。
比如我们现在要实现一个算法,这个算法能够识别一张图片,判断图片中是否有人脸,对于人类来讲,这是不经思索的,一眼就能看出来的,但是问题是,你自己也不知道你自己是怎么看出来的,这个判断过程,其实我们的大脑内部发生了很复杂的化学反应,但是我们却说不清,我们到底是怎么判断的,还比如判断人脸的情绪等等。
但有人说,这说明计算机比人笨,事实上完全不是这样,计算机从来就不比人笨,事实上比起人类能做到的事计算机做不到,计算机能做到人类做不到的事反而更多。
计算机的特点就是容错率低,但是可预测性强,确定的代码和输入就能得到确定的结果和输出。
人类的特点是容错率高,但是可预测性差,对一个东西的执行结果经常受情绪,身体状况,心态等的影响,结果常常是不确定的。
所以其实,一个系统的”容错率“和”可预测性”是个永远不可调和的矛盾,一个编程语言如果具有一定的容错率,看起来好像编程更为容易,实际上反而是灾难,bug更难发现,问题更难复现,一个运行正常的系统很有可能有一天脑子一抽输出一个错误结果, 这就是为什么几乎所有语言都有异常抛出机制, 就是为了降低容错率。
所以我们要注意一点就是,编程困难,并不是因为计算机不够先进,不够智能,而是我们为了“可预测性”,心甘情愿地接受了这种极低的“容错率”,这直接导致你写程序,少打一个分号,编译错误,打错一个变量名,编译错误,数组越界,编译错误。
容错性这么低,你还指望它能接受人类这种模糊的自然语言?当然不可能了,所以它一定有自己的,语义清晰的语言。
一个语言一旦语义清晰了,语言灰度底了,它一定是逻辑性很强的,或者说,我们其实是用逻辑写代码,而不是用“感觉”或是“情感”写代码,这和我们人类理解世界的方式本来就是有区别的。
人类虽然有逻辑思维的能力,但是逻辑思维从来就不是人脑的强项,人脑的强项其实是整体感知,和一些“系统预设”的功能,比如你眼睛睁开了,投入你视网膜的光就会自动的转换成图像信息被你感知,这个过程你调用了任何逻辑思维了吗?你几乎什么感觉都没有就完成了一个极其复杂的过程。
所以我们的大脑绝大多数复杂的进程,或者说运行过程,都不能被我们逻辑性地去感知,比如你听到一首歌,这首歌给你一种很舒缓的感觉,你说你听着这首歌仿佛进入了一片天灵之地,但是这个过程,请问你进行了任何逻辑思维没有?
你就是感觉了一下而已,但是我们想让机器去做这样的“感知”,因为计算机体系的极低容错性,我们必须用我们的逻辑思维搞明白,这首歌,究竟是因为什么,它有什么样的特质,才导致了它给你这样的舒缓感觉,这样你才可能把这个过程转换成计算机语言输入到计算机中,让计算机去判断这首歌是“舒缓的“还是”狂野的“。
所以我总结一下编程在算法上的难,就是:
人类这种容错率高,可预测性差的生命,非要逼着自己去和一个容错率低的,可预测性强的这样一种”硅基生命“交流,不得不逼着自己用逻辑性很强的语言去和他交流, 在这个过程中我们不得不打破我们原有的感觉和感知, 对其进行逻辑解构, 这样才能让跨物种交流得以实现.
看到这里,你一明白了,要想让两种不同的生物更好的交流,要不就是让人类变得更像计算机,要不就是让计算机变得更像人。
你想一下siri,cortana这种语音助手,它是不是具有一定的不可预测性?你和它说一句话,你知道他要回复你什么吗?不知道,但也正因为如此,我们让计算机变得更像人类,从而让人类这一端,可以更轻松地和计算机交流,这其实就是人工智能。
一个AI的算法模型种,往往要引入大量的参数,然后不断地输入样本,根据预测值和真值是否匹配而动态地改进这些参数,这样一个系统,容错率是低了,可预测性呢?确定的AI算法模型能得到确定的预测结果吗?
所以与此同时,你也要注意。
容错率低的,重要的事不要交给siri去做,因为它和人一样,不可预测。
本文素材由牛岱授权
来源:https://www.zhihu.com/question/311432227
更新整理:极客重生