# 实验指南

---

## 作业难度

每个实验任务都标有大致预计用时：

* **Easy（简单）**：数小时。
* **Moderate（中等）**：约每周 6 小时。
* **Hard（困难）**：每周超过 6 小时。若起步较晚，你的实现很可能无法通过全部测试。

多数实验只需适度的代码量（每个实验部分可能几百行），但**概念上可能较难**，需要较多思考和调试。部分测试较难通过。

**不要在截止前一晚才开始做实验**；分多天、多次完成会更高效。由于并发、崩溃和不可靠网络，在分布式系统中排查 bug 较为困难。

---

## 提示

* 完成 [Go 在线教程](https://go.dev/tour/)并参考 [Effective Go](https://go.dev/doc/effective_go)。参见 [Editors](https://go.dev/doc/editors.html) 配置 Go 编辑器。
* 实验的 Makefile 已配置为使用 Go 的 **race detector**。请修复其报告的所有数据竞争。参见 [race detector 博客文章](https://go.dev/blog/race-detector)。
* 实验中的 [加锁建议](../papers/raft-locking-cn.txt)。
* [Raft 实验结构建议](../papers/raft-structure-cn.txt)。
* 该 [Raft 交互示意图](../papers/raft_diagram.pdf) 有助于理解系统各部分之间的代码流程。
* 学习 Go 的 Printf 格式字符串：[Go format strings](https://pkg.go.dev/fmt)。
* 进一步了解 git，可参阅 [Pro Git 书](https://git-scm.com/book/en/v2) 或 [git 用户手册](https://git-scm.com/docs/user-manual)。

---

## 调试

高效调试需要经验。**系统化**会很有帮助：对可能原因形成假设；收集可能相关的证据；分析已收集的信息；按需重复。长时间调试时做笔记有助于积累证据并提醒自己为何排除之前的假设。

最有效的调试方法之一往往是在代码中**添加 print 语句**，运行失败的测试并将输出保存到文件，然后浏览输出找出开始出错的位置。可能需多轮迭代，随问题更清晰而增加更多 print。

不同节点之间以及同一节点内多线程的**并发**会使操作以意想不到的方式交错。例如，前一个 leader 仍认为自己是 leader 时，某个 Raft 节点可能已被选为新 leader；或 leader 发出 RPC 但在失去 leader 身份后才收到回复。添加 print 有助于发现这类情况。

可以**查看测试代码**（`mr/mt_test.go`、`raft1/raft_test.go` 等）以理解测试在测什么。可以在测试中加 print 帮助理解其行为及失败原因，但**提交前请确保你的代码在原始测试代码下能通过**。

Raft 论文的 **Figure 2** 必须较为严格地遵守。很容易漏掉 Figure 2 要求检查的条件或要求发生的状态变化。若有 bug，**请再次核对你的代码是否严格遵循 Figure 2**。

在写代码时（即尚未出现 bug 时），对代码假定成立的条件添加**显式检查**可能很有用，例如使用 Go 的 [panic](https://go.dev/blog/defer-panic-and-recover)。这类检查有助于发现后续代码无意违反假设的情况。

助教乐于在答疑时间帮你思考代码，但若你**已经尽可能深入分析过**，有限的答疑时间才能发挥最大作用。

---
*来源: [Lab guidance](https://pdos.csail.mit.edu/6.824/labs/guidance.html)*
