软件开发新手五大核心技能:逻辑思维与问题排查
摘要
日志调试需结构化,如JSON格式加时间戳和级别;请求追踪使用唯一ID贯穿全流程;循环中每
第五章 打印日志的艺术
打印,可以说是最简单也最强大的调试手段。但怎么打印,其实是个技术活儿。打印打得好,事半功倍;打印得糟糕,只会让终端输出变成一堵信息瀑布墙,看半天也不知道问题出在哪儿。

5.1 结构化日志
别跟以前一样傻傻地只打印字符串了。把日志弄成结构化的格式——最常见的就是JSON,这样后续拿来做分析、归档、检索,会顺手得多。比如,给每条日志打上时间戳、级别、核心消息,再顺手带几个关键参数,这就是一个基础的日志格式:
import json
import time
def log(level, message, **kwargs):
record = {
"timestamp": time.time(),
"level": level,
"message": message,
**kwargs
}
print(json.dumps(record))
log("INFO", "User login", user_id=123, ip="192.168.1.1")
5.2 带上下文的调试 ID
在Web服务、或者多线程的环境里,请求飘来飘去,日志交叉在一起,根本分不清谁是谁。这时候的惯例做法是:为每个请求生成一个全局唯一的ID(trace ID或correlation ID),让它从入口一直贯穿到出口。所有日志都带上这个ID,排查问题时,按ID一搜,整个请求的全貌就出来了。
import contextvars
import uuid
request_id = contextvars.ContextVar('request_id')
def set_request_id():
rid = str(uuid.uuid4())
request_id.set(rid)
return rid
def debug(msg):
rid = request_id.get(None)
print(f"[{rid}] {msg}")
5.3 循环中的进度打印
如果循环几十万次、上百万次,每次迭代都打一行日志,那速度会慢得让人崩溃。更好的做法是:每隔N次才打印一次。比如每1000次打一条,既能看到进度,又不拖慢性能。
for i, item in enumerate(large_list):
if i % 1000 == 0:
print(f"Processed {i} items")
process(item)
第六章 断言与防御式编程
断言这个东西,它的设计初衷就是捕捉那些“绝对不应该发生”的情况。它不是用来做业务校验的,而是逻辑层面的安全网。
实际使用中可以把握几个原则:
- 在函数入口检查参数是否合法——这是防御式编程的基本动作。
- 在复杂算法的关键不变量处断言,保证中间结果符合预期。
- 但不要断言那些会因为外部输入而失败的条件,比如“文件是否存在”。那种事情应该交给业务逻辑去处理。
def complex_calculation(data):
assert len(data) > 0, "data cannot be empty"
# ... 其他逻辑 ...
# 循环后断言结果范围
assert 0 <= result <= 100, f"Result out of range: {result}"
return result
生产环境里,默认用 -O 标志可以关掉断言。但关键服务中,很多人会选择把它们保留下来(通过配置开关来动态控制)。这个取舍,得根据实际情况来判断。
第七章 IDE 高级调试技巧
IDE的断点功能,很多人只用了最简单的那一档。但稍微深入一点,有几个技巧非常实用。
7.1 条件断点
循环几千万次,只想在某个特定条件满足时停下来。直接在断点上右键,输入条件就好了——比如 i == 500 && list.get(i).equals("error")。IDE会自动处理,只有条件满足时才会暂停。
7.2 日志断点(非侵入式打印)
有时候不希望停住程序,只想在某个位置打个日志。IDE支持“日志断点”:把它设置为不暂停,只求值并打印表达式。在IntelliJ里,右键断点,取消“Suspend”,勾选“Log evaluated expression”即可。在VS Code里也一样,创建断点后改模式为“Log Message”。
7.3 字段/变量断点
Ja va、C#这些语言里,可以给一个字段设置断点。当这个字段的值被修改——哪怕是通过反射改的——调试器都会停下来。这对排查“这个变量怎么莫名其妙变了”的情况极其有用。
7.4 异常断点
如果程序抛了一个异常,但被 catch 吞掉了,排查起来很头疼。设置一个“异常断点”,让IDE在任何地方抛出这个异常时都停下来(在进入 catch 块之前)。这样就能看到异常发生时的完整调用堆栈。
7.5 逆向调试(反向执行)
听起来像科幻小说:程序运行到后面了,但你能倒回去,重新观察之前的某条指令发生了什么。GDB(7.0以上)、Mozilla的RR、UndoDB都支持。
用RR录制:
rr record ./my_program
rr replay
然后在回放里可以前后任意移动,现场回放过去的每一个指令。
第八章 专业工具专题
调试不只有断点,下面这几个工具,属于专业调试必备。
8.1 内存调试三剑客
- Valgrind(Linux):
valgrind --leak-check=full ./my_program,查内存泄漏的利器。 - AddressSanitizer(Clang/GCC):编译时加
-fsanitize=address -g,更轻量的运行时检查。 - Dr. Memory(Windows):Windows环境下的类似工具。
8.2 性能 Profiler
- perf(Linux内核级):
perf record -g ./my_program → perf report,精准分析热点。 - py-spy(Python):无需改代码,直接附加到运行中的Python进程:
py-spy record -o profile.svg --pid 1234。 - JProfiler(Ja va):商业但好用。
- Chrome DevTools:前端性能的标配。
8.3 网络调试
- Wireshark / tshark:抓包分析HTTP、TCP等协议。
- mitmproxy:命令行的中间人袋里,可以查看和修改请求。
- Charles Proxy:GUI上的替代方案,跨平台。
8.4 系统调用追踪
- strace(Linux):
strace -f -e trace=file,network ./my_program,追踪文件操作和网络调用。 - dtruss(macOS)。
- Process Monitor(Windows)。
第九章 远程调试与生产环境问题排查
生产环境通常情况比较苛刻:不能安装开发工具,不能插入断点,甚至不能随便加日志。遇到线上问题了,怎么办?
- 增强日志:通过管理接口动态调整日志级别。比如把某个类的日志级别临时改成DEBUG,问题定位完再改回去。
- JMX(Ja va):用JConsole连接到正在运行中的JVM,在线查看线程、内存信息。
- Arthas(阿里开源):堪称Ja va诊断神器,可以在线查看方法调用参数、返回值、异常,甚至修改字节码。适合那种“不能停服但必须搞清楚问题”的场景。
- btrace:类似动态跟踪工具。
- py-spy:对Python开发者来说,py-spy可以安全地附加到生产进程,不会对性能造成明显影响。
- 核心转储(core dump):程序崩溃后,保留一份内存快照。之后离线加载分析,比现场重试要方便得多。
ulimit -c unlimited # 允许生成 core dump
./my_program # 崩溃后生成 core
gdb ./my_program core # 离线分析
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。