Evitar números mágicos
Cuando manipulas la siguiente tabla en VBA, ¿qué harías?
Número | Nombre | Dirección | Teléfono | Producto | Opción |
---|---|---|---|---|---|
1 | Satou | Hokkaido | 090-0000-0000 | Juguete | Opción 1 |
2 | Tanaka | Aomori | 090-1111-1111 | Dulce | Opción 2 |
3 | Honda | Akita | 090-2222-2222 | Juego | Opción 3 |
4 | Okamoto | Niigata | 090-3333-3333 | Deporte | Opción 4 |
5 | Yamada | Yamaguchi | 090-4444-4444 | Zapato | Opción 5 |
En programación, los números mágicos deben evitarse en principio.
En otros lenguajes de programación, con un poco de aprendizaje, es casi seguro que no aparecerán números mágicos, pero en VBA hay muchos casos en los que es difícil evitarlos.
Personalmente, creo que las razones principales son las siguientes:
- La mayoría de los datos que se manejan son datos en hojas o archivos CSV.
- La gestión de la memoria es compleja y, a menudo, la cantidad de memoria utilizada y la legibilidad son inversamente proporcionales.
- El comportamiento es muy diferente al de los lenguajes de programación actuales (problema de competencia).
Debido a la naturaleza de los datos que se manejan, los números mágicos surgen naturalmente si se programa sin pensar. En esta ocasión, me gustaría presentar los métodos que uso con frecuencia junto con el código utilizando la tabla anterior como ejemplo.
Métodos
Recomendación personal: Collection y Dictionary
El método que personalmente recomiendo más es el patrón de matriz asociativa que combina los objetos Collection y Dictionary. Creo que es un método familiar para aquellos que han aprendido lenguajes que manejan archivos JSON, como JavaScript.
________________________________________________
'
' Convierte el rango objetivo en una matriz asociativa
'
' @param r Objeto Range a convertir
' @return Matriz asociativa
'
'________________________________________________
Public Function GetCollection(ByRef r As Range) As Collection
Dim items As Collection, item As Object
Set items = New Collection
Dim row As Long, col As Long
For row = 2 To r.Rows.Count
Set item = CreateObject("Scripting.Dictionary")
For col = 1 To r.Columns.Count
item.Add r(1, col).Value, r(row, col).Value
Next col
items.Add item
Next row
Set GetCollection = items
End Function
Puede parecer redundante reemplazar un objeto Range, que se puede tratar como una matriz bidimensional, con una matriz asociativa, pero en términos de legibilidad, este método es mi favorito.
Después de la conversión, el objeto Range se convierte en un estado similar a un archivo JSON, como se muestra a continuación.
{
"1": {
"Número": "1",
"Nombre": "Satou",
"Dirección": "Hokkaido",
"Teléfono": "090-0000-0000",
"Producto": "Juguete",
"Opción": "Opción 1"
},
"2": {
"Número": "2",
"Nombre": "Tanaka",
"Dirección": "Aomori",
"Teléfono": "090-1111-1111",
"Producto": "Dulce",
"Opción": "Opción 2"
},
"3": {...},
"4": {...},
"5": {...}
}
Para extraer datos, se hace de la siguiente manera.
Dim items as Collection
Set items = GetCollection(Range)
items(1)("Nombre") ' → "Satou"
Usando un iterador (for each), se vuelve aún más claro.
Dim items as Collection, item as Object
Set items = GetCollection(Range)
For Each item in items
item("Nombre") ' → "Satou", "Tanaka"...
next
Este método tiene algunas desventajas, la primera es la velocidad. Simplemente porque los datos se recorren dos veces (creación de la matriz y procesamiento real), la velocidad de procesamiento disminuye. Otra es que no debe haber valores duplicados en la fila del encabezado. Si hay duplicados… agregar condiciones hace que el código sea más difícil de seguir, por lo que es mejor usar otro método.
La mayoría usa Enum
El método más común para evitar números mágicos es usar Enum. Es más fácil de entender para los principiantes que el método que usa Collection y Dictionary, y es el mejor en términos de mantenibilidad.
Enum TableColumn
Número = 1
Nombre = 2
Dirección = 3
Teléfono = 4
Producto = 5
Opción = 6
End Enum
Dado que solo se están definiendo grupos de constantes, la implementación es posible simplemente reemplazando los números con Enum. También es fácil eliminar los números mágicos de un programa ya completado.
Range(1, TableColumn.Nombre).value ' → "Satou"
Para recorrer en bucle, se hace de la siguiente manera.
Dim i as Long
For i = 1 to Range.Rows.Count
Range(i, TableColumn.Nombre).value ' → "Satou", "Tanaka"...
Next
No creo que haya desventajas en este método. Si tienes dudas, usa este método.
El más simple: constantes
Dudé en presentarlo porque creo que todos los que están leyendo este artículo ya lo saben, pero el método más rápido en términos de velocidad de procesamiento es definir cada columna del encabezado una por una.
Private Const TABLE_COLUMN_Número As Long = 1
Private Const TABLE_COLUMN_Nombre As Long = 2
Private Const TABLE_COLUMN_Dirección As Long = 3
Private Const TABLE_COLUMN_Teléfono As Long = 4
Private Const TABLE_COLUMN_Producto As Long = 5
Private Const TABLE_COLUMN_Opción As Long = 6
Dado que se especifican activamente el alcance y el tipo, la velocidad es la más rápida. Sin embargo, creo que la parte más pesada es el procesamiento en bucle, por lo que no hay mucha diferencia con Enum. A menos que haya reglas de codificación específicas, Enum está bien.