1

潘爱民:计算机程序的演进——我的程序人生三十年

 2 years ago
source link: https://mp.weixin.qq.com/s/5XoCr1-X2fjFZ-ODv-op1g
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

潘爱民:计算机程序的演进——我的程序人生三十年

Original 潘爱民 CSDN 2022-03-25 08:20
收录于话题 #新程序员 25个
640?wx_fmt=gif

【CSDN 编者按】大神潘爱民,编程三十余年,从学生时代兴趣出发进入计算机世界,经历过北大教学科研、微软亚研系统研究,盛大、阿里的工业研发,走上创业之路,创立指令集进行系统软件的研发及落地场景应用。《新程序员004》,一起走进潘爱民的程序人生,从中可以一窥过去三十年计算机程序的演进,也可以通过他的视角,看透未来十年的发展。面向未来,潘爱民说,在中国写代码的人数将在两到三年后达到高峰,更将诞生基于中文的编程语言。

作者 | 潘爱民       责编 | 唐小引出品 | 《新程序员》编辑部

我在 1985 年第一次接触到计算机程序,尽管那只是娃娃机上的一些作业型的 BASIC 程序,但我依然感受到了编程的乐趣,并且乐此不疲地编写各种花样程序。三十多年过去,计算机程序于我而言已经成为一种思考方式,无论是应用层的功能,还是系统层的能力,或者是背后的数据处理逻辑,都转化成了机器的指令。

我有幸完整地经历了从 PC 时代,到互联网的兴起与发展,再到移动互联网,又到万物互联时代和产业数字化的发展历程。这三十多年,既是软件技术和产业的发展期,也是我个人的职业生涯和程序人生。本文介绍我认知中的计算机程序的演进,正好是程序人生三十年的一篇小结。

640?wx_fmt=png

潘爱民(摄于 1999 年北大工作期间)

640?wx_fmt=png

软件栈 —— 从源代码到机器指令

通常而言,计算机程序是指软件中的代码部分。软件涵盖的内容要多得多,比如还包括数据、文档,甚至有硬件(比如加密狗),还可能有相应的服务等。计算机程序则是指一组指示计算机或其他具有信息处理能力的装置执行各种动作的指令。计算机程序既可以是机器指令的形式(由二进制数 0 和 1 构成的序列,人难以解读),也可以是人编写的原始代码的形式(通常由程序员来解读和维护)。

过去三十多年软件技术的发展,可以从程序编写方式和运行方式来看待其中的变化。典型的有以下一些方式:

1. 代码直译执行

早期的程序编写方式是,程序员按照机器执行指令的思路来控制一台机器。最典型的是用 C 语言来编写程序,几乎每一行代码都可以对应到一个指令序列,甚至可以在 C 语言源代码中直接嵌入汇编指令(机器指令的字符描述方式)。

2. 代码解释执行

原始的代码被解释成一种中间抽象语言描述,再进一步转换成机器语言被执行。以 Java 语言的哲学思想“一次编写,到处运行(Write once, run anywhere.)”为基础,用 Java 语言编写的程序天然具有跨平台特性。程序员面对的是一个抽象的计算环境,所编写的 Java 代码是如何被执行的,这中间有一个间接层。

3. 虚拟机和容器化

程序员编写的代码最终由机器上的 CPU 来执行,随着计算机硬件能力越来越强,一台机器可以被虚拟成多台计算机。源代码被编译或者解释出来的机器代码,又进一步被映射成一个指令序列。云计算的发展和普及让这种程序运行方式变成了主流。容器是虚拟机的一种轻量形式,其思想本质上是一致的。

4. 前后端分离

数据显示用 Web 技术来完成,后端处理用合适的编程语言来完成,两者之间通过符合 Web 标准的 API 进行通信。将可视化和用户交互部分单独划出来,而不是跟业务逻辑耦合在一起,这是前后端分离的核心思想。

以上四种方式,前三种侧重于计算机程序在同一台机器上被引入了一个或者多个中间层,导致源代码被多次解释或映射才到达物理 CPU;第四种方式则是从横向跨机器的角度将可视化和用户交互部分与业务逻辑(特别是数据处理)解耦。这些变化与软件产业的发展息息相关。下面几点值得一提。

  • 从源代码到对应的机器指令,路径越来越不清晰。在物理机器环境下,源代码与 CPU 之间隔了一层或多层;在云计算环境下,程序员甚至完全不知道物理上代码是如何被执行的。这种变化趋势导致了程序调试和性能优化变得更加复杂和困难。

  • 当程序运行时,业务功能的代码路径变长,甚至分布到不同的计算环境中。一个业务逻辑被触发的地点,与它被处理的地点,之间可能跨了执行环境,甚至跨了网络。这对于程序员的复合能力要求更高,单一技术栈的程序员将面临很大的挑战。

  • 这些编程方式是融合的,有各自的适应场景。随着互联网络的不断发展,并深入到各个产业中,混合编程方式已经成为软件行业的主流。对于软件开发人员,我们需要在深度和广度上并进:所谓深度,是指对源代码往下一层递进执行的理解;所谓广度,是指对执行链从一个执行环境到另一个执行环境迁移过程的理解。

  • 技术的进步是产业发展的内在动力,云计算得益于硬件的极大进步以及虚拟化技术的成熟。反过来,产业的发展又带动了软件从业人员的大规模扩大。中国的程序员有数百万,甚至按有的统计途径达到了上千万。这些编程方式的发展与软件产业的效率有极大的关系。新的软件技术进步,有可能成倍地提高产业效率,进而又可能导致大量的从业人员需要进行技术变迁。

我始终认为,程序员写代码是一种创造活动,这是程序员职业的神圣之处。理想的情况是,程序员所写的代码都是创造性的,并且是高价值的,是机器人和人工智能所无法企及的。否则的话,这些工作迟早会被技术的革新取代。如果程序员编写的代码只是一些定式化的重复,或者可以用相对简单的形式化来描述,那么按如今的技术演进,这些代码可以不用人来编写了。这是目前很多低代码或零代码开发平台正在努力的目标,也导致了这样一个趋势:写工具是非常有价值的,而不依赖于工具来写大量逻辑的工作是价值有限的。

640?wx_fmt=png

研究生期间在宿舍写代码

在我职业生涯的前半段,我一直信奉“用二进制的方式来理解程序或系统”,为每一个功能或任务都揣摩底层的指令序列。随着程序或系统的复杂性增加、云计算及前后端分离模式的普及,我们越来越无法做到精细化地用二进制方式来理解它们了。在这种情况下,对软件架构的把握变得越来越重要,机缘巧合之下我实现了从系统程序员到软件架构师的升级。

640?wx_fmt=png

网络 —— 无处不在的连接

网络的发展改变了我们的生活,这是过去将近三十年人类社会最大的变化之一。网络也同样改变了计算机程序的运行方式,甚至改变了编程的思想。我们首先看一下网络本身的演变:

  • 初期的网络是在机房环境或者办公环境中使用的,网络的物理形态非常直观,因为大多数情况下每台机器都拖着一根线。常用的网络功能都通过专门的应用程序来完成,比如电子邮件程序、浏览器、文件传输工具等。网络功能的程序编写属于高级编程技术。

  • 网络开始普及,家庭和很多公共场所都有了网络,连接的方式可以是一根线,也可以是 Wi-Fi 无线。在这样的条件下,越来越多的程序加上了网络的能力,网络编程逐渐普及,但仍然属于高级编程技术。不过,通过很多中间件,编程门槛已经降低。

  • 移动数据网络的普及。网络的进步是全面的,包括硬件基础设施和软件栈。移动数据网络相对不稳定,这对于软件编程是一个挑战,其复杂性来自于两个方面:对网络异常的处理,以及网络连接涉及到两方协同。然而,得益于移动操作系统原生提供的网络基础功能,网络编程的门槛被大大降低。

其次,网络的思想与操作系统密不可分,两者的演进更是紧密关联。网络的核心哲学思想是协议分层,每一层的功能实现只依赖于下一层提供的语义,同时也为上一层提供标准或约定的语义。操作系统也有类似的分层结构,层次越往上,离硬件越远;越往上,编程的门槛越低。网络的软件栈大部分位于操作系统中,相对应地,因网络带来的复杂性大部分由操作系统消化掉了,因此应用程序的编写并不显著地变得更加困难。譬如,移动数据网络带来的复杂性,绝大部分由移动操作系统处理了,它对上面提供的应用开发框架中并没有暴露出移动数据网络环境的复杂性。

然而,网络本身对于应用程序的编写还是有极大影响的,从软件设计到代码编写都有深远的影响,以下是一些显著的变化点。

  • 网络编程最基础的模式是异步编程,以及对异常的处理和恢复。TCP 和 UDP 不仅是两个传输协议,更是两种编程思想。它们指导我们如何设计和编写服务器程序和客户程序。

  • 对于在网络环境下运行的程序,我们编写的可能只是半个程序。另外半个程序可能在完全不熟识的人手里进行开发,可能在地球的另一侧,甚至不在地球上。我们需要遵守网络双方的约定、遵守对方的规范,或者要有足够的灵活性来应对可能的意外。

  • 网络功能容易遭受性能和体验的问题。网络往往是一个共享资源,所以它的不稳定通常是可以预料的,编写网络功能的时候若处理得好,就有可能化解掉不稳定的因素;若处理不恰当,就会造成性能极差,乃至体验极差,甚至进程死锁或崩溃等。从这个角度而言,网络编程总是有优化改进的空间。

  • 网络的不稳定带来了不确定性,导致网络程序的诊断变得困难。一方面,程序的网络环境可能会有抖动,导致网络行为可能无法重现,从而增加了诊断难度;另一方面,网络程序可能运行在物理上不可达的环境(比如云主机)中,这要求程序员对网络环境的理解要更加全面,否则难以从现象或错误代码来分析问题的根源。

640?wx_fmt=jpeg

计算机网络经历了大量的技术择优和淘汰,今天我们享受到的稳定网络和良好的网络应用程序是历史沉淀的结果。硬件上,我们的网络越来越稳定,无线网络的基站(或访问点)之间可以做到无缝切换;软件上,操作系统解决了大量的网络复杂性问题,留给应用程序的是相对容易实现的处理逻辑,比如方便处理的 HTTP 协议、无状态的远程请求、自动的离线缓存等等。在移动数据网络的早期,很多应用程序一遇到网络不稳定,就出现白屏、不响应,为了改善用户体验,应用开发人员需要编写大量的代码。随着移动数据网络的普及和稳定,以及移动操作系统的成熟,这一类应用代码已经大大减少了。

随着移动互联网的发展,我们又迈进了万物互联时代,产业数字化如火如荼地进行。网络上连接的已经不再限于计算机或者个人设备,越来越多各种各样的设备都连接到互联网上。网络技术和操作系统都面临一次升级,从概念到功能外延都在发生变化。我有幸在从业这么多年以后又赶上一次技术浪潮。在这一轮技术大升级中,面向设备连接的操作系统应运而生,因而我在 2018 年创立了指令集公司,专门从事物联网操作系统的研发和商业化。

640?wx_fmt=png

人工智能 —— 从模拟智能到超越人类智能

人工智能的发展代表了人类使用计算的一种追求。计算是一种能力,可以做很多事情,包括科学计算和事务型的任务等;其中人工智能的任务是指,让机器通过计算,可以像人类一样拥有智能。自从计算机诞生以来,人工智能的发展经历了起起落落,但过去三十年间,人工智能学科总体上一直是在向前发展的,下面是人工智能领域的一些典型事件:

  • 深蓝计算机(IBM 制造),1997 年,深蓝击败人类象棋冠军卡斯帕罗夫。

  • 仿生机器人“大狗”(波士顿动力学工程公司研制),2005 年,可以四条腿行走。

  • 阿尔法围棋(AlphaGo,Google 研发),2016 年,击败人类围棋冠军李世石。

  • 人脸识别技术应用于移动 App,例如,2015 年马云在 CeBIT(德国)展会上演示了刷脸支付。

  • AlphaFold/AlphaFold2(Google 研发),2020/2021 年,基本上攻克了困扰人类科学家已经很久的预测蛋白质折叠结构的问题。

此外,最近 10 年来,大多数汽车制造企业(无论是传统车企,还是新势力造车企业)以及一些互联网科技公司都在研究自动驾驶汽车,并且陆续有一些自动驾驶汽车上市。从以上这些事件我们可以看出,人工智能应用有很多种探索路径:

  • 模拟智能

将人类的思考过程,利用计算能力进行模拟。比较典型的是象棋和围棋这一类规则化的智力活动,人类的思考过程可以恰当地提炼出来。因此,只要有足够的存储和算力支撑,以及人类的经验模型,就有机会做得比人类还好。

  • 利用算法实现智能任务

在许多应用场景中,可利用人工智能算法(主要是深度学习算法)来完成一些明确定义的任务,比如人脸识别、车牌识别、语音识别等等。这一类人工智能应用需要具备两个条件:足够多的样本和足够强的算力。在过去十年中,移动互联网的蓬勃发展使得很多业务场景汇聚了足够多的样本数据,再结合云计算的发展,因而这一类人工智能应用发展迅速。

  • 综合替代人类,达到人类智能

比较典型的是自动驾驶汽车,以及各种具有复杂决策能力的机器人。自动驾驶汽车可以将人从驾驶任务中解放出来,机器人可以代替人类进入到复杂场景中执行任务。这一类人工智能应用需要综合各种软硬件技术,近几年在产业界是一个科创热点。

  • 超越人类智能

探索未知领域,造福人类。比较典型的是在一些科研领域,结合了人工智能的技术以后获得了革命性的突破,例如上文提到的 AlphaFold2 使蛋白质折叠结构预测问题得到了突破,达到了原本人类通过实验无法做到的结果。

人工智能的核心三要素是数据、算力和算法。算力是计算的物理基础,数据是计算的原料,算法是计算的逻辑,其最终形式即软件代码。人工智能的发展催生了大量的数据工程师和算法工程师岗位。数据工程师负责采集数据,对它们进行各种处理,归集起来以供算法使用;算法工程师负责实现各种算法,或者调用一些通用的算法来完成特定的任务。经过多年的发展,目前有很多算法库已经沉淀下来,有不少以开源的方式供业界使用,例如 TensorFlow、PyTorch、Ray 和 SparkML 等。

算法的编程尤其要关注性能,以确保算法的性能足够优,这不是一项简单的任务,它需要扎实的底层系统知识,甚至要理解硬件架构。一方面,数据的传递和分布对于一个大计算量的算法是非常重要的;另一方面,在众多计算节点中,要避免出现单点性能瓶颈。有很多的领域专家在使用人工智能算法时,并不洞悉底层计算平台的配置要求,或者未能正确地使用计算库,从而造成资源浪费或者计算时间过长,这在实践中较为常见。

我有机会在之江实验室建设一个大计算装置,称为智能计算数字反应堆,其旨在搭建一个大计算平台。该数字反应堆可以聚合多种异构算力资源,并通过一些计算框架或者算法库,为各种应用(包括科学发现、数字经济、工业仿真等,称为应用反应堆)提供统一的计算平台。可以想象,一旦有了这样的计算设施,将人工智能算法与各个领域模型结合起来,在局部领域超越人类智能将会成为一种发展模式。

640?wx_fmt=png

可视化和用户交互 —— 从 GUI 到数字孪生

在应用软件开发工程中,可视化和用户交互部分往往会占据相当大的比例。写这部分程序逻辑的工程师往往比较受欢迎,因为他们可以快速搭建出一些看得见效果的程序。在当前如火如荼的产业数字化形势下,除了数据工程师,可能最受欢迎的就是做可视化和用户交互开发的工程师了。在过去三十年中,主流的用户交互和可视化软件技术有过几次变迁:

  • 原生的 GUI(来自于操作系统的支持)

在 1990 年代早期,编写用户界面往往要直接面对操作系统提供的 API。为了做出好看又整齐有美感的图形界面,程序员不仅要熟悉操作系统的窗口管理和图形 API,还要精通计算机图形学,甚至还需要懂色彩美学。更要紧的是,程序的交互界面部分的代码量极大,并且这些代码对于硬件显示器和操作系统版本的兼容性非常不友好。

  • 应用编程框架中的 GUI

通过应用编程框架来实现图形用户界面,可以有效地解决直接在原生 GUI 基础上编写交互界面逻辑的不足,因此从 1990 年代中后期开始诞生了很多应用编程框架,最为经典的当属微软的 MFC 应用编程框架,以及跨平台支持的 Qt。Java 环境提供了用户交互和可视化支持,.NET 也提供了强大的图形用户界面开发功能。

  • 图形界面交互引擎

对于用户界面动态性要求高的应用程序,譬如它们的数据是动态的,或者表单是动态可配置的,它们倾向于内置一个图形界面交互引擎,从而将可视化界面的渲染效果和交互事件的处理流程控制在自己的程序内部。比较典型的渲染和交互引擎是 Apple 支持的 WebKit 引擎(其前身是 KHTML)、Adobe 的 Flash 引擎,以及 Google 的 Chromium 浏览器引擎(源自 WebKit 和 V8 引擎)。当 Adobe Flash 还在如日中天(2010 年)的时候,业界曾经引发过 Flash 和 HTML5 哪个是未来的争论。

  • B/S 架构

经过多年的发展,可视化和用户交互逐渐趋向于标准化——HTML+CSS+JavaScript,这正是前文提到的前后端分离的基础。前提是每个前端环境都有 Web 浏览器(Browser),前端逻辑运行在浏览器中,它们通过 HTTP 或 HTTPS 协议与后端(Server)进行通信。这种架构与云计算的服务器虚拟化完美地切合起来。应用程序可以部署在云上,用户只需通过一个浏览器,就可以在任何联网的地方看到程序运行的结果,并且进行交互。

图形用户界面的技术演进经历了“单机图形显示优化——图形用户界面标准化——图形用户界面前后端分离”这一过程,主流的可视化交互界面开发形式在往高效率方向发展,架构也趋向合理。然而,这些开发方式并非简单的替代关系,每一种方式在今天的产业环境中,也仍然有它适用的软硬件环境。

在 PC 互联网时期,可视化和用户交互技术侧重于渲染性能和响应及时性的不断提升;在移动互联网时期,除了性能要求以外,动态性是一项更为侧重的需求,很多应用程序中的页面内容和交互逻辑需要方便地定制;而到了产业互联网时期,可视化和用户交互又有了新的需求和趋势,以下两方面的发展值得特别提一下:

1. 低代码开发平台

顾名思义,低代码开发平台是指只需少量的代码,甚至无需编写代码,就可以完成应用程序的开发。这是在云计算虚拟化成熟以后,发生在应用层软件开发的一种新模式。在产业数字化场景中存在大量动态的数据可视化需求,这就催生出了各种页面定制工具,进一步扩展就形成了低代码开发平台。低代码开发平台的优势在于:低门槛准入、生成页面快捷,以及页面测试流程短等。

2. 数字孪生

数字孪生首先在工业领域中被提出并发展起来,后来被应用于建筑和城市数字化,进而被各行各业所采用。数字孪生将物理世界与数字空间连接起来进行映射,并形成反馈。数字孪生涉及到的技术很广泛,主要包括物联网技术(用于物理世界数字化以及从数字空间反馈回物理世界)、数据建模(构建数字空间中的孪生体)、3D 可视化(将数字空间中的孪生体呈现出来)、GIS 和 BIM(在数字空间中建立起一致的坐标系)等。

过去三十年间,可视化和用户交互技术,从初期要解决最基本的渲染效果、性能以及可用性等问题,到逐渐形成系统性的、高生产效率的解决方案和平台工具,这使得软件开发工程可以花越来越少的人力在可视化和用户交互方面,而更多关注在软件业务本身。得益于图形界面交互引擎的不断发展,以及人工智能技术的进步,未来越来越多的可视化和用户交互工作将由机器来完成,最终做到零代码开发。

在我近几年的实践中,我们团队充分利用了可视化和用户交互技术的进步,在指令集物联网操作系统中内置了孪生模型和低代码开发平台。这样做的一个直接好处是,对于每个用户场景,只要设备接入进来,它就自然成为了孪生模型的一部分,参与到业务模型中,并且能够在可视化界面中显现出来。这种做法可以极大地提高物联场景的数字化效率。

640?wx_fmt=png

640?wx_fmt=png

对程序技术进步的预测

前面回顾了过去三十年重要的程序技术进步,最后我们也展望一下未来。我按照接下来十年可能发生的事情来描述:

  • 操作系统的发展路线将进入一个新的轨道——行业操作系统。

行业操作系统的本质是抓住行业的共性部分,形成一个软件系统,可复制到该行业中具有显著共性的硬件计算环境中。典型的例子是汽车操作系统和智慧建筑操作系统。只有可抽象出硬件计算环境和共性需求的行业场景,才能形成行业操作系统。

640?wx_fmt=jpeg

图源:CSDN 下载自东方 IC

  • 人工智能将会有重大突破,这些突破对人类的生产、生活的改变将是深远的。

这一轮推动人工智能发展的契机是,各个基础研究领域在使用计算能力上有了长足的进步。包括生物、医药、材料、天文、地理等领域中的研究人员,更加深刻地理解了可以用算力和算法来做什么。AlphaFold 是一个例子,该模式可以扩展到其他领域。

  • 公共的数字孪生空间。

很多公共设施设备在数字孪生空间中是开放的,可以被公开网络通过一个 URI 访问到,它们构成了一个公共的数字孪生空间。在十年以后设备接入非常方便,若本地网络的防火墙对一个设备不加限制的话,则该设备接入以后就成为公共数字孪生空间中的一个节点。基于这一公共的数字孪生空间,也会诞生一些应用和服务。与公共的数字孪生空间相对应的是各个组织的私有数字孪生空间。

  • 在中国,写代码的人数在 2-3 年后达到高峰,10 年后降低一个数量级。

这两年产业数字化全面推进,并且都从应用需求做起,势必造成大量的人员去做应用功能,包括各种数据工程和可视化等。随着系统工具的完善以及数字化建设回归理性,写代码人员的机会减少。好工具的诞生并普及,将会削减大量的工作岗位。当然,数字化的深入也会带来新的代码机会,这就看哪些人的再学习能力强了。

  • 诞生基于中文的编程语言,用于应用层逻辑定义。

这个编程语言从某一个行业中诞生,也适用于更多行业。结合 NLP 和其他人工智能技术,我们国家的全民编程有望实现。

以上这些预测是一个程序员的思考,部分也承载着我个人的愿望,甚至是我当前已经在做的工作的延续,或期待要达成的目标。从这个角度来看,这些预测太现实,不够离谱。

640?wx_fmt=png

结语

最后,我提一下在计算机程序和编程技术发展背后的两个基础原则:

  • 随着计算机程序的使用场景和范围越来越广,适应这种广度扩展的基本手段是分层,即增加层次;

  • 在计算机系统的层次结构中,越是下面的层次,越是提供共性的能力,反之,越往上越个性化。

随着产业数字化的广泛推动,行业操作系统就是一个新的层次,而在行业操作系统下面还有一个更加抽象的技术操作系统。所谓计算机程序技术的进步,就是按照这样的原则使得层次和分工趋向合理。

本文分别从软件栈、网络、人工智能以及可视化与用户交互的角度回顾了过去三十年计算机程序的演进,正好对应了我的程序生涯三十年经历中的一些感悟。期待未来十年计算机程序技术继续进步,为人类开启更加美好的生活和体验。

作者简介:潘爱民,指令集创始人兼董事长、之江实验室智能计算数字反应堆首席架构师。长期从事软件和系统技术的研究与开发工作,撰写了大量软件技术文章,著译了多部经典计算机图书,在国内外学术刊物上发表了 30 多篇论文。曾经任教于北京大学和清华大学(兼职),后进入工业界,先后任职于微软亚洲研究院、盛大网络和阿里巴巴。主要研究兴趣包括移动操作系统、信息安全、大数据、移动互联网、物联网和智慧城市。

【END】

本文为《新程序员004》内容,二十年前,《程序员》创刊时,我们要全面关注软件人的成长。今天,我们依然初心不变:在一行行代码的背后,是一颗颗鲜活的开发者想要改变世界的雄心壮志。因此,《新程序员 004》从潘爱民到 MySQL 之父、MariaDB 创始人 Michael "Monty" Widenius,PostgreSQL 全球开发组联合创始人 Bruce Momjian,阿里巴巴副总裁贾扬清,著名科技作者吴军,Vue.js 作者尤雨溪……共谈我们的程序人生,我们的技术时代。《新程序员004》即将上市,敬请期待。

640?wx_fmt=jpeg
— 推荐阅读 —
☞称钉钉将上线“下班勿扰”功能;苹果发生大规模网络宕机;.NET 7 Preview 2发布|极客头条☞速度是 macOS 的两倍?首个支持 M1 Mac 的 Linux 发行版终于出现!
0?wx_fmt=png
CSDN
成就一亿技术人
2961篇原创内容
Official Account

一键三连 「分享」「点赞」「在看」

成就一亿技术人


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK