【问题标题】:Lua Spaghetti ModulesLua 意大利面条模块
【发布时间】:2016-08-22 07:12:16
【问题描述】:

我目前正在开发自己的编程语言。代码库(在 Lua 中)由几个模块组成,如下所示:

  • 第一个 error.lua 没有依赖项;
  • lexer.lua 只依赖于error.lua;
  • prototypes.lua 也没有依赖关系;
  • parser.lua 依赖于上述所有模块;
  • interpreter.lua 是整个代码库的支点。它依赖于error.lua、parser.lua和memory.lua;
  • memory.lua 依赖于functions.lua;
  • 最后,functions.lua 依赖于memory.lua 和interpreter.lua。它是memory.lua内部需要的,所以我们可以说memory.lua也依赖于interpreter.lua。

“A 依赖于 B”是指 A 中声明的函数需要 B 中声明的函数。

然而,真正的问题是,当 A 依赖于 B 而 B 又依赖于 A 时,正如您从上面的列表中所理解的那样,这在我的代码中经常发生。

举个具体的例子来说明我的问题,interpreter.lua 是这样的:

--first, I require the modules that DON'T depend on interpreter.lua
local parser, Error = table.unpack(require("parser"))
--(since error.lua is needed both in the lexer, parser and interpreter module,
--I only actually require it once in lexer.lua and then pass its result around)

--Then, I should require memory.lua. But since memory.lua and
--functions.lua need some functions from interpreter.lua to work, I just
--forward declare the variables needed from those functions and then those functions themself:

--forward declaration
local globals, new_memory, my_nil, interpret_statement

--functions I need to declare before requiring memory.lua
local function interpret_block()
    --uses interpret_statement and new_memory
end
local function interpret_expresion()
    --uses new_memory, Error and my_nil
end

--Now I can safely require memory.lua:
globals, new_memory, my_nil = require("memory.lua")(interpret_block, interpret_espression)
--(I'll explain why it returns a function to call later)

--Then I have to fulfill the forward declaration of interpret_executement:
function interpret_executement()
    --uses interpret_expression, new_memory and Error
end

--finally, the result is a function
return function()
    --uses parser, new_fuction and globals
end

memory.lua 模块返回一个函数,以便它可以接收interpret_blockinterpret_expression 作为参数,如下所示:

--memory.lua
return function(interpret_block, interpret_expression)
    --declaration of globals, new_memory, my_nil
    return globals, new_memory, my_nil
end

现在,我了解了前向声明 here 和作为模块的函数(如在 memory.lua 中,将一些函数从需要模块传递到所需模块)here 的概念。它们都是很棒的想法,我必须说它们非常有效。但是您要为可读性付出代价。

事实上,这次将代码分解成更小的部分让我的工作更加困难,如果我将所有内容都编码在一个文件中,这对我来说是不可能的,因为它有超过 1000 行代码而且我正在编码来自智能手机。

我的感觉是使用意大利面条代码,只是规模更大。

那么,由于某些模块需要彼此工作(当然,这不涉及将所有变量设为全局),我该如何解决我的代码无法理解的问题?其他语言的程序员如何解决这个问题?我应该如何重新组织我的模块?使用 Lua 模块时是否有任何标准规则也可以帮助我解决这个问题?

【问题讨论】:

    标签: module lua modularity readability code-readability


    【解决方案1】:

    如果我们将您的 lua 文件视为有向图,其中一个顶点从依赖项指向其使用,目标是将您的图修改为树或森林,因为您打算摆脱循环。

    一个循环是一组节点,沿顶点方向遍历可以到达起始节点。

    现在,问题是如何摆脱循环?

    答案如下:

    让我们考虑节点 N 并将 {D1, D2, ..., Dm} 视为它的直接依赖项。如果该集合中没有直接或间接依赖于 N 的 Di,那么您可以保持 N 不变。在这种情况下,有问题的依赖项集如下所示:{}

    但是,如果您有一个非空集合,例如:{PD1, ..., PDk} 怎么办?

    然后您需要分析 1 和 k 之间的 i 以及 N 的 PDi,并查看每个 PDi 中不依赖于 N 的子集是什么,以及不依赖于任何 PDi 的 N 的子集是什么。这样您就可以定义 N_base 和 N、PDi_base 和 PDi。 N 依赖于 N_base,就像所有 PDi 元素一样,PDi 依赖于 PDi_base 和 N_base。

    这种方法最小化了依赖树中的圆圈。但是,很有可能该组中存在 {f1, ..., fl} 的函数集,由于依赖关系而无法迁移到 _base 中,并且仍然存在循环。在这种情况下,您需要为相关组命名,为其创建一个模块并将所有功能迁移到该组中。

    【讨论】:

    • 哇,我没想到会有这样的答案。目前的问题是:解释器 -> 函数 -> 内存 解释器。我现在要像你解释的那样分析子集,看看我能不能从中得到什么。
    • 祝你好运,请告诉我结果。
    • 哦,谢谢。我不再专注于模块,而是构建函数的依赖树。
    • 那么你需要把你的重构分成可以测试的小测试,这样你就永远不会偏离正确的道路太多。
    • 一般来说,一个人喜欢避免加载两次。但是,如果其他两个模块相互独立,那么为此目的创建依赖关系并不是一个好主意。一般来说,如果您有一组模块标识符/签名,那是一件好事。您的两个文件都应检查模块是否已加载,并且仅在未加载时才加载。事实上,这种加载机制看起来就像一个根依赖。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 2010-09-16
    • 2012-01-26
    相关资源
    最近更新 更多