首先,我不太清楚为什么不经常问这个问题。阅读代码一点也不直观!我们应该能够很好地可视化过程或功能的演变,以便接近每个人都能够理解它的行为。在 60 年代左右,人们必须合理地确定他们的程序会运行,因为访问计算机需要时间。今天我们执行或编译我们的程序,运行测试(如果有的话),并立即知道它是否有效。发生的事情是现在的脑力劳动减少了,我们在头脑中执行的代码少了一点,而计算机执行的更多了。但是我们仍然必须考虑程序在执行过程中的行为以便进行调试。将来,如果计算机能告诉我们程序的行为方式,那就太好了。
您建议将程序的某种树视为一种果断,毕竟抽象语法树实际上是一棵树,但我认为这不是我们应该在它出现时花费我们的努力可视化系统。更可取的是,如果我们可以查看问题如何随时间改变其中间数据结构的交互式视图。
目前,我们有调试器——但这类似于通过询问函数在多个值处是什么来查看问题,而我们更愿意查看它的图表。很多编程都是通过做一些你认为正确的事情来完成的,观察行为是否正确,如果不正确,那么我们通过反应进行修改并纠正所述行为。
Bret Victor 在他的文章Learnable Programming 中讨论了这个话题。我强烈推荐它,即使它现在对你没有帮助,也许你可以通过让这些想法更流行来帮助其他人。
然后,我告诉你你现在可以做什么。在他的《清洁代码》一书中,Robert C. Martin 提议将代码结构化,就像报纸的布局一样。
想想一篇写得很好的报纸文章。你竖着读。在顶部,您希望有一个标题,它将告诉您故事的内容,并允许您决定是否要阅读。第一段为您提供了整个故事的概要,隐藏了所有细节,同时为您提供了粗略的概念。随着您继续向下,详细信息会增加,直到您掌握所有日期、姓名、报价、声明和其他细节。
建议是自上而下组织您的程序,较高级别的程序调用中级程序,而中级程序又调用较低级别的程序。在任何地方,很明显(1)您处于适当的抽象级别,并且(2)您正在查看实现您想要修改的行为的程序部分。
这意味着将状态存储在它所属的级别,并将其暴露在其他任何地方。过程应该只取它们需要的参数,因为更多的参数意味着你在推理代码时也必须考虑更多的参数。
这是将程序分解为更小的程序的主要原因。例如,这里有一些我之前写过的代码。它并不完美,但你可以非常清楚地看到如果你想改变什么,你需要去程序的哪个部分。
当然,高阶程序列在其他程序之前。我先告诉你我要做什么,然后再告诉你我是怎么做的。
function formatLinks(matches, stripped) {
let formatted_links = []
for (match of matches) {
let diff = difference(match, stripped)
if (isSimpleLink(diff)) {
formatted_links.push(formatAsSimpleLink(diff))
} else if (hasPrefix(diff)) {
formatted_links.push(formatAsPrefixedLink(diff))
} else if (hasSuffix(diff)) {
formatted_links.push(formatAsSuffixedLink(diff))
}
}
// There might be multiple links within a word
// in which case we join them and strip any duplicated parts
// (which are otherwise interpreted as suffixes)
if (formatted_links.length > 1) {
return combineLinks(formatted_links)
}
// Default case
return formatted_links[0]
}
如果 JavaScript 是一种类型化语言,并且如果我们可以看到代码中做出的决定的图像,作为输入和时间的一个因素,这可能会更好。
我认为Quokka.js 和VS Code Debug Visualizer 都在这个领域做着有趣的工作。