开发者需警惕的编程语言和开发工具陷阱
对开发者陷阱的总结。这些陷阱是容易被误解且导致 bug 的非直观事物。
陷阱总结
Java
==
比较对象引用。应使用.equals
比较对象内容。- 忘记重写
equals
和hashcode
。在地图键和集合中,默认会使用对象身份相等性。 - 修改地图键对象(或集合元素对象)的内容会导致容器故障。
- 返回
List<T>
的方法有时会返回可变的ArrayList
,有时会返回不可变的Collections.emptyList()
。尝试修改Collections.emptyList()
会抛出UnsupportedOperationException
。 - 返回
Optional<T>
的方法可能返回null
(不推荐,但实际代码库中确实存在这种情况)。 finally
块中的返回值会吞噬try
或catch
块中抛出的任何异常。方法将返回finally
块中的值。- 中断。某些库会忽略中断。中断可能导致包含 I/O 的类初始化失败。
- 线程池默认不会记录通过
.submit()
提交的任务的异常。您只能从.submit()
返回的未来对象中获取异常。不要丢弃未来对象。如果抛出异常,scheduleAtFixedRate
任务会静默停止。 - 以 0 开头的字面量数字将被视为八进制数。(
0123
是 83) - 在调试时,调试器会调用
.toString()
方法来显示局部变量。某些类的.toString()
方法具有副作用,这会导致代码在调试器下运行时行为不同。此行为可在 IDE 中禁用。

Golang
append()
在容量允许的情况下会复用内存区域。向子切片追加元素可能会覆盖父切片,如果它们共享内存区域。defer
在函数返回时执行,而非在词法作用域退出时执行。defer
会捕获可变变量。- 关于
nil
: - 存在
nil
切片和空切片(两者不同)。但不存在nil
字符串,只有空字符串。nil
映射可像空映射一样读取,但无法写入。 - 接口
nil
的特殊行为。接口指针是包含类型信息和数据指针的胖指针。若数据指针为空但类型信息不为空,则不会等于nil
。 - 死等待。 理解 Go 中的实际并发错误
- 不同类型的超时。 Go net/http 超时的完整指南
C/C++
- 将元素的指针存储在
std::vector
中,然后扩展向量,vector
可能会重新分配内容,导致元素指针不再有效。 - 由字面量字符串创建的
std::string
可能是临时对象。从临时字符串中获取c_str()
是错误的。 - 迭代器失效。在循环遍历容器时修改容器。
std::remove
不会删除元素,只是重新排列元素。erase
实际上会删除元素。- 以 0 开头的字面量数字将被视为八进制数。(
0123
表示 83) - 未定义行为。编译器优化旨在保持已定义行为不变,但可自由更改未定义行为。依赖未定义行为可能导致程序在优化时崩溃。参见
- 访问未初始化的内存属于未定义行为。将
char*
转换为结构体指针可能被视为访问未初始化的内存,因为对象的生命周期尚未开始。建议将结构体放置在其他位置并使用memcpy
进行初始化。 - 访问无效内存(如空指针)属于未定义行为。
- 整数溢出/下溢是未定义行为。注意,无符号整数可能下溢到 0 以下。
- 访问未初始化的内存属于未定义行为。将
- 别名。
- 别名意味着多个指针指向内存中的同一位置。
- 严格别名规则:如果存在两个类型为
A*
和B*
的指针,则编译器假设这两个指针绝不会相等。如果它们相等,则属于未定义行为。例外情况:1.A
和B
之间存在子类型关系 2. 将指针转换为字节指针(char*
、unsigned char*
或std::byte*
)(反向转换不适用)。 - 进行强制转换时,安全的方法是使用
memcpy
或std::bit_cast
- 未对齐的内存访问属于未定义行为。
- 对齐。
- 例如,64 位整数的地址必须能被 8 整除。在 ARM 架构中,以未对齐方式访问内存可能导致系统崩溃。
- 直接将字节缓冲区的一部分视为结构体可能引发对齐问题。
- 对齐可能导致结构体中出现填充,从而浪费空间。
- 某些SIMD指令仅支持对齐数据。例如,AVX指令通常要求32字节对齐。
Python
- 默认参数是一个存储值,不会在每次调用时重新创建。
SQL 数据库
- 空值是特殊的。
x = null
无法正常工作。x is null
有效。空值不等于自身,与 NaN 类似。
- 唯一索引允许重复空值(除 Microsoft SQL Server 外)。
select distinct
可能将空值视为相同(这取决于数据库)。
count(x)
和count(distinct x)
会忽略x
为空值的行。- 日期隐式转换可能受时区影响。
- 复杂的连接操作与
DISTINCT
结合可能比嵌套查询更慢。参见 - 在 MySQL(InnoDB)中,如果字符串字段未设置为
character set utf8mb4
,则尝试插入包含 4 字节 UTF-8 码点的文本时会引发错误。 - MySQL(InnoDB)默认不区分大小写。
- MySQL(InnoDB)默认支持隐式转换。
select ‘123abc’ + 1;
的结果为 124。 - MySQL(InnoDB)的间隙锁可能导致死锁。
- 在 MySQL(InnoDB)中,您可以选择一个字段并按另一个字段分组。这会产生非确定性结果。
- 在 SQLite 中,字段类型无关紧要,除非表设置为
strict
。 - 外键可能导致隐式锁定,这可能引发死锁。
- 锁定可能破坏可重复读隔离级别(这取决于数据库实现)。
- 分布式 SQL 数据库可能不支持锁定或具有异常的锁定行为。这取决于数据库实现。
- 如果后端存在 N+1 查询问题,慢查询日志中可能不会显示性能问题,因为后端会串行执行多个小型查询,而每个单独的查询速度较快。
- 长时间运行的事务可能引发问题(例如锁定)。建议确保所有事务快速完成。
- 全表锁定可能导致服务暂时不可用:
- 在 MySQL(InnoDB)8.0 及更高版本中,添加唯一索引或外键操作大多是并发进行的(仅短暂锁定),不会阻塞其他操作。但在较旧版本中可能触发全表锁定。
- 使用
mysqldump
时若未指定--single-transaction
参数,会导致全表读锁定。- 在 PostgreSQL 中,
create unique index
或alter table ... add foreign key
会导致全表读锁。为避免此情况,可使用create unique index concurrently
添加唯一索引。对于外键,可先执行alter table ... add foreign key ... not valid;
,再执行alter table ... validate constraint ...
。
- 在 PostgreSQL 中,
- 关于范围:
- 如果存储不重叠的范围,通过
select ... from ranges where p >= start and p <= end
查询包含某个点的范围是低效的(即使拥有(start, end)
的复合索引)。高效方法:select * from (select ... from ranges where start <= p order by start desc limit 1) where end >= p
(仅需start
列的索引)。 - 对于可重叠的范围,普通B-树索引无法实现高效查询。建议在MySQL中使用空间索引,在PostgreSQL中使用GiST索引。
并发与并行
volatile
:volatile
本身无法替代锁。volatile
本身不提供原子性。- 对于由锁保护的数据,无需使用
volatile
。锁定操作已能建立内存顺序并防止某些错误优化。 - 在 C/C++ 中,
volatile
仅避免某些错误优化,但不会自动为volatile
访问添加内存屏障指令。 - 在 Java 中,
volatile
访问具有顺序一致性排序(JVM 会在需要时使用内存屏障指令) - 在 C# 中,
volatile
访问具有释放-获取排序(CLR 会在需要时使用内存屏障指令) volatile
可以避免与内存读写重新排序和合并相关的错误优化。
- 对于由锁保护的数据,无需使用
- 检查时与使用时(TOCTOU)。
- 在 SQL 数据库中,对于不适合简单唯一索引的特殊唯一约束(例如跨两个表的唯一性、条件唯一性、时间范围内的唯一性),且约束由应用程序强制执行时:
- 在 MySQL(InnoDB)中,若处于可重复读取级别,应用程序通过
select ... for update
检查后执行插入操作,且唯一性检查的列已建立索引,则可通过间隙锁定机制正常工作。(需注意间隙锁定在高并发环境下可能引发死锁,因此需确保死锁检测功能启用并采用重试机制。) - 在 PostgreSQL 中,如果处于可重复读取级别,应用程序使用
select ... for update
进行检查后再插入数据,这不足以在并发情况下强制执行约束(由于写入偏斜)。一些解决方案:
- 在 MySQL(InnoDB)中,若处于可重复读取级别,应用程序通过
- 使用可串行化级别
- 不要依赖应用程序来强制执行约束:
- 对于条件唯一性,使用部分唯一索引。
* 对于跨两个表的唯一性场景,将冗余数据插入到一个额外的表中并添加唯一索引。 - 对于时间范围排他性场景,使用范围类型和排除约束。
- 原子引用计数(
Arc
、shared_ptr
)在多个线程频繁修改同一计数器时会变慢。参见 - 关于读写锁:某些读写锁实现不支持从读锁升级为写锁,且在持有读锁时尝试获取写锁可能导致死锁。
许多语言中常见的问题
- 忘记检查空值/None/nil。
- 在循环遍历容器时修改容器。单线程“数据竞争”。
- 意外共享可变数据。例如在 Python 中
[[0] * 10] * 10
不会创建正确的二维数组。 - 对于非负整数,
(low + high) / 2
可能发生溢出。更安全的方法是low + (high - low) / 2
。 - 短路评估。
a() || b()
在a()
返回 true 时不会执行b()
。a() && b()
在a()
返回 false 时不会执行b()
。 - 使用性能分析器时:性能分析器默认可能仅包含 CPU 时间,不包括等待时间。如果您的应用程序有 90% 的时间在等待数据库,火焰图可能不会包含这 90%,这会产生误导。
- 正则表达式有许多不同的“方言”。不要假设在 JavaScript 中工作的正则表达式在 Java 中也能工作。
Linux 和 Bash
- 如果当前目录被移动,
pwd
仍显示原始路径。pwd -P
显示真实路径。 cmd > file 2>&1
使标准输出和标准错误都重定向到文件。但cmd 2>&1 > file
仅使标准输出重定向到文件,而不重定向标准错误。- 文件名区分大小写(与 Windows 不同)。
- 可执行文件有独立于文件权限系统的权限系统。使用
getcap
查看权限。 - 取消设置变量。如果
DIR
未设置,rm -rf $DIR/
将变为rm -rf /
。使用set -u
可使 Bash 在遇到未设置变量时报错。 - 若希望脚本向当前 shell 添加变量和别名,应通过
source script.sh
执行,而非直接执行。但source
的效果并非永久性,且不会在重新登录后生效。可通过将其放入~/.bashrc
文件中实现永久生效。 - Bash 在命令名称与命令文件路径之间存在缓存机制。若将
$PATH
中的某个文件移动,使用该命令时会引发 ENOENT 错误。可通过hash -r
命令刷新缓存。 - 若未对变量进行引号包裹,其换行符将被视为空格处理。
set -e
可使脚本在子命令失败时立即退出,但它不适用于结果被条件检查的函数内部(例如||
、&&
的左侧,或if
语句的条件)。参见- K8s
livenessProbe
与调试器配合使用。断点调试器通常会阻塞整个应用程序,使其无法响应健康检查请求,因此可能被 K8slivenessProbe
终止。
React
- 在渲染代码中修改状态。
- 在
if
或循环内部使用钩子。 - 值未包含在
useEffect
依赖数组中。 - 在
useEffect
中忘记清理。 - 闭包陷阱(捕获过时的状态)。
- 意外在错误的位置更改数据(不纯组件)。
- 忘记使用
useCallback
导致不必要的重新渲染。 - 将未缓存的值传递给缓存组件会使缓存失效。
Git
- Rebase 会重写历史。在本地分支进行 Rebase 后,正常推送会产生异常结果(因为历史已被重写)。Rebase 应与 force push 配合使用。如果远程分支的历史记录被重写,拉取时应使用
--rebase
。 - 使用
--force-with-lease
进行 force push 有时可以避免覆盖其他开发者的提交。但如果你先拉取再不进行拉取,--force-with-lease
无法提供保护。 - 撤销合并并不会完全取消合并的副作用。如果你将 B 合并到 A,然后撤销合并,再次将 B 合并到 A 不会产生任何效果。一个解决方案是撤销合并的撤销操作。(取消合并的更干净方法,而不是撤销合并,是备份分支,然后硬重置到合并前的提交,然后挑选合并后的提交,然后强制推送。)
- 在 GitHub 中,如果你不小心提交了机密信息(例如 API 密钥)并推送到公共仓库,即使你使用强制推送覆盖它,GitHub 仍会记录该机密信息。客座文章:如何扫描 GitHub 上所有“意外提交”以查找泄露的机密信息 示例活动标签
- 在 GitHub 中,如果有一个私有仓库 A,你将其分叉为私有仓库 B,那么当 A 变为公开时,私有仓库 B 的内容也会公开可见,即使你删除了 B。参见。
git stash pop
在存在冲突时不会丢弃暂存区。- 建议将
**/.DS_Store
添加到.gitignore
中,因为 MacOS 会自动在每个文件夹中添加.DS_Store
文件。
HTML 和 CSS
- 在 flexbox 或网格布局中,
min-width
的默认值为auto
。min-width: auto
表示最小宽度由内容决定。它比许多其他 CSS 属性具有更高的优先级,包括flex-shrink
、overflow: hidden
、width: 0
和max-width: 100%
。建议设置min-width: 0
。 - 在 CSS 中,水平和垂直方向是不同的:
- 通常
width: auto
会尝试填充父元素的可用空间。但height: auto
通常仅尝试扩展以适应内容。 - 对于行内元素、行内块元素和浮动元素,
width: auto
不会尝试扩展。 margin: 0 auto
可实现水平居中。但margin: auto 0
通常会变成margin: 0 0
,这不会实现垂直居中。在flex-direction: column
的弹性盒布局中,margin: auto 0
可以实现垂直居中。
- 通常
- 垂直方向会发生边距合并,但水平方向不会。
- 当布局方向翻转(例如
writing-mode: vertical-rl
)时,上述情况会发生逆转。 - 块格式化上下文(BFC):
display: flow-root
创建 BFC。(还有其他创建 BFC 的方法,如overflow: hidden
、overflow: auto
、overflow: scroll
、display:table
,但会产生副作用)
- 边距坍缩。两个垂直相邻的兄弟元素可以重叠边距。子元素的边距可能“溢出”到父元素之外。通过 BFC 可以避免边距坍缩。当指定
border
或padding
时,边距合并也不会发生 - 如果父元素仅包含浮动子元素,父元素的高度将折叠为 0。可通过 BFC 修复。
- 堆叠上下文。在以下情况下,将创建新的堆叠上下文:
- 具有特殊渲染效果的属性(如
transform
、filter
、perspective
、mask
、opacity
等)将创建新的堆叠上下文 position: fixed
或position: sticky
将创建堆叠上下文- 指定
z-index
且position
为absolute
或relative
- 指定
z-index
且元素位于 flexbox 或 grid 布局中 isolation: isolate
- …
堆叠上下文可能导致以下行为:
z-index
在不同堆叠上下文之间无效。它仅在同一堆叠上下文中生效。position: absolute
或position: fixed
的坐标基于最近的定位祖先元素。堆叠上下文的定位会影响这一点。position: sticky
在不同堆叠上下文之间无效。overflow: visible
仍会受到堆叠上下文的裁剪background-attachment: fixed
基于堆叠上下文进行定位
- 具有特殊渲染效果的属性(如
- 在移动浏览器中,当向下滚动时,顶部地址栏和底部导航栏可能超出屏幕范围。
100vh
对应于顶部栏和底部栏超出屏幕时的高度,该高度大于两栏在屏幕内时的高度。现代解决方案是使用100dvh
。 position: absolute
并非基于其父元素。它基于其最近的定位祖先(即最近具有position
为relative
、absolute
或创建堆叠上下文的祖先)。- 模糊效果不考虑环境因素。
- 如果父元素的
display
为flex
或grid
,则子元素的float
属性无效。 - 如果父元素的宽度/高度未预先确定,则百分比宽度/高度(例如
width: 50%
,height: 100%
)无法正常工作。(这避免了循环依赖,即父元素的高度由内容高度决定,但内容高度又由父元素的高度决定。) display: inline
会忽略width
、height
以及margin-top
、margin-bottom
- 空白压缩。HTML 空白处理存在问题
- 默认情况下,HTML 中的换行符会被视为空格。多个连续的空格会压缩为一个。
<pre>
标签可避免空白压缩,但在内容的开头和结尾处行为异常。- 通常,内容开头和结尾的空格会被忽略,但在
<a>
中不会发生这种情况。
- 两个
display: inline-block
元素之间的任何空格或换行都会被渲染为间距。这种情况在 flexbox 或 grid 中不会发生。 text-align
用于对齐文本和行内元素,但不会对齐块级元素(如普通 div)。- 默认情况下,
width
和height
不包含内边距和边框。即使设置width: 100%
并同时设置padding: 10px
,内容仍可能超出父元素的边界。通过设置box-sizing: border-box
,可以使宽度/高度包含边框和内边距。 - 累积布局偏移。建议在
<img>
标签中明确指定width
和height
属性,以避免因图片加载延迟导致的布局偏移。 - 文件下载请求在 Chrome 开发者工具中不会显示,因为该工具仅显示当前标签页的网络请求,而文件下载被视为在另一个标签页中进行。要检查文件下载请求,请使用
chrome://net-export/
。 - HTML 中的 JavaScript 可能干扰 HTML 解析。例如
<script>console.log(‘</script>’)</script>
会导致浏览器将第一个</script>
视为结束标签。参见
Unicode 和文本编码
- 两个概念:码点(rune)、字符集群:
- 字母群是图形用户界面(GUI)中的“字符单位”。
- 对于可见的 ASCII 字符,一个字符是一个代码点,一个字符是一个字母群。
- 表情符号是一个字母群,但它可能由多个代码点组成。
- 在 UTF-8 中,一个代码点可以是 1、2、3 或 4 个字节。字节数并不一定代表码点数。
- 在 UTF-16 中,一个码点可以是 2 个字节或 4 个字节(代理对)。
- 标准并未对字符集群可包含的码点数量设置上限。但出于性能考虑,实现通常会设置限制。
- 不同语言中内存字符串的行为差异:
- Rust 使用 UTF-8 作为内存中字符串的编码。
s.len()
返回字节数。Rust 不允许直接对str
进行索引(但允许子切片)。s.chars().count()
返回代码点数。Rust 对 UTF-8 代码点的有效性有严格要求(例如,Rust 不允许子切片在无效代码点边界处截断)。 - Go 语言的字符串没有编码限制,与字节数组类似。字符串的长度和索引操作与字节数组相同。但最常见的编码是 UTF-8。参见
- Rust 使用 UTF-8 作为内存中字符串的编码。
- Java、C# 和 JS 的字符串在概念上使用 UTF-16 编码。UTF-16 以 2 字节为单位进行操作。但一个字符码点可能由 1 个 2 字节单位或 2 个 2 字节单位(代理对)组成。字符串的长度是 2 字节单位的计数,而非字符码点计数。索引操作基于 2 字节单位。
- 在 Python 中,
len(s)
返回字符码点计数。索引操作返回包含一个字符码点的字符串。- 在 C++ 中,
std::string
没有编码限制。它可以被视为std::vector<char>
的封装。字符串长度和索引基于字节。
- 在 C++ 中,
- 上述语言均不基于字母集群进行字符串长度和索引操作。
- 某些文本文件在开头带有字节顺序标记(BOM)。例如,EF BB BF 是一个 BOM,表示文件采用 UTF-8 编码。它主要用于 Windows 系统。部分非 Windows 软件无法处理 BOM。
- 在将二进制数据转换为字符串时,通常会将无效位置替换为 �(U+FFFD)
- 易混淆字符。
- 规范化。例如,é 可以是 U+00E9(一个码点)或 U+0065 U+0301(两个码点)。
- 零宽度字符,不可见字符
- 换行符。Windows 通常使用 CRLF
\r\n
作为换行符。Linux 和 MacOS 通常使用 LF\n
作为换行符。 - 汉字统一。不同语言中外观略有不同的字符可能使用相同的代码点。通常字体中会包含针对不同语言的变体,这些变体在渲染时会有所不同。选择正确的字体变体对于国际化至关重要。HTML 代码 !
浮点数
- NaN。浮点数中的NaN不等于任何数(包括其自身)。NaN == NaN始终为假(即使位相同)。NaN != NaN始终为真。对NaN进行运算通常会得到NaN(它可能“污染”计算结果)。
- 存在+Inf和-Inf。它们不是NaN。
- 存在一个负零 -0.0,它与普通零不同。在浮点数比较中,负零等于零。普通零被视为“正零”。这两个零在某些计算中表现不同(例如
1.0 / +0.0 == +Inf
,1.0 / -0.0 == -Inf
) - JSON 标准不允许使用 NaN 或 Inf:
- JavaScript 的
JSON.stringify
会将 NaN 和 Inf 转换为 null。 - Python 的
json.dumps(...)
会直接将NaN
和Infinity
写入结果,这不符合 JSON 标准。json.dumps(..., allow_nan=False)
若包含 NaN 或 Inf 则会引发ValueError
。
- JavaScript 的
- Go 语言的
json.Marshal
若包含 NaN 或 Inf 则会报错。 - 直接比较浮点数的相等性可能失败。建议通过
abs(a - b) < 0.00001
等方式进行比较。 - JavaScript 使用浮点数表示所有数字。最大“安全”整数为 253−1253−1。这里的“安全”意味着该范围内的每个整数都能准确表示。超出安全范围后,大多数整数将不准确。对于大型整数,建议使用
BigInt
。如果 JSON 中包含大于该范围的整数,且 JavaScript 使用JSON.parse
进行反序列化,结果中的数字很可能不准确。解决方法是使用其他方式反序列化 JSON 或使用字符串表示大整数。(将毫秒时间戳整数放入 JSON 是安全的,因为毫秒时间戳在公元 287396 年后会超出限制。但纳秒时间戳会遇到此问题。) - 由于精度损失,结合律和分配律并不严格成立。使用这些律进行矩阵乘法和求和并行化可能导致非确定性结果。示例 示例
- 除法比乘法慢得多(除非使用近似值)。对多个数进行除法运算时,可通过先计算倒数再与倒数相乘来优化。
- 以下因素可能导致不同硬件产生不同的浮点运算结果:
- 硬件对FMA(融合乘加)的支持。
fma(a, b, c) = a * b + c
(在某些地方为a + b * c
)。大多数现代硬件在FMA的中间结果具有更高精度。部分老旧硬件或嵌入式处理器不支持此功能,将其视为普通乘法和加法。 - 浮点数具有一个 次正常数范围,用于使接近零的数值更准确。大多数现代硬件可以处理它们,但一些旧硬件和嵌入式处理器将次正常数视为零。
- 舍入模式。标准允许使用不同的舍入模式,如舍入到最近的偶数(RNTE)或舍入到零(RTZ)。
- 硬件对FMA(融合乘加)的支持。
- 在 X86 和 ARM 架构中,舍入模式是线程局部可变状态,可通过特殊指令设置。不建议修改舍入模式,因为这可能影响其他代码。
* 在GPU中,没有可变状态用于舍入模式。光栅化通常使用RNTE舍入模式。在CUDA中,不同的舍入模式与不同的指令相关联。 - 不同硬件在数学函数(如sin、log)上的行为可能不同。
- X86具有遗留FPU,其具有80位浮点寄存器和按核心的舍入模式状态。建议不要使用它们。
- … 还有许多其他因素会导致浮点计算的差异。
- 如何提高浮点计算精度:
- 使计算图更扁平。例如,3层计算
a * (b * (c * d))
可能不如2层计算(a * b) * (c * d)
准确(具体取决于实际情况)。 - 避免临时结果具有非常大的绝对值或非常接近零的值。
- 使计算图更扁平。例如,3层计算
- 利用硬件融合操作,如FMA(融合乘加)。
时间
- 闰秒。Unix 时间戳对闰秒是“透明”的,这意味着在 Unix 时间戳与 UTC 时间之间转换时会忽略闰秒。Unix 时间戳测量的时间会在闰秒附近拉伸或压缩(闰秒模糊),以隐藏闰秒的存在。
- 时区。UTC 和 Unix 时间戳在全球范围内是统一的,不考虑时区。但人类可读的时间是时区依赖的。建议将时间戳存储在数据库中,并在 UI 中转换为人类可读的时间,而不是将人类可读的时间存储在数据库中。
- 夏令时(DST):在某些地区,人们会在温暖的季节将时钟向前调整一小时。
- 由于NTP同步,时间可能“倒退”。
- 建议将服务器的时区配置为UTC。不同节点使用不同时区会在分布式系统中引发问题。更改系统时区后,数据库可能需要重新配置或重启。
- 系统中有两个时钟:硬件时钟和系统时钟。硬件时钟本身不关心时区。Linux 默认将其视为 UTC。Windows 默认将其视为本地时间。
网络
- 某些路由器和防火墙会静默终止空闲的 TCP 连接,而不会通知应用程序。某些代码(如 HTTP 客户端库、数据库客户端)会维护一个 TCP 连接池以供重复使用,这些连接可能被静默无效化。为解决此问题,可配置系统 TCP 保持活动。
traceroute
的结果不可靠。Traceroute 并非真实。有时 tcptraceroute 可能有用。- TCP 慢启动会增加延迟。可通过禁用
tcp_slow_start_after_idle
解决。 参见 - TCP 粘性数据包。Nagle 算法会延迟数据包发送,从而增加延迟。可通过启用
TCP_NODELAY
解决。参见 - 若将后端部署在 Nginx 之后,需配置连接复用。否则在高并发情况下,由于内部端口不足,Nginx 与后端之间的连接可能失败。
- Nginx 默认对数据包进行缓冲,这会延迟 SSE。
- HTTP 协议并未明确禁止 GET 和 DELETE 请求携带请求体。部分场景确实会在 GET 和 DELETE 请求中使用请求体。但许多库和 HTTP 服务器不支持此功能。
- 一个 IP 地址可托管多个网站,通过域名进行区分。HTTP 头部
Host
和 TLS 握手中的 SNI 携带域名信息,这些信息至关重要。部分网站无法通过 IP 地址访问。 - 跨源资源共享(CORS)。对于请求其他网站(源)的请求,浏览器将阻止 JavaScript 获取响应,除非服务器响应中包含
Access-Control-Allow-Origin
头部且与客户端网站匹配。这需要配置后端。如果要将 cookie 传递到其他网站,则需要更多配置。通常,如果前端和后端位于同一网站(同一域名和端口),则不存在 CORS 问题。
其他
- YAML:
- YAML 对空格敏感,与 JSON 不同。
key:value
是错误的。key: value
是正确的。 - 使用 Microsoft Excel 打开 CSV 文件时,Excel 会进行大量转换,例如日期转换(例如将
1/2
和1-2
转换为2-Jan
),且 Excel 不会显示原始字符串。基因 SEPT1 因该 Excel 问题而更名。Excel还会导致大数字不准确(例如将12345678901234567890
转换为12345678901234500000
),并且不会显示原始准确的数字,因为Excel内部使用浮点数来处理数字。
本文文字及图片出自 Traps to Developers
你也许感兴趣的:
- 仓颉编程语言速览
- 程序员不再那么谦逊了——也许是因为没人再用 Perl 编程
- 【程序员搞笑图片】这张图给了我三个警告和一个头痛
- Rust 比 C 更快吗?
- 【程序员搞笑图片】Rust:愿者上钩
- 对 Rust 10 年的押注以及我对未来的期待
- Java 30 年:一门为失败的小工具设计的语言如何成为全球强势语言
- Rust 10 周年:一部破电梯如何彻底改变了软件
- 世界末日的最佳编程语言
- 编程语言的选择
你对本文的反应是: