十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

框架会过时,计算机基础才是程序员的核心竞争力

框架会过时,计算机基础才是程序员的核心竞争力 刚工作头两年的我电脑里装了一堆框架GitHub 上 star 了一堆项目张口闭口都是最新的技术名词但你要突然问我一句“int 在内存里到底占几个字节、负数是怎么存的”我真得愣一下。后来被线上问题按在地上摩擦了几次才慢慢明白一个很朴素的道理框架是时髦衣裳计算机基础才是贴身棉袄天冷的时候才知道它多管用。这也是我想跟你聊这个话题的原因。标题里开玩笑说这是“老祖宗的技术家底”其实不算夸张。你去看那些能解决复杂问题的人聊天聊到最后拼的往往不是谁新框架用得更溜而是对操作系统、网络、内存、数据结构这些底层知识的理解深度。新人容易觉得这些课又老又枯燥但等真正工作三五年再回头看会发现当年课本上划过的重点全都在工作里等着你呢。这篇文章不是什么教科书就是我以一个过来人的身份把计算机基础里那些真正会在日常开发里反复用到的东西重新捋一遍重点说清楚“它到底是什么”“为什么重要”“遇到真实问题的时候怎么用它来排查”。适合刚入行的程序员也适合工作两三年但总觉得基础不扎实的朋友。你边看可以边回想自己踩过的坑大概率会有种“原来当初是栽在这上面”的感觉。1. 技术迭代这么快基础为什么反而更值钱1.1 你写的每一行代码最后都会被翻译成什么现在的开发体验已经很舒服了。写 Java 有 Spring Boot 一把梭写前端有各种框架和脚手架写 Python 更是几行代码就能调起一个服务。但不管上层多花哨最终所有代码都要落到 CPU 能执行的机器指令上。机器指令只有 0 和 1CPU 不认识什么“对象”“接口”“依赖注入”它只认识操作码、内存地址和寄存器。这意味着什么意味着你写的任何高级语言代码本质上都是在跟一个极其“笨”但又极其快的机器解释你的意图。它不聪明但执行力强你把逻辑写对了它就好好跑写错了它就按你能想到的最糟糕的方式报错给你看。很多新手遇到诡异 bug 第一反应是“编译器有问题”“框架有问题”“服务器有问题”但绝大多数情况问题出在开发者自己身上。而能不能看出问题在哪靠的就是对基础的理解。比如一个 Java 新手可能会被“栈溢出”和“堆溢出”搞糊涂但只要理解函数调用栈和堆的区别看一眼报错类型就能大概判断问题出在哪。再比如很多人被 ConcurrentModificationException 折磨过如果理解集合在迭代时通过 modCount 做快速失败检测就不会在 foreach 里 remove 元素了。这些都不是什么高深的东西就是基础课的延伸。1.2 框架年年换基础知识的“保质期”却很长你去翻十年前的技术文章Spring 还是 XML 配置前端还在用 jQuery数据库分库分表还在起步阶段。但你再去看操作系统原理、TCP/IP 协议、数据结构这些内容跟现在的教材几乎没太大变化。内存依旧分堆和栈TCP 依然要三次握手HashMap 的扩容阈值还是 0.75。这些知识像地基不管上面的楼层怎么装修、怎么改格局地基永远是那一套。我见过一个让我印象很深的事。有次同事排查线上 OOM他第一反应是“是不是 JVM 参数没配好”于是反复调-Xmx结果问题还是隔三差五出现。后来我帮他看了下堆快照发现是一个业务缓存把大量历史数据全塞进了静态 Map根本没设上限内存当然越吃越多。这类问题如果你脑子里没有“堆是运行时对象存放的地方”“静态变量生命周期很长”这两个基础概念就很容易在表面参数上打转始终摸不到关键点。框架能让你快速做出东西基础能让你知道东西为什么会坏、坏了怎么修。这两者不矛盾但绝大多数人只重视前者等到出事了才开始后悔当初没好好学基础。2. 数据在计算机里到底怎么“躺”进制、编码与字节序2.1 一个 int 溢出让你见识补码的厉害很多人在学校学过原码、反码、补码但考完就忘了总觉得这东西平时用不上。直到某天线上数据突然变成一个很大的负数或者一个无符号整数读出来是个天文数字才想起来好像跟“补码”有一腿。我举个最简单的例子。Java 里的 int 默认是 4 字节也就是 32 位用补码表示有符号整数范围是 -2147483648 到 2147483647。你如果给一个 int 加 1加到顶了会直接溢出变成 -2147483648。很多人第一次看到这个结果觉得是“灵异事件”其实底层逻辑很简单32 位二进制全为 1 的那个数按有符号整数读就是 -1按无符号整数读就是 4294967295。同一个二进制用不同“视角”去看得到的结果完全不一样。这就是补码设计的巧妙之处。补码让正负数在加法运算上完全统一不需要为减法单独设计电路。计算机里没有“减法器”它只有加法器减去一个数就是加上这个数的补码。理解了这一点你就不难理解为什么 C 语言里int和unsigned int混用会出现特别反直觉的结果。比如下面这段代码#include stdio.h int main() { int a -1; unsigned int b 1; if (a b) { printf(a b\n); } else { printf(a b\n); } return 0; }你直觉会觉得 -1 肯定小于 1对吧但实际运行结果是a b。因为在比较时a被转成了无符号整数变成了 4294967295当然比 1 大。这类坑在底层开发、网络协议解析、文件格式处理里太常见了。2.2 乱码折磨你一天编码原理十分钟乱码这个问题我觉得是每个开发者都会遇到的“老朋友”。你用 Java 读一个文本文件或者从接口拿一串字符串打印出来全是“锟斤拷”“烫烫烫”这时候十有八九是编码不对。要理解乱码先弄明白几个基本事实。ASCII 码总共 127 个字符一个字节就装得下但它只覆盖英文和基本符号。中文这个量级一个字节根本不够于是出现了各种中文编码方案。GBK 用两个字节表示一个汉字UTF-8 是变长编码一个汉字通常占三个字节。关键问题来了同一个“中”字在 GBK 里的字节序列和 UTF-8 里的字节序列不一样。你用 GBK 去解码 UTF-8 的字节流得到的自然就是一堆乱码。我之前处理过一个文件导出的 bug用户在页面上传一个 CSV 文件服务端读出来解析时中文全乱了。查了半天最后发现用户上传的文件是 UTF-8 编码而服务端读取时默认用了操作系统平台的 GBK 字符集。解决方案也很简单读取时显式指定 UTF-8BufferedReader reader new BufferedReader( new InputStreamReader(new FileInputStream(file), UTF-8) );这件事给我的教训是凡是涉及文件读写、网络传输、数据库存储编码永远要显式指定不要依赖默认值。默认值这东西在 Windows 和 Linux 上不一样不同版本的 JDK 也可能不一样一旦环境变了数据就乱了。还有网页返回的 Content-Type 里如果有 charset 参数一定要关注数据库连接串里的 characterEncoding 参数也一定要写明确。2.3 字节序跨端通信时字节顺序也会“打架”字节序这个问题很多人只在《计算机网络》课上听过“大端”“小端”两个词但真到用的时候才发现容易翻车。我来说个简单的例子。数字 0x12345678 要存到内存里有两种存法一种是按高位到低位存也就是12 34 56 78这叫大端序另一种是按低位到高位存也就是78 56 34 12这叫小端序。x86 和绝大多数 ARM 处理器用的是小端序而网络协议规定用大端序。为什么网络协议要用大端序这是个历史约定当年设计 TCP/IP 协议的人定了一套标准字节序大家都按这个来不同机器之间通信才不至于混乱。所以你在 C 语言网络编程里会看到htonl、htons、ntohl、ntohs这一系列函数作用就是在主机字节序和网络字节序之间转换。我们平时写业务代码可能很少直接碰字节序但一旦涉及二进制协议、音视频流、嵌入式设备通信这个问题就绕不开了。记得有一次朋友找我帮忙看一个硬件上报的数据不对设备端上报一个温度值服务端解析出来差得离谱。后来用十六进制抓包一看发现设备端按小端序发送服务端按大端序解析字节顺序反了数值自然不对。解决方式是在解析层统一做一次交换。所以今后你要是遇到类似情况先别急着怀疑算法看一看到底是“端”的问题还是“序”的问题。3. 程序从源码到运行编译、链接与内存布局3.1 编译器做的事远比你想象得多很多人写 Java、Go、Python很少去关心代码是怎么变成可执行程序的总觉得这是编译器的“黑魔法”。但黑魔法用多了迟早会遇到魔法失效的时候。理解编译器的工作流程对你排查某些问题会特别有帮助。一个 C 程序从源码到可执行文件要经过预处理、编译、汇编、链接四个阶段。预处理处理#include和宏定义编译把 C 代码翻译成汇编汇编把汇编变成机器码链接把多个目标文件和库文件拼在一起生成最终的可执行程序。链接又分静态链接和动态链接。静态链接把库代码直接打包进可执行文件里文件大但部署简单动态链接在运行时才把共享库加载进来文件小但运行时可能因为找不到.so或.dll文件而报错。作为一个 Java 开发者你可能觉得这些离你很远但其实思路是相通的。Java 里的 ClassLoader 负责把.class文件加载到 JVM它有双亲委派模型目的是避免类被重复加载也防止核心 API 被篡改。你遇到NoClassDefFoundError、ClassNotFoundException的时候如果理解这套机制排查方向就会非常清晰要么是 jar 包没打进去要么是类加载器用错了。我印象比较深的一次是项目里用了一个自定义 ClassLoader 做热部署结果新版本的类一直没生效。排查了半天发现是因为“双亲委派”机制导致自定义加载器里的类最终被父加载器加载了代码改了也没用。学基础的时候觉得这些东西“离业务十万八千里”真踩到坑才发现它就是问题的核心。3.2 函数调用栈与递归爆栈的真相“栈溢出”这个词凡是写过递归的人应该都不陌生。但你有没有想过为什么函数调用用的是“栈”因为函数调用天然符合“后进先出”的规律——你调用 A 函数A 函数里又调用了 B 函数B 执行完了要返回到 A 继续执行最后才是 A 返回。这个“最后调用的最先返回”的模式跟栈这个数据结构完全吻合。每次函数调用操作系统会为这次调用分配一块栈帧栈帧里存放局部变量、函数参数、返回地址等等。函数返回后这块栈帧就会被释放。问题是栈的空间是有限的。Linux 上默认的栈空间大概是 8MB你可以用ulimit -s查看。如果一个递归函数没有终止条件或者递归深度太大每一层调用都往栈上压一帧很快就把栈空间消耗光了于是出现StackOverflowError。我记得大学时有一次跑一个二叉树遍历的递归程序数据量一百万结果程序直接崩了。当时不理解为什么递归会崩后来看了系统栈大小才明白每次递归调用都要存一堆状态几百万层的深度栈根本扛不住。从那以后我写递归会特别留意深度该改成迭代就改迭代该用尾递归优化的就优化。业务代码里虽然不会频繁写递归但理解栈帧的机制能帮你在遇到栈溢出时快速判断是递归太深、局部变量太大还是线程栈设置不合理而不是急着去搜“StackOverflowError 解决方案”一通乱试。3.3 堆、栈、全局区OOM 到底发生在哪程序的内存布局简单来说分这么几块代码段存机器指令数据段存全局变量和静态变量堆是运行时动态分配内存的地方栈是函数调用的工作区。对应到 Java 里堆存放对象实例栈存放基本类型的局部变量和对象引用方法区JDK 8 以后叫元空间存放类信息、常量、静态变量。理解这个布局对解决 OOM 问题特别有帮助。因为 OOM 并不是铁板一块它分好几种场景OOM 类型发生位置常见原因排查方向Java heap space堆对象太多、内存泄漏、堆设置过小用 jmap 导堆快照用 MAT 分析对象引用链stack overflow栈递归过深、栈帧过大看线程 dump定位递归调用点Metaspace方法区/元空间动态生成类太多、类加载器泄漏检查反射、CGLIB 等动态代理的使用Direct buffer memory堆外堆外内存分配过多、未释放检查 DirectByteBuffer 使用调整 MaxDirectMemorySize有一次我在线上系统里看到java.lang.OutOfMemoryError: Metaspace当时第一反应是“堆内存不够吗”后来一想不对Metaspace 是存放类的元数据的地方。查了一圈发现是这个服务用了 CGLIB 做动态代理每次请求都生成新的类类加载器一直没被回收元空间越占越多。如果不懂内存布局这类问题真的很让人摸不着头脑因为日志里根本不会提示你去查 CGLIB。4. 操作系统与并发为什么程序会“卡”4.1 进程线程与上下文切换多线程不是越多越好很多人学并发的时候记住了一句话“开线程能提高性能”于是遇到任务就开线程结果发现程序反而更慢了。要理解为什么就得从进程和线程的本质说起。进程是操作系统分配资源的最小单位每个进程有自己独立的内存空间线程是 CPU 调度的最小单位同一个进程里的多个线程共享内存空间。你要跑一个多线程程序其实是在让操作系统在多个线程之间来回切换执行。每次切换操作系统都要保存当前线程的状态恢复下一个线程的状态这个过程叫上下文切换它有成本。更现实的问题是一台机器上的 CPU 核心数是有限的。假设你有个 8 核的机器理想情况下同时只有 8 个线程在真正执行。如果你开了 100 个线程多出来的 92 个线程并不是“在跑”而是在排队等待被调度。如果这些线程之间还有锁竞争那情况就更糟线程 A 占着锁等线程 B线程 B 又在等别的资源可能就死锁了。我自己有个很深的体会开发的时候容易“想当然”地加线程但到了线上线程调度、锁竞争、CPU 缓存失效这些因素都会拖慢系统。所以写多线程代码之前先问自己一个问题“这个任务真的一定要并发吗是 CPU 密集还是 IO 密集”CPU 密集的任务线程数接近核心数就行IO 密集的任务可以适当多开因为线程大部分时间在等待 IO。这些判断本质上是操作系统基础知识在工作中的延伸。4.2 一次 CPU 飙高的复盘基础知识如何救我讲一个实际案例。去年有个服务突然收到告警CPU 使用率直接飙到接近 100%接口响应变得特别慢。当时我第一反应是“是不是有死循环”于是用top -H找到 CPU 占用最高的线程 ID再用jstack把那个线程的堆栈打印出来。结果发现线程卡在一个HashMap.get方法上代码里这个 HashMap 被多个线程共享而且会有高并发的写入。这里面的问题如果你只把 HashMap 当成“能存键值对的容器”就很难理解为什么它会卡死。但如果你知道 JDK 7 的 HashMap 在并发扩容时可能出现链表环导致 get 操作在链表里无限循环就知道这事有多严重。JDK 8 修了一部分问题但在并发场景下 HashMap 依然不是线程安全的数据丢失、覆盖都是常态。正确做法是换成ConcurrentHashMap或者用Collections.synchronizedMap加锁。那次排查下来直接修改代码、重新上线问题就消失了。但这件事给我最大的触动是一个线上问题背后的知识线其实是穿起来的。如果你懂操作系统里的线程调度懂数据结构里的 HashMap 实现懂并发编程里的线程安全你就能很快从“CPU 高”这个现象一路追到“共享 HashMap 并发写入”这个根因。缺了任何一块基础知识你大概率还在怀疑是不是定时任务写了个死循环。5. 网络基础三次握手之外真正帮你排障的细节5.1 TIME_WAIT 过多端口不够用怎么办网络基础里大家最熟悉的就是 TCP 的三次握手和四次挥手。但很多人只记住了“三次握手建立连接、四次挥手断开连接”换个场景就不会用了。比如线上突然出现Cannot assign requested address这个报错你可能很懵但懂 TCP 的人一眼就能想到是端口不足。短连接服务很容易遇到这个问题。每次请求新建一个 TCP 连接请求结束后断开连接。断开时主动关闭方会进入一个叫TIME_WAIT的状态这个状态会持续一段时间默认是 2MSL最大报文段生存时间目的有两个一是确保最后一个 ACK 能到达对方二是让旧连接的报文段在网络中消失避免干扰新连接。问题来了如果并发量很大每秒钟创建和销毁成千上万个连接系统里就会有大量 socket 堆积在 TIME_WAIT 状态。这些 socket 不能立即释放导致可用的本地端口越来越少。当端口被占满时新连接就建不起来了于是报Cannot assign requested address。排查方式很简单netstat -an | grep TIME_WAIT | wc -l解决办法通常有这么几种一是把短连接改成连接池长连接减少连接的创建和销毁二是调整内核参数比如把net.ipv4.tcp_tw_reuse打开让处于 TIME_WAIT 的连接可以被安全复用三是评估一下是不是客户端并发端口不够增大本地端口范围。这些手段里最推荐的还是长连接因为连接复用不仅解决了端口问题还减少了握手开销能明显降低延迟。5.2 页面打开慢先查 DNS 再查代码有一次同事反馈线上某个页面打开特别慢从点击到页面出现要好几秒用户体验很差。大家第一反应是后端接口慢了查了半天接口耗时其实只有几十毫秒。后来用curl -v一看卡在Connected to xxx之前的 DNS 解析阶段。也就是说问题根本不在后端服务而是域名解析太慢。DNS 解析的过程是这样的浏览器先查自己的缓存没有就去查操作系统的缓存还没有就发请求到本地配置的 DNS 服务器DNS 服务器如果没有再向上级 DNS 服务器递归查询。这个链条里任何一环慢都会拖累你的“首包时间”。排查 DNS 慢可以用dig或nslookup看具体耗时。我曾经遇到过一个情况是某个内网服务器配置了一个不通的 DNS 服务器地址解析外网域名时老是等到超时才切换结果每个请求都多等了好几秒。后来把 DNS 配置改好速度立刻恢复正常。这类问题如果你不懂 DNS 的基本链路很容易在应用层代码里翻来覆去找原因白白浪费时间。网络底子里还有一层是连接池。很多语言和框架都自带了 HTTP 连接池像是HttpClient、OkHttp。有时候性能上不去可能是连接池太小导致大量请求在等连接而不是真正在传输数据。你要是只看接口日志可能什么都看不出来但如果你知道 TCP 连接的复用机制就会想到看一眼连接池的配置是不是合理。6. 数据结构和算法在业务代码里的真实位置6.1 HashMap 不是面试专属业务里全是它的影子一说数据结构很多人就想到面试。但说句实话业务代码里你天天都在用数据结构只是没意识到罢了。最简单的例子就是 HashMap。你在 Java 里随手map.put(key, value)背后其实是一套“数组 链表 红黑树”的组合。你明白了哈希表的基本原理就能理解为什么 HashMap 的查询平均是 O(1)为什么 hashCode 要尽量均匀为什么扩容因子默认是 0.75为什么自定义对象作为 key 时必须重写 hashCode 和 equals。我自己在业务里踩过一个典型的坑。有个项目用自定义对象做 Map 的 key但只重写了 equals没有重写 hashCode。结果存进去的值get不出来因为 HashMap 先按 hashCode 定位桶hashCode 不对equals 压根没机会被调用。排查这个问题时如果你不理解哈希表的“先找桶、再比较”的过程就只能一脸懵。还有一次是遍历的时候往集合里删数据跑着跑着就抛ConcurrentModificationException。这个异常跟 HashMap 的迭代器机制有关迭代器在创建时会记录一个modCount每次对集合做结构性修改都会让modCount加一迭代过程中如果发现modCount变了就立刻抛异常。这个设计叫“快速失败”。理解了这个机制你就会知道在遍历的时候不能直接添加或删除元素应该用Iterator.remove()或收集后统一处理。6.2 时间复杂度直觉帮你少写“慢代码”也许你会觉得算法课上学的时间复杂度分析没什么用但我觉得哪怕你忘了所有算法的细节只要保留“估计算法耗时量级”这个意识就能避开很多性能坑。举个特别常见的例子。两个列表找交集如果直接两层循环写法很简单ListInteger result new ArrayList(); for (int a : listA) { for (int b : listB) { if (a b) { result.add(a); break; } } }这个代码在数据量小的时候没问题但如果 listA 和 listB 各有 1 万条数据就是 1 亿次比较程序就会肉眼可见地变慢。你要是有点算法直觉会立刻意识到这个嵌套循环是 O(n²)然后改成先放到 HashSet 里再遍历判断SetInteger setB new HashSet(listB); ListInteger result new ArrayList(); for (int a : listA) { if (setB.contains(a)) { result.add(a); } }这样时间复杂度从 O(n²) 降到 O(nm)速度能提升好几个数量级。类似情况还有“循环里查数据库”的 N1 问题。比如先查出 100 个订单再循环去查每个订单对应的用户信息那就是 100 次数据库查询。改成批量查询一次WHERE id IN (...)可能 2 次查询就搞定了。数据库的索引能帮上忙但前提是你要有“减少查询次数”的意识。这个意识其实就是数据结构与算法基础教给你的。7. 把计算机基础与程序设计接起来一套有效的补课思路7.1 别按学校课表学按“问题”学很多人一提到补基础第一反应是翻出大学课本从头看结果翻了二十页就放弃。我特别理解因为学校的知识体系是按学科划分的但工作场景是按问题组织的。你按课表学学了操作系统进程管理但你平时不写操作系统根本用不上自然记不住。我自己比较受用的思路是“倒着学”从问题出发追到知识点再追到基础原理。比如你遇到 Redis 缓存雪崩你会去查为什么缓存集中失效会导致数据库压力大这时候就扯到并发了你再深入看并发就会理解线程池、锁、队列这些概念。又比如你解决接口响应慢会一层层从网络、到操作系统、到应用代码去分析每个环节都会逼着你去查底层知识。这种“打怪升级”式的学习方式虽然知识零散一点但因为每次都是在真实场景里碰到的印象特别深不会睡一觉就忘。等攒的多了再回头系统看一遍经典教材你会有一种“原来那时候遇到的问题是这么回事”的通透感。7.2 推荐的学习路线和资源如果你现在基础比较薄弱想系统补一遍我给一个参考顺序先弄懂进制、编码、字节序这些“数据底层表示”再学数据结构和算法因为这些相对独立、见效快然后学计算机组成原理了解 CPU、内存、IO 的基本协作方式接下来是操作系统重点看进程线程、虚拟内存、IO 模型再往后是计算机网络重点看 TCP/IP、HTTP、DNS最后可以看数据库原理重点是索引、事务、锁。书的话我比较推荐这几本讲数据底层和程序运行机制可以看《编码》和《深入理解计算机系统》简称 CSAPP计算机网络可以看《图解 HTTP》和《TCP/IP 详解》数据结构可以看《数据结构与算法分析》操作系统相关的教材可以随便挑一本别啃太深先建立整体框架。最重要的是要动手。光看书是不行的我建议你做几个小实验用 C 语言打印一下变量的内存地址和字节内容感受一下内存布局写一个递归函数故意制造栈溢出用调试器看栈帧到底是什么用 Wireshark 抓包看 TCP 三次握手、四次挥手的过程自己封装一个简单的二进制协议然后解析它。这些实验做完你对基础知识的理解会不只停留在“背概念”上而是真的变成自己能用的工具。8. 补基础路上常见的几个疑问8.1 “我是做业务的有必要学这么深吗”我觉得有必要但可以分层次。如果你只想当一个“熟练操作工”那确实不懂底层也能写业务。但问题在于你写业务的过程中一定会遇到接口慢、内存涨、数据错乱这些问题它们都有底层根因。不懂基础你只能靠猜、靠试、靠搜懂基础你才能从现象推本质。坦白讲知道“为什么”的人往往比只知道“怎么用”的人更早下班。8.2 “基础那么多精力有限优先补哪一块”如果工作很忙我建议按优先级排第一优先是数据结构与算法因为所有程序都在操作数据这是所有语言通用的第二是网络基础因为现在几乎没有单机系统排查问题绕不开网络第三是操作系统重点补线程、内存、IO 模型第四再去看编译原理、计算机组成原理这些离业务更远的东西。8.3 “看视频还是看书”我的建议是视频入门、书籍深化。视频适合建立概念框架比如看 B 站上的计算机基础课程配合动画演示很快能理解内存、进程这些抽象概念。真正深入的时候还是得看书因为书里能把很多细节讲清楚视频通常做不到。不要一直看视频不动手。最好的学习材料其实是“你自己的线上问题”带着问题去查、去补记忆最深。8.4 三条值得记住的实战经验最后聊几句我自己的感受。第一排查问题的时候永远先怀疑自己的代码再怀疑框架和系统。你越了解底层就越会发现很多看似是“平台问题”的情况其实根子还是在使用姿势上。第二不要怕“慢”。我在刚开始补这些基础的时候也觉得自己学得慢别人都在学新框架又出新版本了。但基础这个东西它不像框架那样能立竿见影它是慢慢发酵的。某天你解决了一个别人解决不了的问题你才会发现自己已经跟以前不一样了。第三记录自己的“基础缺口清单”。我有个习惯每次遇到一个解决不了的问题复盘时都会问一句“这个问题背后缺的是哪块基础知识”然后记下来抽空补上。一年多下来这个清单会越来越短而你自己解决问题的速度会越来越快。计算机基础这块“老祖宗的家底”不是拿来背的是拿来在关键时刻救命的。
返回列表