【问题标题】:How to write clear and maintainable code when dealing with tables?处理表格时如何编写清晰可维护的代码?
【发布时间】:2016-01-07 12:24:03
【问题描述】:

在我的项目中,我经常利用表和底层的 ListObjects 和 ListColumns。我喜欢它们,因为它们比裸 Range 对象更容易引用和更新。然而,我仍然没有找到一种合理且可维护的方法来处理由许多 ListColumns 组成并在项目中的所有 Worksheets 中引用的多个 ListObjects。

假设我有一个工作表(将(名称)属性设置为“WorksheetA”),其中包含具有少量列(称为 Column1、Column2、...、Column10)的表(称为 TableA)。

现在我想从另一个工作表的代码中引用其中一列。我可以这样做:

WorksheetA.ListObjects("TableA").ListColumns("Column7")

现在,直接使用字符串是一种不好的做法,因为它难以维护并且容易出错。

那么现在呢?

我可以创建专门的模块来将我的字符串存储为常量。例如,名为“Constants”的模块:

Public Const TABLE_A As String = "TableA"
Public Const COLUMN7 As String = "Column7"

然后我的引用可以转换为:

WorksheetA.ListObjects(Constants.TABLE_A).ListColumns(Constants.COLUMN7)

但是,这种解决方案有一些缺点:

  1. 添加每个表和列后,常量模块的增长速度会快得离谱。
  2. 引用本身会增长并变得不那么可读。
  3. 所有工作簿中与表相关的所有常量都被扔进一个大坑。

我可以在 WorksheetA 中存储常量,并通过以下公共函数使它们可用:

Private Const TABLE_A As String = "TableA"
Private Const COLUMN7 As String = "Column7"

Public Function GetTableAName() As String
    GetTableAName = TABLE_A
End Function

Public Function GetTableA() As ListObject
    Set GetTableA = WorksheetA.ListObjects(TABLE_A)
End Function

Public Function GetTableAColumn7() As ListColumn
    Set GetTableAColumn7 = GetTableA().ListColumns(COLUMN7)
End Function

这个解决方案实际上解决了上面提到的所有三个问题,但它仍然有点“脏”和耗时,因为添加新表需要为每列创建一个函数。

你知道如何处理这个问题吗?

EDIT1(为清楚起见):假设用户不得更改任何名称(表名和列名都不能更改)。如果用户这样做,那就是他/她的责任。

EDIT2(为清楚起见):我使用 Column7 作为列名仅作为示例。让我们假设列有更有意义的名称。

【问题讨论】:

  • 有趣的问题。如果有一个简单的解决方案,我想我们会在 DAO、ADO、JDBC 等数据访问库中看到它。开发人员唯一能做的就是封装这些“字段名”和“表名”编写“持久”类,即每个表或数据对象一个类。那是“干净”的解决方案,但它增加了一层并且很耗时。建议用于大型数据库项目,但我怀疑 Excel 项目会达到如此规模,以至于该解决方案变得真正必要。
  • 我猜你担心在字符串方法中,用户可以更改表名,从而破坏引用。如果工作表上只有一个表,则可以使用索引号 (1) 代替字符串。这仅对每张表最多一张表有效。不知道,这是否适用于您的所有工作表。它会在你的例子中。
  • 如果你的列名真的和Column7一样不起眼,那为什么不通过数字索引来引用它们呢?
  • 我是常量的大力支持者,但我认为你的想法太过分了。我根本不会为此烦恼。你做了很多工作却没有任何好处。它不会阻止用户更改表或列的名称。然后归结为您想对列做什么。我主要使用 VBA 中的数组。因此,您可以从结构化引用中获取列的所有值,而不是处理所有的引号和对象引用,如下所示:v = [Table1[Column7]]
  • 你也可以创建一个通用的 ListColumn 返回函数。将 ws 名称、表名称和列名称传递给它,它会返回 ListColumn 对象。我不会在任何地方定义这些东西的名称。

标签: vba excel worksheet listobject


【解决方案1】:

这是我的两分钱。我不是一个受过教育的程序员,但我确实得到报酬,所以我想这让我很专业。

第一道防线是我创建了一个类来为表格建模。我从表中填写类,甚至没有其他代码知道数据所在的位置。当我初始化时,我会运行类似的代码

clsEmployees.FillFromListObject wshEmployees.ListObjects(1)

然后在类中,代码是这样的

vaData = lo.DataBodyRange.Value
...
clsEmployee.EeName = vaData(i,1)
clsEmployee.Ssn = vaData(i,2) 
etc

每个工作表只有一个 ListObject。这是我的规则,我从不打破它。任何有权访问工作表的人都可以重新排列列并破坏我的代码。如果我想使用 Excel 作为数据库,有时我会这样做,那么这就是我承担的风险。如果它非常重要以至于我不能冒这个风险,那么我会将我的数据存储在 SQL Server、SQLite 或 JET 中。

我实际上可以调用 ListColumns 名称,而不是将范围放入数组中。这样,如果有人重新排列了列,我的代码仍然可以工作。但它介绍了他们可以重命名列,所以我只是用一种风险换另一种风险。它会使代码更具可读性,所以它可能是你想做的交易。我喜欢从数组中填充的速度,所以这就是我所做的交易。

如果我的项目足够小或者应该直接使用 ListObjects,那么我将遵循与任何字符串相同的规则。

  • 我在代码中只使用了一次字符串。
  • 如果我多次使用它,我会创建一个过程级常量
  • 如果我在多个过程中使用它,我会尝试将它作为参数传递
  • 如果我不能将它作为参数传递,我会创建一个模块级常量
  • 如果两个程序在不同的模块中,我首先问自己为什么两个程序在使用相同常量的不同模块中。相关程序不应该在同一个模块中吗?
  • 如果这两个过程确实属于不同的模块,那么我尝试将其作为参数传递
  • 如果这些都不起作用,那么它确实是一个全局常量,我在我的 MGlobals 模块中进行了设置。

如果 MGlobals 占据了超过半个屏幕,我就做错了,我需要退后一步,仔细考虑我想要完成的工作。然后我创建了一个自定义类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多