Welcome! Lately you’ll find me here scribbling down notes on what I’m learning. If you read anything here and have feedback, corrections or thoughts, I’d love to hear from you.
Linux 网络 —— 数据包的发送
背景 Linux 网络–数据包的接收 一文中分析了 Linux 是如何接收一个数据包的,那么 Linux 在发送数据包的时候又是什么样的呢?本文聚焦于 Linux 数据包的发送,结合内核源码分析数据包的发送过程。 关于 Linux 网络数据包的接收可以阅读前文: Linux 网络 —— 数据包的接收 日期: 2024-08-24 标签: #Linux #net 背景 最初学习计算机网络的时候,通常都是从 TCP/IP 网络分层模型入手,学习各种协议,如 TCP、IP等,以及相关的原理,并未过多关注整个协议栈具体是如何实现的。在开发的过程中,通过高级语言往往只需要几行就能实现一个简单的网络程序,并从网络接收数据。然而数据具体是如何从网卡达到我们的进程中的呢?在这个过程中网卡和内核到底又做了些什么呢?数据在这个过程中是如何流转、复制的呢?带着这些问题,笔者最近学习了下 Linux 网络数据包的接收,并总结如下。 通过阅读本文应该能够了解: Linux 网络栈是如何接收数据的,数据从网卡到内核各个部分是如何进行流转的; 网络栈在正式工作之前需要经过哪些初始化,这些初始化工作的目的是什么; 数据包从网卡到内核需要经过几次复制; 网络相关的硬中断、软中断分别是如何实现的,二者是如何配合的; ethtool、tcpdump、iptables 等分别工作在何处,原理是什么。 本文基于内核 3.10 版本,网卡举例使用的是 igb 网卡。 <!DOCTYPE html> 系统初始化 Linux 系统在接收数据包之前需要做很多的准备工作,比如创建内核 ksoftirqd 线程,便于后续处理软中断;网络子系统初始化,注册各个协议的处理函数,便于后面的协议栈处理;网卡设备子系统初始化,便于后面从网卡接收数据包。这里首先对这些初始化的过程进行记录,其中重点是网卡设备的初始化。 内核线程 ksoftirqd 初始化 Linux 在接收网络数据的时候需要用到中断机制,包括硬中断和软中断。 其中 Linux 的软中断都是在内核线程 ksoftirq 中进行的,该线程在系统中有 N 个 (N=机器核数),线程被 CPU 调度。 系统初始化的时候创建了这一线程,之后它就进入到这自己的线程循环函数 ksoftirqd_should_run 和 run_ksoftirqd,判断有无中断需要处理。这里的中断不仅仅包括网络相关的软中断,还有其他的中断类型, 具体可以查看 Linux 的定义。 关于Linux中断相关的原理与实现,推荐阅读下面这篇文章: Linux 中断(IRQ/softirq)基础:原理及内核实现(2022) 网络子系统初始化 Linux 内核通过 subsys_initcall调用来初始化各个子系统,这里我们来看一看网络子系统的初始化 net_dev_init。 static int __init net_dev_init(void) { . ...
Linux 网络 —— 数据包的接收
背景 最初学习计算机网络的时候,通常都是从 TCP/IP 网络分层模型入手,学习各种协议,如 TCP、IP等,以及相关的原理,并未过多关注整个协议栈具体是如何实现的。在开发的过程中,通过高级语言往往只需要几行就能实现一个简单的网络程序,并从网络接收数据。然而数据具体是如何从网卡达到我们的进程中的呢?在这个过程中网卡和内核到底又做了些什么呢?数据在这个过程中是如何流转、复制的呢?带着这些问题,笔者最近学习了下 Linux 网络数据包的接收,并总结如下。 通过阅读本文应该能够了解: Linux 网络栈是如何接收数据的,数据从网卡到内核各个部分是如何进行流转的; 网络栈在正式工作之前需要经过哪些初始化,这些初始化工作的目的是什么; 数据包从网卡到内核需要经过几次复制; 网络相关的硬中断、软中断分别是如何实现的,二者是如何配合的; ethtool、tcpdump、iptables 等分别工作在何处,原理是什么。 本文基于内核 3.10 版本,网卡举例使用的是 igb 网卡。 <!DOCTYPE html> 系统初始化 Linux 系统在接收数据包之前需要做很多的准备工作,比如创建内核 ksoftirqd 线程,便于后续处理软中断;网络子系统初始化,注册各个协议的处理函数,便于后面的协议栈处理;网卡设备子系统初始化,便于后面从网卡接收数据包。这里首先对这些初始化的过程进行记录,其中重点是网卡设备的初始化。 内核线程 ksoftirqd 初始化 Linux 在接收网络数据的时候需要用到中断机制,包括硬中断和软中断。 其中 Linux 的软中断都是在内核线程 ksoftirq 中进行的,该线程在系统中有 N 个 (N=机器核数),线程被 CPU 调度。 系统初始化的时候创建了这一线程,之后它就进入到这自己的线程循环函数 ksoftirqd_should_run 和 run_ksoftirqd,判断有无中断需要处理。这里的中断不仅仅包括网络相关的软中断,还有其他的中断类型, 具体可以查看 Linux 的定义。 关于Linux中断相关的原理与实现,推荐阅读下面这篇文章: Linux 中断(IRQ/softirq)基础:原理及内核实现(2022) 网络子系统初始化 Linux 内核通过 subsys_initcall调用来初始化各个子系统,这里我们来看一看网络子系统的初始化 net_dev_init。 static int __init net_dev_init(void) { ... ... for_each_possible_cpu(i) { struct softnet_data *sd = &per_cpu(softnet_data, i); memset(sd, 0, sizeof(*sd)); skb_queue_head_init(&sd->input_pkt_queue); skb_queue_head_init(&sd->process_queue); sd->completion_queue = NULL; INIT_LIST_HEAD(&sd->poll_list); sd->output_queue = NULL; sd->output_queue_tailp = &sd->output_queue; #ifdef CONFIG_RPS sd->csd. ...
Python 中的元类
元类(metaclass)是 Python 中一个高级概念,它控制了类的创建过程。在 Python 的开源框架中,可以看到很多元类的相关身影,学习并理解元类也可以帮助理解 Python 中的类的创建过程。 一切皆对象 在 Python 中一切皆对象,类也不例外,类也是一个对象,可以像普通对象一样进行赋值、拷贝以及作为函数的参数使用。我们知道每个对象都有一个类型,在 Python 中可以通过 type() 函数进行查看,一个 Python 实例它的类型是 class 类名,那类这个对象的类型又是什么呢,可以通过下面的一组例子进行查看。 class Foo: def __init__(self): pass foo = Foo() # 也可以通过 obj.__class__ 查看类型 print(type(foo)) # <class '__main__.Foo'> print(type(Foo)) # <class 'type'> print(type(1)) # <class 'int'> print(type(int)) # <class 'type'> print(type(type)) # <class 'type'> 我们可以看到实例 foo 的类型是class Foo, 而 Foo (类本身) 的类型是 type ,更近一步地,查看内置类型以及 type 本身的类型。我们可以发现,类的类型是 type,这实际上就是元类。正如普通对象是类的实例一样,Python 中的任何类都是type元类的实例,我们可以用下面的图来表示这种关系。 <!DOCTYPE html> 图1. Python 类型示意图 创建类:type 类也是对象,可以在运行时动态的创建它们,就像其他任何实例对象一样,例如可以在函数中创建类。另外,借助type,也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。可以像这样使用:type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值)),使用这种方式上一节中的类可以使用下面的方式创建: ...
Kubernetes 整体架构与核心组件
Kubernetes 整体架构 从整体来看 Kubernetes (k8s) 主要由两种节点组成,分别是 Master 节点和 Node 节点,其整体结构如图1 所示。Master 节点由三个紧密协作的独立组件组合而成,分别是:kube-apiserver、kube-scheduler 以及 kube-controller-manager;Node 节点上最核心的是名为 kubelet 的组件以及 kube-proxy。下面结合图1 对一些组件的功能进行说明。 <!DOCTYPE html> 图1. kubernetes (k8s) 整体架构图(来源于网络) 核心组件 kubectl & cliet-go kubectl 是 k8s 官方提供的命令行工具,它通过 HTTP/JSON 通信协议以命令行的方式来与 kube-apiserver 交互。 client-go 则是通过编程的方式来与 API server 进行交互,使开发者能够以编程的方式管理和操作集群中的资源,它实现的功能与 kubectl 相同。k8s 的其他组件与 api server 进行通信也是基于 client-go 实现的。如果需要对 k8s 进行二次开发,也可以使用 client-go。 Master 节点核心组件 kube-apiserver Kube-apiserver 主要负责 API 服务,集群中所有的组件都是通过 API server 组件来操作资源对象,它也是集群中唯一与 ETCD 集群进行交互的核心组件。k8s 中所有的资源对象被封装成 RESTful 风格的 API 接口进行管理。同时,API server 还提供了集群的安全访问机制,如认证、授权、准入控制、审计等。 ...
[译]阅读的重要性
原文:The Need To Read 在我小的时候阅读的科幻小说中,阅读常常被一些更高效获取知识的方式所取代。神秘的“磁带”会将知识加载到一个人的大脑中,就像将程序加载到计算机中一样。 这种情况很可能不会很快发生。不仅因为构建阅读的替代品很困难,而且即使存在一个替代品,也是不够的。阅读关于 x 的内容不仅教给你关于 x 的知识,还教给你如何写作1。 那又怎样?如果我们替代了阅读,还需要有人擅长写作吗? 之所以重要,是因为写作不仅是传达思想的一种方式,还是产生思想的一种方式。 一个好的作家不只是思考,然后将他的想法写下来,作为记录。一个好的作家在写作的过程中几乎总是会发现新事物。据我所知,对这种发现没有替代品。与其他人讨论你的想法是发展它们的好方法。但即使这样做了,当你坐下来写作时,你仍然会发现新事物。有一种思考只能通过写作来完成。 当然有些思考是可以在没有写作的情况下完成的。如果你不需要深入研究一个问题,你可以在不写作的情况下解决它。如果你在考虑两台机器应该如何组合在一起,写作可能对此帮助也不大。当一个问题可以被正式描述时,有时你可以在脑中解决它。但是,如果你需要解决一个复杂而不明确的问题,写作几乎总是有助于解决问题。反过来这意味着在解决这类问题时,写作能力较差的人几乎总是处于劣势。 没有良好的写作就无法进行良好的思考,没有良好的阅读就无法进行良好的写作。我指的是最后一个“好”在两个方面都是如此。你必须擅长阅读,而且要读好的东西2。 只想获取信息的人可能会找到其他获取途径。但想要产生思想的人不能不重视。 有声读物可以为你提供优秀写作的示例,但让别人为你朗读并不能像自己阅读那样教你很多关于写作的东西。 ↩︎ 这里的“擅长阅读”不是指擅长阅读的技巧,相比于阅读技巧,获取文字含义更加重要。 ↩︎
rsync 用法
在不同的机器和服务器之间进行数据同步是一个常见的场景,也有不同的命令工具(如 scp、FTP 等),甚至是不同的图形界面软件(如 Termius 等)可供我们选择操作。本文记录的 rsync (remote sync)也是用于文件同步的工具之一,其最大的特点是会检查发送方和接收已有的文件,并仅传输有变动的部分。rsync 不仅可以在本地不同的目录之间使用,同时也支持本地主机与远程服务器之间进行数据同步。由于其具有仅传输变动、增量备份、 断点续传等特点,在一些场景下非常实用。本文记录了其一些常见的用法,主要关注其在本地主机与远程主机之间同步的操作。 在使用 rysnc 之前需要确保本地和远程主机都安装了 rsync,可通过 rsync --version 检查是否安装,如果没有,可使用下面的命令进行安装: sudo apt-get install rsync # ubuntu brew install rsync # macOS rsync 的基本使用如下: rsync -av [source] [destination] -a: 表示递归同步,并同步元信息 -v: 输出到终端 如果目标位置不存在,执行上诉命令之后将会自动创建。 常用参数 -n 如果不确定执行 rsync 会有什么结果,可以使用该参数查看执行后的结果,并不真正执行命令。 x – + yandaojiang@Daojiang-macbook-pro [ydj-macbook-pro ~ % rsync -anv ./Desktop ubuntu@110.42.182.66:~/ sending incremental file list Desktop/ Desktop/.DS_Store Desktop/.localized Desktop/截屏2023-03-06 14.40.26.png sent 179 bytes received 29 bytes 83.20 bytes/sec total size is 600,104 speedup is 2,885.12 (DRY RUN) [ydj-macbook-pro ~ % --delete 默认情况下,rsync 只是将源目录的所有内容都复制到目标目录,并不会保证两个目录相同,也不会删除文件。因此,如果是想要构建源目录的镜像就需要使用 --delete 参数,这样保证目标目录和源目录相同。 --exclude --exclude参数用于排除文件或者目录,利用该参数指定排除模式。排除多个模式的文件时,可以使用 Bash 的扩展功能。如果需要排除的文件数量比较多,那么可以将其需要排除的文件所对应的模式写入文件,每个模式负责一行,然后利用 --exclude-from 参数指定文件。 与 --exclude 参数所类似的有 --include 参数,用于指定必须同步的文件,二者一般联合使用。 ...
Linux 60秒性能分析
原文:Linux Performance Analysis in 60,000 Milliseconds 当你登陆到一台性能出现问题的 Linux 服务器:你在第一分钟会检查什么? 在 Netflix,我们拥有一个庞大的 EC2 Linux 云和大量的性能分析工具,用于监控和调查其性能。其中包括 Atlas 用于云范围的监控和 Vector 用于按需实例分析。虽然这些工具帮助我们解决了大多数问题,但有时我们需要登录到一台实例并运行一些标准的 Linux 性能工具。 60秒摘要 这篇文章将介绍 Netflix 性能工程团队在命令行下使用标准 Linux 工具进行优化性能调查的前60秒,你也可以使用这些工具。在60秒内,你可以通过运行以下十个命令了解系统资源使用情况和正在运行的进程。查找错误和饱和度指标,然后查看资源利用率。饱和是指资源的负载超过其处理能力,可表现为请求队列的长度或等待时间。 uptime dmesg | tail vmstat 1 mpstat -P ALL 1 pidstat 1 iostat -xz 1 free -m sar -n DEV 1 sar -n TCP,ETCP 1 top 其中一些命令需要安装 sysstat 软件包。这些命令输出的指标将有助于完成一些性能瓶颈的定位(USE 方法)。这涉及检查所有资源(CPU,内存,磁盘等)的利用率、饱和度和错误指标。还要注意何时检查和排除了资源,因为通过排除,可以缩小要研究的目标,并指导任何后续的分析。 以下部分总结了这些命令,在生产系统中给出了示例。有关这些工具的更多信息,请参阅它们的 man 页面。 1. uptime $ uptime 23:51:26 up 21:31, 1 user, load average: 30. ...
Git 常用命令总结
配置 git config --list # 查看配置 git config -e [--global] # 编辑配置文件 # 设置提交代码时的用户信息 git config [--global] user.name "[name]" git config [--global] user.email "[email address]" 版本管理 版本回退 git reset 版本回退是指将版本库恢复到上个或者某个 git commit 之后。Git 中每次进行 commit 操作会在版本库中保存一个快照,如果把文件改乱了,或者误删了文件,可以从某个 commit 恢复,用到的操作命令如下: git reset [--soft | --mixed | --hard] <commit-id | HEAD> 说明: --mixed: 默认参数,不删除工作空间改动代码,撤销 commit,并且撤销 git add . --soft: 不删除工作空间改动代码,撤销 commit,不撤销 git add . --hard: 删除工作空间改动代码,撤销 commit,撤销 git add . (即完全恢复到某次 git commit 之后) 如果使用 --mixed 或者 --soft 参数回到多个版本之前时,需要注意后面多次修改的内容都会留在工作区或者暂存区 其中 commit-id 可用命令 git log --pretty=oneline 查看。回退的版本较近时可以用 HEAD 表示,在 Git 中,用 HEAD 表示当前版本,上一个版本就是 HEAD^,上上一个版本就是 HEAD^^,依次类推。 ...
Hugo Shortcodes 示例
大多数的静态博客会使用 Markdown 进行写作,因为它的格式简单,但是 Markdown 也有它的不足之处。当我们想要将图片或者视频等加入到文章内容中时,Markdown 本身并不能提供很好的支持,因此不得不将一些 HTML 片段加入到 Markdown 内容中。尽管在一些简单的场景中这样做能够解决问题,但是却破坏了 Markdown 文件的简洁性质。另外如果想对一些内容进行装饰也变得非常麻烦,而且不能起到复用的效果。为此,Hugo 引入了 shortcodes,允许我们在 Markdown 文件中以一种简洁的形式书写 HTML。本文记录了此博客中实现和使用到的一些 shortcodes。 图片 在博客内容中插入图片是一个常见的需求,Markdown 语法本身也支持图片的插入。但是如果要对图像的大小,图像的位置进行修改就需要在 Markdown 文件中直接使用 HTML 进行控制,如果更进一步对图像添加描述,或者使用 CSS 对图像进行修饰就变得比较麻烦,为此本博客中针对图像显示问题实现了多个 shortcodes,用于增强博客的图像显示功能。下面是实现的用于插入图片的 shortcodes 的实际效果: 样式一: <!DOCTYPE html> 图1. 通过 Hugo shortcode 插入图片,并在图片底部加入图片描述 样式二: <!DOCTYPE html> Youtube 为了进一步丰富博客的内容,本博客还添加了对视频的支持,在博客内容中对 Youtube 的视频内容进行嵌入,并支持播放和调转到官方网站进行播放,下面是实际效果: Youtube 视频 (由于国内无法直接播放 Youtube 视频,这里采用图片的方式对最终的效果进行了展示) Podcast / Music Markdown 本身并不支持对音频的插入,使用 HTML 虽然能够简单的完成音频的插入,但是其显示效果并不美观,并且仅仅支持播放、暂停。为了让博客支持插入播客等音频内容,本博客实现了一个播放器,效果如下: <!DOCTYPE html> Hold My Hand Lady Gaga Your browser does not support the audio tag. ...
Channel原理与实现
数据结构 channel内部的数据结构表示如下: type hchan struct { qcount uint // channel中元素的个数 dataqsiz uint // channel中循环队列的长度 buf unsafe.Pointer // channel缓冲数据指针 elemsize uint16 // 收发的元素大小 closed uint32 elemtype *_type // 收发的元素类型 sendx uint // channel的发送操作处理到缓冲位置 recvx uint // channel的接收操作处理到缓冲位置 recvq waitq // 当前因为缓冲区不足而阻塞的Gorontine列表 sendq waitq lock mutex } 阻塞的 GoRoutine 等待队列使用双向链表表示,链表中所有的元素是 runtime.sudog 结构: type waitq struct { first *sudog last *sudog } 该结构中存储了两个分别指向前后 runtime.sudog 的指针构成的链表。 创建channel Go 语言中所有的 channel 都使用 make 关键字进行创建,并转换为 runtime.makechan 或者 runtime. ...
个人博客技术选型与实践
技术选型与可替代方案 在搭建博客的过程中涉及到很多的选择,例如我们会考虑博客的类型,是使用静态博客还是动态博客。之后又涉及到博客框架的选择,当前有很多的框架可供我们选择,如果所有的选择都不满意甚至可以考虑自己从头设计一个或者基于一个框架或者主题等进行个性化的修改。我们的博客最终是需要通过互联网可访问的,这又涉及到我们将其部署在哪里,采用何种方式进行部署。下表展示了本站的技术选型以及其他一些可替代的方案。在本文的剩余内容中,我也将从这几个方面对本站的建设进行说明。 技术选型 替代方案 类型 静态博客 WordPress 等动态博客 框架 Hugo Hexo, Jekyll 评论 Twikoo Valine 等第三方评论系统 部署平台 腾讯云 GitHubPages,阿里云 / 华为云 / AWS / Azure / Digital Ocean 等主流云平台 存储 腾讯云 COS 阿里云OSS,七牛云等 持续集成 Github Actions Travis CI,Gitlab CI 等 主题 PaperMode 博客类型选择 目前个人搭建博客通常会有两种选择:静态博客和动态博客。静态博客其格式一般为静态网页。动态博客则是通过与后端数据库交互,再动态渲染而成的。每当用户访问博客时浏览器会发送 HTTP 请求到后端服务,后端服务查询数据库,渲染页面返回给用户的浏览器。因此,动态博客需要更多计算资源和存储资源。相比而言,静态博客的资源占用更少,访问速度也更快,同时安全性更高。因此本站选择的是静态博客这种类型。 框架选择 目前有很多的博客框架可供我们进行选择,那么我们该如何进行选择呢?通常而言我认为需要从以下几个方面进行考虑: 框架的使用者数量要多。选择一个大家都在使用的框架在后面遇到问题的时候能够更好的寻找解决方案。同时,一个使用者数量多的博客生成框架通常也意味着会有更多的主题可供选择。 框架的文档完善。在考虑各个框架的时候我们还要看它的文档是否是完善的。一般而言,文档通常都是英文的,对于英文不好的人还可以考虑是否有完善的中文文档。 框架的性能。由于我们最终部署的是静态网页,因此我认为对于一个框架的性能不是我们主要需要考虑的。 在综合考虑了上面的几个方面后,我最终选择了Hugo 作为本站的构建框架。Hugo 是最流行的开源静态站点生成器之一,有着丰富的主题可供选择,而且其构建过程简单灵活。 评论系统 为了在博客上提供一个交流平台,通常都需要建设一个评论系统。动态博客其服务器上通常有评论相关的数据库可以非常方便的实现评论功能,但是对于静态博客,通常就需要一些第三方的评论系统来进行支持。这方面的选择非常多,但是很多都不是一个完美的方案。对于本站最终选择的是Twikoo,一个简洁、安全、免费的静态网站评论系统,是基于腾讯云开发的。该评论系统的文档是中文的,并且比较完善,参考起文档可以非常方便的进行部署。 部署平台与存储 在网站的部署平台上可分为两大类,一类是使用 GitHub Pages 这样的服务,部署非常的简单,但是问题是访问速度可能比较慢。另外一类方案就是借助一些云平台提供的服务进行部署。国内可供选择的有阿里云、腾讯云、华为云等,但是在部署网站的时候需要进行备案。国外也有很多云平台可供选择,甚至价格更低,但是通常访问速度可能偏慢。 对于一些内容的存储如图片等,我们可以直接选择使用服务器进行存储,但是如果数量比较大的话可能加重服务器的存储负担,因此另外一种选择就是使用一些云厂商提供的对象存储服务。本站采用了腾讯云的 COS 服务作为图像等内容的存储。 持续集成 我们搭建了博客之后通常是需要时常进行更新的,这就涉及到网站的部署与集成,如果每次进行更新之后都手动进行服务器的上传就显得比较麻烦,因此我们可以考虑采用持续集成的方案。如果你使用 Github 托管静态博客的代码,那么 Github Actions 更是你自动化部署的不二选择!使用 GitHub Actions 之后每次我们只用在内容上进行更新,然后将其 Push 到远程仓库的对应仓库就能实现对应的自动化部署,非常方便。当然也有其他非常好的 CI/CD 工具可以选择,如 Travis CI 等。 ...
职业的乐趣与苦恼
摘自 FrederickP.Brooks.Jr 的《人月神话》第一章 焦油坑 职业的乐趣 编程为什么有趣?作为回报,它的从业者期望得到什么样的快乐? 首先是一种创建事物的纯粹快乐。如同小孩在玩泥巴时感到愉快一样,成年人喜欢创建事物,特别是自己进行设计。我想这种快乐是上帝创造世界的折射,一种呈现在每片独特、崭新的树叶和雪花上的喜悦。 其次,快乐来自于开发对其他人有用的东西。内心深处,我们期望其他人使用我们的劳动成果,并能对他们有所帮助。从这个方面,这同小孩用粘土为"爸爸办公室"捏制铅笔盒没有本质的区别。 第三是整个过程体现出魔术般的力量。将相互啮合的零部件组装在一起,看到它们精妙地运行,得到预先所希望的结果。比起弹珠游戏或点唱机所具有的迷人魅力,程序化的计算机毫不逊色。 第四是学习的乐趣,来自于这项工作的非重复特性。人们所面临的问题,在某个或其它方面总有些不同。因而解决问题的人可以从中学习新的事物:有时是实践上的,有时是理论上的,或者兼而有之。 最后,乐趣还来自于工作在如此易于驾驭的介质上。程序员,就像诗人一样,几乎仅仅工作在单纯的思考中。程序员凭空地运用自己的想象,来建造自己的"城堡"。很少有这样的介质–创造的方式如此得灵活,如此得易于精炼和重建,如此得容易实现概念上的设想。(不过我们将会看到,容易驾驭的特性也有它自己的问题)然而程序毕竟同诗歌不同,它是实实在在的东西;可以移动和运行,能独立产生可见的输出;能打印结果,绘制图形,发出声音,移动支架。神话和传说中的魔术在我们的时代已变成了现实。在键盘上键入正确的咒语,屏幕会活动、变幻,显示出前所未有的或是已经存在的事物。 编程非常有趣,在于它不仅满足了我们内心深处进行创造的渴望,而且还愉悦了每个人内在的情感。 职业的苦恼 然而这个过程并不全都是喜悦。我们只有事先了解一些编程固有的烦恼,这样,当它们真的出现时,才能更加坦然地面对。 首先,必须追求完美。因为计算机也是以这样的方式来变戏法:如果咒语中的一个字符、一个停顿,没有与正确的形式一致,魔术就不会出现。(现实中,很少的人类活动要求完美,所以人类对它本来就不习惯。)实际上,我认为学习编程的最困难部分,是将做事的方式往追求完美的方向调整。 其次,是由他人来设定目标,供给资源,提供信息。编程人员很少能控制工作环境和工作目标。用管理的术语来说,个人的权威和他所承担的责任是不相配的。不过,似乎在所有的领域中,对要完成的工作,很少能提供与责任相一致的正式权威。而现实情况中,实际(相对于正式)的权威来自于每次任务的完成。 对于系统编程人员而言,对其他人的依赖是一件非常痛苦的事情。他依靠其他人的程序,而往往这些程序设计得并不合理,实现拙劣,发布不完整(没有源代码或测试用例),或者文档记录得很糟。所以,系统编程人员不得不花费时间去研究和修改,而它们在理想情况下本应该是可靠完整的。 下一个烦恼–概念性设计是有趣的,但寻找琐碎的bug却只是一项重复性的活动。伴随着创造性活动的,往往是枯燥沉闷的时间和艰苦的劳动。程序编制工作也不例外。 另外,人们发现调试和查错往往是线性收敛的,或者更糟糕的是,具有二次方的复杂度 。结果,测试一拖再拖,寻找最后一个错误比第一个错误将花费更多的时间。 最后一个苦恼,有时也是一种无奈——当投入了大量辛苦的劳动,产品在即将完成或者终于完成的时候,却已显得陈旧过时。可能是同事和竞争对手已在追逐新的、更好的构思;也许替代方案不仅仅是在构思,而且已经在安排了。 现实情况比上面所说的通常要好一些。当产品开发完成时,更优秀的新产品通常还不能投入使用。事实上,只有实际需要时,才会用到最新的设想,因为所实现的系统已经能满足要求,体现了回报。 诚然,产品开发所基于的技术在不断地进步。一旦设计被冻结,在概念上就已经开始陈旧了。不过,实际产品需要一步一步按阶段实现。实现落后与否的判断应根据其它已有的系统,而不是未实现的概念。因此,我们所面临的挑战和任务是在现有的时间和有效的资源范围内,寻找解决实际问题的切实可行方案。 这,就是编程。一个许多人痛苦挣扎的焦油坑以及一种乐趣和苦恼共存的创造性活动。