必须检查os.Open返回的err,否则对失效*os.File操作会确定性panic;大文件需先stat限大小再流式读取;Write后须校验n值防截断;符号链接须用Lstat并校验路径可信域。

os.Open 后不检查 err 导致 panic
直接对 nil *os.File 调用 Read() 或 Close() 会立即 panic,这不是“偶尔出错”,而是确定性崩溃。Go 的 os.Open、os.OpenFile 等函数从不 panic,但返回的 *os.File 在 err != nil 时**不能假设为 nil**——某些系统调用部分成功时可能返回非 nil 文件句柄,但语义上已失效。
- 永远用
if err != nil判断,而不是if file == nil -
defer file.Close()必须放在err == nil分支内,否则错误时调用Close()仍会 panic - 需要区分错误类型时,优先用
errors.Is(err, os.ErrNotExist)或os.IsPermission(err),而非字符串匹配
ReadAll() 在大文件上触发 OOM
io.ReadAll()(替代已弃用的 ioutil.ReadAll())会把整个文件加载进内存,对 >10MB 的文件极易被系统 kill。这不是“性能差”,而是资源失控风险。
- 先用
os.Stat().Size()检查文件大小,超阈值(如 10MB)直接拒绝或走流式路径 - 读取配置/日志等文本文件,用
bufio.Scanner按行处理,避免一次性加载 - 二进制或不确定大小的文件,用
io.CopyN(dst, src, n)或循环调用file.Read(buf)分块读取 -
io.ReadAll()返回的[]byte和原*os.File无绑定关系,Close()不影响已读数据,但别指望它自动释放底层 fd
Write() 后忽略 n 值引发数据截断
Write() 和 WriteString() 都返回 (int, error),其中 int 是实际写入字节数。网络文件系统(NFS)、磁盘满、信号中断等场景下,error 可能为 nil,但 n 小于预期长度——这是合法行为,不是 bug。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 不要只判
err != nil,必须校验n == len(data) - 简单场景用
io.WriteString(file, s)替代file.WriteString(s),前者内部已做补全 - 关键写入(如日志、事务记录)建议调用
file.Sync()确保落盘,但注意它会阻塞并增加延迟 - 批量写入时,用
bufio.NewWriter(file)提升吞吐,但记得最后调用wr.Flush(),否则缓冲区内容丢失
符号链接处理不当导致越权或死循环
用 os.Stat() 代替 os.Lstat() 会自动解析符号链接,可能跳转到任意路径(如 /etc/passwd),造成越权访问;手动递归遍历时若不检测循环引用,filepath.EvalSymlinks() 可能卡住或返回 too many levels of symbolic links。
立即学习“go语言免费学习笔记(深入)”;
- 判断是否为软链接,必须用
os.Lstat(),它只读取路径自身元数据 - 需要解析真实路径时,用
filepath.EvalSymlinks(),但之后必须用strings.HasPrefix(realPath, allowedBase)校验是否在可信目录内 - 坏链(dangling symlink)既不是有效文件也不是安全链接,
os.Lstat()能看见它,os.Stat()和EvalSymlinks()都失败,这个中间态需显式处理 - 自定义目录遍历逻辑中,需维护已访问路径集合,防止循环引用


















